Подтвердить что ты не робот

Перегрузка в Java и многократная отправка

У меня есть коллекция (или список или список массивов), в которой я хочу поместить как значения String, так и двойные значения. Я решил сделать это коллекцией объектов и с помощью перегрузки ond полиморфизма, но я сделал что-то не так.

Я запускаю небольшой тест:

public class OOP {
    void prova(Object o){
        System.out.println("object");
    }

    void prova(Integer i){
    System.out.println("integer");
    }

    void prova(String s){
        System.out.println("string");
    }

    void test(){
        Object o = new String("  ");
        this.prova(o); // Prints 'object'!!! Why?!?!?
    }

    public static void main(String[] args) {
        OOP oop = new OOP();
        oop.test(); // Prints 'object'!!! Why?!?!?
    }
}

В тесте кажется, что тип аргумента определяется во время компиляции, а не во время выполнения. Почему это?

Этот вопрос связан с:

Полиморфизм против перегрузки и перегрузки
Попробуйте описать полиморфизм так легко, как вы можете

EDIT:

Хорошо, что метод, который будет вызываться, определяется во время компиляции. Есть ли обходной путь, чтобы избежать использования оператора instanceof?

4b9b3361

Ответ 1

Этот ответ в секундах voo и содержит сведения о/альтернативах позднего связывания.

Общие JVM используют только одиночную рассылку: тип времени выполнения учитывается только для объекта-получателя; для параметров метода рассматривается статический тип. Эффективная реализация с оптимизацией довольно проста с помощью таблиц методов (которые похожи на виртуальные таблицы С++). Вы можете найти подробную информацию, например. в HotSpot Wiki.

Если вы хотите несколько отправки для своих параметров, посмотрите

  • groovy. Но к моим последним знаниям, которые имеют устаревшую, медленную реализацию нескольких рассылок (см., Например, сравнение производительности), например. без кеширования.
  • clojure, но это сильно отличается от Java.
  • MultiJava, который предлагает несколько диспетчеров для Java. Кроме того, вы можете использовать
    • this.resend(...) вместо super(...), чтобы вызвать наиболее специфичный переопределенный метод входящего метода;
    • отправка значения (пример кода ниже).

Если вы хотите придерживаться Java, вы можете

  • перепроектируйте ваше приложение, перемещая перегруженные методы по более мелкой иерархии классов. Пример приведен в Джош Блох Эффективная Java, пункт 41 (разумно использовать перегрузку);
  • используйте некоторые шаблоны проектирования, такие как Strategy, Visitor, Observer. Они часто могут решить те же проблемы, что и множественная отправка (т.е. В тех ситуациях у вас есть тривиальные решения для этих шаблонов, использующих множественную отправку).

Отправка значения:

class C {
  static final int INITIALIZED = 0;
  static final int RUNNING = 1;
  static final int STOPPED = 2;
  void m(int i) {
    // the default method
  }
  void m([email protected]@INITIALIZED i) {
    // handle the case when we're in the initialized `state'
  }
  void m([email protected]@RUNNING i) {
    // handle the case when we're in the running `state'
  }
  void m([email protected]@STOPPED i) {
    // handle the case when we're in the stopped `state'
  }
}

Ответ 2

То, что вы хотите, это двойная или более общая множественная отправка, то, что фактически реализовано на других языках (общий ум lisp приходит на ум)

Предположительно основная причина, по которой у java нет, заключается в том, что она выполняется при снижении производительности, поскольку разрешение перегрузки должно выполняться во время выполнения, а не во время компиляции. Обычным способом является шаблон посетителя - довольно уродливо, но это как.

Ответ 3

При вызове перегруженного метода Java выбирает наиболее ограничительный тип, основанный на типе переменной, переданной функции. Он не использует тип фактического экземпляра.

Ответ 4

это не полиморфизм, вы просто перегрузили метод и вызвали его с параметром типа объекта

Ответ 5

Все в Java - это Object/object (кроме примитивных типов). Вы сохраняете строки и целые числа как объекты, а затем, когда вы вызываете метод prove, они все еще называются объектами. Вы должны посмотреть ключевое слово instanceof. Проверьте эту ссылку

void prove(Object o){
   if (o instanceof String)
    System.out.println("String");
   ....
}

Ответ 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 - это пользовательская аннотация, обработанная во время компиляции.