Qual è la differenza tra instanceof e Class.isAssignableFrom (...)?


458

Quale delle seguenti è migliore?

a instanceof B

o

B.class.isAssignableFrom(a.getClass())

L'unica differenza che conosco è che, quando 'a' è null, il primo restituisce false, mentre il secondo genera un'eccezione. A parte questo, danno sempre lo stesso risultato?


17
Per la cronaca, isInstance () è il metodo più conveniente per verificare se un oggetto può essere inserito in un tipo di classe (per maggiori dettagli, consultare: tshikatshikaaa.blogspot.nl/2012/07/… )
Jérôme Verstrynge

Risposte:


498

Durante l'utilizzo instanceof, è necessario conoscere la classe di Bal momento della compilazione. Quando lo si utilizza isAssignableFrom()può essere dinamico e cambiare durante il runtime.


12
non capisco - per favore approfondisci perché non possiamo scrivere a instanceof Bref.getClass(). come può essere questa la risposta accettata con così poca spiegazione (o la sua mancanza)?
Eliran Malka,

65
La sintassi a instanceof Brefno a instanceof Bref.class. Il secondo argomento per l'operatore instanceof è un nome di classe, non un'espressione che si risolve in un'istanza di oggetto di classe.
Brandon Bloom,

2
sì "dinamico" va da sé :) Oltre alle prestazioni, questa è una vera differenza.
Peter

2
@EliranMalka forse puoi avere una classe che è stata generata in fase di esecuzione. Come oggetti proxy.
Wagner Tsuchiya,

Quindi, in B.class.isAssignableFrom(a.getClass()), B è noto ed a instanceof Bè migliore. Giusto?
Florian F,


116

Parlando in termini di prestazioni:

TL; DR

Utilizzare isInstance o instanceof con prestazioni simili. isAssignableFrom è leggermente più lento.

Ordinati per prestazione:

  1. isinstance
  2. istanza di (+ 0,5%)
  3. isAssignableFrom (+ 2,7%)

Basato su un benchmark di 2000 iterazioni su JAVA 8 Windows x64, con 20 iterazioni di riscaldamento.

In teoria

Usando un visualizzatore di bytecode soft come possiamo tradurre ogni operatore in bytecode.

Nel contesto di:

package foo;

public class Benchmark
{
  public static final Object a = new A();
  public static final Object b = new B();

  ...

}

GIAVA:

b instanceof A;

bytecode:

getstatic foo/Benchmark.b:java.lang.Object
instanceof foo/A

GIAVA:

A.class.isInstance(b);

bytecode:

ldc Lfoo/A; (org.objectweb.asm.Type)
getstatic foo/Benchmark.b:java.lang.Object
invokevirtual java/lang/Class isInstance((Ljava/lang/Object;)Z);

GIAVA:

A.class.isAssignableFrom(b.getClass());

bytecode:

ldc Lfoo/A; (org.objectweb.asm.Type)
getstatic foo/Benchmark.b:java.lang.Object
invokevirtual java/lang/Object getClass(()Ljava/lang/Class;);
invokevirtual java/lang/Class isAssignableFrom((Ljava/lang/Class;)Z);

Misurando quante istruzioni di bytecode sono utilizzate da ciascun operatore, potremmo aspettarci che instanceof e isInstance siano più veloci di isAssignableFrom . Tuttavia, le prestazioni effettive NON sono determinate dal bytecode ma dal codice macchina (che dipende dalla piattaforma). Facciamo un micro benchmark per ciascuno degli operatori.

Il punto di riferimento

Credito: Come consigliato da @ aleksandr-dubinsky, e grazie a @yura per aver fornito il codice di base, ecco un benchmark JMH (vedi questa guida alla messa a punto ):

class A {}
class B extends A {}

public class Benchmark {

    public static final Object a = new A();
    public static final Object b = new B();

    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    @OutputTimeUnit(TimeUnit.MICROSECONDS)
    public boolean testInstanceOf()
    {
        return b instanceof A;
    }

    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    @OutputTimeUnit(TimeUnit.MICROSECONDS)
    public boolean testIsInstance()
    {
        return A.class.isInstance(b);
    }

    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    @OutputTimeUnit(TimeUnit.MICROSECONDS)
    public boolean testIsAssignableFrom()
    {
        return A.class.isAssignableFrom(b.getClass());
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(TestPerf2.class.getSimpleName())
                .warmupIterations(20)
                .measurementIterations(2000)
                .forks(1)
                .build();

        new Runner(opt).run();
    }
}

Ha ottenuto i seguenti risultati (il punteggio è un numero di operazioni in un'unità di tempo , quindi più è alto il punteggio, meglio è):

Benchmark                       Mode   Cnt    Score   Error   Units
Benchmark.testIsInstance        thrpt  2000  373,061 ± 0,115  ops/us
Benchmark.testInstanceOf        thrpt  2000  371,047 ± 0,131  ops/us
Benchmark.testIsAssignableFrom  thrpt  2000  363,648 ± 0,289  ops/us

avvertimento

  • il benchmark è JVM e dipende dalla piattaforma. Poiché non vi sono differenze significative tra ciascuna operazione, potrebbe essere possibile ottenere un risultato diverso (e forse un ordine diverso!) Su una versione JAVA diversa e / o piattaforme come Solaris, Mac o Linux.
  • il benchmark confronta le prestazioni di "è B un'istanza di A" quando "B estende A" direttamente. Se la gerarchia di classi è più profonda e più complessa (come B estende X che estende Y che estende Z che estende A), i risultati potrebbero essere diversi.
  • di solito si consiglia di scrivere prima il codice selezionando uno degli operatori (il più conveniente) e quindi profilare il codice per verificare se ci sono colli di bottiglia nelle prestazioni. Forse questo operatore è trascurabile nel contesto del tuo codice, o forse ...
  • rispetto al punto precedente, instanceofnel contesto del codice potrebbe essere ottimizzato più facilmente di un isInstanceesempio ...

Per fare un esempio, prendi il seguente ciclo:

class A{}
class B extends A{}

A b = new B();

boolean execute(){
  return A.class.isAssignableFrom(b.getClass());
  // return A.class.isInstance(b);
  // return b instanceof A;
}

// Warmup the code
for (int i = 0; i < 100; ++i)
  execute();

// Time it
int count = 100000;
final long start = System.nanoTime();
for(int i=0; i<count; i++){
   execute();
}
final long elapsed = System.nanoTime() - start;

Grazie a JIT, il codice è ottimizzato ad un certo punto e otteniamo:

  • istanza di: 6ms
  • isInstance: 12ms
  • isAssignableFrom: 15ms

Nota

Inizialmente questo post stava facendo il proprio benchmark usando un ciclo for in JAVA non elaborato, che ha dato risultati inaffidabili in quanto alcune ottimizzazioni come Just In Time possono eliminare il ciclo. Quindi è stato soprattutto misurare il tempo impiegato dal compilatore JIT per ottimizzare il loop: vedere Test delle prestazioni indipendentemente dal numero di iterazioni per maggiori dettagli

Domande correlate


6
Sì, instanceofè un bytecode che utilizza essenzialmente la stessa logica di checkcast(il bytecode dietro il casting). Sarà intrinsecamente più veloce delle altre opzioni, indipendentemente dal grado di ottimizzazione JITC.
Hot Licks,

1
Il che ha senso, come isAssignableFrom()è dinamico.
Matthieu,

sì, con JMH i risultati sono completamente diversi (stessa velocità per tutti).
Yura,

Ciao, bel benchmark, si è appena imbattuto in una situazione in cui isAssignableFrom è stato chiamato migliaia di volte, il cambiamento in istanza di davvero ha fatto la differenza. Questa risposta varrebbe un post sul blog da qualche parte ...;)
Martin

33

Un equivalente più diretto a instanceof Bè

B.class.isInstance(a)

Funziona (restituisce false) anche quando lo aè null.


Fantastico, ma questo non risponde alla domanda e avrebbe dovuto essere un commento.
Madbreaks,

23

A parte le differenze di base sopra menzionate, esiste una sottile differenza fondamentale tra l'istanza dell'operatore e il metodo isAssignableFrom nella classe.

Leggi instanceofcome "è questa (la parte sinistra) l'istanza di questa o qualsiasi sottoclasse di questa (la parte destra)" e leggi x.getClass().isAssignableFrom(Y.class)come "Posso scrivere X x = new Y()". In altre parole, l'operatore instanceof controlla se l'oggetto sinistro è uguale o sottoclasse della classe destra, mentre isAssignableFromcontrolla se possiamo assegnare l'oggetto della classe di parametri (da) al riferimento della classe su cui viene chiamato il metodo.
Si noti che entrambi considerano l'istanza effettiva non il tipo di riferimento.

Considera un esempio di 3 classi A, B e C dove C estende B e B estende A.

B b = new C();

System.out.println(b instanceof A); //is b (which is actually class C object) instance of A, yes. This will return true.  
System.out.println(b instanceof B); // is b (which is actually class C object) instance of B, yes. This will return true.  
System.out.println(b instanceof C); // is b (which is actually class C object) instance of C, yes. This will return true. If the first statement would be B b = new B(), this would have been false.
System.out.println(b.getClass().isAssignableFrom(A.class));//Can I write C c = new A(), no. So this is false.
System.out.println(b.getClass().isAssignableFrom(B.class)); //Can I write C c = new B(), no. So this is false.
System.out.println(b.getClass().isAssignableFrom(C.class)); //Can I write C c = new C(), Yes. So this is true.

3
b instanceof Aè equivalente a A.class.isAssignableFrom(b.getClass())(come notato dall'OP). Il tuo esempio è corretto ma irrilevante.
Karu,

Dal momento che new Y()potrebbe non essere legale se Yè astratto o senza costruttore predefinito pubblico, puoi dire che X x = (Y)nullè legale se e solo se x.getClass().isAssignableFrom(Y.class)è vero.
Earth Engine,

2
Perché 'b.getClass (). IsAssignableFrom (A.class)' in questo esempio? Immagino che l'esempio dovrebbe essere al contrario A.class.isAssignableFrom (b.getClass ()).
loshad vtapkah,

14

C'è anche un'altra differenza:

l'istanza nulla di X falsenon importa quale sia X

null.getClass (). isAssignableFrom (X) genererà una NullPointerException


4
-1, errato: null instanceof X(dove X è una classe conosciuta al momento della compilazione) tornerà sempre false.
Caspar,

4
@Caspar mentre hai ragione, l'idea di base era un buon punto. Ho modificato il post in modo che sia corretto.
Erickson,

1
questo è utile, il caso limite è sempre importante :).
trilioni il

Per essere equivalente alla prima riga, la seconda riga dovrebbe essere X.class.isAssignableFrom(null.getClass())non dovrebbe? Sì, la chiamata getClass()a un riferimento null comporterà NPE.
William Price,

Questa risposta manca il punto: una dereferenza nulla non è rilevante perché l'errore si verifica al di fuori dell'operazione (è sempre necessario verificare la presenza di null prima di utilizzare un riferimento simile). In generale getClass()non dovrebbe essere usato con isAssignableFromin primo luogo - l'operazione è pensata per la situazione di non avere oggetti. Se avete l'oggetto di riferimento a, l'uso a instanceof SomeClass(se si fa conoscere il tipo SomeClass) oppure someObject.getClass().isInstance(a)(se non si conosce il tipo di someObject).
AndrewF

12

C'è ancora un'altra differenza. Se il tipo (Classe) da testare è dinamico, ad esempio passato come parametro del metodo, allora instanceof non lo taglierà per te.

boolean test(Class clazz) {
   return (this instanceof clazz); // clazz cannot be resolved to a type.
}

ma puoi fare:

boolean test(Class clazz) {
   return (clazz.isAssignableFrom(this.getClass())); // okidoki
}

Oops, vedo che questa risposta è già coperta. Forse questo esempio è utile a qualcuno.


3
in realtà nessuna risposta è veramente corretta isAssignableDal lavoro con classi, Class.isInstance è l'analogo di 'instanceof'
bestsss

Per inserire il commento corretto di @ bestsss in un codice concreto: poiché hai un oggetto ( this), clazz.isInstance(this)sarebbe meglio nel tuo esempio.
AndrewF

7

Questo thread mi ha fornito alcune informazioni su come instanceofdifferiva da isAssignableFrom, quindi ho pensato di condividere qualcosa di mio.

Ho scoperto che usare isAssignableFromper essere l'unico (probabilmente non l'unico, ma forse il più semplice) chiedere a se stessi se un riferimento di una classe può prendere istanze di un'altra, quando uno ha istanze di nessuna classe per fare il confronto.

Quindi, non ho trovato che usare l' instanceofoperatore per confrontare l'assegnabilità fosse una buona idea quando tutto ciò che avevo erano le classi, a meno che non avessi pensato di creare un'istanza da una delle classi; Ho pensato che sarebbe stato sciatto.


5

instanceof non può essere utilizzato con tipi primitivi o generici. Come nel seguente codice:

//Define Class< T > type ... 

Object e = new Object();

if(e instanceof T) {
  // Do something.
}

L'errore è: impossibile eseguire il controllo dell'istanza del parametro di tipo T. Utilizzare invece l'oggetto di cancellazione poiché ulteriori informazioni di tipo generico verranno cancellate in fase di esecuzione.

Non viene compilato a causa della cancellazione del tipo rimuovendo il riferimento di runtime. Tuttavia, il codice seguente compilerà:

if( type.isAssignableFrom(e.getClass())){
  // Do something.
}

4

Valuta la seguente situazione. Supponiamo che tu voglia verificare se il tipo A è una super classe del tipo di obj, puoi andare anche tu

... A.class.isAssignableFrom (obj.getClass ()) ...

O

... obj esempio di A ...

Ma la soluzione isAssignableFrom richiede che il tipo di oggetto sia visibile qui. In caso contrario (ad esempio, il tipo di obj potrebbe appartenere a una classe interna privata), questa opzione non è disponibile. Tuttavia, la soluzione instanceof funzionerebbe sempre.


2
Quello non è vero. Si prega di consultare la sezione "Adam Rosenfield" commento stackoverflow.com/questions/496928/...
Maxim Veksler

1
Potresti elaborare "Questo non è vero"? Il commento a cui ti riferisci non ha nulla a che fare con lo scenario nel mio post. Ho un codice di prova che supporta la mia spiegazione.
Algebra,

Se si dispone di un riferimento non nullo a un'istanza di oggetto ( objin questo esempio) di qualsiasi tipo, è possibile chiamare il getClass()metodo pubblico su di esso per ottenere i metadati di riflessione per la classe di implementazione. Ciò è vero anche se quel tipo di classe di implementazione non sarebbe legalmente visibile in quella posizione al momento della compilazione. Va bene, in fase di esecuzione, perché, per voi di tenere il objriferimento, qualche percorso di codice che alla fine ha fatto avere accesso legale alla classe ha creato uno e ha dato (trapelato?) A voi.
William Price,

0
isAssignableFrom(A, B) =

if (A == B) return true
else if (B == java.lang.Object) return false
else return isAssignableFrom(A, getSuperClass(B))

Lo pseudo codice sopra è una definizione di, se riferimenti di tipo / classe A sono assegnabili da riferimenti di tipo / classe B. È una definizione ricorsiva. Per alcuni può essere utile, per altri può essere fonte di confusione. Lo aggiungo nel caso qualcuno lo trovasse utile. Questo è solo un tentativo di catturare la mia comprensione, non è la definizione ufficiale. Viene utilizzato in una determinata implementazione di VM Java e funziona per molti programmi di esempio, quindi anche se non posso garantire che acquisisca tutti gli aspetti di isAssignableFrom, non è completamente spento.


2
Spiegare cosa fa questo codice e come risponde alla domanda.
Fondi Monica's Lawsuit,

0

Parlando in termini di prestazioni "2" (con JMH):

class A{}
class B extends A{}

public class InstanceOfTest {

public static final Object a = new A();
public static final Object b = new B();

@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public boolean testInstanceOf()
{
    return b instanceof A;
}

@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public boolean testIsInstance()
{
    return A.class.isInstance(b);
}

@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public boolean testIsAssignableFrom()
{
    return A.class.isAssignableFrom(b.getClass());
}

public static void main(String[] args) throws RunnerException {
    Options opt = new OptionsBuilder()
            .include(InstanceOfTest.class.getSimpleName())
            .warmupIterations(5)
            .measurementIterations(5)
            .forks(1)
            .build();

    new Runner(opt).run();
}
}

Dà:

Benchmark                            Mode  Cnt  Score   Error  Units
InstanceOfTest.testInstanceOf        avgt    5  1,972 ? 0,002  ns/op
InstanceOfTest.testIsAssignableFrom  avgt    5  1,991 ? 0,004  ns/op
InstanceOfTest.testIsInstance        avgt    5  1,972 ? 0,003  ns/op

In modo che possiamo concludere: istanza di veloce quanto isInstance () e isAssignableFrom () non lontano (+ 0,9% tempo di esecuzione). Quindi nessuna vera differenza qualunque cosa tu scelga


0

Che ne dici di alcuni esempi per mostrarlo in azione ...

@Test
public void isInstanceOf() {
    Exception anEx1 = new Exception("ex");
    Exception anEx2 = new RuntimeException("ex");
    RuntimeException anEx3 = new RuntimeException("ex");

    //Base case, handles inheritance
    Assert.assertTrue(anEx1 instanceof Exception);
    Assert.assertTrue(anEx2 instanceof Exception);
    Assert.assertTrue(anEx3 instanceof Exception);

    //Other cases
    Assert.assertFalse(anEx1 instanceof RuntimeException);
    Assert.assertTrue(anEx2 instanceof RuntimeException);
    Assert.assertTrue(anEx3 instanceof RuntimeException);
}

@Test
public void isAssignableFrom() {
    Exception anEx1 = new Exception("ex");
    Exception anEx2 = new RuntimeException("ex");
    RuntimeException anEx3 = new RuntimeException("ex");

    //Correct usage = The base class goes first
    Assert.assertTrue(Exception.class.isAssignableFrom(anEx1.getClass()));
    Assert.assertTrue(Exception.class.isAssignableFrom(anEx2.getClass()));
    Assert.assertTrue(Exception.class.isAssignableFrom(anEx3.getClass()));

    //Incorrect usage = Method parameter is used in the wrong order
    Assert.assertTrue(anEx1.getClass().isAssignableFrom(Exception.class));
    Assert.assertFalse(anEx2.getClass().isAssignableFrom(Exception.class));
    Assert.assertFalse(anEx3.getClass().isAssignableFrom(Exception.class));
}

-2

alcuni test che abbiamo fatto nel nostro team mostrano che A.class.isAssignableFrom(B.getClass())funziona più velocemente di B instanceof A. questo può essere molto utile se devi verificarlo su un gran numero di elementi.


13
Hm, se hai un collo di bottiglia instanceof, credo che tu abbia seri problemi di progettazione ...
sleske,

1
La risposta di JBE presenta un'ipotesi che differisce dalla tua ipotesi.
Alastor Moody,
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.