0

I created an interface:

public interface Person {
    <T extends Food> void eat(T food);
}

where Food is a concrete but non-final class.

I would like to implement a Person's eat method with a subclass of Food, i.e. Fruit, like this:

public class Man implements Person {
    @Override
    public void eat(Fruit food) {
        //implementation here
    }
}

But there is a compilation error

Question: Why is there a compilation error? How to fix the error without adding generic type to the interface like public interface Person<T>?

Rui
  • 3,454
  • 6
  • 37
  • 70

2 Answers2

1

You cannot do it with generics.

Imagine that the code you wrote compiled. Then doing this would make no sense

Person person = new Man();
person.eat(new Steak()); //where Steak is a subclass of Food

Now what method should be called? Person defines only a method eat that has Fruit parameter.

Generics provide compile-time type safety so they are explicited during compilation. This is called erasure procedure. The compiler will remove all the type parameters and add some casts when necessary.

This method defined in Person

<T extends Food> void eat(T food);

will become when compiled

void eat(Food food);

So if you implement Man like the way you did there will be two methods in Person called eat that have different parameters so they are different methods.

void eat(Food food);
void eat(Fruit food);

One way to do this is to move the generic to class level like this

public interface Person<T extends Food> {
    void eat(T food);
}

//We enforce the Man class to only "eat" Fruit foods.
public class Man implements Person<Fruit> {
    public void eat(Fruit fruit) {
        //Eat some fruit
    }
}

//This will work and compile just fine
Person<Fruit> person = new Man();
Fruit fruit = new Fruit();
person.eat(fruit);

But this will not compile.

Person<Fruit> person = new Man();
Food food = new Fruit();
person.eat(food); //food must be casted to Fruit

That because when you declare person the <Fruit> instructs the compiler to check that you are effectively passing a Fruit instance. It knows at compile time that you are passing the supertype Food instead of Fruit which can lead to cast exceptions on runtime if the code gets compiled in this state, breaking type safety which is what generics are all about. In fact

public class Man implements Person<Fruit> {
    public void eat(Fruit fruit) {
         System.out.println(fruit.toString());
    }
}

Will be compiled like this

public class Man implements Person {
    public void eat(Food fruit) {
         //The compiler will automatically add the type cast!
         System.out.println(((Fruit) fruit).toString());
    }
}

One way to bypass compiler type safety checking is to omit generic like this

//This will compile with a warning
//Unchecked use of 'eat(T)' method
((Person)person).eat(food); 
Flood2d
  • 1,338
  • 8
  • 9
  • the first line, did you mean `Person person = new Man();`? then `person` can eat fruit only, isn't it? – Rui Apr 19 '20 at 23:16
  • Check the new version – Flood2d Apr 19 '20 at 23:29
  • Also check this similar question https://stackoverflow.com/questions/5346847/java-generic-method-inheritance-and-override-rules – Flood2d Apr 19 '20 at 23:38
  • Thanks really so much for your explanation. I tried also by adding generic type to the interface, `interface Person`, then it could work. Why is it so? – Rui Apr 20 '20 at 14:27
  • I edited my answer, removed the part about overloading that is out of the question context and added the part about class level generic usage – Flood2d Apr 20 '20 at 14:46
  • I'm sorry! I've made a big mistake. I edited my answer. – Flood2d Apr 20 '20 at 15:02
  • Thanks so much again for improving the answer (Y) Seems that it is the compiler's behavior to not accept the generic type on the method level? I mean if the sub-class `Man` is not generic, it would not accept `@Override eat(Fruit food)` but only `eat(Food) food)`? – Rui Apr 20 '20 at 15:09
  • I still can not understand why with `Man` instead of `Man`, the compiler will not compile `@Override void eat(Fruit fruit)` – Rui Apr 20 '20 at 15:16
  • Without generics it will not accept overriding with Fruit as parameter. But they are both compiled as eat(Food food)! When using generic you can just omit casting to Fruit inside the man methods because the compiler will do this automatically for you. That is why the compiler will not allow you to compile if you dont pass a Fruit as argument, it can predict at compile time what you are actually passing to the Man's methods and do type safety checks. If you use a variable named Person without the generic specified then the compiler will just give you a warning about possible cast exceptions. – Flood2d Apr 20 '20 at 15:18
  • Let me explain my question in more detail: I still can not understand why with `Man` instead of `Man`, the compiler will not compile `@Override void eat(Fruit fruit)` even the contracted method in `interface Person` is ` void eat(T food)`. This interface `Person` can compile in any case without the generic type on the class level, i.e. not `interface Person` but merely `interface Person` – Rui Apr 20 '20 at 15:26
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/212093/discussion-between-flood2d-and-rui). – Flood2d Apr 20 '20 at 15:27
1

The contract of the eat method in the Person interface specifies that the method works for any type that is a subtype of Food. When you define another method in the Person class that accepts only Food instances, you're not adhering to the contract of the method in the interface, thus you're not overriding it.

As you mentioned, to set a concrete type in an implementation of a class using generics, the class itself has to be generic (you'd have to have the generic type at the class level).

Jihed Amine
  • 2,198
  • 19
  • 32
  • Thanks a lot for your answer. Your last stament: to set a concrete type in an implementation of a class using generics, the class itself has to be generic It sounds pretty logic, and I tested it to confirm its validity. But I actually could not understand the logic why the the class itself has to be generic – Rui Apr 20 '20 at 14:41