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 HashMap
que 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 Integer
y tengo un problema de tipo.
Respuestas:
Eso no tiene sentido, en
String a = (theType) 5;
el tipo de
a
está estáticamente obligado a ser,String
por 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 usarlastringClass
para convertir variables.fuente
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
Object
tipo. Si necesita que la variable sea de una clase determinada, puede lanzarla a esa clase.Si desea obtener una clase determinada,
Number
por 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
instanceof
oClass.isInstance()
.Actualizar
De acuerdo con su última actualización, el problema real es que tiene un
Integer
en suHashMap
que debe asignarse a unDouble
. Lo que puede hacer en este caso es verificar el tipo de campo y usar losxxxValue()
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
)fuente
Necesitarás escribir algo
ObjectConverter
para 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 destinoField#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.fuente
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 elgetType()
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); } }
fuente
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()); } } }
fuente
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.
fuente
Tu problema no es la falta de "transmisión dinámica". Transmitir
Integer
aDouble
no 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.
fuente
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.
fuente
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.
fuente
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.
fuente
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);
fuente
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
fuente
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));
fuente