У меня есть коллекция (или список или список массивов), в которой я хочу поместить как значения String, так и двойные значения. Я решил сделать это коллекцией объектов и с помощью перегрузки ond полиморфизма, но я сделал что-то не так.
В тесте кажется, что тип аргумента определяется во время компиляции, а не во время выполнения. Почему это?
Хорошо, что метод, который будет вызываться, определяется во время компиляции. Есть ли обходной путь, чтобы избежать использования оператора instanceof
?
Ответ 6
Старый вопрос, но никакой ответ не дает конкретного решения на Java, чтобы решить проблему чистым способом.
На самом деле это непростой, но очень интересный вопрос. Вот мой вклад.
Хорошо, что метод, который будет вызываться, определяется во время компиляции. Есть ли обходной путь, чтобы избежать использования экземпляра оператора?
Как сказано в превосходном ответе @DaveFar, Java поддерживает только метод одиночной отправки.
В этом режиме диспетчеризации компилятор привязывает метод к вызову сразу же после компиляции, опираясь на объявленные типы параметров, а не на их типы времени выполнения.
У меня есть коллекция (или список или список массивов), в которую я хочу поставить как значения String, так и двойные значения.
Чтобы решить ответ чистым способом и использовать двойную отправку, мы должны принести абстракцию для управляемых данных.
Почему?
Здесь наивный подход посетителей, чтобы проиллюстрировать проблему:
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), поэтому мы могли бы определить более конкретный тип для каждого подкласса, чтобы избежать downcasts.
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 = еще одна строка
целое число = 1
объект = 100
Улучшение реализации
Фактическая реализация требует введения определенного класса оболочки для каждого типа buit-in, который мы хотим обернуть.
Как обсуждалось, у нас нет выбора для двойной отправки.
Но обратите внимание, что повторного кода в подклассах 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
sublasses легче объявить:
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
- это пользовательская аннотация, обработанная во время компиляции.