Как «динамически» привести тип объекта в его конкретный тип данных? - PullRequest
14 голосов
/ 11 апреля 2011
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?

Ответы [ 5 ]

17 голосов
/ 11 апреля 2011

Лучший способ справиться с этим эффективно и аккуратно - заставить 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();
}
3 голосов
/ 11 апреля 2011

Проблема здесь может заключаться в разделении интересов. Java - это объектно-ориентированный язык, и он может помочь решить проблему объектно-ориентированным способом. В этом случае вы можете спросить, почему main должно заботиться о том, что это за Object 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 () фактически становится фабрикой, и с этого момента вам больше не нужно заботиться о том, что за Thing был возвращен foo.

3 голосов
/ 11 апреля 2011

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

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

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

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

2 голосов
/ 11 апреля 2011

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

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

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

//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.
0 голосов
/ 11 апреля 2011

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

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...