List<Object> variable being assignment compatible with other generic Lists like List<String> in Java

Arjun :

I've been trying to get my head around java generics for the last few days. From what I understand, Java generics are not covariant so List<Object> is not assignment compatible with other generics Lists

But here in the following program, nameAndPhone.collect() method returns a List of type List<NamePhone> and when I replace reference variable List<NamePhone> npList with List<Object> npList the program still compiles without warnings.

I tried this with a similar method returning List<String> as well, and using List<Object> reference variable did not result in any error.

Why is List<Object> assignment compatible with List<NamePhone> here?

import java.util.*;
import java.util.stream.*;

class NamePhoneEmail
{
    String name;
    String phonenum;
    String email;

    NamePhoneEmail(String n, String p, String e)
    {
        name = n;
        phonenum = p;
        email = e;
    }
}

class NamePhone
{
    String name;
    String phonenum;

    NamePhone(String n, String p)
    {
        name = n;
        phonenum = p;
    }
}

public class CollectDemo
{
    public static void main(String[] args)
    {

        ArrayList<NamePhoneEmail> myList = new ArrayList<>();
        myList.add(
            new NamePhoneEmail("Larry", "555-5555", "[email protected]"));

        myList.add(
            new NamePhoneEmail("James", "555-4444", "[email protected]"));

        Stream<NamePhone> nameAndPhone =
            myList.stream().map((a) -> new NamePhone(a.name, a.phonenum));

        List<NamePhone> npList = nameAndPhone.collect(Collectors.toList());
    }
}
Marco13 :

You are right that the types are not assignment compatible.

In doubt, this can easily be verified:

List<Object> a = null;
List<NamePhone> b = null;
a = b;                      // Error!

The reason of why it seems to be assignment compatible in this case is the target type inference. The inference process can be complicated - particularly in this case, which involves a Collector, which has three type parameters.

I'll try to flesh out the relevant parts here:

The signature of the collect method is as follows:

<R, A> R collect(Collector<? super T, A, R> collector);

This is called on the Stream<T> instance. In your case, this is a Stream<NamePhone>. But note that the method itself has additional generic parameters, namely R and A. The relevant one here is R, which is the return type.

The Collector that is passed in there is the one created by the toList method, which looks as follows:

public static <T> Collector<T, ?, List<T>> toList()

It is also generic. The type parameter will basically be "substituted", based on the context in which the method is called.

So when you write this:

List<NamePhone> npList = nameAndPhone.collect(Collectors.toList());

then you will have the following type assignments:

  1. The T of the Stream is NamePhone
  2. The T of the Collector is NamePhone
  3. The R of the collect method is List<NamePhone>

But you could also write

List<Object> npList = nameAndPhone.collect(Collectors.toList());

In this case

  1. The T of the Stream is NamePhone
  2. The T of the Collector is Object
  3. The R of the collect method is List<Object>

Note that this is only possible because the collect method accepts a Collector<? super T, ...>. It would not work if it expected a Collector<T, ...>. This basically means that you can use the elements from the Stream and collect them into a new List, as long as the type parameter of the desired list is a supertype of the elements in the stream.


Conceptually, this makes sense, because it's in some way analogous to

List<Integer> integers = ...;
List<Number> numbers = ...;
for (Integer i : integers) numbers.add(i); // This should work as well!

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=443167&siteId=1