Java Generics supertypes Wildcard ? VS Object VS AbstractList

123 Views Asked by At

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

2

There are 2 best solutions below

7
David Jones On BEST ANSWER

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.

2
Daniel On

The first screenshot (with Box(Number) and Box(Integer)) is there to show that inheritance is not applied to generic type (that is inside).

Maybe it would be easier if this example would have additional diagram, where it would show that both Box and Box actually have common parent Box<?>.

Your testing idea with TempFruitHolder is actually right in terms of subclasses - but issue is that examples (Box and List screenshorts) are generalised not by superclasses, but by the class itself - as Box holding an Orange and Box holding an BloodOrange are still a Box - and that is what was referred to in the screenshots.

I suppose tutorial does this so it would be easier to say about type erasure a bit later (as in Java runtime there are actually a Box of objects (same as ?))