I've been doing some code archeology in some odd code and I came across something similar to this:
public abstract class Outer<T>
{
protected Outer(Inner<?> inner)
{
// ...
}
public static abstract class Inner<U extends Outer>
{
// ...
}
}
The thing that struck me was that there wasn't an unbounded wildcard type on Inner
's usage of the Outer
type (the <U extends Outer>
bit).
What is the implication of using Inner<U extends Outer<?>>
vs. Inner<U extends Outer>
?
I can compile and run tests successfully with both type versions, but am stumped on what is going on under the hood.
An unbounded wildcard is the one which enables the usage of all the subtypes of an unknown type i.e. any type (Object) is accepted as typed-parameter. For example, if want to accept an ArrayList of object type as a parameter, you just need to declare an unbounded wildcard.
Bounded and unbounded wildcards in Generics are two types of wildcards available on Java. Any Type can be bounded either upper or lower of the class hierarchy in Generics by using bounded wildcards.
Terminology: Nested classes are divided into two categories: non-static and static. Non-static nested classes are called inner classes. Nested classes that are declared static are called static nested classes. A nested class is a member of its enclosing class.
Inner
in the example, it's not actually an inner class, but a static nested class. Inner classes are non-static nested classes (see https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html).Inner
can either use the raw type Inner
(in which all type checks are bypassed - not interesting here), or specify an actual type for the type parameter U
. In the latter case the upper bound restricts that type to subtypes of the generic type Outer<T>
where T
can be any type, regardless of whether Inner
is declared as Inner<U extends Outer>
or Inner<U extends Outer<?>>
.The use of a raw type in the class signature still makes it possible to use strong type checking when declaring variables or parameters. For example, the following will compile (assuming Inner
has a no-args constructor):
Outer.Inner<Outer<String>> x = new Outer.Inner<Outer<String>>();
But replacing Outer<String>
on either side (but not on the other) with Outer
will yield a compiler error. This behavior would be exactly the same in case an unbounded wildcard would be used instead of a raw type, so no difference so far.
The actual difference is in how the the class Inner
is allowed to use variables of type U
. Suppose you are passing in such a variable in the constructor:
public Inner(U u) { this.u = u; }
Suppose also that Outer
has a method that takes an argument of type T
(its own type parameter), e.g.:
void add(T) { ...}
Now, in case of the raw upper bound (U extends Outer
), it would be legal for code in class Inner
to call this method with any object, e.g. a String:
this.u.add("anything")
although a compiler warning would be issued (unless suppressed), and in case the actual runtime type T
would be different from String
, a ClassCastException
would be thrown in code that depends on the object being a different type.
In the case of an unbounded wildcard however (U extends Outer<?>
), since T
is a specific but unknown type, calling the add
method will result in a compiler error regardless of which argument you give it.
Since you mention the code compiles fine in both cases, such method consuming T
either doesn't exist in Outer
, or it is not being called from Inner
. But by adding the unbounded wildcard, you can prove to the users of the class that this is not happening (because otherwise the code wouldn't compile).
In order to allow calling this.u.add(s)
with s
being a String
argument without using a raw type for the upper bound, Inner
would have to be declared as Inner<U extends Outer<? super String>>
, following the PECS principle, since U
is the type of a consumer in this case.
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