Type List vs type ArrayList in Java [duplicate]
(1) List<?> myList = new ArrayList<?>();
(2) ArrayList<?> myList = new ArrayList<?>();
I understand that with (1), implementations of the List interface can be swapped. It seems that (1) is typically used in an application regardless of need (myself I always use this).
I am wondering if anyone uses (2)?
Also, how often (and can I please get an example) does the situation actually require using (1) over (2) (i.e. where (2) wouldn't suffice..aside coding to interfaces and best practices etc.)
Almost always List
is preferred over ArrayList
because, for instance, List
can be translated into a LinkedList
without affecting the rest of the codebase.
If one used ArrayList
instead of List
, it's hard to change the ArrayList
implementation into a LinkedList
one because ArrayList
specific methods have been used in the codebase that would also require restructuring.
You can read about the List
implementations here.
You may start with an ArrayList
, but soon after discover that another implementation is the more appropriate choice.
I am wondering if anyone uses (2)?
Yes. But rarely for a sound reason (IMO).
And people get burned because they used ArrayList
when they should have used List
:
Utility methods like
Collections.singletonList(...)
orArrays.asList(...)
don't return anArrayList
.Methods in the
List
API don't guarantee to return a list of the same type.
For example of someone getting burned, in https://stackoverflow.com/a/1481123/139985 the poster had problems with "slicing" because ArrayList.sublist(...)
doesn't return an ArrayList
... and he had designed his code to use ArrayList
as the type of all of his list variables. He ended up "solving" the problem by copying the sublist into a new ArrayList
.
The argument that you need to know how the List
behaves is largely addressed by using the RandomAccess
marker interface. Yes, it is a bit clunky, but the alternative is worse.
Also, how often does the situation actually require using (1) over (2) (i.e. where (2) wouldn't suffice..aside 'coding to interfaces' and best practices etc.)
The "how often" part of the question is objectively unanswerable.
(and can I please get an example)
Occasionally, the application may require that you use methods in the ArrayList
API that are not in the List
API. For example, ensureCapacity(int)
, trimToSize()
or removeRange(int, int)
. (And the last one will only arise if you have created a subtype of ArrayList that declares the method to be public
.)
That is the only sound reason for coding to the class rather than the interface, IMO.
(It is theoretically possible that you will get a slight improvement in performance ... under certain circumstances ... on some platforms ... but unless you really need that last 0.05%, it is not worth doing this. This is not a sound reason, IMO.)
You can’t write efficient code if you don’t know whether random access is efficient or not.
That is a valid point. However, Java provides better ways to deal with that; e.g.
public <T extends List & RandomAccess> void test(T list) {
// do stuff
}
If you call that with a list that does not implement RandomAccess
you will get a compilation error.
You could also test dynamically ... using instanceof
... if static typing is too awkward. And you could even write your code to use different algorithms (dynamically) depending on whether or not a list supported random access.
Note that ArrayList
is not the only list class that implements RandomAccess
. Others include CopyOnWriteList
, Stack
and Vector
.
I've seen people make the same argument about Serializable
(because List
doesn't implement it) ... but the approach above solves this problem too. (To the extent that it is solvable at all using runtime types. An ArrayList
will fail serialization if any element is not serializable.)
Finally, I'm not going to say "because its is good style". That "reason" is both a circular argument ("Why is it 'good style'?") and an appeal to an unstated (and probably non-existent!) higher authority ("Who says it is 'good style'?").
(I do think it is good style to program to the interface, but I'm not going to give that as a reason. It is better for you to understand the real reasons and come to the (IMO) correct conclusions for yourself. The correct conclusion may not always be the same ... depending on the context.)
For example you might decide a LinkedList
is the best choice for your application, but then later decide ArrayList
might be a better choice for performance reason.
Use:
List list = new ArrayList(100); // will be better also to set the initial capacity of a collection
Instead of:
ArrayList list = new ArrayList();
For reference:
(posted mostly for Collection diagram)
It is considered good style to store a reference to a HashSet
or TreeSet
in a variable of type Set.
Set<String> names = new HashSet<String>();
This way, you have to change only one line if you decide to use a TreeSet
instead.
Also, methods that operate on sets should specify parameters of type Set:
public static void print(Set<String> s)
Then the method can be used for all set implementations.
In theory, we should make the same recommendation for linked lists, namely to save
LinkedList references in variables of type List. However, in the Java library, the List interface is common to both the ArrayList
and the LinkedList
class. In particular, it has get and set methods for random access, even though these methods are very inefficient for linked lists.
You can’t write efficient code if you don’t know whether random access is efficient or not.
This is plainly a serious design error in the standard library, and I cannot recommend using the List interface for that reason.
To see just how embarrassing that error is, have a look at
the source code for the binarySearch
method of the Collections class. That method takes a
List parameter, but binary search makes no sense for a linked list. The code then clumsily
tries to discover whether the list is a linked list, and then switches to a linear search!
The Set
interface and the Map
interface, are well designed, and you should use them.
When you write List
, you actually tell, that your object implements List
interface only, but you don't specify what class your object belongs to.
When you write ArrayList
, you specify that your object class is a resizable-array.
So, the first version makes your code more flexible in future.
Look at Java docs:
Class ArrayList
- Resizable-array implementation of the List
interface.
Interface List
- An ordered collection (also known as a sequence). The user of this interface has precise control over where in the list each element is inserted.
Array
- container object that holds a fixed number of values of a single type.
I use (2) if code is the "owner" of the list. This is for example true for local-only variables. There is no reason to use the abstract type List
instead of ArrayList
.
Another example to demonstrate ownership:
public class Test {
// This object is the owner of strings, so use the concrete type.
private final ArrayList<String> strings = new ArrayList<>();
// This object uses the argument but doesn't own it, so use abstract type.
public void addStrings(List<String> add) {
strings.addAll(add);
}
// Here we return the list but we do not give ownership away, so use abstract type. This also allows to create optionally an unmodifiable list.
public List<String> getStrings() {
return Collections.unmodifiableList(strings);
}
// Here we create a new list and give ownership to the caller. Use concrete type.
public ArrayList<String> getStringsCopy() {
return new ArrayList<>(strings);
}
}
I think the people who use (2) don't know the Liskov substitution principle or the Dependency inversion principle. Or they really have to use ArrayList
.
Actually there are occasions where (2) is not only preferred but mandatory and I am very surprised, that nobody mentions this here.
Serialization!
If you have a serializable class and you want it to contain a list, then you must declare the field to be of a concrete and serializable type like ArrayList
because the List
interface does not extend java.io.Serializable
Obviously most people do not need serialization and forget about this.
An example:
public class ExampleData implements java.io.Serializable {
// The following also guarantees that strings is always an ArrayList.
private final ArrayList<String> strings = new ArrayList<>();
(3) Collection myCollection = new ArrayList<?>();
I am using this typically. And only if I need List methods, I will use List. Same with ArrayList. You always can switch to more "narrow" interface, but you can't switch to more "wide".
Out of the following two:
(1) List<?> myList = new ArrayList<?>();
(2) ArrayList<?> myList = new ArrayList<?>();
First is generally preferred. As you will be using methods from List
interface only, it provides you the freedom to use some other implementation of List
e.g. LinkedList
in future. So it decouples you from specific implementation. Now there are two points worth mentioning:
- We should always program to interface. More here.
- You will almost always end up using
ArrayList
overLinkedList
. More here.
I am wondering if anyone uses (2)
Yes sometimes (read rarely). When we need methods that are part of implementation of ArrayList
but not part of the interface List
. For example ensureCapacity
.
Also, how often (and can I please get an example) does the situation actually require using (1) over (2)
Almost always you prefer option (1). This is a classical design pattern in OOP where you always try to decouple your code from specific implementation and program to the interface.
List is an interface. It doesn't have methods. When you call a method on a List reference, it in fact calls the method of ArrayList in both cases.
And for the future you can change List obj = new ArrayList<>
to List obj = new LinkList<>
or other types which implement List interface.
Somebody asked this again (duplicate) which made me go a little deeper on this issue.
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
ArrayList<String> aList = new ArrayList<String>();
aList.add("a");
aList.add("b");
}
If we use a bytecode viewer (I used http://asm.ow2.org/eclipse/index.html) weĺl see the following (only list initialization and assignment) for our list snippet:
L0
LINENUMBER 9 L0
NEW ArrayList
DUP
INVOKESPECIAL ArrayList.<init> () : void
ASTORE 1
L1
LINENUMBER 10 L1
ALOAD 1: list
LDC "a"
INVOKEINTERFACE List.add (Object) : boolean
POP
L2
LINENUMBER 11 L2
ALOAD 1: list
LDC "b"
INVOKEINTERFACE List.add (Object) : boolean
POP
and for alist:
L3
LINENUMBER 13 L3
NEW java/util/ArrayList
DUP
INVOKESPECIAL java/util/ArrayList.<init> ()V
ASTORE 2
L4
LINENUMBER 14 L4
ALOAD 2
LDC "a"
INVOKEVIRTUAL java/util/ArrayList.add (Ljava/lang/Object;)Z
POP
L5
LINENUMBER 15 L5
ALOAD 2
LDC "b"
INVOKEVIRTUAL java/util/ArrayList.add (Ljava/lang/Object;)Z
POP
The difference is list ends up calling INVOKEINTERFACE whereas aList calls INVOKEVIRTUAL. Accoding to the Bycode Outline Plugin reference,
invokeinterface is used to invoke a method declared within a Java interface
while invokevirtual
invokes all methods except interface methods (which use invokeinterface), static methods (which use invokestatic), and the few special cases handled by invokespecial.
In summary, invokevirtual pops objectref off the stack while for invokeinterface
the interpreter pops 'n' items off the operand stack, where 'n' is an 8-bit unsigned integer parameter taken from the bytecode. The first of these items is objectref, a reference to the object whose method is being called.
If I understand this correctly, the difference is basically how each way retrieves objectref.
The only case that I am aware of where (2) can be better is when using GWT, because it reduces application footprint (not my idea, but the google web toolkit team says so). But for regular java running inside the JVM (1) is probably always better.
I would say that 1 is preferred, unless
- you are depending on the implementation of optional behavior* in ArrayList, in that case explicitly using ArrayList is more clear
- You will be using the ArrayList in a method call which requires ArrayList, possibly for optional behavior or performance characteristics
My guess is that in 99% of the cases you can get by with List, which is preferred.
- for instance
removeAll
, oradd(null)
List
interface have several different classes - ArrayList
and LinkedList
. LinkedList
is used to create an indexed collections and ArrayList
- to create sorted lists. So, you can use any of it in your arguments, but you can allow others developers who use your code, library, etc. to use different types of lists, not only which you use, so, in this method
ArrayList<Object> myMethod (ArrayList<Object> input) {
// body
}
you can use it only with ArrayList
, not LinkedList
, but you can allow to use any of List
classes on other places where it method is using, it's just your choise, so using an interface can allow it:
List<Object> myMethod (List<Object> input) {
// body
}
In this method arguments you can use any of List
classes which you want to use:
List<Object> list = new ArrayList<Object> ();
list.add ("string");
myMethod (list);
CONCLUSION:
Use the interfaces everywhere when it possible, don't restrict you or others to use different methods which they want to use.
精彩评论