java: Wie kann ich eine Variable dynamisch von einem Typ in einen anderen umwandeln?

85

Ich möchte dynamisches Casting für eine Java-Variable durchführen, der Casting-Typ wird in einer anderen Variablen gespeichert.

Dies ist das reguläre Casting:

 String a = (String) 5;

Das ist was ich will:

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

Ist das möglich und wenn ja wie? Vielen Dank!

Aktualisieren

Ich versuche, eine Klasse mit einer zu füllen HashMap, die ich erhalten habe.

Dies ist der Konstruktor:

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

}

Das Problem hier ist, dass einige der Variablen der Klasse vom Typ sind Double, und wenn die Nummer 3 empfangen wird, sieht es so aus Integerund ich habe ein Typproblem.

ufk
quelle
Das macht keinen Sinn. Sie möchten, dass der Variablenname ein Typ ist, mit dem eine Zeichenfolge in eine Zeichenfolge umgewandelt wird? Was?
Cletus
3
Ich weiß keine Antwort, aber ich fürchte, dies kann zur Hölle der Wartung werden ... Ich lerne nur Java selbst, aber ich würde Situationen vermeiden, die einen solchen Ansatz erfordern. Ich bin mir ziemlich sicher, dass alles, was Sie tun, besser umgesetzt werden kann ... nur meine 2 Cent.
Sejanus
ok, ich werde mehr Informationen zu dem geben, was ich erreichen möchte.
Ufk
habe auch meine Antwort unten aktualisiert!
user85421

Antworten:

14

In Bezug auf Ihr Update besteht die einzige Möglichkeit, dies in Java zu lösen, darin, Code zu schreiben, der alle Fälle mit vielen ifund elseund instanceofAusdrücken abdeckt . Was Sie versuchen, sieht so aus, als würden Sie zum Programmieren mit dynamischen Sprachen verwendet. In statischen Sprachen ist das, was Sie versuchen, fast unmöglich, und man würde wahrscheinlich einen völlig anderen Ansatz für das wählen, was Sie versuchen. Statische Sprachen sind einfach nicht so flexibel wie dynamische :)

Gute Beispiele für bewährte Java-Methoden sind die Antwort von BalusC (dh ObjectConverter) und die Antwort von Andreas_D (dh Adapter) unten.


Das macht keinen Sinn

String a = (theType) 5;

Der Typ von aist statisch gebunden, Stringdaher macht es keinen Sinn, eine dynamische Umwandlung in diesen statischen Typ vorzunehmen.

PS: Die erste Zeile Ihres Beispiels könnte so geschrieben werden Class<String> stringClass = String.class;, dass Sie sie dennoch nicht stringClasszum Umwandeln von Variablen verwenden können.

akuhn
quelle
Ich hoffe, dass das Update, das ich gepostet habe, erklärt, was ich versuche zu tun. Ich komme aus einem PHP-Hintergrund, also ist dieses Ding in Java vielleicht nicht möglich.
Ufk
Genau, in Java kann man nicht so dynamisch sein, siehe auch mein Update.
Akuhn
Siehe BalusCs Antwort unten, dies ist die Länge (und der Schmerz), zu der Sie gehen müssen ...
akuhn
Ich weiß, dass dies spät ist, aber ich denke, er meinte [den Typ] a = (den Typ) some_object; Das ist sinnlos, weil Sie einfach Objekt a = some_object fein machen können
George Xavier
120

Ja, es ist möglich, Reflection zu verwenden

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

Das macht aber wenig Sinn, da das resultierende Objekt in einer Variablen vom ObjectTyp gespeichert werden muss . Wenn die Variable einer bestimmten Klasse angehören soll, können Sie sie einfach in diese Klasse umwandeln.

Wenn Sie eine bestimmte Klasse erhalten möchten, Numberzum Beispiel:

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

aber es macht immer noch keinen Sinn, es zu tun, man könnte es einfach besetzen Number.

Das Gießen eines Objekts ändert nichts; Es ist genau so, wie der Compiler es behandelt.
Der einzige Grund, so etwas zu tun, besteht darin, zu überprüfen, ob das Objekt eine Instanz der angegebenen Klasse oder einer Unterklasse davon ist. Dies sollte jedoch besser mit instanceofoder erfolgen Class.isInstance().

Aktualisieren

Laut Ihrem letzten Update besteht das eigentliche Problem darin, dass Sie ein Integerin Ihrem haben HashMap, das einem zugewiesen werden sollte Double. In diesem Fall können Sie den Feldtyp überprüfen und die xxxValue()Methoden von verwendenNumber

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

(Ich bin mir nicht sicher, ob mir die Idee gefällt, den falschen Typ in der zu haben. Map)

user85421
quelle
22

Sie müssen eine Art dafür schreiben ObjectConverter. Dies ist möglich, wenn Sie sowohl das Objekt haben, das Sie konvertieren möchten, als auch die Zielklasse kennen, in die Sie konvertieren möchten. In diesem speziellen Fall können Sie die Zielklasse von erhalten Field#getDeclaringClass().

Ein Beispiel dafür finden Sie hierObjectConverter . Es sollte Ihnen die Grundidee geben. Wenn Sie mehr Konvertierungsmöglichkeiten wünschen, fügen Sie einfach weitere Methoden mit dem gewünschten Argument und Rückgabetyp hinzu.

BalusC
quelle
@BalusC - Ich finde den ObjectConverter-Code interessant. Könnten Sie bitte die Anwendungsfälle dafür beschreiben?
srini.venigalla
Dies ist nützlich, wenn die Konvention der Konfiguration vorgezogen wird und der Quelltyp nicht mit dem Zieltyp übereinstimmt. Ich habe es vor 2-3 Jahren in meinen (für Hobbyzwecke reinen) ORM- und MVC-Frameworks verwendet. Siehe auch den Leittext des Blog-Artikels.
BalusC
12

Sie können dies mit der Class.cast()Methode tun , die den angegebenen Parameter dynamisch in den Typ der vorhandenen Klasseninstanz umwandelt. Um die Klasseninstanz eines bestimmten Felds abzurufen, verwenden Sie die getType()Methode für das betreffende Feld. Ich habe unten ein Beispiel gegeben, aber beachte, dass es jede Fehlerbehandlung auslässt und nicht unverändert verwendet werden sollte.

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
quelle
1
Was ist, wenn der Eintragstyp überhaupt kein Supertyp des Feldtyps ist? Sie müssen dann wirklich programmgesteuert konvertieren.
BalusC
7

Sie können eine einfache castMethod wie die folgende schreiben.

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

In Ihrer Methode sollten Sie es wie verwenden

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
quelle
Ja, ich denke, diese Antwort ist das, was der Fragesteller will. Er / Sie erhält gerade eine Klasse <?> Und möchte eine Instanz in die Klasse "?" Konvertieren. Standardmäßig unterstützt Java dies nicht. Aber mit <T> können wir das tun.
ruiruige1991
@ ruiruige1991 Das ist falsch. T in diesem Fall ist ein Generikum. Generika tun zur Laufzeit nichts. (T) blah wäre zur Laufzeit nur (Object) blah, da der Typ gelöscht wird. Kurz gesagt, Generika -> Kompilierungszeit und haben zur Laufzeit keine Auswirkung. Da dynamisch -> Laufzeit und Generika -> Kompilierungszeit - sind Generika nutzlos.
George Xavier
5

Es funktioniert und es gibt sogar ein allgemeines Muster für Ihren Ansatz: das Adaptermuster . Aber (1) es funktioniert natürlich nicht zum Umwandeln von Java-Grundelementen in Objekte und (2) die Klasse muss anpassbar sein (normalerweise durch Implementieren einer benutzerdefinierten Schnittstelle).

Mit diesem Muster könnten Sie so etwas tun:

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

und die getAdapter-Methode in der Wolf-Klasse:

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
}

Für Sie besondere Idee - das ist unmöglich. Sie können keinen String-Wert für das Casting verwenden.

Andreas Dolk
quelle
2

Ihr Problem ist nicht das Fehlen von "dynamischem Casting". Casting Integerzu Doubleist überhaupt nicht möglich. Sie möchten Java anscheinend ein Objekt eines Typs, ein Feld eines möglicherweise inkompatiblen Typs, geben und es irgendwie automatisch herausfinden lassen, wie zwischen den Typen konvertiert wird.

Diese Art von Dingen ist für eine stark typisierte Sprache wie Java und IMO aus sehr guten Gründen ein Gräuel.

Was versuchst du eigentlich zu tun? All diese Verwendung von Reflexion sieht ziemlich faul aus.

Michael Borgwardt
quelle
@name: Bezüglich der Bearbeitung, die Sie immer wieder vorschlagen: Beachten Sie, dass es sich nicht um primitive Werte handelt, sondern um die Wrapper-Klassen (gekennzeichnet durch die Groß- und Kleinschreibung und das Styling als Code), und ein Casting zwischen diesen ist definitiv nicht möglich.
Michael Borgwardt
1

Tu das nicht. Verwenden Sie stattdessen einfach einen ordnungsgemäß parametrisierten Konstruktor. Der Satz und die Typen der Verbindungsparameter sind ohnehin festgelegt, so dass es keinen Sinn macht, dies alles dynamisch zu tun.

Bandit
quelle
1

Die meisten Skriptsprachen (wie Perl) und nicht statischen Sprachen zur Kompilierungszeit (wie Pick) unterstützen automatische dynamische Konvertierungen von Zeichenfolgen in (relativ willkürliche) Objekte zur Laufzeit. Dies kann auch in Java erreicht werden, ohne die Typensicherheit zu verlieren, und die guten statisch typisierten Sprachen bieten OHNE die bösen Nebenwirkungen einiger anderer Sprachen, die mit dynamischem Casting böse Dinge tun. Ein Perl-Beispiel, das fragwürdige Berechnungen anstellt:

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

In Java wird dies besser erreicht (IMHO), indem eine Methode verwendet wird, die ich "Cross-Casting" nenne. Beim Cross-Casting wird die Reflexion in einem verzögert geladenen Cache von Konstruktoren und Methoden verwendet, die mithilfe der folgenden statischen Methode dynamisch erkannt werden:

Object fromString (String value, Class targetClass)

Leider tun dies keine integrierten Java-Methoden wie Class.cast () für String to BigDecimal oder String to Integer oder andere Konvertierungen, bei denen keine unterstützende Klassenhierarchie vorhanden ist. Für meinen Teil geht es darum, einen vollständig dynamischen Weg zu finden, um dies zu erreichen - für den ich nicht denke, dass die vorherige Referenz der richtige Ansatz ist - und jede Konvertierung codieren zu müssen. Einfach ausgedrückt, die Implementierung ist nur aus dem String zu werfen, wenn es legal / möglich ist.

Die Lösung ist also eine einfache Reflexion, bei der nach öffentlichen Mitgliedern gesucht wird:

STRING_CLASS_ARRAY = (neue Klasse [] {String.class});

a) Mitglied member = targetClass.getMethod (method.getName (), STRING_CLASS_ARRAY); b) Mitglied member = targetClass.getConstructor (STRING_CLASS_ARRAY);

Sie werden feststellen, dass alle Grundelemente (Integer, Long usw.) und alle Grundlagen (BigInteger, BigDecimal usw.) und sogar java.regex.Pattern über diesen Ansatz abgedeckt werden. Ich habe dies mit großem Erfolg bei Produktionsprojekten verwendet, bei denen es eine große Anzahl beliebiger String-Werteingaben gibt, bei denen eine strengere Überprüfung erforderlich war. Wenn bei diesem Ansatz keine Methode vorhanden ist oder wenn die Methode aufgerufen wird, wird eine Ausnahme ausgelöst (da es sich um einen unzulässigen Wert handelt, z. B. eine nicht numerische Eingabe in ein BigDecimal oder eine unzulässige RegEx für ein Muster), die die Prüfung für bereitstellt die inhärente Logik der Zielklasse.

Dies hat einige Nachteile:

1) Sie müssen die Reflexion gut verstehen (dies ist etwas kompliziert und nicht für Anfänger). 2) Einige der Java-Klassen und Bibliotheken von Drittanbietern sind (überraschenderweise) nicht richtig codiert. Das heißt, es gibt Methoden, die ein einzelnes Zeichenfolgenargument als Eingabe verwenden und eine Instanz der Zielklasse zurückgeben, aber es ist nicht das, was Sie denken ... Betrachten Sie die Integer-Klasse:

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

Die obige Methode hat wirklich nichts mit Ganzzahlen als Objekten zu tun, die primitive Ints umschließen. Reflection wird dies als einen möglichen Kandidaten für das falsche Erstellen einer Ganzzahl aus einem String im Vergleich zu den Dekodierungs-, Wert- und Konstruktorelementen finden. Diese sind alle für die meisten willkürlichen String-Konvertierungen geeignet, bei denen Sie wirklich keine Kontrolle über Ihre Eingabedaten haben, sondern nur möchten wissen, ob es möglich ist, eine Ganzzahl.

Um Abhilfe zu schaffen, ist die Suche nach Methoden, die Ausnahmen auslösen, ein guter Anfang, da ungültige Eingabewerte, die Instanzen solcher Objekte erstellen, dies tun sollten , eine Ausnahme . Leider variieren die Implementierungen dahingehend, ob die Ausnahmen als aktiviert deklariert sind oder nicht. Integer.valueOf (String) löst beispielsweise eine überprüfte NumberFormatException aus, aber Pattern.compile () -Ausnahmen werden bei Reflection-Lookups nicht gefunden. Auch hier ist ein Fehlschlagen dieses dynamischen "Cross-Casting" -Ansatzes meiner Meinung nach keine sehr standardmäßige Implementierung für Ausnahmedeklarationen in Objekterstellungsmethoden.

Wenn jemand mehr Details darüber möchte, wie das oben Genannte implementiert wurde, lassen Sie es mich wissen, aber ich denke, diese Lösung ist viel flexibler / erweiterbarer und mit weniger Code, ohne die guten Teile der Typensicherheit zu verlieren. Natürlich ist es immer am besten, "Ihre Daten zu kennen", aber wie viele von uns feststellen, sind wir manchmal nur Empfänger von nicht verwalteten Inhalten und müssen unser Bestes tun, um sie richtig zu verwenden.

Prost.

Darrell Teague
quelle
1

Dies ist also ein alter Beitrag, aber ich denke, ich kann etwas dazu beitragen.

Sie können immer so etwas tun:

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

Natürlich ist dies kein wirklich dynamisches Casting, wie in anderen Sprachen (z. B. Python), da Java eine statisch typisierte Sprache ist. Dies kann jedoch einige Randfälle lösen, in denen Sie einige Daten je nach Kennung auf unterschiedliche Weise laden müssen. Außerdem könnte der Teil, in dem Sie einen Konstruktor mit einem String-Parameter erhalten, möglicherweise flexibler gestaltet werden, indem dieser Parameter von derselben Datenquelle übergeben wird. Dh aus einer Datei erhalten Sie die Konstruktorsignatur, die Sie verwenden möchten, und die Liste der zu verwendenden Werte. Auf diese Weise verbinden Sie beispielsweise den ersten Parameter mit einem String, wobei das erste Objekt ihn als String umwandelt. Das nächste Objekt ist eine Ganzzahl usw., aber irgendwann während der Ausführung Ihres Programms erhalten Sie jetzt zuerst ein Dateiobjekt, dann ein Double usw.

Auf diese Weise können Sie diese Fälle berücksichtigen und ein etwas "dynamisches" Casting im laufenden Betrieb durchführen.

Ich hoffe, dies hilft jedem, da dies in der Google-Suche immer wieder auftaucht.

Acapulco
quelle
0

Ich hatte kürzlich das Gefühl, dass ich dies auch tun musste, fand dann aber einen anderen Weg, der meinen Code möglicherweise ordentlicher aussehen lässt und eine bessere OOP verwendet.

Ich habe viele Geschwisterklassen, die jeweils eine bestimmte Methode implementieren doSomething(). Um auf diese Methode zugreifen zu können, müsste ich zuerst eine Instanz dieser Klasse haben, aber ich habe eine Oberklasse für alle meine Geschwisterklassen erstellt und jetzt kann ich über die Oberklasse auf die Methode zugreifen.

Im Folgenden zeige ich zwei Möglichkeiten alternativer Möglichkeiten zum "dynamischen Casting".

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

und meine derzeit verwendete Methode,

// Method 2.
mSuperFragment = (MySuperFragment) getFragmentManager().findFragmentByTag(MyHelper.getName(mUnitNum));
mSuperFragment.sortNames(sortOptionNum);
Anonsage
quelle
Sie sollten niemals dynamisch wirken müssen. Ihre Formel, eine Oberklasse mit der Methode doSomething () und Unterklassen zu haben, die die Methode doSomething () implementieren, ist einer der Hauptzwecke von OOP, Polymorphismus. Objekt foo = neue Ganzzahl ("1"); foo.toString () gibt 1 zurück. Obwohl es Object zugewiesen ist, ist es eine Ganzzahl und verhält sich daher als solche. Durch Ausführen der Methode toString wird die Integer-Implementierung ausgeführt. Polymorphismus.
George Xavier
0

Ich dachte nur, ich würde etwas posten, das ich sehr nützlich fand und das für jemanden möglich sein könnte, der ähnliche Bedürfnisse hat.

Die folgende Methode wurde von mir für meine JavaFX-Anwendung geschrieben, um zu vermeiden, dass bei jeder Rückgabe des Controllers eine x-Instanz von Objekt-b-Anweisungen umgewandelt werden muss.

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

Die Methodendeklaration zum Erhalt des Controllers war

public <T> T getController()

Durch die Verwendung des Typs U, der über das Klassenobjekt an meine Methode übergeben wird, kann er an die Methode get controller weitergeleitet werden, um ihm mitzuteilen, welcher Objekttyp zurückgegeben werden soll. Ein optionales Objekt wird zurückgegeben, falls die falsche Klasse angegeben wird und eine Ausnahme auftritt. In diesem Fall wird ein leeres optionales Objekt zurückgegeben, auf das wir prüfen können.

So sah der letzte Aufruf der Methode aus (wenn das zurückgegebene optionale Objekt vorhanden ist, wird ein Consumer benötigt

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

Versuchen Sie dies für Dynamic Casting. Es wird klappen!!!

    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
quelle