Il casting Java introduce un sovraccarico? Perché?


104

C'è qualche sovraccarico quando lanciamo oggetti di un tipo su un altro? O il compilatore risolve tutto e non ci sono costi in fase di esecuzione?

È una cosa generale o ci sono casi diversi?

Ad esempio, supponiamo di avere un array di Object [], dove ogni elemento potrebbe avere un tipo diverso. Ma sappiamo sempre per certo che, diciamo, l'elemento 0 è un Double, l'elemento 1 è una stringa. (So ​​che questo è un progetto sbagliato, ma supponiamo che dovessi farlo.)

Le informazioni sul tipo di Java vengono ancora conservate in fase di esecuzione? Oppure tutto viene dimenticato dopo la compilazione, e se facciamo (Double) elementi [0], seguiremo semplicemente il puntatore e interpreteremo quegli 8 byte come un doppio, qualunque cosa sia?

Non sono molto chiaro su come vengono eseguiti i tipi in Java. Se hai qualche consiglio su libri o articoli, grazie anche.


Le prestazioni di instanceof e casting sono abbastanza buone. Ho pubblicato alcune temporizzazione in Java7 intorno differenti approcci al problema qui: stackoverflow.com/questions/16320014/...
Wheezil

Questa altra questione ha ottime risposte stackoverflow.com/questions/16741323/...
user454322

Risposte:


78

Esistono 2 tipi di casting:

Cast implicito , quando si esegue il cast da un tipo a un tipo più ampio, che viene eseguito automaticamente e non vi è alcun sovraccarico:

String s = "Cast";
Object o = s; // implicit casting

Casting esplicito , quando si passa da un tipo più ampio a uno più ristretto. In questo caso, devi usare esplicitamente il casting in questo modo:

Object o = someObject;
String s = (String) o; // explicit casting

In questo secondo caso, c'è un sovraccarico in runtime, perché i due tipi devono essere controllati e nel caso in cui il casting non sia fattibile, JVM deve lanciare una ClassCastException.

Tratto da JavaWorld: il costo del casting

Il casting viene utilizzato per convertire tra tipi, in particolare tra tipi di riferimento, per il tipo di operazione di casting a cui siamo interessati qui.

Le operazioni di upcast (chiamate anche conversioni di ampliamento nella specifica del linguaggio Java) convertono un riferimento di sottoclasse in un riferimento di classe predecessore. Questa operazione di casting è normalmente automatica, poiché è sempre sicura e può essere implementata direttamente dal compilatore.

Le operazioni di downcast (chiamate anche conversioni di restringimento nella specifica del linguaggio Java) convertono un riferimento di una classe predecessore in un riferimento di sottoclasse. Questa operazione di casting crea un sovraccarico di esecuzione, poiché Java richiede che il cast venga controllato in fase di esecuzione per assicurarsi che sia valido. Se l'oggetto a cui si fa riferimento non è un'istanza del tipo di destinazione per il cast o una sottoclasse di quel tipo, il tentativo di cast non è consentito e deve generare un'eccezione java.lang.ClassCastException.


101
Quell'articolo di JavaWorld ha più di 10 anni, quindi prenderei tutte le dichiarazioni che fa sulle prestazioni con un granello molto grande del tuo sale migliore.
skaffman

1
@skaffman, in effetti prenderei con le pinze qualsiasi affermazione (indipendentemente dalle prestazioni di non).
Pacerier

sarà lo stesso caso, se non assegno l'oggetto cast al riferimento e chiamo solo il metodo su di esso? come((String)o).someMethodOfCastedClass()
Parth Vishvajit

3
Ora l'articolo ha quasi 20 anni. E anche le risposte hanno molti anni. Questa domanda necessita di una risposta moderna.
Raslanove

Che ne dici dei tipi primitivi? Voglio dire, ad esempio, lanciare da int a short causa un overhead simile?
luke1985

44

Per un'implementazione ragionevole di Java:

Ogni oggetto ha un'intestazione contenente, tra le altre cose, un puntatore al tipo di runtime (ad esempio Doubleo String, ma non potrebbe mai essere CharSequenceo AbstractList). Supponendo che il compilatore runtime (generalmente HotSpot nel caso di Sun) non sia in grado di determinare il tipo staticamente, è necessario eseguire alcuni controlli dal codice macchina generato.

Per prima cosa è necessario leggere quel puntatore al tipo di runtime. Ciò è comunque necessario per chiamare un metodo virtuale in una situazione simile.

Per eseguire il casting su un tipo di classe, è noto esattamente quante superclassi ci sono fino a quando non si preme java.lang.Object, quindi il tipo può essere letto con un offset costante dal puntatore del tipo (in realtà i primi otto in HotSpot). Anche in questo caso è analogo alla lettura di un puntatore a un metodo per un metodo virtuale.

Quindi il valore letto necessita solo di un confronto con il tipo statico previsto del cast. A seconda dell'architettura del set di istruzioni, un'altra istruzione dovrà diramarsi (o generare un errore) su un ramo errato. ISA come ARM a 32 bit hanno istruzioni condizionali e possono essere in grado di far passare il percorso triste attraverso il percorso felice.

Le interfacce sono più difficili a causa dell'ereditarietà multipla dell'interfaccia. In genere, gli ultimi due cast alle interfacce vengono memorizzati nella cache nel tipo di runtime. All'inizio (oltre un decennio fa), le interfacce erano un po 'lente, ma questo non è più rilevante.

Si spera che tu possa vedere che questo genere di cose è in gran parte irrilevante per le prestazioni. Il tuo codice sorgente è più importante. In termini di prestazioni, il più grande successo nel tuo scenario è suscettibile di mancare nella cache dall'inseguimento di puntatori a oggetti ovunque (le informazioni sul tipo saranno ovviamente comuni).


1
interessante - questo significa che per classi non di interfaccia se scrivo Superclass sc = (Superclass) sottoclasse; che il compilatore (jit ie: load time) metterà "staticamente" l'offset da Object in ciascuna delle Superclass e Subclass nelle loro intestazioni "Class" e quindi tramite un semplice add + compare sarà in grado di risolvere le cose? - che è bello e veloce :) Per le interfacce presumo non peggio di un piccolo hashtable o btree?
Peterk

@peterk Per il casting tra classi, sia l'indirizzo dell'oggetto che "vtbl" (tabella dei puntatori al metodo, più tabella della gerarchia delle classi, cache dell'interfaccia, ecc.) non cambiano. Quindi il cast di [classe] controlla il tipo, e se si adatta non deve succedere nient'altro.
Tom Hawtin - tackline

8

Ad esempio, supponiamo di avere un array di Object [], dove ogni elemento potrebbe avere un tipo diverso. Ma sappiamo sempre per certo che, diciamo, l'elemento 0 è un Double, l'elemento 1 è una stringa. (So ​​che questo è un progetto sbagliato, ma supponiamo che dovessi farlo.)

Il compilatore non rileva i tipi dei singoli elementi di un array. Controlla semplicemente che il tipo di ogni espressione di elemento sia assegnabile al tipo di elemento dell'array.

Le informazioni sul tipo di Java vengono ancora conservate in fase di esecuzione? Oppure tutto viene dimenticato dopo la compilazione, e se facciamo (Double) elementi [0], seguiremo semplicemente il puntatore e interpreteremo quegli 8 byte come un doppio, qualunque cosa sia?

Alcune informazioni vengono mantenute in fase di esecuzione, ma non i tipi statici dei singoli elementi. Puoi capirlo guardando il formato del file di classe.

È teoricamente possibile che il compilatore JIT possa utilizzare l '"analisi di fuga" per eliminare i controlli di tipo non necessari in alcune assegnazioni. Tuttavia, farlo nella misura in cui suggerisci sarebbe oltre i limiti dell'ottimizzazione realistica. Il guadagno dell'analisi dei tipi di singoli elementi sarebbe troppo piccolo.

Inoltre, le persone non dovrebbero comunque scrivere il codice dell'applicazione in questo modo.


1
E i primitivi? (float) Math.toDegrees(theta)Anche qui ci sarà un sovraccarico significativo?
SD

2
C'è un sovraccarico per alcuni cast primitivi. Se è significativo dipende dal contesto.
Stephen C

6

Viene chiamata l'istruzione del codice byte per eseguire il casting in fase di esecuzione checkcast. È possibile disassemblare il codice Java utilizzando javapper vedere quali istruzioni vengono generate.

Per gli array, Java conserva le informazioni sul tipo in fase di esecuzione. La maggior parte delle volte, il compilatore rileva gli errori di tipo per te, ma ci sono casi in cui ti imbatterai in un errore ArrayStoreExceptionquando provi a memorizzare un oggetto in un array, ma il tipo non corrisponde (e il compilatore non lo ha rilevato) . La specifica del linguaggio Java fornisce il seguente esempio:

class Point { int x, y; }
class ColoredPoint extends Point { int color; }
class Test {
    public static void main(String[] args) {
        ColoredPoint[] cpa = new ColoredPoint[10];
        Point[] pa = cpa;
        System.out.println(pa[1] == null);
        try {
            pa[0] = new Point();
        } catch (ArrayStoreException e) {
            System.out.println(e);
        }
    }
}

Point[] pa = cpaè valido poiché ColoredPointè una sottoclasse di Point, ma pa[0] = new Point()non è valido.

Questo è opposto ai tipi generici, in cui non sono presenti informazioni sul tipo mantenute in fase di esecuzione. Il compilatore inserisce le checkcastistruzioni dove necessario.

Questa differenza nella digitazione per tipi e array generici rende spesso inadatto a mescolare array e tipi generici.


2

In teoria, viene introdotto un overhead. Tuttavia, le JVM moderne sono intelligenti. Ogni implementazione è diversa, ma non è irragionevole presumere che possa esistere un'implementazione che JIT ha ottimizzato per i controlli di eliminazione quando potrebbe garantire che non ci sarebbe mai stato un conflitto. Per quanto riguarda quali JVM specifiche offrono questo, non saprei dirtelo. Devo ammettere che mi piacerebbe conoscere personalmente le specifiche dell'ottimizzazione JIT, ma queste sono di cui preoccuparsi per gli ingegneri JVM.

La morale della storia è scrivere prima un codice comprensibile. Se stai riscontrando rallentamenti, profila e identifica il tuo problema. Le probabilità sono buone che non sarà dovuto al casting. Non sacrificare mai un codice pulito e sicuro nel tentativo di ottimizzarlo FINO A QUANDO NON SAI CHE DEVI.

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.