简体   繁体   中英

Generics - Why don't some versions work?

This code is taken from the OCP textbook. Why do versions 1 and 2 of the addSound method not compile but versions 3 and 4 compile? Compilation error message is "add(capture ) in List cannot be applied to java.lang.String"

public static void main(String[] args) {
    List<String> strings = new ArrayList<>();
    strings.add("tweet");
    List<Object> objects = new ArrayList<>(strings);
    addSound(strings);
    addSound(objects);
}

//version 1
public static void addSound(List<?> list) {
    list.add("quack");
}

//version 2
public static void addSound(List<? extends Object> list) {
    list.add("quack");
}

//version 3
public static void addSound(List<Object> list) {
    list.add("quack");
}

//version 4
public static void addSound(List<? super String> list) {
    list.add("quack");
}

What you need to ask is whether the compiler can be certain that list 's element type is consistent with String :

  1. <?> -- the type is unknown. It could be Integer , for example. We can't know that String fits. new List<Integer>().add("quack") wouldn't compile, so neither can this.
  2. <? extends Object> <? extends Object> -- again, the type is unknown. All classes extend Object . So again it could be Integer .
  3. <Object> - No generics here. String is a subclass of Object . new List<Object>().add("quack") works, so this compiles.
  4. <? super String> <? super String> this is unknown, with the condition (" bounds ") that it's a superclass of of String . Since we know that String extends Object , this type could only be Object or String . List<Object>().add("quack") is OK, and List<String>().add("quack") is OK, so this compiles.

The reasoning for number 4, of course applies to more complex type hierarchies:

BufferedInputStream extends FilterInputStream extends InputStream extends Object . InputStream implements the interfaces Closeable and AutoCloseable .

So <? super FilterInputStream> <? super FilterInputStream> matches Object , InputStream , FilterInputStream , Closeable and AutoCloseable .

So a List<? super FilterInputStream> l List<? super FilterInputStream> l could be a List<Closeable> or it could be a List<InputStream >, etc. But all you know for sure is that you can l.add(new FilterInputStream()) or, since it's a subclass l.add(new BufferedInputStream())

In Java you have type safety and type erasure. Both concepts are the reason why version 1 and version 2 are not working.

You can not add anything to List<?> since you have no clue on what kind of objects are inside the list. It could be possibly be a List of Integer or String for example.

public static void addSound(List<?> list) {
    list.add("quack"); // does not work since List<?> can be anything - type safety not guaranteed
}

The Java compiler removes all types on run time which is called type erasure eg List<String> will be List and List<Integer> will also be List - so you can not differentiate them. You might see it in the bytecode but that is irrelevant here. That's the reason why version 2 is not working

//version 2 - has the same type erasure as version 3
public static void addSound(List<? extends Object> list) {
    list.add("quack");
}

//version 3
public static void addSound(List<Object> list) {
    list.add("quack");
}

The version 2 also does not work because of type safety once again. See this answer for generic bounds

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM