Обобщенный класс с параметром реализующим несколько интерфейсов
Создала два интерфейса и хочу создать параметризованный класс, параметр которого является типом этих интерфейсов, но выдается ошибка, что нет такого метода func2() у переменной типа T:
class Param<T extends I1, I2> {
void func(T t) {
t.func1();
t.func2(); // ошибка
}
}
interface I1 {
void func1();
}
interface I2 {
void func2();
}
Скажите, пожалуйста, почему так происходит? Как мне правильно параметр типа унаследовать от нескольких типов?
Ответы (1 шт):
Правильный синтаксис такой:
class Param<T extends I1 & I2> {
void func(T t) {
t.func1();
t.func2();
}
}
Только хочу уточнить, что вы используете терминологию немного неправильно. Некорректно говорить, параметр типа унаследовать от нескольких типов. Правильней сказать наложить несколько ограничений на параметр типа. Т.е. параметр не наследует, мы его для нашего типа ограничиваем, т.е. указываем, какие реальные типы можно использовать в качестве параметра.
Что касается стирания типов (type erasure), то тут ситуация такая. Действительно согласно спецификации при стирании типа используется самое первое ограничение:
The erasure of a type variable (§4.4) is the erasure of its leftmost bound
Но это не означает, что нельзя использовать методы из других интерфейсов. Стирание типов нужно для того, чтоб в скомпилированном коде использовался конкретный тип, так как сама JVM не знает об обобщенных классах.
По сути компилятор трансформирует оригинальный код с обобщенным типом заменяя T на I1 и приводя к I2 во всех местах, где идет обращение к I2. То есть он код превращает в такой:
void func(I1 t) {
t.func1();
((I2)t).func2(); // это приведение можно сделать,
// так как компилятор точно знает, что тип t
// реализует I2
}
В чем можно убедиться глянув на скомпилированный код класса:
void func(T);
descriptor: (LI1;)V
flags:
Signature: #19 // (TT;)V
Code:
stack=1, locals=2, args_size=2
0: aload_1
1: checkcast #20 // class I2
4: invokeinterface #22, 1 // InterfaceMethod I2.func2:()V
9: return
LineNumberTable:
line 6: 0
line 7: 9
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this LParam;
0 10 1 t LI1;
Тут aload_1 загружает переменную с номером 1 (т.е. t), а checkcast проверяет можно ли ее привести к I2.