What's the difference between raw types, unbou

2019-01-07 11:45发布

问题:

I am reading the chapter on Generics in Effective Java.

Help me understand difference between Set, Set<?> and Set<Object>?

The following paragraph is taken from the book.

As a quick review, Set<Object> is a parameterized type representing a set that can contain objects of any type, Set<?> is a wildcard type representing a set that can contain only objects of some unknown type, and Set is a raw type, which opts out of the generic type system.

What is meant by "some unknown type"? Are all unknown types of type Object? In that case what is the specific difference between Set<?> and Set<Object>?

回答1:

  • a raw type (Set) treats the type as if it had no generic type information at all. Note the subtle effect that not only will the type argument T be ignored, but also all other type arguments that methods of that type might have. You can add any value to it and it will always return Object.
  • Set<Object> is a Set that accepts all Object objects (i.e. all objects) and will return objects of type Object.
  • Set<?> is a Set that accepts all objects of some specific, but unknown type and will return objects of that type. Since nothing is known about this type, you can't add anything to that set (except for null) and the only thing that you know about the values it returns is that they are some sub-type of Object.


回答2:

At runtime, the JVM will just see Set because of type erasure.

At compile-time, there's a difference:

Set<Object> parameterized a type E with Object thus, Set.add(E element) will be parameterized to Set.add(Object element).

Set<?> on the other hand, adds a wildcard on a type E so Set.add(E element) is translated to Set.add(? element). Since this is not compilable, java instead "translates" it as Set.add(null element). It means that you cannot add anything to that set (except a null). The reason is that the wildcard is referencing to an unknown type.



回答3:

what is meant by "some unknown type"

Exactly what it means - the Set has some generic parameter, but we don't know what it is.

So the set assigned to a Set<?> variable might be a Set<String>, or a Set<Integer>, or a Set<Map<Integer, Employee>> or a set containing any other specific type.

So what does that mean for how you can use it? Well, anything you get out of it will be an instance of the ?, whatever that is. Since we don't know what the type parameter is, you can't say anything more specific than that elements of the set will be assignable to Object (only because all classes extend from it).

And if you're thinking of adding something to the set - well, the add method takes a ? (which makes sense, since this is the type of objects within the set). But if you try to add any specific object, how can you be sure this is type-safe? You can't - if you're inserting a String, you might be putting it into a Set<Integer> for example, which would break the type-safety you get from generics. So while you don't know the type of the generic parameter, you can't supply any arguments of this type (with the single exception of null, as that's an "instance" of any type).


As with most generics-related answers, this has focused on collections because they're easier to comprehend instinctively. However the arguments apply to any class that takes generic parameters - if it's declared with the unbounded wildcard parameter ?, you can't supply any arguments to it, and any values you receive of that type will only be assignable to Object.



回答4:

Set: No generics here, unsafe. Add what you want.

Set<?>: A set of a certain type we don't know from our scope. The same as Set<? extends Object>. Can reference to Sets of any type, but that type must be defined at the point where the set is actually instantiated. With the wildcarded reference, we can´t modify the set (we can't add or remove with anything not null). Is like a view.

Set<Object>: Set containing Objects (base class only, not subclasses). I mean you can instance the set using Collections of type Object, like HashSet<Object> but not with HashSet<String>. You can of course add elements of any type to the set, but just because it happens that everything is an Object or a subclass of Object. If the set were defined as Set, you can only add Numbers and subclasses of Number, and nothing more.



回答5:

The difference between Set<Object> and Set<?> is that a variable of type Set<?> can have a more specific generic type assigned to it, as in:

Set<?> set = new HashSet<Integer>();

while Set<Object> can only be assigned Set<Object>:

Set<Object> set = new HashSet<Integer>(); // won't compile

The Set<Object> is still useful, since any object can be put into it. It is much like the raw Set in that sense, but works better with the generic type system.



回答6:

I was explaining this item to my friend and specifically asked for the "safeAdd" method as a counter to the unsafeAdd example. So here it is.

public static void main(String[] args) {
    List<String> strings = new ArrayList<String>();

    unsafeAdd(strings, new Integer(42)); // No compile time exceptions

    // New 
    safeAdd(strings, new Integer(42)); // Throwing an exception at compile time


    String s = strings.get(0); // Compiler-generated cast

}

private static void unsafeAdd(List list, Object o) {
    list.add(o);
}


private static <E> void safeAdd(List<E> list, E o) {
    list.add(o);
}


回答7:

Set<?> set = new HashSet<String>();
set.add(new Object()); // compile time error

Since we don’t know what the element type of set stands for, we cannot add objects to it. The add() method takes arguments of type E, the element type of the Set. When the actual type parameter is ?, it stands for some unknown type. Any parameter we pass to add would have to be a subtype of this unknown type. Since we don’t know what type that is, we cannot pass anything in. The sole exception is null, which is a member of every type.

Given a Set<?>, we can call get() and make use of the result. The result type is an unknown type, but we always know that it is an object. It is therefore safe to assign the result of get() to a variable of type Object or pass it as a parameter where the type Object is expected.



回答8:

Let say,you are writing a common method to print the elements appearing in a List. Now, this method could be used for printing Lists of type Integer, Double,Object or any other type. Which one do you choose?

  1. List<Object> : If we use this one, this would help us to print only the elements of type Object. It wont be useful for printing elements belonging to other classes like Double. This is because Generic does not support Inheritance by default and needs to be specified explicitely using 'super' keyword.

    // Would only print objects of type 'Object'
    
    public static void printList(List<Object> list) {
        for (Object elem : list)
            System.out.println(elem + " ");
        System.out.println();
    }
    
  2. List<?> : This could help us have a common method for printing any datatype. We could use this method for printing instances of any type.

    //  The type would really depend on what is being passed
    public static void printList(List<?> list) {
        for (Object elem: list)
            System.out.print(elem + " ");
        System.out.println();
    }