Java interfaces are strange

So, I was doing some code reviews at work recently, and I saw a place where a co-worker needed to create a list of a single item. We use Java at my company, and Java has a lot of different ways to do this. The way that he had chosen seemed wrong to me, but I couldn’t quite put my finger on why.

The two standard ways (since Java 9) that I think most people would do this are either

List<String> stuff = List.of("first", "second");

or

List<String> stuff = Arrays.asList("first", "second");

He had chosen to use the latter, however I always use the former. I assumed that the end-result was the same – after all, they both create a List<String>.

The difference #

In short, List.of creates an immutable List, while Arrays.asList creates a mutable List. They’re both implementations of the List interface, and they both have the same methods available, but the former is immutable. For example, this works fine:

public static void main(String[] args) {
    List<String> thing = Arrays.asList("first", "second");

    thing.set(0, "change");

    for (String item : thing) {
        System.out.println(item); // prints:
                                  // change
                                  // second
    }
}

But, if you swap out our Arrays.asList("first", "second"); with List.of("first", "second");, that same set operation will throw an UnsupportedOperationException at runtime! It just feels so dirty and uncomfortable that two versions of a List have different levels of support for List methods.

The weirdest thing for me is how everyone seems so okay with this! List is an interface, but some of the implementations of that interface don’t satisfy the requirements of being a List. I mean technically they implement the method and just throw an exception, but why is it a List if it can’t do the things required to be a List?! The docs for the set method on the List interface even states:

Replaces the element at the specified position in this list with the specified element (optional operation).

If it’s optional, it shouldn’t be part of a List! This seems to work in the opposite way of my hierarchical brain – I would expect the categories to neatly work like:

My idea of a list hierarchy

with List only having the methods that are required for being a List – reading, copying, union, etc. The methods for mutating a List shouldn’t exist on the List interface, rather on the MutableList interface, which also inherits the common methods from List.

Why does all of this matter? #

Well, let’s ignore a few best practices, and assume that this example is reasonable (it is).

I want to make a function that can be used all across my application, which logs whenever someone does a thing with a list. I want to make this function universal, because I love DRY code, and I want everyone to be able to use it, no matter what kind of List they’re using.

I make my logging:

private static void log(String str) {
    System.out.println(str);
}

public static void logThings(List things) {
    for (Object item : things) {
        log(item.toString());
    }
}

and this works great. But three months later, we’ve made an organizational decision that we want all logs to start with a header before they get printed, containing the time. Okay, no problem, we just update our logThings function:

public static void logThings(List things) {
    things.add(0, "=== 4/21/2019 03:00 ===");
    for (Object item : things) {
        log(item.toString());
    }
}

This works great, because everywhere in our application, we’re using ArrayList where we use logThings, so we can add our date to it. You can probably see where this is going.

We come back to our application much later, we’re working with an unmodifiable list, and we need to log it. We use our logThings method. The application crashes at runtime, and now we need to refactor our logThings method.

Yes, there are compiler warnings for this, and it’s a very simple example, but this shouldn’t even be an issue in the first place!

 
4
Kudos
 
4
Kudos

Now read this

Go has some weird built-in functions

Go is generally an exceptionally simple language, and that’s reflected through it’s very small number of keywords – one of the lowest of any modern language (source). There are also some built-in functions that I’m sure you use every day... Continue →