Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding Raw vs Unbounded Wildcard in Static Nested Class Type Definition

Tags:

java

generics

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.

like image 669
Alex Moore Avatar asked Jan 27 '16 04:01

Alex Moore


People also ask

What is unbounded wildcard?

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.

What is bounded and unbounded wildcards in generics?

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.

What are two types of nested classes What are the differences between them?

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.


1 Answers

  1. Although called 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).
  2. The fact that it's a static nested class instead of a top-level class doesn't matter here.
  3. Code using class 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.

like image 59
herman Avatar answered Oct 29 '22 12:10

herman