Передача в Callback конкретного наследника
Мне нужно в метод класса передать Consumer, но так, чтобы аргументом этого Consumer был конкретный экземпляр этого класса или его наследника. Что-то типа такого
class A {
<T extends A> void test(Consumer<T> consumer) {
consumer.accept(this);
}
}
class B extends A {
int getVal() {
return 0;
}
}
class C {
void consume(B data) {
System.out.println(data.getVal());
}
void test() {
B data = new B();
data.test(this::consume);
}
}
сейчас на строке
consumer.accept(this);
ошибка
accept(T) in Consumer cannot be applied to A
можно поставить явный каст
consumer.accept((T)this);
тогда ошибка заменяется на warning
Unchecked cast 'A' to 'T'
пытался изобразить что-то типа наследования
class A {
void test(Consumer<A> consumer) {
consumer.accept(this);
}
}
class B extends A {
@Override
void test(Consumer<B> consumer) {
consumer.accept(this);
}
int getVal() {
return 0;
}
}
name clash: test(Consumer) in B and test(Consumer) in A have the same erasure, yet neither overrides the other
А как сделать правильно?
Ответы (1 шт):
Ошибка в оригинальном варианте возникает, так как тип Т с ограничением T extends A можно использовать в любом контексте, где можно использовать А, т.е. проще говоря можно безопасно привести к A, а вот наоборот - нельзя.
Именно такое приведение A к T нужно, чтоб использовать A (это есть тип this) в выражении consumer.accept(this).
Ведь если бы это допустил компилятор без явного приведения, то можно было бы написать:
class C extends A {
void consume(C data) {
}
void test() {
B data = new B();
data.test(this::consume);
}
}
С очевидной проблемой, что C::consume получает на вход объект типа B.
С явным приведением так тоже можно написать, но предупреждение компилятора как раз и говорит, что код не является типо безопасным и может бросить ClassCastException во время выполнения.
Что касается того, как правильно исправить, то это зависит от некоторых условий, которые явно не определены в вопросе, очевидно, в связи з попыткой сделать минимальный пример.
Если я правильно предполагаю, что test реализует шаблонный метод, то наиболее универсальным видится вариант явной передачи наследника:
class A {
static <T extends A> void test(T t, Consumer<T> consumer) {
consumer.accept(t);
}
}
...
void test() {
B data = new B();
A.test(data, this::consume);
}
В этом случае сохраняется возможность вызова методов из наследника и private методов из A (c явным приведением):
class A {
static <T extends A> void test(T t, Consumer<T> consumer) {
consumer.accept(t);
t.virtual();
((A)t).private_method();
}
protected void virtual() {
}
private void private_method() {
}
}
class B extends A {
int getVal() {
return 0;
}
protected void virtual() {
}
}
Альтернативный вариант - это добавить в базовый класс информацию о типе наследника:
abstract class A<T> {
void test(Consumer<T> consumer) {
consumer.accept(getThis());
}
protected abstract T getThis();
}
class B extends A<B> {
int getVal() {
return 0;
}
@Override
protected B getThis() {
return this;
}
}