Why is declaring List implementation not needed?

119 Views Asked by At

I am using a method that returns type List<>, and I am then able to call methods on that List :

List<Artist> artists = artistsPager.artists.items;
artists.add(new Artist());

To be clear artistsPager.artists.items is returning type List<Artist>.

Since List is only an interface, how is the compiler letting me use this artists object, doesn't a List<> always need an implementation, like ArrayList?

For example, the compiler won't let me do the following, because "List is abstract; cannot be instantiated":

List<Integer> list = new List<Integer>();

How is artistsPager.artists.items any different than new List<Integer>()?

For those curious, this come from the Java Web API wrapper for Android. All help is greatly appreciated.

I do not believe this is a duplicate of this question because even though the answers are the same, the question that begets them is different. The other question is asking the difference between type List and ArrayList, whereas my question was wondering why the compiler was not complaining when the implementation of List was hidden.

I am fine with having this marked as a duplicate to direct future users to that question, but I do not think it should be closed as the answers here I think would be immensely helpful to other users.

3

There are 3 best solutions below

1
On BEST ANSWER

How is artistsPager.artists.items any different than new List<Integer>()?

artistsPager.artists.items is a field of type List<>. Somewhere in the code, it must have been instantiated with a concrete implementation like ArrayList.

new List<Integer>() is a constructor call, which is invalid because List is an interface.

0
On

This means that a concrete list was created somewhere (for example, an ArrayList).

You only know that the list that is created is implementing methods from the List interface, allowing you to use these methods.

Take this example hiding the concrete LinkedList from the user:

public static List<Integer> makeListOfInts(){
    return new LinkedList<Integer>(){
        {
            add(1);
            add(2);
        }
    }
}

I can be easily replaced with an ArrayList:

public static List<Integer> makeListOfInts(){
    return new ArrayList<Integer>(){
        {
            add(1);
            add(2);
        }
    }
}

And all the function's user knows is that a List is returned, but it does not know the concrete type of the list. It may only use functions from the List interface, and not from the specific implementation that was selected.

0
On

doesn't a List<> always need an implementation, like ArrayList<>?

Yes, you need an implementation in order to create an instance. However, the compiler knows two things:

  • It knows that you have already created some instance, and assigned it to items, and
  • It knows that whatever you have assigned to items, implements List<> interface

Knowing these two things is enough for the compiler to let you call add on a variable declared as an interface type. In this way your code does not need to care what implementation you get - an ArrayList<>, a LinkedList<>, or some custom implementation you developed: your code will work with all of them.

Note that the practice of referring to objects through their interface type is desirable. It is called programming to interface. It lets you hide implementation details, and swap objects for different implementations later on.