Nested wildcards

后端 未结 2 1850
Happy的楠姐
Happy的楠姐 2020-12-04 02:15

Found fact about unbounded wildcards that is annoying me. For example:

public class Test {

      private static final Map

        
2条回答
  •  挽巷
    挽巷 (楼主)
    2020-12-04 02:50

    The short answer is that generics are invariant, so this will not work.

    The long answer takes a while to understand. It starts simple:

    Dog    woof   = new Dog();
    Animal animal = woof; 
    

    Works just fine, since a Dog is an Animal. On the other hand:

    List< Animal > fauna   = new ArrayList<>();
    List<  Dog   > dogs    = new ArrayList<>();
    fauna = dogs;
    

    will fail to compile, because generics are invariant; basically a List is not a List.

    How come? Well if the assignment would have been possible, what is stopping you from doing:

    fauna.add(new Cat());
    dogs.get(0); // what is this now?
    

    A compiler could be smarter here, actually. What if your Lists are immutable? After creation, you can't put anything into them. In such a case, fauna = dogs, should be allowed, but java does not do this (scala does), even with the newly added Immutable collections in java-9.

    When Lists are immutable, they are said to be Producers, meaning they don't take the generic type as input. For example:

    interface Sink {
        T nextElement();
    }
    

    Since Sink never takes T as input, it is a Producer of Ts (not a Consumer), thus it could be possible to say:

    Sink objects ... 
    Sink strings ...
    objects = strings;
    
    
    

    Since Sink has no option to add elements, we can't break anything, but java does not care and prohibits this. kotlinc (just like scalac) allows it.

    In java this shortcoming is solved with a "bounded type":

    List animals = new ArrayList<>();
    animals = dogs;
    

    The good thing is that you still can't do: animals.add(new Cat()). You know exactly what that list holds - some types of Animals, so when you read from it, you always, for a fact, know that you will get an Animal. But because List is assignable to List for example, addition is prohibited, otherwise:

    animals.add(new Cat()); // if this worked
    dogs.get(0); // what is this now?
    

    This "addition is prohibited" is not exactly correct, since it's always possible to do:

    private static  void topLevelCapture(List list) {
        T t = list.get(0);
        list.add(t);
    }
    
    topLevelCapture(animals);
    

    Why this works is explained here, what matters is that this does not break anything.


    What if you wanted to say that you have a group of animals, like a List? May be the first thing you want to do is List>:

    List> groups = new ArrayList<>();
    List> dogs = new ArrayList<>();
    groups = dogs;
    

    this would obviously not work. But what if we added bounded types?

    List> groups = new ArrayList<>();
    List> dogs = new ArrayList<>();
    groups = dogs;
    

    even if List is a List the generics of these are not (generics are invariant). Again, if this would have been allowed, you could do:

    groups.add();
    dogs.get(0); // obvious problems
    

    The only way to make it work would be via:

     List> groups = new ArrayList<>();
     List> dogs = new ArrayList<>();
     groups = dogs;
    

    that is, we found a super type of List in List and we also need the bounded type ? extends List... so that the outer lists themselves are assignable.


    This huge intro was to show that:

    Map> map = new HashMap<>();
    Map broader = new HashMap<>();
    broader = map;
    

    would compile because there are no restrictions what-so-ever here, the broader map basically is a map "of anything".

    If you read what I had to say above, you probably know why this is not allowed:

    Map> map = new HashMap<>();
    Map> lessBroader = new HashMap<>();
    lessBroader = map;
    

    if it would have been allowed, you could do:

    Map newMap = new HashMap<>(); // this is a Map after all
    lessBroader.add(12, newMap);
    map.get(12); // hmm...
    

    If maps were immutable and the compiler would care, this could have been avoided and the assignment could have made to work just fine.

    提交回复
    热议问题