Как "динамически" передать экземпляр типа объекта в его конкретный тип данных?

public Object foo(int opt){
  if (opt == 0) return new String();
  else if (opt == 1) return new Integer(1);
  else if (opt == 2) return new Double(1);
  else if ...
  .. and many more
}

public void doSomething(String s){..}
public void doSomething(Integer i){..}
public void doSomething(Double d){..}
... and many more doSomething method

public static void main(String[] args){
  ...
  Object o = foo(x); //x is a value obtained during runtime, e.g. from user input

  //now I want to call doSomething method
  // (1)
  if (o instanceof String) doSomething((String) o);
  else if (o instanceof Integer) doSomething((Integer) o);
  else if (o instanceof Double) doSomething((Double) o);
  ...
  // (2)
}

Есть ли лучший способ упростить утверждения, заключенные в (1)... (2)?
Помогает ли Java Reflection?

Ответ 1

Лучший способ справиться с этим эффективно и чисто - это вернуть foo класс держателя для объекта.

abstract class Holder<T> {
    private final T object;

    protected Holder(T object) { this.object = object; }
    public T get() { return object; }
    public abstract void doSomething();
}

public Holder foo(int opt) {
    if (opt == 0) return new Holder<String>("") {
        public void doSomething() { }
    };
    else if (opt == 1) return new Holder<Integer>(1) {
        public void doSomething() { }
    };
    else if (opt == 2) return new Holder<Double>(1.0) {
        public void doSomething() { }
    };
    // many more
}

public static void main(String... args) throws IOException {
    Holder h  = foo(x); //x is a value obtained during runtime, e.g. from user input

    //now I want to call doSomething method
    h.doSomething();
}

Ответ 2

Проблема здесь может быть одной из проблем. Java - это объектно-ориентированный язык, и это может помочь решить проблему в объектно-ориентированном виде. В этом случае вы можете спросить, почему main должен заботиться о том, какой именно объект o. Вместо этого вы можете подумать о наборе классов, каждый из которых знает, как сделать что-то по-своему.

abstract class Thing {
   abstract void doSomething();
}

class IntegerThing extends Thing {
  public void doSomething() {  /*whatever*/ };
}

class FloatThing extends Thing  {
  public void doSomething() { /*whatever*/ };
}


//Then later:

int foo(int type) {
  if(type == 0) return new IntegerThing(0);
  if(type == 1) return new FloatThing(7.5);
  if(type == 3) return new StringThing("Florence");
}

int main(String args[]) {
   Thing something = foo(x);
   something.doSomething();
}

Ваш метод foo() эффективно становится factory, и с этого момента вам больше не нужно заботиться о том, какая вещь была возвращена foo.

Ответ 3

В основном вы хотите, чтобы во время выполнения выполнялось разрешение перегрузки - вы не сможете сделать это очень просто.

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

Не удалось ли foo вызвать правильную перегрузку doSomething вместо того, чтобы просто вернуть значение? Что бит кода, который знает, что строится - если бы вы могли передать ему объект для вызова doSomething с соответствующей перегрузкой, вы в конечном итоге столкнулись бы с логикой типа в одном месте.

В Java 7 возможно, что invokedynamic будет полезен в такой ситуации - конечно, тип dynamic в С# 4 поможет - но я не заглянул в invokedynamic достаточно, чтобы сказать наверняка.

Ответ 4

Отражение Java помогает несколько, но есть недостающая часть данных. Кроме того, отражение обычно бросает МНОГИЕ проверенные исключения, которые вам нужно поймать. (Я включил список после кода)

Что такое объект, который содержит методы doSomething? В этом примере я использую имя переменной "someObject" для представления объекта, содержащего метод "doSomething". Вы должны подставить это для чего-то более чувственного.

Кроме того, просто предупреждение, это не будет захватывать производные типы, поэтому, если определение метода не соответствует указанному типу, вы получите исключение метода, не найденное.

//now I want to call doSomething method
// (1)
Method method = someObject.getClass.getMethod("doSomething",new Class[] {o.getClass()});
method.invoke(someObject, new Object[] {o});
// (2)

Предупреждение. При использовании отражения вам нужно иметь дело со следующими исключениями: (Это не необычный список, кстати, отражение, как правило, очень шумно с точки зрения исключений)

NoSuchMethodException - if a matching method is not found or if the name is "<init>"or "<clinit>". 
NullPointerException - if name is null
SecurityException - if access to the information is denied.
IllegalAccessException - if this Method object enforces Java language access control and the underlying method is inaccessible.
IllegalArgumentException - if the method is an instance method and the specified object argument is not an instance of the class or interface declaring the underlying method (or of a subclass or implementor thereof); if the number of actual and formal parameters differ; if an unwrapping conversion for primitive arguments fails; or if, after possible unwrapping, a parameter value cannot be converted to the corresponding formal parameter type by a method invocation conversion.
InvocationTargetException - if the underlying method throws an exception.
NullPointerException - if the specified object is null and the method is an instance method.
ExceptionInInitializerError - if the initialization provoked by this method fails.

Ответ 5

В Java нет смысла делать это. Java статически типизирована, если вы должны использовать ее динамически, вы ДОЛЖНЫ иметь оператор switch для этого, чтобы вызвать разные методы для разных объектов.

Пример - если у вас есть строка или int, и вы хотите "динамически" ее отличать (без переключения), какую операцию вы можете сделать с обоими из них, для которой не требуется другой код.

Я предполагаю, что я говорю, что если вам нужно бросить, потому что вы хотите получить доступ к чему-то, что отличается от двух объектов (другой метод), тогда как вы можете получить доступ к этому другому методу без переключения?

Единственное исключение может быть внутренними переменными - для тех, кого вы хотите использовать в качестве генераторов, но использование встроенных переменных вне классов - это плохая идея.

О, то, что вы, вероятно, действительно хотите, состоит в том, чтобы все классы реализовали один и тот же интерфейс - тогда вы не бросаете.

Литье должно быть крайне редким.