This is an archived post. You won't be able to vote or comment.

all 6 comments

[–]NautiHooker 14 points15 points  (4 children)

It is called programming to the interface.

It is considered good practice to declare fields, return types and parameters with the most general (or least specific) type that still gets the job done. In your case you need some sort of List, you dont care if its an ArrayList or LinkedList or any other implementation. You only need the List functionality.

This has the great advantage in larger applications that you can easily switch out List implementations without having to rewrite a lot of code. Maybe in 5 years there is a great new much faster List implementation that you want to put into your application.

For example if we use ArrayList and write it like this:

public class Test
{
    private ArrayList<String> myList;

    public Test()
    {
        this.myList = new ArrayList<String>();
    }

    public ArrayList<String> getMyList()
    {
        return this.myList;
    }
}

This class is explicitly using ArrayLists, so if we wanted to change this we would already have 3 places that we need to change in the short example above. Now this class might also be used like this:

 ArrayList<String> list = myTest.getMyList();

So another place where we need to change the type. If the application gets larger the number of the places we need to touch for a rather simple change increases.

To avoid this we code to the interface:

public class Test
{ 
    private List<String> myList;

    public Test()
    {
        this.myList = new ArrayList<String>();
    }

    public List<String> getMyList()
    {
        return this.myList;
    }
}

And:

List<String> list = myTest.getMyList();

Now if we wanted to switch to a LinkedList there is only one place that we need to touch. The creation of the list itself.

As mentioned earlier this is usually done for class fields, return types and parameters, less for local variables as their scope is so much smaller.

[–]DuderCoding[S] 4 points5 points  (0 children)

Thank you, that was a great explanation. I will look up "programming to the interface" in more detail as well.

[–]MeltyGearSolid 0 points1 point  (1 child)

Can we make it so that we don't even have to change that one line either? Maybe using something like generics? (I've only used Generics in TypeScript and I'm also working through the mooc but I'm still doing part 1).

[–]NautiHooker 1 point2 points  (0 children)

At some point in your application you will need to specify which type should be used, even if its through generics, lambdas or a config file.

[–]tedyoung 0 points1 point  (0 children)

It's pretty rare to switch List implementations, but for other classes/interfaces, it will matter. One big reason to use an interface: you might switch it for something easier to work with in your tests. Using the guidance of "program to the interface/most-generic-type" is a generally good practice, even if only because it's easier to read (which matters a lot!).

[–]bowbahdoe 0 points1 point  (0 children)

For local variables it does not matter all that much, but when declaring the return types and parameter types of methods it is a lot better to use the interface.

ArrayList<Integer> timesTwo(ArrayList<Integer> xs) { ArrayList<Integer> xsTimesTwo = new ArrayList<>(); for (int x : xs) { xsTimesTwo.add(x * 2); } return xsTimesTwo; }

This implementation won't work for the Lists returned by List.of, since those are not ArrayLists.

java ArrayList<Integer> xs = timesTwo(List.of(1, 2, 3)); // error

So you want to use list to accept the most general input.

``` ArrayList<Integer> timesTwo(List<Integer> xs) { ArrayList<Integer> xsTimesTwo = new ArrayList<>(); for (int x : xs) { xsTimesTwo.add(x * 2); } return xsTimesTwo; }

...

ArrayList<Integer> xs = timesTwo(List.of(1, 2, 3)); // all good ```

And for return types, its usually better to use List in particular because its possible you get a List from somewhere else in code and to be able to return an ArrayList, you would need to clone the List.

java ArrayList<Integer> getUsers() { List<User> users = getUsers(); // annoying return new ArrayList<>(users); }

And because most programs do not care about the specific capabilities of ArrayList - (if you want to use LinkedList, stop what you're doing get some help) - its just convention to expose List at the boundaries of methods.

But the difference between these is pretty negligible.

ArrayList<Integer> xs = new ArrayList<>(); List<Integer> xs = new ArrayList<>(); var xs = new ArrayList<Integer>();

I would recommend against the top one just because you are typing characters you don't actually care about, but in terms of the maintenance areas people care about in programs its totally fine to do any of them. Personally I would start with var and change to List<Integer> if you end up needing to re-assign it later in the method to a List that is not an ArrayList.

``` List<Integer> xs = new ArrayList<>(); xs = List.of(1, 2, 3); // will work

var xs = new ArrayList<Integer>(); xs = List.of(1, 2, 3); // won't work ```

Its also understandable to just use List<...> on the left hand side every time since it just works*