java:我如何动态地将变量从一种类型转换为另一种类型?

时间:2010-01-24 14:11:59

标签: java casting dynamic-cast

我想为java变量进行动态转换,转换类型存储在不同的变量中。

这是常规演员:

 String a = (String) 5;

这就是我想要的:

 String theType = 'String';
 String a = (theType) 5;

有可能吗?如果是这样怎么样?谢谢!

更新

我正在尝试用我收到的hashMap填充一个类。

这是构造函数:

public ConnectParams(HashMap<String,Object> obj) {

    for (Map.Entry<String, Object> entry : obj.entrySet()) {
        try {
            Field f =  this.getClass().getField(entry.getKey());                
            f.set(this, entry.getValue()); /* <= CASTING PROBLEM */
        } catch (NoSuchFieldException ex) {
            log.error("did not find field '" + entry.getKey() + '"');
        } catch (IllegalAccessException ex) {
            log.error(ex.getMessage());         
        }
    }

}

这里的问题是一些类变量是Double类型,如果接收到数字3,它会将其视为Integer,并且我有类型问题。

13 个答案:

答案 0 :(得分:97)

是的,可以使用Reflection

Object something = "something";
String theType = "java.lang.String";
Class<?> theClass = Class.forName(theType);
Object obj = theClass.cast(something);

但这没有多大意义,因为生成的对象必须保存在Object类型的变量中。如果您需要变量属于给定类,则可以转换为该类。

如果你想获得一个给定的类,例如:

Object something = new Integer(123);
String theType = "java.lang.Number";
Class<? extends Number> theClass = Class.forName(theType).asSubclass(Number.class);
Number obj = theClass.cast(something);

但仍然没有必要这样做,你可以直接转向数字。

投射物体不会改变任何东西;它只是编译器对待它的方式 做这样的事情的唯一原因是检查对象是否是给定类或其任何子类的实例,但使用instanceofClass.isInstance()更好。

更新

根据您的上一次更新,真正的问题是您的HashMap中有一个应该分配给Double的Integer。在这种情况下你可以做的是检查字段的类型并使用数字的xxxValue()方法

...
Field f =  this.getClass().getField(entry.getKey());
Object value = entry.getValue();
if (Integer.class.isAssignableFrom(f.getType())) {
    value = Integer.valueOf(((Number) entry.getValue()).intValue());
} else if (Double.class.isAssignableFrom(f.getType())) {
    value = Double.valueOf(((Number) entry.getValue()).doubleValue());
} // other cases as needed (Long, Float, ...)
f.set(this, value);
...

(不确定我是否喜欢在地图中输入错误类型的想法)

答案 1 :(得分:20)

你需要为此编写一些ObjectConverter。如果您同时拥有要转换的对象并且您知道要转换为的目标类,则这是可行的。在这种特殊情况下,您可以通过Field#getDeclaringClass()获取目标类。

您可以找到here此类ObjectConverter的示例。它应该给你基本的想法。如果您想要更多的转换可能性,只需使用所需的参数和返回类型添加更多方法。

答案 2 :(得分:13)

  

关于您的更新,在Java中解决此问题的唯一方法是编写涵盖所有包含大量ifelseinstanceof表达式的案例的代码。您尝试执行的操作看起来好像用于使用动态语言编程。在静态语言中,您尝试做的几乎是不可能的,并且可能会为您尝试的操作选择完全不同的方法。静态语言不像动态语言那样灵活:)

     

Java最佳实践的好例子是answer by BalusC(即ObjectConverter)和answer by Andreas_D(即Adapter)。


这没有意义,

String a = (theType) 5;

a的类型静态绑定为String,因此对此静态类型进行动态转换没有任何意义。

PS: 您的示例的第一行可以写为Class<String> stringClass = String.class;,但仍然不能使用stringClass来投射变量。 < / p>

答案 3 :(得分:11)

您可以使用Class.cast()方法执行此操作,该方法将提供的参数动态转换为您拥有的类实例的类型。要获取特定字段的类实例,请在相关字段上使用getType()方法。我在下面给出了一个例子,但请注意它省略了所有错误处理,不应该不加修改地使用。

public class Test {

    public String var1;
    public Integer var2;
}

public class Main {

    public static void main(String[] args) throws Exception {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("var1", "test");
        map.put("var2", 1);

        Test t = new Test();

        for (Map.Entry<String, Object> entry : map.entrySet()) {
            Field f = Test.class.getField(entry.getKey());

            f.set(t, f.getType().cast(entry.getValue()));
        }

        System.out.println(t.var1);
        System.out.println(t.var2);
    }
}

答案 4 :(得分:6)

您可以编写一个简单的castMethod,如下所示。

private <T> T castObject(Class<T> clazz, Object object) {
  return (T) object;
}

在你的方法中你应该像

一样使用它
public ConnectParams(HashMap<String,Object> object) {

for (Map.Entry<String, Object> entry : object.entrySet()) {
    try {
        Field f =  this.getClass().getField(entry.getKey());                
        f.set(this, castObject(entry.getValue().getClass(), entry.getValue()); /* <= CASTING PROBLEM */
    } catch (NoSuchFieldException ex) {
        log.error("did not find field '" + entry.getKey() + '"');
    } catch (IllegalAccessException ex) {    
        log.error(ex.getMessage());          
    }    
}

}

答案 5 :(得分:4)

它有效,甚至还有一种常用的方法:Adapter pattern。但是,当然,(1)它不适用于将java原语转换为对象,以及(2)该类必须是可适应的(通常通过实现自定义接口)。

使用此模式,您可以执行以下操作:

Wolf bigBadWolf = new Wolf();
Sheep sheep = (Sheep) bigBadWolf.getAdapter(Sheep.class);

和Wolf类中的getAdapter方法:

public Object getAdapter(Class clazz) {
  if (clazz.equals(Sheep.class)) {
    // return a Sheep implementation
    return getWolfDressedAsSheep(this);
  }

  if (clazz.equals(String.class)) {
    // return a String
    return this.getName();
  }

  return null; // not adaptable
}

对于你的特殊想法 - 这是不可能的。您不能使用String值进行强制转换。

答案 6 :(得分:2)

你的问题不在于缺乏“动态铸造”。无法将Integer投射到Double。您似乎想要给Java一个类型的对象,一个可能不兼容类型的字段,并让它以某种方式自动找出如何在类型之间进行转换。

对于像Java和IMO这样的强类型语言来说,这种事情是非常好的理由。

你到底想要做什么?所有使用反射看起来都很腥。

答案 7 :(得分:1)

不要这样做。只需要一个正确参数化的构造函数。无论如何,连接参数的集合和类型都是固定的,因此动态地完成这一操作毫无意义。

答案 8 :(得分:1)

对于它的价值,大多数脚本语言(如Perl)和非静态编译时语言(如Pick)支持自动运行时动态String到(相对任意)对象转换。这可以用Java完成,同时不会丢失类型安全性和静态类型语言提供的好东西,而不会产生一些其他语言的恶意副作用,这些语言通过动态转换来做恶事。一个Perl示例,它做了一些有问题的数学:

print ++($foo = '99');  # prints '100'
print ++($foo = 'a0');  # prints 'a1'

在Java中,通过使用我称之为“交叉投射”的方法,可以更好地完成(IMHO)。 通过交叉构建,反射用于通过以下静态方法动态发现的构造函数和方法的延迟加载缓存中:

Object fromString (String value, Class targetClass)

不幸的是,没有内置的Java方法(例如Class.cast())会为String to BigDecimal或String to Integer或任何其他没有支持类层次结构的转换执行此操作。就我而言,重点是提供一种完全动态的方法来实现这一点 - 我不认为先前的引用是正确的方法 - 必须对每次转换进行编码。简单地说,如果它是合法/可能的话,实现就是从字符串转换。

所以解决方案是简单的反思,寻找公众成员:

STRING_CLASS_ARRAY =(new Class [] {String.class});

a)成员= targetClass.getMethod(method.getName(),STRING_CLASS_ARRAY); b)成员= targetClass.getConstructor(STRING_CLASS_ARRAY);

你会发现所有原语(Integer,Long等)和所有基础(BigInteger,BigDecimal等)甚至java.regex.Pattern都通过这种方法得到了解决。我已经在生产项目上取得了巨大成功,其中存在大量任意String值输入,需要进行更严格的检查。在这种方法中,如果没有方法或者在调用方法时抛出异常(因为它是非法的值,例如BigDecimal的非数字输入或Pattern的非法RegEx),它提供特定于目标类固有的逻辑。

这有一些缺点:

1)你需要很好地理解反射(这有点复杂,不适合初学者)。 2)一些Java类和第三方库(惊喜)没有正确编码。也就是说,有些方法将单个字符串参数作为输入并返回目标类的实例,但它不是您所想的...考虑整数类:

static Integer getInteger(String nm)
      Determines the integer value of the system property with the specified name.

上述方法实际上与Integers无关,因为对象包裹了基元int。 反射会发现这可能是一个可能的候选者,从一个字符串错误地创建一个Integer与decode,valueof和构造函数成员 - 这些都适用于你真正无法控制输入数据但只是想要的大多数任意String转换知道是否有可能是整数。

要解决上述问题,查找抛出异常的方法是一个好的开始,因为创建此类对象的实例的无效输入值应该抛出异常。不幸的是,实现方式因异常是否被声明为已检查而异。例如,Integer.valueOf(String)抛出已检查的NumberFormatException,但在反射查找期间未找到Pattern.compile()异常。同样,不是这种动态“交叉投射”方法的失败,我认为对象创建方法中的异常声明的非标准实现。

如果有人想了解有关上述方法的详细信息,请告诉我,但我认为这个解决方案更加灵活/可扩展,代码更少,而且不会丢失类型安全的优点。当然,最好“了解你的数据”,但正如我们许多人发现的那样,我们有时只接受非托管内容,必须尽最大努力才能正确使用它。

干杯。

答案 9 :(得分:1)

所以,这是一篇旧文章,但我认为我可以为此做出贡献。

您可以随时执行以下操作:

package com.dyna.test;  

import java.io.File;  
import java.lang.reflect.Constructor;  

public class DynamicClass{  

  @SuppressWarnings("unchecked")  
  public Object castDynamicClass(String className, String value){  
    Class<?> dynamicClass;  

    try  
    {  
      //We get the actual .class object associated with the specified name  
      dynamicClass = Class.forName(className);  



    /* We get the constructor that received only 
     a String as a parameter, since the value to be used is a String, but we could
easily change this to be "dynamic" as well, getting the Constructor signature from
the same datasource we get the values from */ 


      Constructor<?> cons =  
        (Constructor<?>) dynamicClass.getConstructor(new Class<?>[]{String.class});  

      /*We generate our object, without knowing until runtime 
 what type it will be, and we place it in an Object as 
 any Java object extends the Object class) */  
      Object object = (Object) cons.newInstance(new Object[]{value});  

      return object;  
    }  
    catch (Exception e)  
    {  
      e.printStackTrace();  
    }  
    return null;  
  }  

  public static void main(String[] args)  
  {   
    DynamicClass dynaClass = new DynamicClass();  

    /* 
 We specify the type of class that should be used to represent 
 the value "3.0", in this case a Double. Both these parameters 
 you can get from a file, or a network stream for example. */  
    System.out.println(dynaClass.castDynamicClass("java.lang.Double", "3.0"));  

    /* 
We specify a different value and type, and it will work as 
 expected, printing 3.0 in the above case and the test path in the one below, as the Double.toString() and 
 File.toString() would do. */  
    System.out.println(dynaClass.castDynamicClass("java.io.File", "C:\\testpath"));  
  }  

当然,这不是真正的动态转换,就像在其他语言(例如Python)中一样,因为java是静态类型的lang。但是,这可以解决一些边缘情况,您实际上需要以不同的方式加载某些数据,具体取决于某些标识符。此外,通过从同一数据源传递该参数,可以使获得具有String参数的构造函数的部分更加灵活。即从文件中,你得到你想要使用的构造函数签名,以及要使用的值列表,你配对的方式,比如说,第一个参数是一个String,第一个对象,把它作为一个String,下一个object是一个Integer等,但是在你的程序执行过程中,你现在首先得到一个File对象,然后是Double等等。

通过这种方式,您可以考虑这些情况,并在运行中进行一些“动态”投射。

希望这可以帮助任何人,因为这会在Google搜索中不断出现。

答案 10 :(得分:0)

我最近觉得我也必须这样做,但后来找到了另一种可能使我的代码看起来更整洁,并使用更好的OOP的方法。

我有许多兄弟类,每个类都实现某种方法doSomething()。为了访问该方法,我必须首先拥有该类的实例,但是我为所有兄弟类创建了一个超类,现在我可以从超类中访问该方法。

下面我将介绍两种“动态投射”的替代方法。

// Method 1.
mFragment = getFragmentManager().findFragmentByTag(MyHelper.getName(mUnitNum));
switch (mUnitNum) {
case 0:
    ((MyFragment0) mFragment).sortNames(sortOptionNum);
    break;
case 1:
    ((MyFragment1) mFragment).sortNames(sortOptionNum);
    break;
case 2:
    ((MyFragment2) mFragment).sortNames(sortOptionNum);
    break;
}

和我目前使用的方法,

// Method 2.
mSuperFragment = (MySuperFragment) getFragmentManager().findFragmentByTag(MyHelper.getName(mUnitNum));
mSuperFragment.sortNames(sortOptionNum);

答案 11 :(得分:0)

我想我会发布一些我发现非常有用的东西,并且对于有类似需求的人来说也是可能的。

以下方法是我为JavaFX应用程序编写的一个方法,以避免必须强制转换,并且每次返回控制器时都避免写入对象x语句对象b语句。

public <U> Optional<U> getController(Class<U> castKlazz){
    try {
        return Optional.of(fxmlLoader.<U>getController());
    }catch (Exception e){
        e.printStackTrace();
    }
    return Optional.empty();
}

获取控制器的方法声明是

public <T> T getController()

通过使用通过类对象传递给我的方法的类型U,它可以被转发到方法get控制器,告诉它要返回什么类型的对象。如果提供了错误的类,则返回一个可选对象,并且发生异常,在这种情况下将返回一个我们可以检查的空选项。

这是对方法的最终调用(如果返回的可选对象的存在需要使用者

getController(LoadController.class).ifPresent(controller->controller.onNotifyComplete());

答案 12 :(得分:0)

尝试使用动态强制转换。它会工作!!!

    String something = "1234";
    String theType = "java.lang.Integer";
    Class<?> theClass = Class.forName(theType);
    Constructor<?> cons = theClass.getConstructor(String.class);
    Object ob =  cons.newInstance(something);
    System.out.println(ob.equals(1234));