web-dev-qa-db-fra.com

java: comment puis-je transtyper dynamiquement une variable d'un type à l'autre?

je voudrais faire un casting dynamique pour une variable Java, le type de casting est stocké dans une variable différente.

c'est un casting régulier:

 String a = (String) 5;

c'est ce que je veux:

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

c'est possible? et si oui comment? Merci!

mettre à jour

J'essaie de remplir une classe avec une hashMap que j'ai reçue.

c'est le constructeur:

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

}

le problème ici est que certaines des variables de classes sont de type Double, et si le nombre 3 est reçu, il le voit comme un entier et j'ai un problème de type. 

67
ufk

En ce qui concerne votre mise à jour, le seul moyen de résoudre ce problème dans Java consiste à écrire du code qui couvre tous les cas avec beaucoup d'expressions if et else et instanceof. Ce que vous essayez de faire semble être utilisé pour programmer avec des langages dynamiques. Dans les langages statiques, ce que vous essayez de faire est presque impossible et vous choisiriez probablement une approche totalement différente de ce que vous tentez de faire. Les langages statiques ne sont pas aussi flexibles que les langages dynamiques :)

Les bons exemples de Java meilleures pratiques sont les réponse de BalusC (c'est-à-dire ObjectConverter) et les réponse de Andreas_D (c'est-à-dire Adapter) au dessous de.


Cela n’a aucun sens, dans

String a = (theType) 5;

le type de a est statiquement lié à String et il n’a donc aucun sens de procéder à une conversion dynamique vers ce type statique.

PS: La première ligne de votre exemple peut être écrite sous la forme Class<String> stringClass = String.class; mais vous ne pouvez toujours pas utiliser stringClass pour convertir les variables.

13
akuhn

Oui, il est possible d’utiliser Reflection

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

mais cela n'a pas beaucoup de sens puisque l'objet résultant doit être sauvegardé dans une variable de type Object. Si vous avez besoin que la variable soit d'une classe donnée, vous pouvez simplement lancer un cast dans cette classe.

Si vous voulez obtenir une classe donnée, numérotez par exemple:

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

mais il n'y a toujours aucun intérêt à le faire, vous pouvez simplement lancer Number.

Le lancement d'un objet ne change rien; c'est juste le chemin le compilateur le traite.
La seule raison de faire quelque chose comme ça, est de vérifier si l'objet est une instance de la classe donnée ou de l'une de ses sous-classes, mais cela serait mieux fait d'utiliser instanceof ou Class.isInstance().

Mettre à jour

selon votre dernier update, le vrai problème est que vous avez un Integer dans votre HashMap qui devrait être assigné à un Double. Dans ce cas, vous pouvez vérifier le type du champ et utiliser les méthodes xxxValue() de 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);
...

(je ne suis pas sûr d'aimer l'idée d'avoir le mauvais type sur la carte)}

94
Carlos Heuberger

Vous aurez besoin d'écrire une sorte de ObjectConverter pour cela. C'est faisable si vous avez à la fois l'objet que vous voulez convertir et que vous connaissez la classe cible vers laquelle vous souhaitez convertir. Dans ce cas particulier, vous pouvez obtenir la classe cible par Field#getDeclaringClass() .

Vous pouvez trouver ici un exemple d'une telle variable ObjectConverter. Cela devrait vous donner une idée de base. Si vous souhaitez davantage de possibilités de conversion, ajoutez simplement plus de méthodes à l'argument et au type de retour souhaités.

20
BalusC

Pour ce faire, utilisez la méthode Class.cast() , qui convertit dynamiquement le paramètre fourni en fonction du type de l'instance de classe dont vous disposez. Pour obtenir l'instance de classe d'un champ particulier, vous utilisez la méthode getType() sur le champ en question. J'ai donné un exemple ci-dessous, mais notez qu'il omet toute gestion des erreurs et ne doit pas être utilisé sans modification.

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);
    }
}
11
Jared Russell

Vous pouvez écrire un castMethod simple, comme celui ci-dessous.

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

Dans votre méthode, vous devriez l'utiliser comme

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

}
6
Hoji

Cela fonctionne et il existe même un modèle commun à votre approche: le modèle Adaptateur . Mais bien sûr, (1) cela ne fonctionne pas pour la conversion des primitives Java en objets et (2) la classe doit être adaptable (généralement en implémentant une interface personnalisée).

Avec ce modèle, vous pouvez faire quelque chose comme:

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

et la méthode getAdapter dans la classe 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
}

Pour vous une idée spéciale - c'est impossible. Vous ne pouvez pas utiliser une valeur de chaîne pour la diffusion.

4
Andreas_D

Votre problème n'est pas le manque de "casting dynamique". Transformer Integer en Double n'est pas du tout possible. Vous semblez vouloir donner à Java un objet d'un type, un champ d'un type éventuellement incompatible, et lui demander de manière automatique de savoir comment convertir les types.

Ce genre de chose est anathème pour un langage fortement typé comme Java et IMO pour de très bonnes raisons.

Qu'essayez-vous de faire? Toute cette utilisation de la réflexion semble assez louche.

2
Michael Borgwardt

Donc, ceci est un vieux post, mais je pense pouvoir apporter quelque chose.

Vous pouvez toujours faire quelque chose comme ça:

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

Bien sûr, ce n'est pas vraiment un casting dynamique, comme dans d'autres langages (Python par exemple), car Java est un langage stat typé. Toutefois, cela peut résoudre certains problèmes marginaux dans lesquels vous devez charger certaines données de différentes manières, en fonction de certains identifiants. En outre, la partie dans laquelle vous obtenez un constructeur avec un paramètre String pourrait probablement être rendue plus flexible en faisant passer ce paramètre à partir de la même source de données. C'est à dire. À partir d'un fichier, vous obtenez la signature du constructeur que vous souhaitez utiliser et la liste des valeurs à utiliser. Ainsi, vous pouvez associer, par exemple, le premier paramètre est une chaîne, avec le premier objet, le transformant en chaîne, puis objet est un entier, etc., mais quelque part au cours de l'exécution de votre programme, vous obtenez maintenant un objet File d'abord, puis un Double, etc.

De cette façon, vous pouvez prendre en compte ces cas et créer un casting "dynamique" à la volée.

J'espère que cela aidera tout le monde car cela continue à apparaître dans les recherches Google.

1
Acapulco

Pour ce qui en vaut la peine, la plupart des langages de script (tels que Perl) et des langages de compilation non statiques (tels que Pick) prennent en charge la conversion dynamique à l'exécution automatique lors de l'exécution de chaînes en objets (relativement arbitraires). Ceci peut être accompli également en Java sans perdre la sécurité de type et les bonnes choses que les langages statiquement typés fournissent SANS les effets secondaires désagréables de certains des autres langages qui font le mal avec un casting dynamique. Un exemple Perl qui fait des calculs douteux:

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

En Java, ceci est mieux accompli (IMHO) en utilisant une méthode que j'appelle "cross-casting" . Avec cross-casting, la réflexion est utilisée dans un cache chargé paresseux de constructeurs et de méthodes dynamiquement découvertes via les éléments suivants: méthode statique: 

Object fromString (String value, Class targetClass)

Malheureusement, aucune méthode Java intégrée telle que Class.cast () ne le fera pour String to BigDecimal ou String to Integer ou toute autre conversion dans laquelle aucune hiérarchie de classes n'est prise en charge. Pour ma part, le but est de fournir un moyen totalement dynamique pour y parvenir - pour lequel je ne pense pas que la référence préalable soit la bonne approche - avoir à coder chaque conversion. En termes simples, l’implémentation consiste simplement à transtyper une chaîne si elle est légale/possible.

La solution est donc une simple réflexion à la recherche de membres publics de:

STRING_CLASS_ARRAY = (nouvelle classe [] {String.class});

a) Membre Member = targetClass.getMethod (method.getName (), STRING_CLASS_ARRAY); b. Membre Member = targetClass.getConstructor (STRING_CLASS_ARRAY);

Vous constaterez que toutes les primitives (Integer, Long, etc.) et toutes les bases (BigInteger, BigDecimal, etc.) et même Java.regex.Pattern sont tous couverts par cette approche. Je l'ai utilisé avec un succès significatif sur des projets de production où il existe une énorme quantité d'entrées de valeur String arbitraires pour lesquelles une vérification plus stricte était nécessaire. Dans cette approche, s'il n'y a pas de méthode ou si la méthode est invoquée, une exception est levée (car il s'agit d'une valeur non autorisée telle qu'une entrée non numérique dans un RegEx BigDecimal ou illégal), qui fournit la vérification spécifique à la logique inhérente à la classe cible.

Il y a quelques inconvénients à ceci:

1) Vous devez bien comprendre la réflexion (c'est un peu compliqué et pas pour les novices) . 2) Certaines classes Java et même les bibliothèques tierces ne sont (surprise) pas correctement codées. Autrement dit, il existe des méthodes qui prennent un seul argument de chaîne en entrée et renvoient une instance de la classe cible, mais ce n'est pas ce que vous pensez ... Considérez la classe Integer:

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

La méthode ci-dessus n’a vraiment rien à voir avec les entiers car les primitives qui encapsulent des objets ints . Reflection le trouvera comme un candidat possible pour la création incorrecte d’un entier à partir d’une chaîne par rapport aux membres decode, valueof et constructor, Conversions de chaînes arbitraires où vous n'avez vraiment pas le contrôle de vos données d'entrée mais que vous voulez juste savoir s'il est possible d'avoir un Integer.

Pour remédier à ce qui précède, rechercher des méthodes générant des exceptions est un bon début car les valeurs d'entrée non valides créant des instances de tels objets devraient déclenchent une exception. Malheureusement, les implémentations varient selon que les exceptions sont déclarées vérifiées ou non. Integer.valueOf (String) lève une exception NumberFormatException vérifiée, par exemple, mais les exceptions Pattern.compile () ne sont pas détectées lors des recherches de réflexion. Encore une fois, ce n’est pas un défaut de cette approche dynamique de "cross-casting", je pense plutôt à une implémentation très non standard des déclarations d’exception dans les méthodes de création d’objets.

Si vous souhaitez plus de détails sur la mise en œuvre de ce qui précède, faites-le-moi savoir, mais je pense que cette solution est beaucoup plus flexible/extensible et avec moins de code, sans perdre les avantages de la sécurité de type. Bien sûr, il est toujours préférable de "connaître ses données", mais comme beaucoup d’entre nous le constatons, nous ne recevons parfois que du contenu non géré et devons faire de notre mieux pour l’utiliser correctement.

À votre santé.

1
Darrell Teague

Ne fais pas ça. Juste avoir un constructeur correctement paramétré à la place. Le jeu et les types de paramètres de connexion sont quand même fixes, il est donc inutile de le faire de manière dynamique.

1
Bandi-T

Récemment, j'ai senti que je devais le faire aussi, mais j'ai ensuite trouvé une autre façon de rendre mon code plus net et d'utiliser mieux la POO.

J'ai plusieurs classes de frères et sœurs qui implémentent chacune une certaine méthode doSomething(). Afin d'accéder à cette méthode, je devrais d'abord avoir une instance de cette classe, mais j'ai créé une super-classe pour toutes mes classes frères et maintenant, je peux accéder à la méthode depuis la super-classe.

Ci-dessous, je montre deux manières alternatives au "casting dynamique".

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

et ma méthode actuellement utilisée,

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

Essayez ceci pour le casting dynamique. Ça va marcher!!! 

    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));
0
Sunil M M

Je pensais que je posterais quelque chose que je trouve très utile et qui pourrait être possible pour quelqu'un qui a des besoins similaires.

La méthode suivante est une méthode que j'ai écrite pour mon application JavaFX afin d'éviter de devoir transtyper et d'éviter d'écrire si une instance d'objet x d'instruction d'objet b chaque fois que le contrôleur a été renvoyé.

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

La déclaration de méthode pour obtenir le contrôleur a été

public <T> T getController()

En utilisant le type U passé dans ma méthode via l'objet class, il pourrait être transmis au contrôleur d'obtention de méthode pour lui indiquer le type d'objet à renvoyer. Un objet facultatif est renvoyé si la mauvaise classe est fournie et une exception se produit. Dans ce cas, un facultatif vide sera renvoyé et nous pourrons le vérifier.

C’est à quoi ressemblait l’appel final à la méthode (le cas échéant, l’objet retourné prend un objet Consumer.

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