95

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

}

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

4

13 回答 13

129

是的,可以使用反射

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

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

如果要获取给定的类,Number例如:

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

但是这样做仍然没有意义,您可以直接转换为Number.

对象的铸造不会改变任何东西;这只是编译器处理它的方式。
这样做的唯一原因是检查对象是否是给定类或其任何子类的实例,但最好使用instanceofor来完成Class.isInstance()

更新

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

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

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

于 2010-01-24T14:35:21.157 回答
23

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

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

于 2010-01-24T14:29:05.477 回答
14

关于您的更新,在 Java 中解决此问题的唯一方法是编写包含大量ifelseinstanceof表达式的所有情况的代码。您尝试做的事情看起来好像用于使用动态语言进行编程。在静态语言中,您尝试做的事情几乎是不可能的,人们可能会为您尝试做的事情选择一种完全不同的方法。静态语言不如动态语言灵活:)

Java 最佳实践的好例子是BalusC (ie ObjectConverter) 的回答和 Andreas_D (ie Adapter) 的回答。


这没有意义,在

String a = (theType) 5;

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

PS: 您的示例的第一行可以写成,Class<String> stringClass = String.class;但仍然不能用于stringClass转换变量。

于 2010-01-24T14:17:23.130 回答
13

您可以使用该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);
    }
}
于 2010-01-24T14:57:01.700 回答
8

您可以编写一个简单的 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());          
    }    
}

}
于 2016-05-04T07:29:23.117 回答
5

它可以工作,甚至还有一种适用于您的方法的通用模式:适配器模式。但是当然,(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
}

对你来说特别的想法——那是不可能的。您不能使用字符串值进行强制转换。

于 2010-01-24T14:29:32.903 回答
2

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

这种事情对于像 Java 和 IMO 这样的强类型语言来说是非常令人厌恶的,这是有充分理由的。

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

于 2010-01-24T14:29:48.097 回答
1

不要这样做。只需使用正确参数化的构造函数即可。无论如何,连接参数的集合和类型都是固定的,所以动态地做这一切是没有意义的。

于 2010-01-24T14:43:57.243 回答
1

值得一提的是,大多数脚本语言(如 Perl)和非静态编译时语言(如 Pick)支持自动运行时动态字符串到(相对任意)对象的转换。这也可以在 Java 中实现,而不会失去类型安全性,并且静态类型语言提供的好东西不会像其他一些通过动态转换做坏事的语言那样产生令人讨厌的副作用。一个做一些有问题的数学的 Perl 例子:

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

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

Object fromString (String value, Class targetClass)

不幸的是,没有内置的 Java 方法(例如 Class.cast() )可以对 String 到 BigDecimal 或 String 到 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 都通过这种方法涵盖。我在需要进行更严格检查的大量任意字符串值输入的生产项目中使用它并取得了重大成功。在这种方法中,如果没有方法或调用该方法时抛出异常(因为它是非法值,例如 BigDecimal 的非数字输入或模式的非法 RegEx),则提供特定于目标类的内在逻辑。

这样做有一些缺点:

1)你需要很好地理解反射(这有点复杂,不适合新手)。2) 一些 Java 类和确实 3rd-party 库(令人惊讶)没有正确编码。也就是说,有些方法将单个字符串参数作为输入并返回目标类的实例,但这不是您所想的......考虑一下 Integer 类:

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

上述方法实际上与 Integers 作为包装基元 int 的对象无关。Reflection 会发现这可能是错误地从字符串创建整数的候选对象,而不是解码、valueof 和构造函数成员 - 它们都适用于大多数任意字符串转换,您实际上无法控制输入数据但只想知道是否可能是整数。

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

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

干杯。

于 2011-06-27T16:59:24.273 回答
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 是一种静态类型的语言。但是,这可以解决一些实际需要以不同方式加载某些数据的边缘情况,具体取决于某些标识符。此外,通过从同一数据源传递该参数,您获得带有 String 参数的构造函数的部分可能会更加灵活。即从文件中,您获得要使用的构造函数签名,以及要使用的值列表,这样您就可以配对,例如,第一个参数是字符串,与第一个对象,将其转换为字符串, next 对象是一个 Integer 等,但在程序执行过程中,您现在首先获得一个 File 对象,然后是一个 Double 等。

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

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

于 2012-11-07T15:02:54.060 回答
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);
于 2013-08-16T21:09:01.507 回答
0

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

以下方法是我为我的 JavaFX 应用程序编写的一种方法,以避免在每次返回控制器时都必须强制转换,也避免编写 if object x instance of object 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());
于 2015-05-07T01:04:23.713 回答
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));
于 2017-04-27T02:33:39.787 回答