[Java] [Java]Adding an element to a collection causes a compile error

2 minute read

It is up to the implementation whether the contents of the Java Collection can be changed. Whether or not the Collection can be changed is unknown until you check the source code or try it.

For example, the javadoc for the List interface says:

UnsupportedOperationException-if the add operation is not supported by this list

UnsupportedOperationException-if the add operation is not supported by this list

Since this is such a specification in itself, it can not be helped, but it is quite inconvenient for the caller. In this article, I’ll show you how to tell callers that this Collection is immutable.

References

https://www.gwtcenter.com/covariance-and-contravariance The content itself is mostly written here. I found it useful to convey the immutability of the Collection to the compiler, so I will dig into that part this time.

Environment

I am using java11 but it works with java8 as well.

$ java --version
openjdk 11.0.8 2020-07-14
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.8+10)
Eclipse OpenJ9 VM AdoptOpenJDK (build openj9-0.21.0, JRE 11 Mac OS X amd64-64-Bit Compressed References 20200715_677 (JIT enabled, AOT enabled)
OpenJ9-34cf4c075
OMR-113e54219
JCL-95bb504fbb based on jdk-11.0.8+10)

Main subject

Make an immutable list

The java.util.Arrays#asList method returns a List, which cannot be used with add.

List<String> unmodifiableList = Arrays.asList("A", "B", "C");
unmodifiableList.add("d"); // UnsupportedOperationException

Make add method compile error

Set the variable generics to ? extends String. Then all the methods that receive generics as an argument will result in a compilation error.

add

List<? extends String> unmodifiableList = Arrays.asList("A", "B", "C");
unmodifiableList.add("d"); // java: incompatible type: cannot convert java.lang.String to capture #1 of extends java.lang.String

addAll

List<? extends String> unmodifiableList = Arrays.asList("A", "B", "C");
List<? extends String> anotherList = Arrays.asList("B");
unmodifiableList.addAll(anotherList); // java: Incompatible type: java.util.List<? extends java.lang.String capture #1> to java.util.Collection<? extends? extends java.lang.String Cannot convert to capture #2>

replaceAll

List<? extends String> unmodifiableList = Arrays.asList("A", "B", "C");
unmodifiableList.replaceAll(s -> s.replaceAll("C", "D"));

By the way, if you return the value as it is, no compilation error will occur.

List<? extends String> unmodifiableList = Arrays.asList("A", "B", "C");
unmodifiableList.replaceAll(s -> s);
unmodifiableList.forEach(System.out::println);

Retrieve value

It can be retrieved as a String type.

get

List<? extends String> unmodifiableList = Arrays.asList("A", "B", "C");
String s = unmodifiableList.get(0);
System.out.println(s); // A

indexOf

The method that takes an Object type argument such as indexOf can be used as is.

List<String> unmodifiableList = Arrays.asList("A", "B", "C");
int index = unmodifiableList.indexOf("B");
System.out.println(index); // 1

resubstitution

Since you can’t reassign variables that don’t use extends for generics, you can’t basically remove extends.

List<? extends String> unmodifiableList = Arrays.asList("A", "B", "C");
List<String> list = unmodifiableList; // java: Incompatible type: java.util.List<? extends java.lang.String capture #1> cannot be converted to java.util.List<java.lang.String>

Since the reverse assignment is possible, it is possible to use List<String as the variable in the method and List<? extends String> as the return value.

List<String> unmodifiableList = Arrays.asList("A", "B", "C");
List<? extends String> list = unmodifiableList;

A loophole called raw type

However, you can assign to variables that do not use generics.

List<? extends String> unmodifiableList = Arrays.asList("A", "B", "C");
List rowList = unmodifiableList;
rowList.add("D"); // UnsupportedOperationException

~~ The raw type itself is an Unsupported Operation ~~ If you want to make a mutable Collection such as ArrayList immutable, Collections#unmodifiableList.

Summary

Extra characters will be added to the generics part, and since it is used differently from the original purpose, I think it is a good idea to use it a lot. It’s not all-round, so it’s not all-purpose. However, is it more kind than telling at runtime that add etc. cannot be used? I think so, so I introduced it this time.