2009-10-12 7 views
83

Dopo essere stato istruito durante i miei giorni C++ sui malvagi dell'operatore di cast in stile C, sono stato contento di scoprire che in Java 5 java.lang.Class era stato acquisito un metodo cast.Java Class.cast() rispetto all'operatore di cast

Ho pensato che finalmente abbiamo un modo OO di gestire il casting.

Risulta Class.cast non corrisponde a static_cast in C++. È più simile a reinterpret_cast. Non genererà un errore di compilazione laddove è previsto e verrà invece rimandato al runtime. Ecco un semplice caso di test per dimostrare diversi comportamenti.

package test; 

import static org.junit.Assert.assertTrue; 

import org.junit.Test; 


public class TestCast 
{ 
    static final class Foo 
    { 
    } 

    static class Bar 
    { 
    } 

    static final class BarSubclass 
     extends Bar 
    { 
    } 

    @Test 
    public void test () 
    { 
     final Foo foo = new Foo(); 
     final Bar bar = new Bar(); 
     final BarSubclass bar_subclass = new BarSubclass(); 

     { 
      final Bar bar_ref = bar; 
     } 

     { 
      // Compilation error 
      final Bar bar_ref = foo; 
     } 
     { 
      // Compilation error 
      final Bar bar_ref = (Bar) foo; 
     } 

     try 
     { 
      // !!! Compiles fine, runtime exception 
      Bar.class.cast(foo); 
     } 
     catch (final ClassCastException ex) 
     { 
      assertTrue(true); 
     } 

     { 
      final Bar bar_ref = bar_subclass; 
     } 

     try 
     { 
      // Compiles fine, runtime exception, equivalent of C++ dynamic_cast 
      final BarSubclass bar_subclass_ref = (BarSubclass) bar; 
     } 
     catch (final ClassCastException ex) 
     { 
      assertTrue(true); 
     } 
    } 
} 

Quindi, queste sono le mie domande.

  1. Il Class.cast() dovrebbe essere esiliato in terra generica? Lì ha alcuni usi legittimi.
  2. I compilatori generano errori di compilazione quando viene utilizzato Class.cast() e possono essere determinate condizioni non valide al momento della compilazione?
  3. Java dovrebbe fornire un operatore di cast come un costrutto di linguaggio simile al C++?
+3

Risposta semplice: (1) Dove si trova "Terra generica"? In che modo è diverso da come viene utilizzato l'operatore di cast adesso? (2) Probabilmente. Ma nel 99% di tutto il codice Java mai scritto, è * estremamente * improbabile che qualcuno usi 'Class.cast() 'quando è possibile determinare condizioni illegali in fase di compilazione. In tal caso, tutti, tranne voi, usano semplicemente l'operatore di cast standard. (3) Java ha un operatore di cast come costrutto linguistico. Non è simile a C++. Questo perché molti dei costrutti del linguaggio Java non sono simili a C++. Nonostante le somiglianze superficiali, Java e C++ sono piuttosto diversi. –

+2

Perché -1 --------- –

risposta

88

Ho sempre usato solo Class.cast(Object) per evitare avvisi in "terreni generici". Vedo spesso metodi di fare le cose in questo modo:

@SuppressWarnings("unchecked") 
<T> T doSomething() { 
    Object o; 
    // snip 
    return (T) o; 
} 

E 'spesso meglio sostituirlo con:

<T> T doSomething(Class<T> cls) { 
    Object o; 
    // snip 
    return cls.cast(o); 
} 

Questo è l'unico caso d'uso per Class.cast(Object) io abbia mai incontrato.

Per quanto riguarda gli avvisi del compilatore: Sospetto che lo Class.cast(Object) non sia speciale per il compilatore. Potrebbe essere ottimizzato se usato in modo statico (ad esempio Foo.class.cast(o) anziché cls.cast(o)) ma non ho mai visto nessuno utilizzarlo, il che rende in qualche modo inutile lo sforzo di creare questa ottimizzazione nel compilatore.

+0

La mia ultima parola è che se 'Foo.class.cast (o)' si comporti come '(Foo)' e '(Foo)' fossero proibiti, questo porterebbe a 2 risultati: cast meno casual (a nessuno piace scrivere molto). Ricerca più semplice delle istanze di cast nel codice sorgente. Con ciò lascerò la Dream Land e bandirò Class.cast in Generics Land :). –

+6

Penso che il modo corretto in questo caso sarebbe quello di fare un'istanza di controllo prima come questa: if (cls.isInstance (o)) { return cls.cast (o); } Tranne se si è sicuri che il tipo sarà corretto, ovviamente. – Puce

+0

Penso che il cast() funzioni abbastanza bene in alcuni pattern di tipo factory. Ad esempio, trasmettere i dati da alcuni XDR a una rappresentazione interna basata su una definizione .class. Funziona molto come ObjectiveC casting –

16

È sempre problematico e spesso fuorviante cercare di tradurre costrutti e concetti tra lingue. Il casting non fa eccezione. Soprattutto perché Java è un linguaggio dinamico e il C++ è in qualche modo diverso.

Tutti i casting in Java, indipendentemente da come lo si fa, vengono eseguiti in fase di esecuzione. Le informazioni sul tipo sono conservate in fase di esecuzione. Il C++ è un po 'più di un mix. Puoi lanciare una struct in C++ ad un'altra ed è semplicemente una reinterpretazione dei byte che rappresentano quelle strutture. Java non funziona in questo modo.

Anche i generici in Java e C++ sono molto diversi. Non preoccuparti eccessivamente di come fai cose C++ in Java. Devi imparare come fare le cose in modo Java.

+0

Non tutte le informazioni vengono conservate in fase di esecuzione. Come potete vedere dal mio esempio '(Bar) pippo' genera un errore in fase di compilazione, ma' Bar.class.cast (pippo) 'no. Secondo me se è usato in questo modo dovrebbe. –

+5

@Alexander Pogrebnyak: Non farlo allora! 'Bar.class.cast (foo)' dice esplicitamente al compilatore che si desidera eseguire il cast in fase di runtime. Se vuoi un controllo in fase di compilazione sulla validità del cast, la tua unica scelta è fare il cast in stile '(Bar) foo'. –

+0

Quale pensi sia il modo di fare allo stesso modo? poiché java non supporta l'ereditarietà di più classi. – YAcaCsh

16

In primo luogo, sei fortemente scoraggiato a fare quasi tutti i cast, quindi dovresti limitarlo il più possibile! Si perdono i vantaggi delle funzionalità fortemente tipizzate in fase di compilazione di Java.

In ogni caso, è consigliabile utilizzare Class.cast() principalmente quando si recupera il token Class tramite riflessione. E 'più idiomatica scrivere

MyObject myObject = (MyObject) object 

piuttosto che

MyObject myObject = MyObject.class.cast(object) 

EDIT: i controlli degli errori in fase di compilazione

Nel complesso, Java esegue lanciato a solo fase di esecuzione. Tuttavia, il compilatore può emettere un errore se può provare che tali cast non possono mai avere successo (ad esempio, lanciare una classe in un'altra classe che non è un supertipo e lanciare un tipo di classe finale in classe/interfaccia che non è nella sua gerarchia di tipi). Qui dal Foo e dal Bar ci sono classi che non sono l'una nell'altra gerarchia, il cast non può mai avere successo.

+0

Sono assolutamente d'accordo sul fatto che i cast dovrebbero essere usati con parsimonia, ecco perché avere qualcosa che può essere facilmente cercato sarebbe di grande beneficio per il refactoring/mantenimento del codice. Ma il problema è che, anche se a prima vista 'Class.cast' sembra adattarsi al progetto, crea più problemi di quanti ne risolva. –

9

Class.cast() viene raramente utilizzato nel codice Java. Se viene utilizzato, di solito con tipi noti solo in fase di esecuzione (vale a dire tramite i rispettivi oggetti Class e alcuni parametri di tipo).È veramente utile solo nel codice che usa i generici (è anche la ragione per cui non è stato introdotto prima).

E 'Non simile a reinterpret_cast, perché sarà non permetterà di rompere il sistema di tipo a runtime non più di quanto un cast normale fa (cioè si può "rompere" parametri di tipo generico, ma non ci riesce "break" tipi "reali").

I mali dell'operatore di cast in stile C in genere non si applicano a Java. Il codice Java simile a un cast in stile C è molto simile a uno dynamic_cast<>() con un tipo di riferimento in Java (ricordare: Java ha le informazioni sul tipo di runtime).

Il confronto degli operatori di casting C++ con il cast di Java è piuttosto difficile, poiché in Java è possibile eseguire il cast di riferimento e non avviene mai alcuna conversione agli oggetti (solo i valori primitivi possono essere convertiti utilizzando questa sintassi).

+0

'dynamic_cast <>()' con un tipo di riferimento. –

+0

@ Tom: la modifica è corretta? Il mio C++ è molto arrugginito, ho dovuto ri-google un sacco di esso ;-) –

+0

+1 per: "I mali dell'operatore di cast in stile C in genere non si applicano a Java." Abbastanza vero. Stavo per pubblicare quelle parole esatte come commento sulla domanda. –

3

C++ e Java sono lingue diverse.

L'operatore di cast in stile Java C è molto più limitato rispetto alla versione C/C++. In effetti, il cast Java è come il dynamic_cast C++ se l'oggetto che hai non può essere lanciato sulla nuova classe otterrà un'eccezione (o se c'è abbastanza informazione nel codice per un tempo di compilazione). Quindi l'idea di C++ di non usare i cast di tipo C non è una buona idea in Java

0

Oltre a rimuovere i brutti avvisi di cast come più accennato, Class.cast è un cast in fase di esecuzione per lo più utilizzato con il cast generico, a causa delle informazioni generiche che verranno cancellate in fase di esecuzione e di come ogni generico sarà considerato Oggetto, questo porta a non lanciare una ClassCastException in anticipo.

ad esempio serviceLoder utilizzare questo trucco durante la creazione degli oggetti, controllare S p = service.cast (c.newInstance()); questo genererà un'eccezione di classe quando S P = (S) c.newInstance(); non e può mostrare un avviso 'sicurezza Tipo: deselezionato lanciato da Object a S' . (uguale Object P = (Object) c.newInstance();)

-semplicemente verifica che l'oggetto colato è un'istanza della classe di casting, quindi utilizzerà l'operatore di cast per eseguire il cast e nascondere l'avviso sopprimendolo.

implementazione Java per dinamico cast:

@SuppressWarnings("unchecked") 
public T cast(Object obj) { 
    if (obj != null && !isInstance(obj)) 
     throw new ClassCastException(cannotCastMsg(obj)); 
    return (T) obj; 
} 




    private S nextService() { 
     if (!hasNextService()) 
      throw new NoSuchElementException(); 
     String cn = nextName; 
     nextName = null; 
     Class<?> c = null; 
     try { 
      c = Class.forName(cn, false, loader); 
     } catch (ClassNotFoundException x) { 
      fail(service, 
       "Provider " + cn + " not found"); 
     } 
     if (!service.isAssignableFrom(c)) { 
      fail(service, 
       "Provider " + cn + " not a subtype"); 
     } 
     try { 
      S p = service.cast(c.newInstance()); 
      providers.put(cn, p); 
      return p; 
     } catch (Throwable x) { 
      fail(service, 
       "Provider " + cn + " could not be instantiated", 
       x); 
     } 
     throw new Error();   // This cannot happen 
    } 
0

Personalmente, ho usato questo prima di costruire un JSON al convertitore POJO.Nel caso in cui il JSONObject elaborato con la funzione contiene una matrice o nidificati JSONObjects (implicando che i dati qui non è di un tipo primitivo o String), tento di richiamare il metodo setter utilizzando class.cast() in questo modo:

public static Object convertResponse(Class<?> clazz, JSONObject readResultObject) { 
    ... 
    for(Method m : clazz.getMethods()) { 
     if(!m.isAnnotationPresent(convertResultIgnore.class) && 
      m.getName().toLowerCase().startsWith("set")) { 
     ... 
     m.invoke(returnObject, m.getParameters()[0].getClass().cast(convertResponse(m.getParameters()[0].getType(), readResultObject.getJSONObject(key)))); 
    } 
    ... 
} 

Non sono sicuro se questo è estremamente utile, ma come detto prima, la riflessione è uno dei pochissimi casi di utilizzo legittimo di class.cast(). Posso pensare, almeno un altro esempio ora.

Problemi correlati