java: come posso eseguire il casting dinamico di una variabile da un tipo a un altro?


85

Vorrei eseguire il casting dinamico per una variabile Java, il tipo di casting è memorizzato in una variabile diversa.

Questo è il casting regolare:

 String a = (String) 5;

Questo è quello che voglio:

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

È possibile, e se sì come? Grazie!

Aggiornare

Sto cercando di popolare una classe con un HashMapche ho ricevuto.

Questo è il costruttore:

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

}

Il problema qui è che alcune delle variabili della classe sono di tipo Doublee se il numero 3 viene ricevuto lo vede come Integere ho un problema di tipo.


Non ha alcun senso. Vuoi che il nome della variabile sia un tipo per eseguire il cast di una stringa in una stringa? Che cosa?
cletus

3
Non conosco una risposta ma temo che questo possa diventare un inferno di manutenzione ... Sto imparando Java da solo, ma eviterei situazioni che richiedono un approccio come questo. Sono abbastanza sicuro che qualunque cosa tu faccia possa essere implementata in un modo migliore ... solo i miei 2 centesimi.
Seiano

ok fornirò maggiori informazioni su ciò che sto cercando di ottenere.
ufk

aggiornato anche la mia risposta di seguito!
user85421

Risposte:


14

Per quanto riguarda il tuo aggiornamento, l'unico modo per risolvere questo problema in Java è scrivere codice che copra tutti i casi con molte espressioni ife elseed instanceof. Ciò che si tenta di fare sembra essere utilizzato per programmare con linguaggi dinamici. Nei linguaggi statici, ciò che si tenta di fare è quasi impossibile e probabilmente si sceglierebbe un approccio completamente diverso per ciò che si tenta di fare. I linguaggi statici non sono flessibili quanto quelli dinamici :)

Buoni esempi di best practice Java sono la risposta di BalusC (ie ObjectConverter) e la risposta di Andreas_D (ie Adapter) di seguito.


Non ha senso, in

String a = (theType) 5;

il tipo di aè staticamente vincolato a essere, Stringquindi non ha alcun senso avere un cast dinamico per questo tipo statico.

PS: la prima riga del tuo esempio potrebbe essere scritta come Class<String> stringClass = String.class;ma comunque non puoi usarla stringClassper eseguire il cast delle variabili.


Spero che l'aggiornamento che ho pubblicato spieghi cosa sto cercando di fare. vengo da un background php quindi forse questa cosa non è possibile ottenere in java.
ufk

Esatto, in Java non puoi essere così dinamico, guarda anche il mio aggiornamento.
Akuhn

Vedi la risposta di BalusC di seguito, questa è la lunghezza (e il dolore) a cui devi andare ...
akuhn

So che è tardi ma penso che intendesse [il tipo] a = (il tipo) qualche_oggetto; il che è inutile perché puoi semplicemente fare Object a = some_object fine
George Xavier

120

Sì, è possibile utilizzare Reflection

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

ma ciò non ha molto senso poiché l'oggetto risultante deve essere salvato in una variabile di Objecttipo. Se hai bisogno che la variabile sia di una data classe, puoi semplicemente eseguire il cast a quella classe.

Se vuoi ottenere una determinata classe, Numberad esempio:

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

ma non ha ancora senso farlo, potresti semplicemente trasmettere Number.

Il casting di un oggetto NON cambia nulla; è solo il modo in cui il compilatore lo tratta.
L'unico motivo per fare qualcosa del genere è controllare se l'oggetto è un'istanza della classe data o di una sua sottoclasse, ma sarebbe meglio farlo usando instanceofo Class.isInstance().

Aggiornare

in base al tuo ultimo aggiornamento il vero problema è che hai un file Integernel tuo HashMapche dovrebbe essere assegnato a un file Double. Quello che puoi fare in questo caso è controllare il tipo di campo e utilizzare i xxxValue()metodi diNumber

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

(non sono sicuro se mi piace l'idea di avere il tipo sbagliato nel Map)


22

Avrai bisogno di scrivere qualcosa ObjectConverterper questo. Questo è fattibile se hai sia l'oggetto che desideri convertire sia se conosci la classe di destinazione in cui desideri convertire. In questo caso particolare puoi ottenere la classe di destinazione da Field#getDeclaringClass().

Puoi trovare qui un esempio di tale ObjectConverter. Dovrebbe darti l'idea di base. Se desideri più possibilità di conversione, aggiungi semplicemente più metodi con l'argomento desiderato e il tipo restituito.


@BalusC - Trovo interessante il codice ObjectConverter, potresti descriverne i casi d'uso?
srini.venigalla

È utile nei casi in cui si preferisce la convenzione alla configurazione e il tipo di origine non corrisponde al tipo di destinazione. L'ho usato 2-3 anni fa nei miei framework ORM e MVC (puri per hobby). Vedi anche il testo principale dell'articolo del blog.
BalusC

12

Puoi farlo utilizzando il Class.cast()metodo, che esegue il cast dinamicamente del parametro fornito nel tipo di istanza della classe che hai. Per ottenere l'istanza della classe di un campo particolare, si utilizza il getType()metodo sul campo in questione. Ho fornito un esempio di seguito, ma nota che omette tutta la gestione degli errori e non dovrebbe essere usato senza modifiche.

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
Cosa succede se il tipo di voce non è affatto un supertipo del tipo di campo? Avrai quindi davvero bisogno di convertire a livello di codice.
BalusC

7

Puoi scrivere un semplice metodo cast come quello qui sotto.

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

Nel tuo metodo dovresti usarlo come

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

}

Sì, penso che questa risposta sia ciò che vuole il richiedente. Ha appena ottenuto una classe <?> E desidera convertire un'istanza nella classe "?". Per impostazione predefinita, java non lo supporta. Ma usando <T> possiamo farlo.
ruiruige1991

@ ruiruige1991 Questo è sbagliato. T in tal caso è un generico. I generici non fanno nulla durante il runtime. (T) blah sarebbe solo (Object) blah durante il runtime a causa della cancellazione del tipo. In breve, generics -> compile-time e non ha alcun effetto durante il runtime. Poiché dinamico -> runtime e generics -> tempo di compilazione, i generici sono inutili.
George Xavier

5

Funziona e c'è anche un modello comune per il tuo approccio: il modello Adapter . Ma ovviamente, (1) non funziona per il cast di primitive java agli oggetti e (2) la classe deve essere adattabile (di solito implementando un'interfaccia personalizzata).

Con questo modello potresti fare qualcosa come:

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

e il metodo getAdapter nella 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
}

Per te un'idea speciale, è impossibile. Non puoi usare un valore String per il cast.


2

Il tuo problema non è la mancanza di "casting dinamico". La trasmissione Integera Doublenon è affatto possibile. Sembra che tu voglia dare a Java un oggetto di un tipo, un campo di un tipo possibilmente incompatibile, e fargli capire in qualche modo automaticamente come convertire tra i tipi.

Questo genere di cose è un anatema per un linguaggio fortemente tipizzato come Java e IMO per ottime ragioni.

Cosa stai effettivamente cercando di fare? Tutto questo uso della riflessione sembra piuttosto strano.


@name: riguardo alla modifica che continui a suggerire: nota che non sto parlando di valori primitivi ma di classi wrapper (indicate dalle maiuscole e dallo stile come codice), e il casting tra questi non è assolutamente possibile.
Michael Borgwardt

1

Non farlo. Avere invece un costruttore adeguatamente parametrizzato. Il set e il tipo dei parametri di connessione sono comunque fissi, quindi non ha senso fare tutto questo dinamicamente.


1

Per quel che vale, la maggior parte dei linguaggi di scripting (come Perl) e non statici in fase di compilazione (come Pick) supportano conversioni di oggetti dinamiche di run-time automatiche (relativamente arbitrarie). Ciò PU essere realizzato anche in Java senza perdere la sicurezza dei tipi e le cose buone che i linguaggi tipizzati staticamente forniscono SENZA gli effetti collaterali negativi di alcuni degli altri linguaggi che fanno cose malvagie con il casting dinamico. Un esempio di Perl che fa una matematica discutibile:

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

In Java, questo è meglio realizzato (IMHO) utilizzando un metodo che chiamo "cross-casting". Con il cross-casting, la reflection viene utilizzata in una cache con caricamento lento di costruttori e metodi che vengono scoperti dinamicamente tramite il seguente metodo statico:

Object fromString (String value, Class targetClass)

Sfortunatamente, nessun metodo Java integrato come Class.cast () lo farà per String in BigDecimal o String in Integer o qualsiasi altra conversione in cui non esiste una gerarchia di classi di supporto. Da parte mia, il punto è fornire un modo completamente dinamico per raggiungere questo obiettivo - per il quale non credo che il riferimento precedente sia l'approccio giusto - dovendo codificare ogni conversione. In poche parole, l'implementazione è solo per eseguire il cast da stringa se è legale / possibile.

Quindi la soluzione è una semplice riflessione alla ricerca di membri pubblici di:

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

a) Membro membro = targetClass.getMethod (method.getName (), STRING_CLASS_ARRAY); b) Membro membro = targetClass.getConstructor (STRING_CLASS_ARRAY);

Scoprirai che tutte le primitive (Integer, Long, ecc.) E tutte le basi (BigInteger, BigDecimal, ecc.) E persino java.regex.Pattern sono tutte coperte da questo approccio. L'ho usato con notevole successo su progetti di produzione in cui è presente un'enorme quantità di input di valori String arbitrari in cui era necessario un controllo più rigoroso. In questo approccio, se non esiste un metodo o quando il metodo viene invocato viene generata un'eccezione (perché è un valore non valido come un input non numerico per un BigDecimal o RegEx illegale per un Pattern), che fornisce il controllo la logica intrinseca della classe di destinazione.

Ci sono alcuni aspetti negativi in ​​questo:

1) Devi capire bene la riflessione (questo è un po 'complicato e non per i principianti). 2) Alcune delle classi Java e in effetti le librerie di terze parti (sorpresa) non sono codificate correttamente. Cioè, ci sono metodi che accettano un singolo argomento stringa come input e restituiscono un'istanza della classe di destinazione ma non è quello che pensi ... Considera la classe Integer:

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

Il metodo sopra non ha davvero nulla a che fare con gli interi come oggetti che avvolgono le primitive int. Reflection lo troverà come un possibile candidato per la creazione di un numero intero da una stringa in modo errato rispetto ai membri di decodifica, valore e costruttore, che sono tutti adatti per la maggior parte delle conversioni arbitrarie di stringhe in cui non si ha davvero il controllo sui dati di input ma si desidera solo farlo sapere se è possibile un numero intero.

Per rimediare a quanto sopra, la ricerca di metodi che generano eccezioni è un buon inizio perché valori di input non validi che creano istanze di tali oggetti dovrebbero generare un'eccezione. Sfortunatamente, le implementazioni variano a seconda che le eccezioni siano dichiarate come selezionate o meno. Integer.valueOf (String) genera un'eccezione NumberFormatException verificata, ad esempio, ma le eccezioni Pattern.compile () non vengono trovate durante le ricerche di riflessione. Ancora una volta, non un fallimento di questo approccio dinamico "cross-casting", penso tanto come un'implementazione molto non standard per le dichiarazioni di eccezioni nei metodi di creazione di oggetti.

Se qualcuno desidera maggiori dettagli su come è stato implementato quanto sopra, fammelo sapere ma penso che questa soluzione sia molto più flessibile / estensibile e con meno codice senza perdere le parti buone della sicurezza dei tipi. Ovviamente è sempre meglio "conoscere i tuoi dati" ma, come molti di noi scoprono, a volte siamo solo destinatari di contenuti non gestiti e dobbiamo fare del nostro meglio per usarli correttamente.

Saluti.


1

Quindi, questo è un vecchio post, tuttavia penso di poter contribuire in qualche modo ad esso.

Puoi sempre fare qualcosa del genere:

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

Ovviamente, questo non è un casting davvero dinamico, come in altri linguaggi (Python per esempio), perché java è un lang tipizzato staticamente. Tuttavia, questo può risolvere alcuni casi marginali in cui è effettivamente necessario caricare alcuni dati in modi diversi, a seconda di un identificatore. Inoltre, la parte in cui si ottiene un costruttore con un parametro String potrebbe essere probabilmente resa più flessibile, facendo passare quel parametro dalla stessa origine dati. Vale a dire da un file, si ottiene la firma del costruttore che si desidera utilizzare e l'elenco dei valori da utilizzare, in questo modo si accoppia, ad esempio, il primo parametro è una stringa, con il primo oggetto, eseguendo il cast come una stringa, il prossimo oggetto è un Integer, ecc., Ma durante l'esecuzione del tuo programma, ora ottieni prima un oggetto File, poi un Double, ecc.

In questo modo, puoi tenere conto di quei casi e fare un casting un po '"dinamico" al volo.

Spero che questo aiuti chiunque poiché questo continua a comparire nelle ricerche su Google.


0

Recentemente mi sono sentito come se dovessi farlo anche io, ma poi ho trovato un altro modo che forse rende il mio codice più ordinato e utilizza un OOP migliore.

Ho molte classi di pari livello che implementano ciascuna un determinato metodo doSomething(). Per accedere a quel metodo, dovrei prima avere un'istanza di quella classe, ma ho creato una superclasse per tutte le mie classi di pari livello e ora posso accedere al metodo dalla superclasse.

Di seguito mostro due modi modi alternativi al "casting dinamico".

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

e il mio metodo attualmente utilizzato,

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

Non dovresti mai aver bisogno di lanciare dinamicamente. La tua formula per avere una superclasse con il metodo doSomething () e sottoclassi che implementano il metodo doSomething () è uno degli scopi principali di OOP, il polimorfismo. Object foo = new Integer ("1"); foo.toString () restituisce 1. Anche se è assegnato a Object, è un Integer e quindi si comporta come tale. L'esecuzione del metodo toString eseguirà l'implementazione di Integer. Polimorfismo.
George Xavier

0

Ho solo pensato di pubblicare qualcosa che ho trovato abbastanza utile e che potrebbe essere possibile per qualcuno che sperimenta esigenze simili.

Il metodo seguente era un metodo che ho scritto per la mia applicazione JavaFX per evitare di dover eseguire il cast e anche evitare di scrivere se l'istanza di oggetto x di istruzioni di oggetto b ogni volta che veniva restituito il controller.

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

La dichiarazione del metodo per ottenere il controller era

public <T> T getController()

Usando il tipo U passato al mio metodo tramite l'oggetto della classe, potrebbe essere inoltrato al metodo get controller per dirgli quale tipo di oggetto restituire. Viene restituito un oggetto opzionale nel caso in cui venga fornita la classe sbagliata e si verifica un'eccezione, nel qual caso verrà restituito un opzionale vuoto che possiamo verificare.

Questo è l'aspetto della chiamata finale al metodo (se presente dell'oggetto opzionale restituito accetta un Consumer

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

0

Prova questo per la trasmissione dinamica. Funzionerà!!!

    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));
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.