Collections.Emptylist() VS. New Instance

Collections.emptyList() vs. new instance

The main difference is that Collections.emptyList() returns an immutable list, i.e., a list to which you cannot add elements. (Same applies to the List.of() introduced in Java 9.)

In the rare cases where you do want to modify the returned list, Collections.emptyList() and List.of() are thus not a good choices.

I'd say that returning an immutable list is perfectly fine (and even the preferred way) as long as the contract (documentation) does not explicitly state differently.


In addition, emptyList() might not create a new object with each call.

Implementations of this method need not create a separate List object for each call. Using this method is likely to have comparable cost to using the like-named field. (Unlike this method, the field does not provide type safety.)

The implementation of emptyList looks as follows:

public static final <T> List<T> emptyList() {
return (List<T>) EMPTY_LIST;
}

So if your method (which returns an empty list) is called very often, this approach may even give you slightly better performance both CPU and memory wise.

Why would someone use Collections.emptyList in java?

Say you have to return a collection and you don't want to creating a couple of objects each time.

interface Configurable {
List<String> getConfigurationList();
}

// class which doesn't have any configuration
class SimpleConfigurable implements Configurable {
public List<String> getConfigurationList() { return Collections.emptyList(); }
}

Returning an empty collection is often preferable to returning null

What is the difference between Collections.emptyList() vs Collections::emptyList

Collections.emptyList() is a static method that returns a List<T>, i.e. the expression Collections.emptyList() will execute the emptyList() method and its value would be a List<T>. Therefore, you can only pass that expression to methods that require a List argument.

Collections::emptyList is a method reference, which can implement a functional interface whose single method has a matching signature.

A Supplier<List<T>> is a functional interface having a single method that returns a List<T>, which matches the signature of Collections.emptyList(). Therefore, a method - such as Optional's orElseGet(Supplier<? extends T> other) - that in your example requires a Supplier<? extends List<Student>> - can accept the method reference Collections::emptyList, which can implement that interface.

List.of() or Collections.emptyList()

What is the preferred Java 9 way of pointing to an empty and immutable list?

The difference is rather subtle so "preferred" depends on what you want to achieve. Some behavioral differences:

  • List.of will throw an exception on contains(null) invocations.
  • You can deserialize emptyList() on JDK 8 and previous, but not List.of.

In terms or conveying that you want an empty list, emptyList() might look better, but this is just a temporary convention. If developers start using List.of() (which is much shorter than Collections.emptyList()) then it will become a known and accepted way, it's just new. If you think about it, there are some constructs we use which do not always convey what they do by themselves, but we got accustomed to them.

So there is no strictly preferred way. If the behavior does not matter use whatever you want.

Collections.emptyList() returns a ListObject?

The issue you're encountering is that even though the method emptyList() returns List<T>, you haven't provided it with the type, so it defaults to returning List<Object>. You can supply the type parameter, and have your code behave as expected, like this:

public Person(String name) {
this(name,Collections.<String>emptyList());
}

Now when you're doing straight assignment, the compiler can figure out the generic type parameters for you. It's called type inference. For example, if you did this:

public Person(String name) {
List<String> emptyList = Collections.emptyList();
this(name, emptyList);
}

then the emptyList() call would correctly return a List<String>.

What is the difference between Collections.emptyList() and Collections.EMPTY_LIST

  • Collections.EMPTY_LIST returns an old-style List
  • Collections.emptyList() uses type-inference and therefore returns
    List<T>

Collections.emptyList() was added in Java 1.5 and it is probably always preferable. This way, you don't need to unnecessarily cast around within your code.

Collections.emptyList() intrinsically does the cast for you.

@SuppressWarnings("unchecked")
public static final <T> List<T> emptyList() {
return (List<T>) EMPTY_LIST;
}

Performance of Collections.emptyList and empty ArrayList with JIT compiler

Disclamer

All that is written below applies only to HotSpot JVM.

Short Answers

the JIT compiler doesn't do inlining or static method calls because
the executed method depends on the type.

This is opposite of true. See my answer.

Is there a performance difference between using
Collections.emptyList() or an empty ArrayList, especially when using a
JIT compiler?

In rare cases - yes. See microbenchmark results.

If I only called this method with ArrayList the JIT compiler could
inline ArrayList.get(). If I also made calls with Collections.empty()
it wouldn't be possible. Is that correct?

The short answer - it depends. JIT compiler is smart enough to recognize monomorphic, bimorphic and polimorphic call patterns and provide appropriate implementations.

Answer

In order to get a detailed answer I would recommend to read the following post about black magic of method dispatching. In few words

C2 does an interesting profile-guided optimization based on the
observed type profile. If there is only a single receiver type (that
is, the call site is monomorphic), it can simply check for the
predicted type, and inline the target directly. The same optimization
can and will be applied if there are two receiver types observed (that
is, the call site is bimorphic), at the cost of two branches.

Let's consider the following JMH example(if you haven’t learned about JMH then I suggest to read about it here).

@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(value = 5)
public class ExampleBench {

@Param("10000")
private int count;

List<Integer>[] arrays;
List<Integer>[] empty;
List<Integer>[] bimorphic;
List<Integer>[] polimorphic;

@Setup
public void setup(){
Random r = new Random(0xBAD_BEEF);
arrays = new List[count];
empty = new List[count];
bimorphic = new List[count];
polimorphic = new List[count];
for (int i = 0; i < arrays.length; i++) {
bimorphic[i] = r.nextBoolean() ? new ArrayList<Integer>(0) : Collections.<Integer>emptyList();
int i1 = r.nextInt(3);
switch (i1) {
case 0 : polimorphic[i] = new ArrayList<>(0);
break;
case 1 : polimorphic[i] = new LinkedList<>();
break;
case 2 : polimorphic[i] = Collections.emptyList();
break;
}
arrays[i] = new ArrayList<>(0);
empty[i] = Collections.emptyList();
}
}

@Benchmark
public float arrayList() {
List<Integer>[] l = arrays;
int c = count;
float result = 0;
for (int i = 0; i < c; i++) {
result += sum(l[i]);
}
return result;
}

@Benchmark
public float emptyList() {
List<Integer>[] l = empty;
int c = count;
float result = 0;
for (int i = 0; i < c; i++) {
result += sum(l[i]);
}
return result;
}

@Benchmark
public float biList() {
List<Integer>[] l = bimorphic;
int c = count;
float result = 0;
for (int i = 0; i < c; i++) {
result += sum(l[i]);
}
return result;
}

@Benchmark
public float polyList() {
List<Integer>[] l = polimorphic;
int c = count;
float result = 0;
for (int i = 0; i < c; i++) {
result += sum(l[i]);
}
return result;
}

int sum(List<Integer> list) {
int sum = 0;
for (int i = 0; i < list.size(); ++i) {
sum += list.get(i);
}
return sum;
}
}

Results are:

Benchmark               (count)  Mode  Cnt       Score       Error  Units
ExampleBench.arrayList 10000 avgt 5 22902.547 ± 27665.651 ns/op
ExampleBench.biList 10000 avgt 5 50459.552 ± 739.379 ns/op
ExampleBench.emptyList 10000 avgt 5 3745.469 ± 211.794 ns/op
ExampleBench.polyList 10000 avgt 5 164879.943 ± 5830.008 ns/op

In case of monomorphic and bimorphic calls JIT replaces virtual call by concrete implementations. For example in case of arrayList() we have the following output for -XX:+PrintInlining:

@ 27   edu.jvm.runtime.ExampleBench::sum (38 bytes)   inline (hot)
@ 6 java.util.ArrayList::size (5 bytes) accessor
\-> TypeProfile (15648/15648 counts) = java/util/ArrayList

for emptyList():

@ 27   edu.jvm.runtime.ExampleBench::sum (38 bytes)   inline (hot)
@ 6 java.util.Collections$EmptyList::size (2 bytes) inline (hot)
\-> TypeProfile (9913/9913 counts) = java/util/Collections$EmptyList

for biList():

@ 27   edu.jvm.runtime.ExampleBench::sum (38 bytes)   inline (hot)
@ 6 java.util.Collections$EmptyList::size (2 bytes) inline (hot)
@ 6 java.util.ArrayList::size (5 bytes) accessor
\-> TypeProfile (2513/5120 counts) = java/util/ArrayList
\-> TypeProfile (2607/5120 counts) = java/util/Collections$EmptyList

In case of polyList() JIT does not inline any implementation and uses true virtual call.

What is the advantages of using inline functions in these methods? Let's look at compiler-generated code for arrayList():

0x00007ff9e51bce50: cmp $0xf80036dc,%r10d     ;instance of 'java/util/ArrayList'
0x00007ff9e51bce57: jne L0000 ;if false go to L0000 (invokeinterface size)
0x00007ff9e51bce59: mov 0x10(%rdx),%ebp ;*getfield size optimization java.util.ArrayList::size@1

.....

0x00007ff9e51bce6d: retq
L0000: mov $0xffffffde,%esi ; true virtual call starts here
0x00007ff9e51bce73: mov %rdx,(%rsp)
0x00007ff9e51bce77: callq 0x00007ff9e50051a0 ; OopMap{[0]=Oop off=92}
;*invokeinterface size
; - edu.jvm.runtime.ExampleBench::sum@6 (line 119)
; {runtime_call}

As you can see JIT replaces virtual call by getfield.

Empty list: What is the difference between Arrays.asList() and Collections.emptyList()?

Collections.emptyList() is your best option because it reuses an object instead of creating a new object as it will be the case with Arrays.asList().

NB: Collections.emptyList() returns an immutable object so if you intend to modify it later in your code you will need to create your list explicitly instead because you will face the same issue with Arrays.asList() as it is immutable too.

What is the purpose of 'empty' Collection?

They actually return a casted, single instance, so you don't create new objects on the heap for each call to emptyList(). So it's faster and saves memory and GC.

It's an initializer that is used internally by List implementations to decide whether they actually need to allocate space (even adding elements) or keep the list untouched if no elements will be added at runtime (because it happens sometimes, more often than you may think).

Those objects are immutable so you can safely reuse the same instance anywhere, without the synchronization you usually put in place to ensure object status integrity.



Related Topics



Leave a reply



Submit