What is the difference between three types of declaration of array list and why we write the interface first then on other side implementing class?

Viewed 158

I am a beginner in Java. I have two basic questions.

  1. When we write declaration of array list as

    1. List l=new ArrayList<String>();
    2. List<String> l=new ArrayList<String>();
    3. List<String> l=new ArrayList<>();

    I saw all the above statements not give any error. What difference it creates when we choose one of the methods to declare any collection?

  2. When we initialise the collections as

    1. List<String>l = new ArrayList<String>();

    so if we declare like below

    1. ArrayList<String> l= new ArrayList<String>();

    What is the difference between 1 and 2, and why we choose mostly 2 while using collections?

6 Answers

a) List l = new ArrayList<String>(); is the same as List<Object> l = new ArrayList<String>();. It means that the variable l is a list that can store any Object, but not only a String. Also you will need to cast the values of the list if you want to use them as Strings. So the following code would compile (which is usually not what you want):

List l = new ArrayList<String>();
l.add("a string");//adding a String type is what is expected here
l.add(42);//adding an int is not realy expected here, but works
l.add(new Date());//also adding any other object is allowed

String s = (String) l.get(0);//s will be "a string"
String s2 = (String) l.get(1);//runtime error, since 42 can't be cast into a String

b) List<String> l=new ArrayList<String>(); means, that the variable l is a list of Strings and will only accept String values. The following code would cause compiler errors:

List<String> l = new ArrayList<String>();
l.add("a string");//adding a String type is what is expected here
l.add(42);//compiler error
l.add(new Date());//compiler error

c) List<String> l=new ArrayList<>(); is basically the same as List<String> l=new ArrayList<String>();. The diamond operator <> just means that it should be clear to the compiler, which type is used here, so the programmer can be lazy and let the compiler do the work.


p) List<String>l = new ArrayList<String>(); tells the compiler, that l is of type List. So if you want to assign an ArrayList to the variable l, that is allowed, and if you then decide, that a LinkedList is better, you can still assign a LinkedList and it will still work, because LinkedList and ArrayList both implement the interface List. So the follwing code will work:

List<String> l = new ArrayList<String>();
//later on you decide to change the type (maybe for a better performance or whatever)
l = new LinkedList<String>();
//use the List l like nothing has changed. You just don't need to care.

q) ArrayList<String> l= new ArrayList<String>(); means, that the variable l is an ArrayList and can never be any other List implementation (like a LinkedList), so you can't change the implementation easily. Therefore the following code will not compile:

List<String> l = new ArrayList<String>();
//later on you decide to change the type (maybe for a better performance or whatever)
l = new LinkedList<String>();//compiler error; now you need to change many parts of your code if you still want to change the type of l to a LinkedList

Therefore usually p is the better solution, because you can change the code easily. Only of you need to call methods that are not part of the List interface, like the trimToSize method of ArrayList it can be needed to use q. But that's a very rare case.


Summary:

In almost every case it's better to use p than q.
Also in almost every case it's better to use b or c than a. Whether you use b or c doesn't realy matter that much. c is just a bit shorter.

Part 1.

a) List l = new ArrayList<String>();

b) List<String> l = new ArrayList<String>();

c) List<String> l = new ArrayList<>();

There is no difference between b) and c).

But a) declares l as a raw type. This is discouraged. When you use l, the compiler doesn't "know" what the type of the array elements will be:

  • If you want to use the elements as strings you will need to cast them.
  • It won't stop you putting things into the list that are not strings. Ooops!!!

Part 2.

p) List<String>l = new ArrayList<String>();

q) ArrayList<String> l = new ArrayList<String>();

The difference is that the type of l in p) says that it could be any List implementation, but in q) it is restricted to ArrayList and subtypes.

If l is only used locally, it probably makes little difference. But if l is part of your API, or if if you need to assign l a different List value, you can run into problems. For example, when you call List.sublist on an ArrayList, the object you get back is not an ArrayList!

So ... as a general rule p) is better.

In general, it is better to "program to an interface" (i.e. p) rather than an implementation (i.e. q).

With a) you declare a List of Object by List l. With b) and c) you declare a List of String by List<String> l. With the generic operator <type> introduced with Java 5 the compiler is able the validate assigned instances. Compiled code works without these types, effect called type errasure.

No error by compiler for this example

List l = new ArrayList<String>();
l.add("a");
l.add(new Integer(1));

Error by compiler for this example

List<String> l = new ArrayList<String>();
l.add("a");
l.add(new Integer(1));  // forces compiler error

Difference between b) and c): at c) you are using Diamond operator <> ( since Java 7) for which you do not need repeat the type of generic.

And for p) and q): you should use p) because List is an interface implemented by ArrayList and you could change type of used List implementation with lesser changes.

Your (b) and (c) items are identical.

A later version of Java enabled the compiler to infer the parameterized type on the right from the declaration on the left. So (c) became a convenient shortcut for (b). I cannot imagine a situation where you would not choose (c) over (b).

In contrast, your (a) is very much different.

When you omit the parameterized type from the left side, you deny the compiler a chance the verify type-safety of objects being added to the collection. Your collection on the left is known as raw.

In your example, your intention is to allow only String objects to be added to the collection. But with a raw collection, any object can be added. Here is example code where I add a DayOfWeek object rather than a String.

List list = new ArrayList<String>();
list.add( DayOfWeek.MONDAY ) ;

See this code run successfully at IdeOne.com.

Change List list to List< String > list to see a compiler error saying you cannot fit a DayOfWeek into a collection for String objects — round hole, square peg.

The key to understanding this is type erasure. Because generics was bolted onto Java later in its life, Java generics comes with a limitation: Java only knows about the parameterized type at compile-time. At run-time, the parameterized type is lost, unknown, erased.

Other programming languages offer reified generics, meaning the parameterized type is not erased, and is known at run-time. But not so in Java.

Since right answer is already given, I will add few things that you should keep a glimpse on the term type erasure. This will help you to understand the Generics mechanism.

https://javarevisited.blogspot.com/2011/09/generics-java-example-tutorial.html#axzz72vrqjSEX this is a good article for understand the mechanism.

List l = new ArrayList<String>(); 

You have already know that this will produce raw type. But why we need raw type? Generics was introduced from Java 5before then a List can hold different type of values (i.e. int, String, ...). Identical as the code-snippet below:

    List rawList = new ArrayList(); 
    rawList.add(10);
    rawList.add("Hello");

After introduce of Generics Java need a option to access the old List which holds different types of value. That's why raw type exists. In short, for backwards compatibility raw type exists. But you shouldn't use it for future development as @Stephen C suggested. Why you shouldn't use raw type is because it violate the type safety and Generics introduce to enable the type safety feature.

As Turing85 said, simply use ArrayList<Foo> l = new ArrayList<>(); List is an interface that ArrayList implements. To write like ArrayList<Foo> l = new ArrayList<Foo>() means that you have enough time to do unnecessary things. This is like casting int i=(int)((float)(1)); Why not just int i=1?

Related