0

I am testing out wild cards, and thought to make a small program

public class WildCards {
    public static void main(String[] args) {
        order(new Box<Animal>());
        order(new Box<Human>());
        order(new Box<Mammal>());
    }
    public static void order(Box<? extends Mammal> box) {
        System.out.println(box.getContent() + " is a Mammal.");
    }
    public static void order(Box<? super Mammal> box) {
        System.out.println("Mammal is a " + box.getContent());
    }
}

class Thing extends Object {}
class Creature extends Thing {}
class Animal extends Creature {}
class Mammal extends Animal {}
class Primate extends Mammal {}
class Gorilla extends Primate {}
class Human extends Primate {}

class Box<T extends Thing> {
    T object;
    String getContent() {
        return new T().getClass().getName();
    }
}

But it isn't working, does anyone know what went wrong?

twee eat
  • 11
  • 2
  • Generics are "erased" at compile time, this means that `public static void order(Box extends Mammal> box)` and `public static void order(Box super Mammal> box) {` will both become `public static void order(Box box) {` which means you now have two methods with the same name. Change the method names. `return new T().getClass().getName();` is also wrong, you can simply make it `return new getClass().getName();` – MadProgrammer Aug 18 '23 at 01:42
  • Can you suggest another way of doing that? – twee eat Aug 18 '23 at 01:53
  • https://docs.oracle.com/javase/tutorial/java/generics/erasure.html – Basil Bourque Aug 18 '23 at 02:18

3 Answers3

2

You have 2 mostly unrelated problems here.

  1. The rules state that the identity of a method is its name and its erased parameter types. All methods must have a unique identity. Your 2 box methods both look like void order(Box) once you apply erasure (toss out all the <>, replace any typevars with their lower bound, i.e. given class Foo<T extends Number>, to erase 'T', replace it with Number. For just class Foo<T>, it becomes Object, same happens to class Foo<T super Number>. Why? Cuz the spec says so. Hence, you can't have 2 methods named box like this. Name one of them something else. Or unify them (make one method that accepts a Box<?>.

  2. Erasure

Generics is a figment of the compiler's imagination; it's compiler-checked documentation. The compiler uses it to link things together; if you declare a new typevar and use it in only one place, it's useless. For example:

public class List<E> {
  void add(E elem) { .. }
  E get(int idx) { .. }
}

This says: Hey, compiler? When someone writes List<String> x = new List<String>(); x.add("foo"); x.get(0).toLowerCase(), link the types together. The same happens here:

public static <T> T identity(T in) {return in;}

This tells the compiler: When some code calls this method, link up the Ts. Whatever the type is of the parameter? That's the type of the whole ordeal.

But that is just a thing the compiler does. The compiler applies this logic and then throws all generics right out the window.

That means new T() is not java and there's nothing you can do to make that java. Given 1 Box object, it is impossible to know at runtime what the generics of that box is. There is no fixing this.

rzwitserloot
  • 85,357
  • 5
  • 51
  • 72
0

A basket containing apples, a basket containing pears, and a basket covered with a cloth. Don’t you think these three things are all baskets?

The following, in fact, are all Box types:

Box
Box<?>
Box<? extends asd>
Box<? super bcd>
Box<xxx>

Therefore, your two order methods are actually duplicated, and you can change their names, one is called order1 and the other is called order2.

As for Wildcards:

You get a basket and find it covered with a cloth,

The man who gave you the basket said, Here are fruits,

So for you, you know, "Oh, there's fruit in it, I don't know what fruit it is, but it's okay, it's edible anyway", that's <? extends fruit>

0

Type-erasure

As mentioned in Comments, Java Generics was implemented with type-erasure (see tutorial). This means the specific class information is not retained after compiling.

The opposite of type-erasure, where class info is retained, is known as reified generics (see this Question). Java does not have reified generics; Java has type-erasure.

Because of type-erasure in Java, your overloaded order methods become ambiguous. You will get errors and warnings from your IDE and your compiler about the two order methods clashing. After erasure, your two order methods are indistinguishable.

public static void order( Box box ) { … }
public static void order( Box box ) { … }

We can resolve the clash by renaming these:

public static void order( Box<? extends Mammal> box ) { … }
public static void order( Box<? super Mammal> box ) { … }

… to distinct names:

public static void orderMammalOnDownToGorillaAndHuman( Box<? extends Mammal> box ) { … }
public static void orderMammalUpThroughObject( Box<? super Mammal> box ) { … }

Also because of type-erasure, your new T() code makes no sense, as explained in the other Answer here, by rzwitserloot. The class info is not known, so new cannot be invoked for an undefined class.

Example code

So let's rename those order methods. And make some other tweaks so this code runs.

package work.basil.example.typing;

import java.util.Objects;

public class WildCards
{
    public static void main ( String[] args )
    {
        WildCards.orderMammalOnDownToGorillaAndHuman ( new Box < Human > ( new Human ( ) ) );
        WildCards.orderMammalOnDownToGorillaAndHuman ( new Box < Mammal > ( new Gorilla ( ) ) );
        WildCards.orderMammalUpThroughObject ( new Box < Animal > ( new Gorilla ( ) ) );
//        WildCards.orderMammalUpThroughObject ( new Box < Primate > (new Gorilla () ) );  // ERROR: incompatible types: work.basil.example.typing.Box<work.basil.example.typing.Primate> cannot be converted to work.basil.example.typing.Box<? super work.basil.example.typing.Mammal>
    }

    public static void orderMammalUpThroughObject ( Box < ? super Mammal > box )
    {
        System.out.println ( box.getClassNameOfMemberField ( ) );
    }

    public static void orderMammalOnDownToGorillaAndHuman ( Box < ? extends Mammal > box )
    {
        System.out.println ( box.getClassNameOfMemberField ( ) );
    }
}

class Thing { }

class Creature extends Thing { }

class Animal extends Creature { }

class Mammal extends Animal { }

class Primate extends Mammal { }

class Gorilla extends Primate { }

class Human extends Primate { }

class Box < T extends Thing >
{
    T object;

    String getClassNameOfMemberField ( )
    {
        return Objects.isNull ( this.object ) ? "null" : this.object.getClass ( ).getName ( );
    }

    public Box ( T object )
    {
        this.object = object;
    }
}

When run:

work.basil.example.typing.Human
work.basil.example.typing.Gorilla
work.basil.example.typing.Gorilla

Notice that our 4th call in main fails to compile:

WildCards.orderMammalUpThroughObject ( new Box < Primate > (new Gorilla () ) );  

We get an error:

incompatible types: work.basil.example.typing.Box<work.basil.example.typing.Primate> cannot be converted to work.basil.example.typing.Box<? super work.basil.example.typing.Mammal>

This makes sense. We said the orderMammalUpThroughObject method takes an argument that is a Box holding any object whose class is Mammal or higher. This means Mammal, Animal, Creature, Thing, and Object. But the argument cannot be an object of the subclasses: Primate, Gorilla, Human. So:

  • Passing a Box of Animal with a Gorilla works.
  • Passing a Box of Primate with a Gorilla fails, a violation of our declaration.

For more discussion of extends versus super, see this Answer, and Questions this and this.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154