Java Class.cast () vs. operatore cast


107

Essendo stato insegnato durante i miei giorni in C ++ sui mali dell'operatore cast in stile C, all'inizio fui contento di scoprire che in Java 5 java.lang.Classavevo acquisito un castmetodo.

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

Si scopre che Class.castnon è la stessa static_castdi C ++. È più simile reinterpret_cast. Non genererà un errore di compilazione dove previsto e rimanderà invece al runtime. Ecco un semplice test case per dimostrare comportamenti diversi.

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. Dovrebbe Class.cast()essere bandito nella terra dei Generics? Lì ha diversi usi legittimi.
  2. I compilatori dovrebbero generare errori di compilazione quando Class.cast()vengono utilizzati e le condizioni illegali possono essere determinate in fase di compilazione?
  3. Java dovrebbe fornire un operatore cast come costrutto di linguaggio simile a C ++?

4
Risposta semplice: (1) Dov'è "la terra dei generici"? In che modo è diverso da come viene utilizzato l'operatore cast ora? (2) Probabilmente. Ma nel 99% di tutto il codice Java mai scritto, è estremamente improbabile che qualcuno lo utilizzi Class.cast()quando le condizioni illegali possono essere determinate in fase di compilazione. In tal caso, tutti tranne te usano solo l'operatore cast standard. (3) Java ha un operatore cast come costrutto del linguaggio. 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 abbastanza diversi.
Daniel Pryden

Risposte:


117

Ho sempre usato solo Class.cast(Object)per evitare avvisi in "terra dei generici". Vedo spesso metodi che fanno cose come questa:

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

Spesso è meglio sostituirlo con:

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

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

Per quanto riguarda gli avvisi del compilatore: sospetto che Class.cast(Object)non sia speciale per il compilatore. Potrebbe essere ottimizzato se usato staticamente (cioè Foo.class.cast(o)piuttosto che cls.cast(o)) ma non ho mai visto nessuno usarlo - il che rende in qualche modo inutile lo sforzo di costruire questa ottimizzazione nel compilatore.


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

1
Perché la seconda variante è migliore? La prima variante non è più efficiente perché il codice del chiamante farebbe comunque il cast dinamico?
user1944408

1
@ user1944408 fintanto che parli strettamente di prestazioni, può essere, anche se praticamente trascurabile. Non mi piacerebbe l'idea di ottenere ClassCastExceptions dove non c'è un caso ovvio però. Inoltre, il secondo funziona meglio dove il compilatore non può dedurre T, ad esempio list.add (this. <String> doSomething ()) vs. list.add (doSomething (String.class))
sfussenegger

2
Ma puoi ancora ottenere ClassCastExceptions quando chiami cls.cast (o) mentre non ricevi alcun avviso in fase di compilazione. Preferisco la prima variante ma utilizzerei la seconda variante quando ho bisogno, ad esempio, di restituire null se non posso eseguire il casting. In tal caso, avvolgerei cls.cast (o) in un blocco try catch. Sono anche d'accordo con te sul fatto che questo è anche meglio quando il compilatore non può dedurre T. Grazie per la risposta.
user1944408

1
Uso anche la seconda variante quando ho bisogno che Class <T> cls sia dinamico, ad esempio se uso la reflection ma in tutti gli altri casi preferisco la prima. Beh, immagino sia solo un gusto personale.
user1944408

20

Innanzitutto, sei fortemente sconsigliato di eseguire quasi tutti i cast, quindi dovresti limitarlo il più possibile! Perdi i vantaggi delle funzionalità fortemente tipizzate in fase di compilazione di Java.

In ogni caso, Class.cast()dovrebbe essere utilizzato principalmente quando si recupera il Classtoken tramite riflessione. È più idiomatico scrivere

MyObject myObject = (MyObject) object

piuttosto che

MyObject myObject = MyObject.class.cast(object)

EDIT: errori in fase di compilazione

In generale, Java esegue i controlli del cast solo durante l'esecuzione. Tuttavia, il compilatore può generare un errore se può dimostrare che tali cast non possono mai avere successo (ad esempio, eseguire il cast di una classe su un'altra classe che non è un supertipo e lanciare un tipo di classe finale alla classe / interfaccia che non è nella sua gerarchia di tipi). Qui poiché Fooe Barsono classi che non fanno parte dell'altra gerarchia, il cast non potrà mai avere successo.


Sono totalmente d'accordo che i cast dovrebbero essere usati con parsimonia, ecco perché avere qualcosa che può essere facilmente cercato sarebbe di grande vantaggio per il refactoring / mantenimento del codice. Ma il problema è che, sebbene a prima vista Class.castsembri adattarsi al conto, crea più problemi di quanti ne risolva.
Alexander Pogrebnyak

16

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

Tutto il casting in Java, indipendentemente da come lo fai, viene eseguito in fase di esecuzione. Le informazioni sul tipo vengono conservate in fase di esecuzione. Il C ++ è un po 'più di un mix. Puoi eseguire il cast di una struttura in C ++ su un'altra ed è semplicemente una reinterpretazione dei byte che rappresentano quelle strutture. Java non funziona in questo modo.

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


Non tutte le informazioni vengono conservate in fase di esecuzione. Come puoi vedere dal mio esempio (Bar) foo, genera un errore in fase di compilazione, ma Bar.class.cast(foo)non lo fa. Secondo me se viene utilizzato in questo modo dovrebbe.
Alexander Pogrebnyak

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

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

13

Class.cast()viene utilizzato raramente nel codice Java. Se viene utilizzato, di solito con tipi noti solo in fase di esecuzione (cioè tramite i rispettivi Classoggetti e da qualche parametro di tipo). È veramente utile solo nel codice che utilizza generici (questo è anche il motivo per cui non è stato introdotto in precedenza).

E ' non è simile a quella reinterpret_cast, perché vi 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 può rompere i tipi "reale").

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

Generalmente il confronto degli operatori di casting C ++ con il casting Java è piuttosto difficile poiché in Java è possibile eseguire solo il cast di riferimenti e nessuna conversione viene mai eseguita sugli oggetti (solo i valori primitivi possono essere convertiti utilizzando questa sintassi).


dynamic_cast<>()con un tipo di riferimento.
Tom Hawtin - tackline

@ Tom: la modifica è corretta? Il mio C ++ è molto arrugginito, ho dovuto ri-google molto ;-)
Joachim Sauer

+1 per: "I mali dell'operatore cast in stile C generalmente non si applicano a Java." Abbastanza vero. Stavo per pubblicare quelle esatte parole come commento alla domanda.
Daniel Pryden

4

In genere l'operatore cast è preferito al metodo cast Class # poiché è più conciso e può essere analizzato dal compilatore per sputare problemi evidenti con il codice.

Class # cast si assume la responsabilità del controllo del tipo in fase di esecuzione piuttosto che durante la compilazione.

Ci sono certamente casi d'uso per il cast di Class #, in particolare quando si tratta di operazioni riflessive.

Da quando lambda è arrivato a java, personalmente mi piace usare Class # cast con l'API collections / stream se lavoro con tipi astratti, ad esempio.

Dog findMyDog(String name, Breed breed) {
    return lostAnimals.stream()
                      .filter(Dog.class::isInstance)
                      .map(Dog.class::cast)
                      .filter(dog -> dog.getName().equalsIgnoreCase(name))
                      .filter(dog -> dog.getBreed() == breed)
                      .findFirst()
                      .orElse(null);
}

3

C ++ e Java sono linguaggi diversi.

L'operatore cast in stile Java C è molto più limitato rispetto alla versione C / C ++. Effettivamente il cast di Java è come il dynamic_cast di C ++ se l'oggetto che hai non può essere convertito nella nuova classe, otterrai un'eccezione in fase di esecuzione (o se ci sono abbastanza informazioni nel codice una fase di compilazione). Quindi l'idea C ++ di non usare cast di tipo C non è una buona idea in Java


0

Oltre a rimuovere i brutti avvisi di cast come più menzionato, Class.cast è il cast di runtime utilizzato principalmente con il casting generico, a causa delle informazioni generiche verranno cancellate in fase di esecuzione e in qualche modo ogni generico sarà considerato Object, questo porta a non farlo lancia un'eccezione ClassCastException.

per esempio serviceLoder usa questo trucco durante la creazione degli oggetti, controlla S p = service.cast (c.newInstance ()); questo genererà un'eccezione di cast di classe quando SP = (S) c.newInstance (); non lo farà e potrebbe mostrare un avviso 'Protezione dal tipo: cast non controllato da Object a S' . (uguale a Object P = (Object) c.newInstance ();)

-semplicemente controlla che l'oggetto cast sia un'istanza della classe casting, quindi userà l'operatore cast per lanciare e nascondere l'avvertimento sopprimendolo.

implementazione java per cast dinamico:

@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, l'ho già usato per creare un convertitore da JSON a POJO. Nel caso in cui il JSONObject elaborato con la funzione contenga un array o JSONObject nidificati (implicando che i dati qui non siano di tipo primitivo o String), cerco di invocare il metodo setter usando 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 che sia estremamente utile, ma come detto prima, la riflessione è uno dei pochissimi casi d'uso legittimi di class.cast() riesco a pensare, almeno ora hai un altro esempio.

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.