Приведение к неизвестному типу, когда имя класса в виде строки - PullRequest
0 голосов
/ 04 февраля 2009
public class ExampleClass {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Horse hr1 = new Horse();
        Horse hr2 = new Horse();
        Horse hr3 = new Horse();
        Horse hr4 = new Horse();
        Set hrSet = new HashSet();
        hrSet.add(hr1);
        hrSet.add(hr2);
        hrSet.add(hr3);
        hrSet.add(hr4);
        Horse hr;
        String hor = "sher_pkg.Horse";
        callHorse(hrSet,hor);
    }
    public static void callHorse(Set xSet,String clsName){
        try {
            Class hrt = Class.forName(clsName);

            Iterator hritr = xSet.iterator();
            while(hritr.hasNext()){
                exam(hrt.cast(hritr.next()));
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    public static void exam(Object obj){ //I want to use exam(Horse hrr)
        System.out.println(obj);
    }
}

Здесь аргументом для функции экзамена является Object. Но я хочу, чтобы аргумент был Horse ... так что же нужно изменить в вызове метода "exam(hrt.cast(hritr.next()))"? Я не хочу явно использовать имя класса Horse в callHorse() ... Так что я должен делать?

Спасибо

Ответы [ 12 ]

3 голосов
/ 05 февраля 2009

Примечание: код с последовательностями "if (x instanceof MyClass) обычно указывает на то, что вы недостаточно используете полиморфизм. Код обычно может быть реорганизован, чтобы избавиться от необходимости проверять это. Но я проигнорирую это ради ответа на вопрос заданный вопрос.

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

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

public class Test {
  public void test(Object obj) {
    if (obj instanceof Horse) {
      Horse c = (Horse) obj;
      noise(c);
    }
    if (obj instanceof Cow) {
      Cow c = (Cow) obj;
      noise(c);
    }
  }

  public void noise(Horse h) {
    System.out.println("Neigh");
  }

  public void noise(Cow c) {
    System.out.println("Moo");
  }

  public static void main(String[] args) {
    Object o1 = new Horse();
    Object o2 = new Cow();
    Test tester = new Test();
    tester.test(o1);
    tester.test(o2);
  }
}

class Horse {}

class Cow {}

Этот код запускается и делает то, что вы ожидаете. Он печатает «Neigh», а затем «Moo».

Вы пытаетесь заменить

    if (obj instanceof Horse) {
      Horse c = (Horse) obj;
      noise(c);
    }

с

    if (obj instanceof Horse) {
      handleNoise(obj, Horse.class);
    }

и добавление метода для его обработки (упрощенно):

void handleNoise(Object obj, Class clazz) {
  noise(clazz.cast(obj));
}

и, как я уже говорил, это не работает, перегрузка noise определяется во время компиляции. Компилятор видит, что вы используете приведение, но не знает во время компиляции, что это за тип. Таким образом, он не может выбрать перегрузку, и компиляция не удалась.

Лучший способ решить эту проблему - использовать полиморфизм, потому что полиморфизм определяется во время выполнения. То есть, чтобы все эти классы реализовали некоторый интерфейс, а затем переместили соответствующий код в отдельные классы. Вот пример, который делает это:

public class Test {
  public void test(Animal obj) {
    obj.noise();
  }

  public static void main(String[] args) {
    Animal o1 = new Horse();
    Animal o2 = new Cow();
    Test tester = new Test();
    tester.test(o1);
    tester.test(o2);
  }
}

interface Animal {
  void noise();
}

class Horse implements Animal {
  public void noise() {
    System.out.println("Neigh");
  }
}

class Cow implements Animal {
  public void noise() {
    System.out.println("Moo");
  }
}

Обратите внимание, насколько проще метод теста! Если вы можете заставить каждый элемент реализовывать интерфейс, который обрабатывает то, что вы называете stringProp ниже, то вы можете упростить часть пути:

if (obj instanceof Cust) {
  loopOverSet(c.getCustPhonSet());
} else if (obj instanceof Name) {
  loopOverSet(c.getCustNameSet());
}
// and so on for the rest...

и затем добавьте метод:

void loopOVerSet(Set cxSet) {
  if (cxSet != null && cxSet.size() > 0) {
    Iterator cxSetIterator = cxSet.iterator();
    while (cxSetIterator.hasNext())
    {
      ((StringProp)cxSetIterator.next()).stringProp();
    }
  }
}

Это предполагает, что ранее перегруженные методы stringProp были перемещены в отдельные классы CustPhone и CustName и т. Д., И что все эти классы реализуют некоторый интерфейс, который я назвал StringProp, где этот интерфейс определяет метод stringProp(). Поскольку этот код использует переопределение вместо перегрузку , решение будет принято во время выполнения.

1 голос
/ 04 февраля 2009

Когда вы говорите:

exam(Horse hrr)

вы сообщаете компилятору, что хотите, чтобы он проверял все вызовы exam () и проверял, чтобы каждый вызов предоставлял объект Horse в качестве аргумента. Однако в callHorse () вы вызываете exam () с динамически приводимым аргументом, и компилятор не может проверить аргумент.

Вполне возможно, что вы могли бы обойти это, используя отражение и динамически вызывая метод exam ().

1 голос
/ 04 февраля 2009

Возможно, вы захотите взглянуть на дженерики.

public static void callHorse(Set<Horse> xSet) {
    Iterator<Horse> hritr = xSet.iterator();
    while (hritr.hasNext()) {
        exam(hritr.next());
    }
}
public static void exam(Horse obj) { //I want to use exam(Horse hrr)
    System.out.println(obj);
}

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

0 голосов
/ 05 февраля 2009

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

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

То, что дается Эдди, может быть наиболее подходящим решением для этого, я думаю?

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

У тебя есть?

0 голосов
/ 05 февраля 2009

HI

После поиска я обнаружил, что динамическая типизация во время выполнения не может быть выполнена. То, что я пытался понять, кажется абсурдным.

Я пытался уменьшить цикломатическую сложность метода1. Я пытался создать method2, который содержит обобщенный шаблон шаблона повторения, найденный в method1, и вызывает метод2 из method1, где это необходимо ...

шаблон был таким же в первом методе ..

if (obj instanceof Cust)
{
    Cust c = (Cust) obj;
    Set cxSet = c.getCustPhonSet();
    CustPhon cx;
    if (cxSet != null && cxSet.size() > 0)
    {
        Iterator cxSetIterator = cxSet.iterator();
        while (cxSetIterator.hasNext())
        {
            cx = (CustPhon) cxSetIterator.next();
            this.stringProp(cx);
        }
    }
    //....pattern continues here... CustPhon is replaced by various classes like CustNam etc... Also getCustPhonSet by getCustNamSet etc...
}

поэтому я подумал о написании обобщенного метода для вышеприведенного шаблона, подобного этому:

public void dynamicIteration(Set xlSet, String clsName)
{
    if (xSet != null && xSet.size() > 0)
    {
        try{
            Class clsinstance = Class.forName(clsName);
            Iterator itr = generalSet.iterator();
            while(itr.hasNext())
            {
                this.stringProp(clsinstance.cast(itr.next()));// See this is wrong.. thats y i posted here by using a simple Horse example
            }
        }catch(ClassNotFoundException e)
        {
            e.printStackTrace();
        }
    }
}

Вызов метода2 из метода 1

//process customer email address
Set cxSet = c.getCustPhonSet();
className = "pkg.CustPhon";
dynamicIteration(cxSet,className);
// Similarly for other patterns

Таким образом, я должен быть в состоянии уменьшить цикломатическую сложность

Это то, что я пытался сделать ..

0 голосов
/ 04 февраля 2009

Ваша реальная цель - иметь несколько версий метода exam (), которые принимают разные типы в качестве параметров и динамически выбирают версию, необходимую во время выполнения?

Вы можете сделать это явно с помощью отражения. Вот пример программы.

import java.lang.reflect.*;

public class Test {

public static void exam( Object o ) {
  System.out.println( "Object version called" );
}


public static void exam( Test t ) {
  System.out.println( "Test version called" );
}

public static void main (String[] args) {

try {

  // Create an instance of Test but reference it as an Object

  Object untypedTest = new Test();

  // Calling exam directly will invoke the Object version

  exam( untypedTest );

  // But if we use reflection to select the version of exam
  // that takes the desired class name, we can invoke it without
  // even explicitly casting

  String className = "Test";

  Class[] examMethodParams = { Class.forName( className ) };

  Method examMethod = Test.class.getMethod( "exam", examMethodParams  );

  Object[] actualParams = { untypedTest };

  examMethod.invoke( null, actualParams );

} catch (Exception e) {
  e.printStackTrace();
}

}

}
0 голосов
/ 04 февраля 2009

Почему бы не написать это так? Каковы ваши требования?

public static void main(String[] args) {
    Set<Horse> horses = new HashSet<Horse>();
    horses.add(new Horse());
    horses.add(new Horse());
    horses.add(new Horse());
    horses.add(new Horse());

    callHorse(horses);
}

public static void callHorse(Set<Horse> horses) {
    for (Horse horse : horses) {
        exam(horse);
    }
}

public static void exam(Horse horse) {
    System.out.println(horse);
}

В зависимости от того, что вы делаете в методе exam (), также может иметь смысл сделать его методом экземпляра Horse, например:

public static void main(String[] args) {
    Set<Horse> horses = new HashSet<Horse>();
    horses.add(new Horse());
    horses.add(new Horse());
    horses.add(new Horse());
    horses.add(new Horse());

    examineHorses(horses);
}

public static void examineHorses(Set<Horse> horses) {
    for (Horse horse : horses) {
        horse.examine();
    }
}

// in Horse.java
public class Horse {
    public void examine() {
        System.out.println(this);
    }
    ...
}
0 голосов
/ 04 февраля 2009

Если вы выполняете динамическое приведение с использованием Class.cast() с аргументом, который вы передаете другой функции, то во время компиляции ничего не известно о типе, который вы передаете. Вот почему вы не можете использовать Horse в качестве типа аргумента, где вы определяете метод, но затем вызываете метод, используя отражение таким, как вы. Ваш актерский состав делает очень мало, за исключением проверки того, что - пока вы не получите Exception - набор, который вы проходите, целиком состоит из членов Class, которые вы передаете от имени.

Обратите внимание, что метод Class.cast() был введен в Java 5, что означает, что у вас есть доступ к Generics, если у вас есть доступ к Class.cast(). Обобщения могут помочь очистить вещи, хотя они не решат проблему, которую вы пытаетесь решить.

Используя цикл Java 5 for, вы можете переписать ваш цикл следующим образом:

  public static void callHorse(Set<?> xSet, String clsName) {
    try {
      Class<?> hrt = Class.forName(clsName);
      for (Object x : xSet) {
        exam(hrt.cast(x));
      }
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    }
  }

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

Даже если вы попытаетесь перегрузить, это не сработает. То есть, если вы создаете методы:

public static void exam(Object obj) {
  System.out.println("Object " + obj);
}

public static void exam(Horse obj) {
  System.out.println("Horse " + obj);
}

метод exam(Object) будет всегда вызванным. Попробуй.

Суть в том, что то, что вы пытаетесь сделать, не может быть сделано. Прежде чем мы сможем вам помочь, вы должны предоставить нам больше информации о том, какова ваша цель.

0 голосов
/ 04 февраля 2009

Я не уверен, что возможно или желательно избегать ссылки на «Horse» в методе callHorse. Судя по трассировке печати после ClassNotFoundException, вы выдаете серьезную ошибку, если класс по какой-то причине не найден.

Не могли бы вы по той же причине просто привести к "Лошади", а затем поймать исключение класса, если что-то в Сете не Лошадь?

Можете ли вы объяснить, почему именно вам нужно передать имя класса вместо класса?

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

0 голосов
/ 04 февраля 2009

Похоже, ваш дизайн не подходит для Java, и вы не можете напрямую делать то, что просите.

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

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