Search code examples
javagenericsjava-17lower-boundunbounded-wildcard

Java Generics supertypes Wildcard ? VS Object VS AbstractList


I have been trying to get a deep understanding of Java Generics, because I felt I needed to know how this works.

I have been going through the Oracle documentation and one thing confuses me.

In the Generics, Inheritance and Subtypes here: https://docs.oracle.com/javase/tutorial/java/generics/inheritance.html

It says the Common super type for any two Generic classes is the Object

Screenshot:

Generics, Inheritance and Subtypes in Java Generics

But later on when I read Wildcard and Subtyping here: https://docs.oracle.com/javase/tutorial/java/generics/subtyping.html

I see that the common super type between List and List is List<?> as can be seen below:

Common ancestor here is List<?>

So why is the common ancestor in the above example just Object, and in the example below a wildcard ?

Is it because it's a collection ? Or is there some other reason for this ?

I have used my examples to see what i get and I am getting something different.

I am building a dummy project to learn and here is the code:

abstract class Fruit implements PlantEatable {

    private boolean isRipe;

    private boolean isEatable;

    public boolean isRipe() {
        return isRipe;
    }

    public void setRipe(boolean ripe) {
        isRipe = ripe;
    }

    @Override
    public boolean isEatable() {
        return isEatable;
    }

    public void setEatable(boolean eatable) {
        isEatable = eatable;
    }
}

public class Orange extends Fruit{

}

public class BloodOrange extends Orange{

}

class TempFruitHolder<T> {
    private T fruit;

    public TempFruitHolder setFruit(T t) {
        fruit = t;
        return this;
    }

    public T getFruit() {
        return fruit;
    }

}

Running the program:

public class WildCardSubtypes {

    public static void main(String[] args) {
        BloodOrange bloodOrange = new BloodOrange();
        Orange bloodOrange2 = new BloodOrange();
        Orange orange = new Orange();

        TempFruitHolder<Orange> orangeTempFruitHolder = new TempFruitHolder<>();
        TempFruitHolder<BloodOrange> bloodOrangeTempFruitHolder = new TempFruitHolder<>();

        List<Orange> oranges = new ArrayList<>();
        List<BloodOrange> bloodOranges = new ArrayList<>();

        System.out.println("TempFruitHolder<Orange>'s super class: " +
                orangeTempFruitHolder.getClass().getSuperclass()
        );

        System.out.println("TempFruitHolder<BloodOrange>'s super class: " +
                bloodOrangeTempFruitHolder.getClass().getSuperclass()
        );

        System.out.println("List<Orange>'s super class: " +
            oranges.getClass().getSuperclass()
        );

        System.out.println("List<BloodOrange>'s super class: " +
            bloodOranges.getClass().getSuperclass()
        );

    }

}

Result 1:

Result of finding the Super Class:

I try to create a class level upper bound to see if the super class is different:

class TempFruitHolder<T extends Eatable> {
    private T fruit;

    public TempFruitHolder setFruit(T t) {
        fruit = t;
        return this;
    }

    public T getFruit() {
        return fruit;
    }

}

Result 2:

Same result

I apologise, maybe Im missing something straight forward or maybe i am looking at outdated documentation, but I am not sure what the super class is.

I even watched videos that said super class with Generics is always object.

Any advice would be much appreciated

Thanks


Solution

  • Java generics are a developer convenience feature that exists during compilation but does not at runtime. A process called called type erasure is applied by the compiler on generics that removes all type parameters and replaces them with their bounds or with Object if the type parameter is unbounded.

    The bound of TempFruitHolder<T> is Object because there are no constraints on what class the type parameter T may be.

    TempFruitHolder<T extends Fruit>, on the other hand, has a bound of Fruit because the compiler can enforce the contract that T is a subtype of Fruit. Type erasure will replace T with Fruit at compile time.

    Wildcards are useful when you don't need to use the generic type elsewhere, just handle an unknown type.

    public static void doSomethingWithFruit(List<? extends Fruit> fruit) { ... }
    

    This translates to give me a list of any type that subclasses Fruit. It's valid to pass a List<Orange> or List<BloodOrange> but not List<Potato> or List<Customer>.

    The wildcard is necessary because List<Orange> does not descend from List<Fruit> - type erasure will reduce both to List<Object> at runtime.