Is there any practical difference between the following approaches to print all elements in a range?
public static void printA(Iterable<?> range)
{
for (Object o : range)
{
System.out.println(o);
}
}
public static <T> void printB(Iterable<T> range)
{
for (T x : range)
{
System.out.println(x);
}
}
Apparently, printB
involves an additional checked cast to Object (see line 16), which seems rather stupid to me -- isn't everything an Object anyway?
public static void printA(java.lang.Iterable);
Code:
0: aload_0
1: invokeinterface #18, 1; //InterfaceMethod java/lang/Iterable.iterator:()Ljava/util/Iterator;
6: astore_2
7: goto 24
10: aload_2
11: invokeinterface #24, 1; //InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
16: astore_1
17: getstatic #30; //Field java/lang/System.out:Ljava/io/PrintStream;
20: aload_1
21: invokevirtual #36; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V
24: aload_2
25: invokeinterface #42, 1; //InterfaceMethod java/util/Iterator.hasNext:()Z
30: ifne 10
33: return
public static void printB(java.lang.Iterable);
Code:
0: aload_0
1: invokeinterface #18, 1; //InterfaceMethod java/lang/Iterable.iterator:()Ljava/util/Iterator;
6: astore_2
7: goto 27
10: aload_2
11: invokeinterface #24, 1; //InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
16: checkcast #3; //class java/lang/Object
19: astore_1
20: getstatic #30; //Field java/lang/System.out:Ljava/io/PrintStream;
23: aload_1
24: invokevirtual #36; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V
27: aload_2
28: invokeinterface #42, 1; //InterfaceMethod java/util/Iterator.hasNext:()Z
33: ifne 10
36: return
In your example, the generic type is used in exactly one spot of the signature. In this scenario, a type T
has no advantage over a wildcard for the caller. In your example, the type also has no advantage for the implementor of the method.
I find the wildcard version easier to understand for a caller, since it explicitly says "I don't care about the type at all".
In your example, the checkcast
is indeed superfluous. It would be required if T
was bounded, like in T extends Number
. Then a checkcast for Number
is required, because the local variable x
will be of type Number
, but the Iterator.next()
method still returns Object
. Seems like the Java compiler does not bother optimizing away the cast. The JIT probably will do so at runtime.
UPDATE:
If the generic type is used in several spots, like in cletus's answer, you have no option but to use a generic type T
, or else the compiler sees no connection between the parameter type/return type (any two wildcards are distinct for the compiler).
A borderline case is when the signature only has the type in one spot, but the implementation needs it to be a generic type rather than a wildcard. Think of a void swap(List<T> list, int a, int b)
method. It needs to take elements out of the list and put them back in. IIRC, Effective Java suggests using a public method with the wildcard, and an internal helper method with a type containing the actual implementation. This way, the user gets a simple API, and the implementer still has type safety.
public void swap(List<?> list, int a, int b){
swapHelper(list, a, b);
}
private <T> void swapHelper(List<T> list, int a, int b){
...
}
The second is more flexible. A better example is: It's saying something about the type. Whether or not that's useful to you depends on what the function does.
The second shows its usefulness when you want to return something from the method:
public static <T> List<T> reverse(List<T> list) {
for (int i=0; i<n/2; i++) {
T value = list.get(i);
list.set(i, list.get(list.size() - i - 1));
list.set(list.size() - i = 1, value);
}
return list;
}
It's possibly a better example to copy the array but the point remains that you can't really do the above with List<?>
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With