简体   繁体   中英

How to use generic type parameter in a method of an interface

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 , ie 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> ?

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); 

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).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM