Do not declare variables in List in Java

Do not declare variables in List in Java

//bad example
List<String> list = new ArrayList<>();

//Good example
ArrayList<String> list = new ArrayList<>();

Until now, it has been said that variables should be declared in the List type, which is an interface, and in fact I have also declared a large number of variables in the List type. But I came to think it was a mistake. The reason is explained below.

The reason why it has been said that it should be declared in List

The interface is the definition of the specification. It promises that "the class that implements this interface will have the methods written in this interface". By using the implementation class through the interface, the user side does not need to be aware of the implementation and loose coupling is realized.

To explain this concretely in Java List, by declaring List <String> list = new ArrayList <> ();, you will not depend on the ArrayList class. Therefore, even if you replace the ArrayList with an existing LinkedList or my own thought-provoking Saikyo Norisu, it will work without any problem. A new List class may be added to the API in the future, but this too can be easily replaced.

The current predominant idea is to achieve loose coupling by handling implementation classes through the List interface, making the program highly extensible and resistant to change.

Actually try

Let's try it out.

sample

public static void main(String[] args)
{
	List<Integer> list = getList();
	listTest(list);
}

public static List<Integer> getList()
{
	return new ArrayList<>();
}
public static void listTest(List<Integer> list)
{
	System.out.println(list.getClass());
	
	list.add(1);
	list.add(2);
	list.add(3);
	
	list.forEach(System.out::println);
}

It is a sample that just makes a list, puts numbers and displays it. You can also check the class name in the list.

Naturally, the result will be like this.

class java.util.ArrayList
1
2
3

Now let's replace the List class.

■CopyOnWriteArrayList

public static void main(String[] args)
{
	List<Integer> list = getList();
	listTest(list);
}

public static List<Integer> getList()
{
	return new CopyOnWriteArrayList<>();
}
public static void listTest(List<Integer> list)
{
	System.out.println(list.getClass());
	
	list.add(1);
	list.add(2);
	list.add(3);
	
	list.forEach(System.out::println);
}
class java.util.concurrent.CopyOnWriteArrayList
1
2
3

■LinkedList

public static void main(String[] args)
{
	List<Integer> list = getList();
	listTest(list);
}

public static List<Integer> getList()
{
	return new LinkedList<>();
}
public static void listTest(List<Integer> list)
{
	System.out.println(list.getClass());
	
	list.add(1);
	list.add(2);
	list.add(3);
	
	list.forEach(System.out::println);
}
class java.util.LinkedList
1
2
3

■ Self-made list class

public static void main(String[] args)
{
	List<Integer> list = getList();
	listTest(list);
}

public static List<Integer> getList()
{
	return new SaikyoList<>();
}
public static void listTest(List<Integer> list)
{
	System.out.println(list.getClass());
	
	list.add(1);
	list.add(2);
	list.add(3);
	
	list.forEach(System.out::println);
}

/**
 *When I think about it
 *Implementing a List is tedious, so extend an ArrayList.
 */
public class SaikyoList<E> extends ArrayList<E>
{
	@Override
	public boolean add(E e)
	{
		//Since it is a good day, add twice as much as the normal list
		super.add(e);
		super.add(e);
		return true;
	}
}
class test.SaikyoList
1
1
2
2
3
3

■Collections.emptyList();

public static void main(String[] args)
{
	List<Integer> list = getList();
	listTest(list);
}

public static List<Integer> getList()
{
	return Collections.emptyList();
}
public static void listTest(List<Integer> list)
{
	System.out.println(list.getClass());
	
	list.add(1);
	list.add(2);
	list.add(3);
	
	list.forEach(System.out::println);
}
class java.util.Collections$EmptyList
Exception in thread "main" java.lang.UnsupportedOperationException
	at java.base/java.util.AbstractList.add(AbstractList.java:153)

Oh?

■Collections.singletonList(1);

public static void main(String[] args)
{
	List<Integer> list = getList();
	listTest(list);
}

public static List<Integer> getList()
{
	return Collections.singletonList(1);
}
public static void listTest(List<Integer> list)
{
	System.out.println(list.getClass());
	
	list.add(1);
	list.add(2);
	list.add(3);
	
	list.forEach(System.out::println);
}
class java.util.Collections$SingletonList
Exception in thread "main" java.lang.UnsupportedOperationException
	at java.base/java.util.AbstractList.add(AbstractList.java:153)

■Arrays.asList();

public static void main(String[] args)
{
	List<Integer> list = getList();
	listTest(list);
}

public static List<Integer> getList()
{
	return Arrays.asList();
}
public static void listTest(List<Integer> list)
{
	System.out.println(list.getClass());
	
	list.add(1);
	list.add(2);
	list.add(3);
	
	list.forEach(System.out::println);
}
class java.util.Arrays$ArrayList
Exception in thread "main" java.lang.UnsupportedOperationException
	at java.base/java.util.AbstractList.add(AbstractList.java:153)

■List.of();

public static void main(String[] args)
{
	List<Integer> list = getList();
	listTest(list);
}

public static List<Integer> getList()
{
	return List.of();
}
public static void listTest(List<Integer> list)
{
	System.out.println(list.getClass());
	
	list.add(1);
	list.add(2);
	list.add(3);
	
	list.forEach(System.out::println);
}
class java.util.ImmutableCollections$List0
Exception in thread "main" java.lang.UnsupportedOperationException
	at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:70)

■List.of(1);

public static void main(String[] args)
{
	List<Integer> list = getList();
	listTest(list);
}

public static List<Integer> getList()
{
	return List.of(1);
}
public static void listTest(List<Integer> list)
{
	System.out.println(list.getClass());
	
	list.add(1);
	list.add(2);
	list.add(3);
	
	list.forEach(System.out::println);
}
class java.util.ImmutableCollections$List1
Exception in thread "main" java.lang.UnsupportedOperationException
	at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:70)

■List.of(1, 2);

public static void main(String[] args)
{
	List<Integer> list = getList();
	listTest(list);
}

public static List<Integer> getList()
{
	return List.of(1, 2);
}
public static void listTest(List<Integer> list)
{
	System.out.println(list.getClass());
	
	list.add(1);
	list.add(2);
	list.add(3);
	
	list.forEach(System.out::println);
}
class java.util.ImmutableCollections$List2
Exception in thread "main" java.lang.UnsupportedOperationException
	at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:70)

■List.of(1, 2, 3);

public static void main(String[] args)
{
	List<Integer> list = getList();
	listTest(list);
}

public static List<Integer> getList()
{
	return List.of(1, 2, 3);
}
public static void listTest(List<Integer> list)
{
	System.out.println(list.getClass());
	
	list.add(1);
	list.add(2);
	list.add(3);
	
	list.forEach(System.out::println);
}
class java.util.ImmutableCollections$ListN
Exception in thread "main" java.lang.UnsupportedOperationException
	at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:70)

■Collections.unmodifiableList(new ArrayList<>());

public static void main(String[] args)
{
	List<Integer> list = getList();
	listTest(list);
}

public static List<Integer> getList()
{
	return Collections.unmodifiableList(new ArrayList<>());
}
public static void listTest(List<Integer> list)
{
	System.out.println(list.getClass());
	
	list.add(1);
	list.add(2);
	list.add(3);
	
	list.forEach(System.out::println);
}
class java.util.Collections$UnmodifiableRandomAccessList
Exception in thread "main" java.lang.UnsupportedOperationException
	at java.base/java.util.Collections$UnmodifiableCollection.add(Collections.java:1056)

UnsupportedOperationException!

UnsupportedOperationException!!

UnsupportedOperationException!!!

I just said that I would implement add ...

It was a lie if there was Sman

How did this happen

The List interface defines add, remove, set, etc., and it's clear that we have a variable collection in mind. However, lists with a fixed number of elements (java.util.Arrays \ $ ArrayList) and immutable lists (java.util.Collections \ $ UnmodifiableList, etc.) also implement the List interface, and one of the methods to realize immutability. It takes the form of just throwing an "UnsupportedOperationException" for the part.

In other words, Java List has immutable and variable implementation classes even though it defines methods to change the list, so the methods to change those lists become methods that cannot be used without being aware of the implementation class. It's closed. Far from being loosely coupled, it may not work if the implementation class is replaced, so even the coupling is not done properly, it is a lie coupling.

This is clearly shown in the "Collectors.toList" method used as an argument to stream.collect. The current implementation returns an ArrayList, but the documentation says:

There is no guarantee of the type, variability, serializability, or thread safety of the returned List. https://docs.oracle.com/javase/jp/8/docs/api/java/util/stream/Collectors.html#toList--

Originally, I think immutable collections should have been treated as Iterable. It must be said that it is a design error to implement the List interface because we want to treat things that do not have the function as a List together as a List.

~~ Java is shit ... ~~

What's more, it's also annoying to know at compile time in the form of run-time exceptions.

What should be done

As mentioned above, the List interface and its implementation class have a fatal flaw. Therefore, the purpose of this article is to show that add and other methods are valid by declaring variables as ArrayList type instead of List type. In fact, a list that is so immutable that List <Integer> list = new ArrayList <> (Arrays.asList (1,2,3)); is recognized as an idiom is not easy to use, so convert it to a variable ArrayList and use it. You're right. (I think it depends on the application ...)

If you declare it in ArrayList like this, all the sample code of "Try it" could detect the problem before execution as a compile error. However, LinkedList etc. that have no problem will also cause a compile error, but I think that many people would appreciate it if they made an error at compile time rather than leaving a runtime exception.

However, this does not seem to be well recognized as a problem. Probably not a big problem because sensible programmers are using List with care, but it's okay if you use it with care! If you can do it, you don't need inspection exceptions or null safety. Personally, I feel that an immutable list that throws an exception only when a specific method is called depending on the implementation class is scarier than null, which can be easily determined by list! = null.

However, considering the current situation where the problem is not recognized as such,

ArrayList<String> list = new ArrayList<>();
Iterable<Integer> iterable = getList();
ArrayList<Hoge> hogeList = new ArrayList<>(getList());

It's hard to say that writing like this will result in heretical code. [^ 1]

Effective Java also recommends "returning an empty array or collection instead of null", so you may have written a method like this.

public List<String> readFile(String path)
{
	File file = new File(path);
	if(!file.exists())
	{
		return Collections.emptyList();
	}

	List<String> list = new ArrayList<>();
	
	//File reading process
	
	return list;
}

This method may return an "empty immutable list, an empty mutable list, a mutable list with elements". Isn't that scary?

Summary

The realization of loose coupling itself is good. However, Java's List is poorly designed, so even if you use the List interface, it will not be loosely coupled, and there is a risk of runtime exceptions.

The interface is a promise to have defined methods. Don't write a promise-breaking implementation class.

Implement ...! I will implement it ... This time, the implementation method is still Not specified </ font>

Please you guys too I want you to remember

In other words ... If we feel like it add is You can just throw an UnsupportedOperationException It will be possible ...! </ font>

[^ 1]: By the way, the Iterable type can get the stream with StreamSupport.stream (iterable.spliterator (), false);. However, even though list.stream () is troublesome, writing this every time is ...

Recommended Posts