java: ¿Cómo puedo realizar la conversión dinámica de una variable de un tipo a otro?

85

Me gustaría hacer una conversión dinámica para una variable de Java, el tipo de conversión se almacena en una variable diferente.

Este es el casting habitual:

 String a = (String) 5;

Esto es lo que quiero:

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

¿Es posible? y si lo es, cómo? ¡Gracias!

Actualizar

Estoy tratando de completar una clase con un HashMapque recibí.

Este es el constructor:

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

}

El problema aquí es que algunas de las variables de la clase son de tipo Double, y si se recibe el número 3, lo ve como Integery tengo un problema de tipo.

ufk
fuente
Eso no tiene ningún sentido. ¿Quiere que el nombre de la variable sea un tipo para convertir una cadena en una cadena? ¿Qué?
cletus
3
No sé una respuesta, pero me temo que esto puede convertirse en un infierno de mantenimiento ... Solo estoy aprendiendo Java, pero evitaría situaciones que requieran un enfoque como este. Estoy bastante seguro de que cualquier cosa que hagas se puede implementar de una mejor manera ... solo mis 2 centavos.
Sejanus
ok, proporcionaré más información sobre lo que estoy tratando de lograr.
ufk
¡También actualicé mi respuesta a continuación!
user85421

Respuestas:

14

Con respecto a su actualización, la única forma de resolver esto en Java es escribir código que cubra todos los casos con muchas expresiones ify elsey instanceof. Lo que intenta hacer parece estar acostumbrado a programar con lenguajes dinámicos. En los lenguajes estáticos, lo que intenta hacer es casi imposible y probablemente uno elegiría un enfoque totalmente diferente para lo que intenta hacer. Los lenguajes estáticos no son tan flexibles como los dinámicos :)

Buenos ejemplos de las mejores prácticas de Java son la respuesta de BalusC (ie ObjectConverter) y la respuesta de Andreas_D (ie Adapter) a continuación.


Eso no tiene sentido, en

String a = (theType) 5;

el tipo de aestá estáticamente obligado a ser, Stringpor lo que no tiene ningún sentido tener una conversión dinámica para este tipo estático.

PD: La primera línea de su ejemplo podría escribirse como, Class<String> stringClass = String.class;pero aún así, no puede usarla stringClasspara convertir variables.

akuhn
fuente
Espero que la actualización que publiqué explique lo que estoy tratando de hacer. Vengo de un entorno php, así que tal vez esto no sea posible de lograr en Java.
ufk
Exactamente, en Java no puedes ser tan dinámico, mira mi actualización también.
akuhn
Vea la respuesta de BalusC a continuación, esta es la longitud (y el dolor) a la que debe llegar ...
akuhn
Sé que es tarde, pero creo que se refería a [el tipo] a = (el tipo) algún_objeto; lo cual no tiene sentido porque puedes hacer Object a = some_object bien
George Xavier
120

Sí, es posible usar Reflection

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

pero eso no tiene mucho sentido ya que el objeto resultante debe guardarse en una variable de Objecttipo. Si necesita que la variable sea de una clase determinada, puede lanzarla a esa clase.

Si desea obtener una clase determinada, Numberpor ejemplo:

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

pero todavía no tiene sentido hacerlo, simplemente puedes lanzar a Number.

El lanzamiento de un objeto NO cambia nada; es simplemente la forma en que el compilador lo trata.
La única razón para hacer algo así es comprobar si el objeto es una instancia de la clase dada o de alguna subclase de ella, pero sería mejor hacerlo usando instanceofo Class.isInstance().

Actualizar

De acuerdo con su última actualización, el problema real es que tiene un Integeren su HashMapque debe asignarse a unDouble . Lo que puede hacer en este caso es verificar el tipo de campo y usar los xxxValue()métodos deNumber

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

(no estoy seguro si me gusta la idea de tener el tipo incorrecto en el Map)

usuario85421
fuente
22

Necesitarás escribir algo ObjectConverterpara esto. Esto es factible si tiene el objeto que desea convertir y conoce la clase de destino a la que desea convertir. En este caso particular, puede obtener la clase de destino Field#getDeclaringClass().

Puede encontrar aquí un ejemplo de tal ObjectConverter. Debería darte la idea básica. Si desea más posibilidades de conversión, simplemente agregue más métodos con el argumento deseado y el tipo de retorno.

BalusC
fuente
@BalusC - Encuentro interesante el código de ObjectConverter, ¿podría describir los casos de uso para él?
srini.venigalla
Es útil en los casos en que se prefiere la convención sobre la configuración y el tipo de fuente no coincide con el tipo de destino. Lo he usado hace 2-3 años en mis marcos ORM y MVC (puros para fines de hobby). Consulte también el texto principal del artículo del blog.
BalusC
12

Puede hacer esto usando el Class.cast()método, que envía dinámicamente el parámetro proporcionado al tipo de instancia de clase que tiene. Para obtener la instancia de clase de un campo en particular, usa el getType()método en el campo en cuestión. He dado un ejemplo a continuación, pero tenga en cuenta que omite todo el manejo de errores y no debe usarse sin modificar.

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);
    }
}
Jared Russell
fuente
1
¿Qué pasa si el tipo de entrada no es un supertipo del tipo de campo en absoluto? Entonces realmente necesitará convertir programáticamente.
BalusC
7

Puedes escribir un método de conversión simple como el siguiente.

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

En tu método deberías usarlo como

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

}
Hoji
fuente
Sí, creo que esta respuesta es lo que quiere el solicitante. Él / Ella acaba de obtener una Clase <?> Y quiere convertir una instancia a la clase "?". De forma predeterminada, Java no admite esto. Pero usando <T> podemos hacer esto.
ruiruige1991
@ ruiruige1991 Esto está mal. T en cuyo caso es un genérico. Los genéricos no hacen nada durante el tiempo de ejecución. (T) blah solo sería (Object) blah durante el tiempo de ejecución debido al borrado de tipo. En resumen, genéricos -> tiempo de compilación, y no tiene ningún efecto durante el tiempo de ejecución. Desde dinámica -> tiempo de ejecución y genéricos -> tiempo de compilación, los genéricos son inútiles.
George Xavier
5

Funciona e incluso hay un patrón común para su enfoque: el patrón Adaptador . Pero, por supuesto, (1) no funciona para convertir primitivas de Java en objetos y (2) la clase tiene que ser adaptable (generalmente implementando una interfaz personalizada).

Con este patrón podrías hacer algo como:

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

y el método getAdapter en la clase Wolf:

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
}

Para tu idea especial, eso es imposible. No puedes usar un valor de cadena para transmitir.

Andreas Dolk
fuente
2

Tu problema no es la falta de "transmisión dinámica". Transmitir Integera Doubleno es posible en absoluto. Parece que desea darle a Java un objeto de un tipo, un campo de un tipo posiblemente incompatible, y que de alguna manera descubra automáticamente cómo convertir entre los tipos.

Este tipo de cosas es un anatema para un lenguaje fuertemente tipado como Java, y en mi opinión por muy buenas razones.

¿Qué estás intentando hacer realmente? Todo ese uso de la reflexión parece bastante sospechoso.

Michael Borgwardt
fuente
@name: con respecto a la edición que sigue sugiriendo: tenga en cuenta que no estoy hablando de valores primitivos sino de las clases contenedoras (indicadas por las mayúsculas y el estilo como código), y la conversión entre esos definitivamente no es posible.
Michael Borgwardt
1

No hagas esto. En su lugar, solo tenga un constructor correctamente parametrizado. El conjunto y los tipos de parámetros de conexión están fijos de todos modos, por lo que no tiene sentido hacer todo esto de forma dinámica.

Bandido
fuente
1

Por lo que vale, la mayoría de los lenguajes de scripting (como Perl) y los lenguajes en tiempo de compilación no estáticos (como Pick) admiten conversiones automáticas de cadenas dinámicas en tiempo de ejecución a objetos (relativamente arbitrarios). Esto también se PUEDE lograr en Java sin perder la seguridad de tipos y las cosas buenas que proporcionan los lenguajes de tipo estático SIN los desagradables efectos secundarios de algunos de los otros lenguajes que hacen cosas malas con la conversión dinámica. Un ejemplo de Perl que hace algunas matemáticas cuestionables:

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

En Java, esto se logra mejor (en mi humilde opinión) utilizando un método que llamo "cross-casting". Con la transmisión cruzada, la reflexión se usa en una caché de constructores y métodos con carga diferida que se descubren dinámicamente a través del siguiente método estático:

Object fromString (String value, Class targetClass)

Desafortunadamente, ningún método Java integrado como Class.cast () hará esto para String to BigDecimal o String to Integer o cualquier otra conversión donde no haya una jerarquía de clases de soporte. Por mi parte, el punto es proporcionar una forma completamente dinámica de lograr esto, para lo cual no creo que la referencia previa sea el enfoque correcto, tener que codificar cada conversión. En pocas palabras, la implementación es solo para lanzar desde una cadena si es legal / posible.

Entonces, la solución es una simple reflexión buscando Miembros públicos de:

STRING_CLASS_ARRAY = (nueva Clase [] {String.class});

a) Miembro miembro = targetClass.getMethod (method.getName (), STRING_CLASS_ARRAY); b) Miembro miembro = targetClass.getConstructor (STRING_CLASS_ARRAY);

Encontrará que todas las primitivas (Integer, Long, etc.) y todos los conceptos básicos (BigInteger, BigDecimal, etc.) e incluso java.regex.Pattern están cubiertos a través de este enfoque. He utilizado esto con un éxito significativo en proyectos de producción donde hay una gran cantidad de entradas de valor de cadena arbitrarias donde se necesitaba una verificación más estricta. En este enfoque, si no hay un método o cuando se invoca el método, se lanza una excepción (porque es un valor ilegal, como una entrada no numérica para un BigDecimal o una expresión regular ilegal para un patrón), eso proporciona la verificación específica para la lógica inherente de la clase de destino.

Esto tiene algunas desventajas:

1) Necesitas entender bien la reflexión (esto es un poco complicado y no para principiantes). 2) Algunas de las clases de Java y, de hecho, las bibliotecas de terceros (sorpresa) no están codificadas correctamente. Es decir, hay métodos que toman un único argumento de cadena como entrada y devuelven una instancia de la clase de destino, pero no es lo que piensas ... Considere la clase Integer:

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

El método anterior realmente no tiene nada que ver con los enteros como objetos que envuelven enteros primitivos. Reflection encontrará esto como un posible candidato para crear un entero a partir de una cadena de forma incorrecta frente a los miembros decodificación, valor y constructor, que son todos adecuados para la mayoría de las conversiones de cadenas arbitrarias en las que realmente no tiene control sobre sus datos de entrada, pero solo desea saber si es posible un entero.

Para remediar lo anterior, buscar métodos que arrojen Excepciones es un buen comienzo porque los valores de entrada inválidos que crean instancias de tales objetos deben una Excepción. Desafortunadamente, las implementaciones varían en cuanto a si las Excepciones se declaran como verificadas o no. Integer.valueOf (String) arroja una NumberFormatException marcada, por ejemplo, pero las excepciones Pattern.compile () no se encuentran durante las búsquedas de reflejos. Una vez más, no es una falla de este enfoque dinámico de "conversión cruzada", creo que es una implementación muy no estándar para las declaraciones de excepción en los métodos de creación de objetos.

Si alguien desea obtener más detalles sobre cómo se implementó lo anterior, hágamelo saber, pero creo que esta solución es mucho más flexible / extensible y con menos código sin perder las partes buenas de seguridad de tipos. Por supuesto, siempre es mejor "conocer sus datos" pero, como muchos de nosotros descubrimos, a veces solo somos destinatarios de contenido no administrado y tenemos que hacer todo lo posible para usarlo correctamente.

Salud.

Darrell Teague
fuente
1

Entonces, esta es una publicación antigua, sin embargo, creo que puedo aportar algo.

Siempre puedes hacer algo como esto:

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

Por supuesto, esto no es una conversión realmente dinámica, como en otros lenguajes (Python por ejemplo), porque java es un idioma de tipo estático. Sin embargo, esto puede resolver algunos casos marginales en los que realmente necesita cargar algunos datos de diferentes maneras, dependiendo de algún identificador. Además, la parte en la que obtiene un constructor con un parámetro de cadena probablemente podría hacerse más flexible si ese parámetro se pasa desde la misma fuente de datos. Es decir, de un archivo, obtiene la firma del constructor que desea usar y la lista de valores que se usarán, de esa manera se empareja, por ejemplo, el primer parámetro es una Cadena, con el primer objeto, convirtiéndolo como una Cadena, El siguiente objeto es un Integer, etc., pero en algún momento de la ejecución de su programa, ahora obtiene primero un objeto File, luego un Double, etc.

De esta manera, puede dar cuenta de esos casos y hacer un casting algo "dinámico" sobre la marcha.

Espero que esto ayude a cualquiera, ya que sigue apareciendo en las búsquedas de Google.

Acapulco
fuente
0

Recientemente sentí que también tenía que hacer esto, pero luego encontré otra forma que posiblemente haga que mi código se vea más ordenado y use mejor OOP.

Tengo muchas clases de hermanos que implementan un método determinado. doSomething() . Para acceder a ese método, primero tendría que tener una instancia de esa clase, pero creé una superclase para todas mis clases hermanas y ahora puedo acceder al método desde la superclase.

A continuación, muestro dos formas alternativas de "casting dinámico".

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

y mi método utilizado actualmente,

// Method 2.
mSuperFragment = (MySuperFragment) getFragmentManager().findFragmentByTag(MyHelper.getName(mUnitNum));
mSuperFragment.sortNames(sortOptionNum);
Anonage
fuente
No debería necesitar lanzar dinámicamente nunca. Tu fórmula de tener una superclase con el método doSomething () y subclases que implementan el método doSomething () es uno de los propósitos principales de OOP, polimorfismo. Objeto foo = new Integer ("1"); foo.toString () devuelve 1. Aunque está asignado a Object, es un Integer y, por lo tanto, se comporta como tal. La ejecución del método toString ejecutará la implementación de Integer. Polimorfismo.
George Xavier
0

Solo pensé en publicar algo que encontré bastante útil y que podría ser posible para alguien que tenga necesidades similares.

El siguiente método fue un método que escribí para mi aplicación JavaFX para evitar tener que transmitir y también evitar escribir si el objeto x instancia de las declaraciones del objeto b cada vez que se devolvió el controlador.

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

La declaración de método para obtener el controlador fue

public <T> T getController()

Al usar el tipo U pasado a mi método a través del objeto de clase, podría reenviarse al controlador de obtención del método para indicarle qué tipo de objeto devolver. Se devuelve un objeto opcional en caso de que se proporcione la clase incorrecta y se produzca una excepción, en cuyo caso se devolverá un opcional vacío que podemos verificar.

Así es como se veía la llamada final al método (si está presente el objeto opcional devuelto, toma un Consumer

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

Prueba esto para Dynamic Casting. ¡¡¡Funcionará!!!

    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));
Sunil MM
fuente