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


85

我想对Java变量进行动态转换,转换类型存储在其他变量中。

这是常规转换:

 String a = (String) 5;

这就是我要的:

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

这有可能吗?谢谢!

更新资料

我正在尝试使用HashMap收到的A填充类。

这是构造函数:

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类型问题。


那没有任何意义。您希望变量名是一种将字符串转换为字符串的类型吗?什么?
cletus 2010年

3
我不知道答案,但是我担心这会变成维护地狱。。。我自己学习Java,但是我会避免需要这种方法的情况。我很确定您所做的任何事情都可以以更好的方式实现-仅需2美分。
Sejanus 2010年

好的,我将提供更多有关我要实现的信息。
ufk 2010年

在下面也更新了我的答案!
user85421 '01

Answers:


14

关于您的更新,用Java解决此问题的唯一方法是编写涵盖所有情况的代码,其中包含许多ifandelseinstanceof表达式的。您尝试执行的操作似乎就像是使用动态语言进行编程一样。在静态语言中,您尝试做的事情几乎是不可能的,并且您可能会为尝试做的事情选择完全不同的方法。静态语言不如动态语言灵活:)

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


那是没有道理的

String a = (theType) 5;

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

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


我希望我发布的更新内容能够解释我的工作意图。我来自php背景,所以也许这件事不可能在java中实现。
ufk 2010年

的确,在Java中,您不能那么动态,也请参阅我的更新。
akuhn,2010年

请参阅下面的BalusC答案,这是您必须走的长度(和痛苦)...
akuhn 2010年

我知道这很晚了,但我认为他的意思是[thetype] a =(thetype)some_object; 这是没有意义的,因为您可以执行Object a = some_object fine
George Xavier

120

是的,可以使用反射

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

投射对象不会改变任何东西。这只是编译器对待它的方式
这样做的唯一原因是检查对象是否是给定类或其任何子类的实例,但最好使用instanceofClass.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


22

您需要为此编写某种形式ObjectConverter。如果您同时具有要转换的对象并且知道要转换为的目标类,则此方法可行。在这种情况下,您可以通过获得目标类Field#getDeclaringClass()

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


@BalusC-我发现ObjectConverter代码很有趣,请您描述一下它的用例?
srini.venigalla,2010年

在首选使用约定优于配置且源类型与目标类型不匹配的情况下,这很有用。我在2-3年前(纯粹出于业余爱好)在ORM和MVC框架中使用了它。另请参阅博客文章的开头文字。
BalusC,2010年

12

您可以使用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);
    }
}

1
如果输入类型根本不是字段类型的超类型怎么办?然后,您真的需要以编程方式进行转换。
BalusC,2010年

7

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

}

是的,我认为这是质询者想要的。他/她只得到Class <?>,并想将实例转换为类“?”。默认情况下,java不支持此功能。但是使用<T>我们可以做到这一点。
ruiruige1991 '18

@ ruiruige1991这是错误的。在这种情况下,T是泛型的。泛型在运行时不执行任何操作。由于类型擦除,(T)等在运行时仅是(Object)等。简而言之,泛型->编译时,在运行时无效。由于动态->运行时和泛型->编译时,泛型是无用的。
乔治Xavier

5

它有效,您的方法甚至还有一个通用模式:Adapter模式。但是,当然,(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值进行转换。


2

您的问题不是缺少“动态转换”。根本无法投射IntegerDouble。您似乎想为Java提供一种类型的对象,一个可能不兼容的类型的字段,并以某种方式自动确定如何在这些类型之间进行转换。

这种事情对于Java和IMO这样的强类型语言来说是非常糟糕的。

您实际上想做什么?所有使用反射的方法看起来都很混乱。


@name:关于您不断建议的编辑:请注意,我并不是在谈论原始值,而是在谈论包装器类(由大写字母和样式表示为代码),而在它们之间的强制转换绝对是不可能的。
Michael Borgwardt

1

不要这样 只需有一个正确参数化的构造函数即可。无论如何,连接参数的设置和类型都是固定的,因此没有必要动态地进行所有操作。


1

值得一提的是,大多数脚本语言(如Perl)和非静态编译时语言(如Pick)都支持自动运行时动态String到(相对任意)对象转换。这也可以用Java来完成,而又不会丢失类型安全性,而静态类型化语言的优点就不会给其他一些用动态转换造成邪恶的语言带来讨厌的副作用。一个执行一些可疑数学的Perl示例:

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

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

Object fromString (String value, Class targetClass)

不幸的是,对于从String到BigDecimal或从String到Integer或任何其他不支持类层次结构的转换,没有内置的Java方法(如Class.cast())会执行此操作。就我而言,重点是提供一种完全动态的方法来实现此目的-我认为先前的参考文献不是正确的方法-必须对每次转换进行编码。简而言之,如果合法/可行,则实现只是从字符串强制转换。

因此,解决方案是简单的反思,寻找以下任一个的公共成员:

STRING_CLASS_ARRAY =(新类[] {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类以及实际上第3方库未正确编码。也就是说,有些方法将单个字符串参数作为输入并返回目标类的实例,但这不是您想的...考虑Integer类:

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

上面的方法与包装原始整型int的对象确实没有任何关系。反射会认为这可能是错误地根据String与解码,valueof和构造函数成员而不是Integer创建整数的可能候选者-它们都适合于大多数任意String转换,在这些转换中您实际上无法控制输入数据,而只想知道是否有可能是整数。

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

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

干杯。


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,下一个对象是Integer等,但是在执行程序的过程中,您现在首先获得File对象,然后是Double,等等。

这样,您可以解决这些情况,并即时进行“动态”投放。

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


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

您不需要动态地进行投射。具有doSomething()方法的超类和实现doSomething()方法的子类的公式是OOP多态性的主要目的之一。对象foo =新的Integer(“ 1”); foo.toString()返回1。即使将其分配给Object,它也是一个Integer,因此具有相同的行为。运行方法toString将运行Integer实现。多态性。
乔治Xavier

0

只是以为我会发布一些我认为非常有用的信息,并且对于遇到类似需求的人来说可能是可行的。

以下方法是我为JavaFX应用程序编写的一种方法,该方法避免了强制转换,并且还避免了每次返回控制器时是否编写对象b语句的对象x实例。

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控制器,以告诉它要返回哪种类型的对象。如果提供了错误的类并发生异常,则返回一个可选对象,在这种情况下,将返回一个空的可选对象,我们可以检查该对象。

这就是对该方法的最终调用的样子(如果返回的可选对象存在,则使用Consumer

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

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));
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.