Старый вопрос, но нет ответа, предоставляет конкретное решение в Java для четкого решения проблемы.
На самом деле не простой, но очень интересный вопрос. Вот мой вклад.
Хорошо, метод, который нужно вызвать, определяется во время компиляции. Есть ли
Обходной путь, чтобы избежать использования оператора instanceof?
Как сказано в превосходном ответе @DaveFar, Java поддерживает только метод единой отправки.
В этом режиме диспетчеризации компилятор ограничивает метод для вызова сразу после компиляции, полагаясь на объявленные типы параметров, а не на их типы времени выполнения.
У меня есть коллекция (или список или список массивов), в который я хочу поместить
как строковые, так и двойные значения.
Чтобы правильно решить ответ и использовать двойную диспетчеризацию, мы должны принести абстракцию для манипулируемых данных.
Почему?
Вот наивный подход посетителя, чтобы проиллюстрировать проблему:
public class DisplayVisitor {
void visit(Object o) {
System.out.println("object"));
}
void visit(Integer i) {
System.out.println("integer");
}
void visit(String s) {
System.out.println("string"));
}
}
Теперь вопрос: как посещаемые классы могут вызывать метод visit()
?
Вторая диспетчеризация реализации двойной диспетчеризации опирается на контекст «this» класса, который принимает посещение.
Поэтому нам необходим метод accept()
в классах Integer
, String
и Object
для выполнения этой второй отправки:
public void accept(DisplayVisitor visitor){
visitor.visit(this);
}
Но невозможно! Посещаемые классы - это встроенные классы: String
, Integer
, Object
.
Поэтому у нас нет возможности добавить этот метод.
И вообще, мы не хотим добавлять это.
Таким образом, чтобы реализовать двойную диспетчеризацию, мы должны иметь возможность изменять классы, которые мы хотим передать в качестве параметра во второй диспетчеризации.
Таким образом, вместо манипулирования Object
и List<Object>
как объявленного типа, мы будем манипулировать Foo
и List<Foo>
, где класс Foo
является оберткой, содержащей пользовательское значение.
Вот интерфейс Foo
:
public interface Foo {
void accept(DisplayVisitor v);
Object getValue();
}
getValue()
возвращает пользовательское значение.
В качестве типа возврата указывается Object
, но Java поддерживает ковариационные возвраты (начиная с версии 1.5), поэтому мы могли бы определить более конкретный тип для каждого подкласса, чтобы избежать падений.
ObjectFoo
public class ObjectFoo implements Foo {
private Object value;
public ObjectFoo(Object value) {
this.value = value;
}
@Override
public void accept(DisplayVisitor v) {
v.visit(this);
}
@Override
public Object getValue() {
return value;
}
}
StringFoo
public class StringFoo implements Foo {
private String value;
public StringFoo(String string) {
this.value = string;
}
@Override
public void accept(DisplayVisitor v) {
v.visit(this);
}
@Override
public String getValue() {
return value;
}
}
IntegerFoo
public class IntegerFoo implements Foo {
private Integer value;
public IntegerFoo(Integer integer) {
this.value = integer;
}
@Override
public void accept(DisplayVisitor v) {
v.visit(this);
}
@Override
public Integer getValue() {
return value;
}
}
Вот класс DisplayVisitor , посещающий Foo
подклассов:
public class DisplayVisitor {
void visit(ObjectFoo f) {
System.out.println("object=" + f.getValue());
}
void visit(IntegerFoo f) {
System.out.println("integer=" + f.getValue());
}
void visit(StringFoo f) {
System.out.println("string=" + f.getValue());
}
}
А вот пример кода для проверки реализации:
public class OOP {
void test() {
List<Foo> foos = Arrays.asList(new StringFoo("a String"),
new StringFoo("another String"),
new IntegerFoo(1),
new ObjectFoo(new AtomicInteger(100)));
DisplayVisitor visitor = new DisplayVisitor();
for (Foo foo : foos) {
foo.accept(visitor);
}
}
public static void main(String[] args) {
OOP oop = new OOP();
oop.test();
}
}
Вывод:
string = a String
строка = другая строка
* * +1091 целое число = 1
объект = 100
Улучшение реализации
Фактическая реализация требует введения определенного класса-обертки для каждого встроенного типа, который мы хотим обернуть.
Как уже говорилось, у нас нет выбора для двойной отправки.
Но обратите внимание, что повторяющегося кода в подклассах Foo можно избежать:
private Integer value; // or String or Object
@Override
public Object getValue() {
return value;
}
Мы действительно можем ввести абстрактный обобщенный класс, который содержит пользовательское значение и обеспечивает доступ к:
public abstract class Foo<T> {
private T value;
public Foo(T value) {
this.value = value;
}
public abstract void accept(DisplayVisitor v);
public T getValue() {
return value;
}
}
Теперь Foo
подклассы легче объявить:
public class IntegerFoo extends Foo<Integer> {
public IntegerFoo(Integer integer) {
super(integer);
}
@Override
public void accept(DisplayVisitor v) {
v.visit(this);
}
}
public class StringFoo extends Foo<String> {
public StringFoo(String string) {
super(string);
}
@Override
public void accept(DisplayVisitor v) {
v.visit(this);
}
}
public class ObjectFoo extends Foo<Object> {
public ObjectFoo(Object value) {
super(value);
}
@Override
public void accept(DisplayVisitor v) {
v.visit(this);
}
}
И метод test()
следует изменить, чтобы объявить тип подстановочного знака (?
) для типа Foo
в объявлении List<Foo>
.
void test() {
List<Foo<?>> foos = Arrays.asList(new StringFoo("a String object"),
new StringFoo("anoter String object"),
new IntegerFoo(1),
new ObjectFoo(new AtomicInteger(100)));
DisplayVisitor visitor = new DisplayVisitor();
for (Foo<?> foo : foos) {
foo.accept(visitor);
}
}
Фактически, если это действительно необходимо, мы могли бы еще больше упростить подклассы Foo
, введя генерацию кода Java.
Объявление этого подкласса:
public class StringFoo extends Foo<String> {
public StringFoo(String string) {
super(string);
}
@Override
public void accept(DisplayVisitor v) {
v.visit(this);
}
}
можно просто объявить класс и добавить аннотацию:
@Foo(String.class)
public class StringFoo { }
Где Foo
- пользовательская аннотация, обрабатываемая во время компиляции.