当类名作为字符串时,转换为未知类型

时间:2009-02-03 21:45:32

标签: java reflection casting

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 个答案:

答案 0 :(得分:3)

注意:序列为“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已被移动到各个类CustPhoneCustName等等,并且这些类都实现了我调用的一些接口StringProp此接口定义方法stringProp()。由于此代码使用覆盖而不是重载,因此将在运行时决定。

答案 1 :(得分:1)

你可能想看一下泛型。

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);
}

当然,在您的示例中,您始终只能投射对象。为什么你不想那样做是超出我的。

答案 2 :(得分:1)

当你说:

exam(Horse hrr)

您告诉编译器您希望它检查对exam()的所有调用,并确保每个调用都提供一个Horse对象作为参数。但是,在callHorse()中,您使用动态转换参数调用exam(),并且编译器无法检查参数。

您可以通过使用反射并动态调用exam()方法来解决此问题。

答案 3 :(得分:0)

你可以在函数调用中明确地转换 -

    try {
            Class hrt = Class.forName(clsName);

            Iterator hritr = xSet.iterator();
            while(hritr.hasNext()){
                    exam((Horse)hrt.cast(hritr.next()));
            }
    } 

但我不确定你在这里想要实现的目标 - 如果你正在编写明确引用Horses的代码,为什么还需要从字符串中动态确定类类型?

答案 4 :(得分:0)

首先,你的集合应该使用泛型或明确定义为仅持有Horse Objects。

(final Set xSet<Horse>, final String clsName){
...}

修复此问题,您已解决了90%的问题。

答案 5 :(得分:0)

看起来你的设计对Java来说是错误的,你不能直接做你想要的。

也许您需要重塑代码才能使用访问者模式?如果做不到这一点,您需要解释您的要求,而不是您想要使用的解决方案。通过这种方式,我们可以告诉您适合您需求的Java解决方案。

答案 6 :(得分:0)

我不确定在callHorse方法中避免引用“Horse”是可能的或可取的。从ClassNotFoundException之后的printstacktrace判断,如果由于某种原因找不到类,则抛出一个硬错误。

出于同样的原因,难道你不能仅仅投射到“马”,然后如果Set中的某些东西不是马,那就赶上classcastexception吗?

你能解释为什么你需要传入classname而不是类吗?

也许你也可以使用方法重载,但我必须对此进行测试,因为在这种情况下我不完全确定优先级是什么。

答案 7 :(得分:0)

如果您使用Class.cast()使用您要传递给另一个函数的参数进行动态转换,那么在编译时,您所传递的类型一无所知。这就是为什么你不能使用Horse作为定义方法的参数类型,而是以你自己的方式使用反射来调用方法。除了验证之外,你的演员阵容很少 - 只要你没有得到Exception - 你传入的集合完全由你传递的Class成员组成。

请注意,{5}中引入了Class.cast()方法,这意味着如果您有权访问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)方法始终是被调用的方法。试试吧。

最重要的是,你要做的事情是无法完成的。在我们为您提供帮助之前,您需要向我们提供有关您的目标的更多信息。

答案 8 :(得分:0)

为什么不这样写呢?你的要求究竟是什么?

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);
    }
    ...
}

答案 9 :(得分:0)

您的真正目标是拥有多个版本的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();
}

}

}

答案 10 :(得分:0)

HI,

搜索完i后,发现无法在运行时进行动态类型转换。所以我想弄清楚的似乎是荒谬的。

我试图减少方法1的圈复杂度。我试图创建一个方法2,其中包含方法1中找到的重复模式的通用模式,并在必要时从方法1调用方法2 ...

第一种方法中的模式是这样的..

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();
        }
    }
}

从方法1调用方法2

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

通过这种方式,我必须能够降低圈复杂度

这就是我想要做的事情。

答案 11 :(得分:0)

嘿,我认为这可以解决问题。你需要决定它是哪个对象,以便你可以调用相应的操作,对吧???

由于我们使用重写的命令,因此可以实现所需的功能。

Eddie给出的可能是最合适的解决方案吗?

你会覆盖相应类中的方法,以便在调用它时采用corressponding方法。

你知道了吗?