Was ist der Unterschied zwischen instanceof und Class.isAssignableFrom (…)?


458

Welche der folgenden Möglichkeiten ist besser?

a instanceof B

oder

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

Der einzige Unterschied, den ich kenne, ist, wenn 'a' null ist, gibt der erste false zurück, während der zweite eine Ausnahme auslöst. Geben sie sonst immer das gleiche Ergebnis?


17
Für die Datensätze ist isInstance () die bequemste Methode, um zu überprüfen, ob ein Objekt in einen Klassentyp umgewandelt werden kann (weitere Informationen finden Sie unter: tshikatshikaaa.blogspot.nl/2012/07/… )
Jérôme Verstrynge

Antworten:


498

Bei der Verwendung instanceofmüssen Sie die Klasse von Bzur Kompilierungszeit kennen. Bei der Verwendung isAssignableFrom()kann es dynamisch sein und sich zur Laufzeit ändern.


12
Ich verstehe es nicht - bitte erläutern Sie, warum wir nicht schreiben können a instanceof Bref.getClass(). Wie kann dies die akzeptierte Antwort mit so wenig Erklärung sein (oder deren Fehlen)?
Eliran Malka

65
Die Syntax ist a instanceof Brefnicht a instanceof Bref.class. Das zweite Argument für den Operator instanceof ist ein Klassenname, kein Ausdruck, der in eine Klassenobjektinstanz aufgelöst wird.
Brandon Bloom

2
ja "dynamisch" versteht sich von selbst :) Abgesehen von der Leistung ist dies ein echter Unterschied.
Peterk

2
@EliranMalka Vielleicht können Sie eine Klasse haben, die zur Laufzeit generiert wurde. Wie Proxy-Objekte.
Wagner Tsuchiya

Also ist in B.class.isAssignableFrom(a.getClass())B bekannt und a instanceof Bbesser. Recht?
Florian F

208

instanceofkann nur mit Referenztypen verwendet werden, nicht mit primitiven Typen. isAssignableFrom()kann mit allen Klassenobjekten verwendet werden:

a instanceof int  // syntax error
3 instanceof Foo  // syntax error
int.class.isAssignableFrom(int.class)  // true

Sehen http://java.sun.com/javase/6/docs/api/java/lang/Class.html#isAssignableFrom(java.lang.Class) .


10
Ich sehe keinen Sinn darin, instanceof / isAssignableFrom mit primitiven Typen zu verwenden.
Jimmy T.

116

In Bezug auf die Leistung sprechen:

TL; DR

Verwenden Sie isInstance oder instanceof mit ähnlicher Leistung. isAssignableFrom ist etwas langsamer.

Sortiert nach Leistung:

  1. isInstance
  2. Instanz von (+ 0,5%)
  3. isAssignableFrom (+ 2,7%)

Basierend auf einem Benchmark von 2000 Iterationen unter JAVA 8 Windows x64 mit 20 Aufwärmiterationen.

In der Theorie

Mit einem weichen Bytecode-Viewer können wir jeden Operator in Bytecode übersetzen.

Im Zusammenhang mit:

package foo;

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

  ...

}

JAVA:

b instanceof A;

Bytecode:

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

JAVA:

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

JAVA:

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

Wenn wir messen, wie viele Bytecode-Anweisungen von jedem Operator verwendet werden, können wir erwarten, dass instanceof und isInstance schneller sind als isAssignableFrom . Die tatsächliche Leistung wird jedoch NICHT durch den Bytecode bestimmt, sondern durch den Maschinencode (der plattformabhängig ist). Lassen Sie uns für jeden Operator einen Mikro-Benchmark durchführen.

Der Benchmark

Gutschrift: Wie von @ aleksandr-dubinsky empfohlen und danke an @yura für die Bereitstellung des Basiscodes, hier ein JMH- Benchmark (siehe diese Tuning-Anleitung ):

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

Hat die folgenden Ergebnisse geliefert (Punktzahl ist eine Anzahl von Operationen in einer Zeiteinheit . Je höher die Punktzahl, desto besser):

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

Warnung

  • Der Benchmark ist JVM- und plattformabhängig. Da es zwischen den einzelnen Vorgängen keine signifikanten Unterschiede gibt, kann es sein, dass auf einer anderen JAVA-Version und / oder Plattformen wie Solaris, Mac oder Linux ein anderes Ergebnis (und möglicherweise eine andere Reihenfolge!) Erzielt wird.
  • Der Benchmark vergleicht die Leistung von "Ist B eine Instanz von A", wenn "B A direkt erweitert". Wenn die Klassenhierarchie tiefer und komplexer ist (wie B X erweitert, das Y erweitert, das Z erweitert, das A erweitert), können die Ergebnisse unterschiedlich sein.
  • Es wird normalerweise empfohlen, den Code zuerst zu schreiben, indem Sie einen der Operatoren auswählen (den bequemsten), und dann Ihren Code zu profilieren, um zu überprüfen, ob ein Leistungsengpass vorliegt. Vielleicht ist dieser Operator im Kontext Ihres Codes vernachlässigbar, oder vielleicht ...
  • In Bezug auf den vorherigen Punkt kann instanceofder Kontext Ihres Codes möglicherweise einfacher optimiert werden als isInstancebeispielsweise ...

Nehmen Sie als Beispiel die folgende Schleife:

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;

Dank der JIT wird der Code irgendwann optimiert und wir erhalten:

  • Instanz von: 6ms
  • isInstance: 12ms
  • isAssignableFrom: 15ms

Hinweis

Ursprünglich hat dieser Beitrag einen eigenen Benchmark mit einer for- Schleife in JAVA durchgeführt, was zu unzuverlässigen Ergebnissen führte, da einige Optimierungen wie Just In Time die Schleife beseitigen können. Daher wurde hauptsächlich gemessen, wie lange der JIT-Compiler für die Optimierung der Schleife benötigt hat: Weitere Informationen finden Sie unter Leistungstest unabhängig von der Anzahl der Iterationen

Verwandte Fragen


6
Ja, instanceofist ein Bytecode, der im Wesentlichen dieselbe Logik verwendet wie checkcast(der Bytecode hinter dem Casting). Es ist von Natur aus schneller als die anderen Optionen, unabhängig vom Grad der JITC-Optimierung.
Hot Licks

1
Was Sinn macht, isAssignableFrom()ist dynamisch.
Matthieu

Ja, mit JMH sind die Ergebnisse völlig unterschiedlich (gleiche Geschwindigkeit für alle).
Yura

Hallo, netter Benchmark, bin gerade in eine Situation geraten, in der isAssignableFrom tausende Male aufgerufen wurde, und der Wechsel zu instanceof hat wirklich einen Unterschied gemacht. Diese Antwort wäre irgendwo einen Blog-Beitrag wert ...;)
Martin

33

Ein direkteres Äquivalent zu a instanceof Bist

B.class.isInstance(a)

Dies funktioniert (false zurück) , wenn aist nullauch.


Cool, aber das beantwortet die Frage nicht und hätte ein Kommentar sein sollen.
Madbreaks

23

Abgesehen von den oben erwähnten grundlegenden Unterschieden gibt es in der Klasse einen subtilen Kernunterschied zwischen der Instanz des Operators und der Methode isAssignableFrom.

Lesen Sie instanceofals "Ist dies (der linke Teil) die Instanz dieser oder einer Unterklasse davon (der rechte Teil)" und lesen Sie x.getClass().isAssignableFrom(Y.class)als "Kann ich schreiben X x = new Y()". Mit anderen Worten, die Instanz des Operators prüft, ob das linke Objekt dasselbe oder eine Unterklasse der rechten Klasse ist, während isAssignableFromprüft, ob wir der Referenz der Klasse, für die die Methode aufgerufen wird, ein Objekt der Parameterklasse (von) zuweisen können.
Beachten Sie, dass beide die tatsächliche Instanz und nicht den Referenztyp berücksichtigen.

Betrachten Sie ein Beispiel für 3 Klassen A, B und C, wobei C B und B A erweitert.

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 Aist äquivalent zu A.class.isAssignableFrom(b.getClass())(wie das OP bemerkt hat). Ihr Beispiel ist richtig, aber irrelevant.
Karu

Da dies new Y()möglicherweise nicht legal ist, wenn Yes abstrakt ist oder keinen öffentlichen Standardkonstruktor enthält, können Sie sagen, dass X x = (Y)nulles genau dann legal ist, wenn x.getClass().isAssignableFrom(Y.class)es wahr ist.
Earth Engine

2
Warum in diesem Beispiel 'b.getClass (). IsAssignableFrom (A.class)'? Ich denke, das Beispiel sollte umgekehrt sein. A.class.isAssignableFrom (b.getClass ()).
Loshad Vtapkah

14

Es gibt noch einen weiteren Unterschied:

Die Nullinstanz von X ist falseegal, was X ist

null.getClass (). isAssignableFrom (X) löst eine NullPointerException aus


4
-1, falsch: null instanceof X(wobei X eine zur Kompilierungszeit bekannte Klasse ist) wird immer zurückgegeben false.
Caspar

4
@Caspar Während Sie richtig sind, war die Grundidee ein guter Punkt. Ich habe den Beitrag so bearbeitet, dass er korrekt ist.
Erickson

1
das ist hilfreich, der Randfall ist immer wichtig :).
Billionen

Um der ersten Zeile zu entsprechen, sollte die zweite Zeile sein, X.class.isAssignableFrom(null.getClass())sollte es nicht? Aber ja, das Aufrufen getClass()einer Nullreferenz führt zu NPE.
William Price

Diese Antwort verfehlt den Punkt - eine Null-Dereferenzierung ist nicht relevant, da der Fehler außerhalb des Vorgangs auftritt (Sie müssen immer nach Null suchen, bevor Sie eine solche Referenz verwenden). Im Allgemeinen getClass()sollte es überhaupt nicht verwendet werden isAssignableFrom- die Operation ist für die Situation gedacht, in der keine Objekte vorhanden sind. Wenn Sie den Objektverweis haben a, verwenden a instanceof SomeClass(wenn Sie tun die Art kennen SomeClass) oder someObject.getClass().isInstance(a)(wenn Sie nicht die Art von wissen someObject).
AndrewF

12

Es gibt noch einen weiteren Unterschied. Wenn der Typ (Klasse), gegen den getestet werden soll, dynamisch ist, z. B. als Methodenparameter übergeben, wird die Instanz ihn nicht für Sie schneiden.

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

aber du kannst tun:

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

Hoppla, ich sehe, dass diese Antwort bereits behandelt wird. Vielleicht ist dieses Beispiel für jemanden hilfreich.


3
Eigentlich ist keine Antwort wirklich richtig. Zuweisbar Von der Arbeit mit Klassen ist Class.isInstance das Analogon von 'instanceof'
bestsss

So fügen Sie den richtigen Kommentar von @ bestsss in konkreten Code ein: Da Sie ein Objekt ( this) haben, clazz.isInstance(this)ist es in Ihrem Beispiel besser.
AndrewF

7

Dieser Thread gab mir einen Einblick in die instanceofUnterschiede isAssignableFrom, also dachte ich, ich würde etwas von mir teilen.

Ich habe festgestellt, dass die Verwendung isAssignableFromdie einzige (wahrscheinlich nicht die einzige, aber möglicherweise die einfachste) Möglichkeit ist, sich selbst zu fragen, ob eine Referenz einer Klasse Instanzen einer anderen annehmen kann, wenn man Instanzen keiner Klasse hat, um den Vergleich durchzuführen.

Daher fand ich es nicht gut, den instanceofOperator zum Vergleichen der Zuweisbarkeit zu verwenden, wenn ich nur Klassen hatte, es sei denn, ich wollte eine Instanz aus einer der Klassen erstellen. Ich dachte, das wäre schlampig.


5

instanceof kann auch nicht mit primitiven oder generischen Typen verwendet werden. Wie im folgenden Code:

//Define Class< T > type ... 

Object e = new Object();

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

Der Fehler lautet: Es kann keine Instanz der Prüfung für den Typparameter T durchgeführt werden. Verwenden Sie stattdessen das Löschobjekt, da zur Laufzeit weitere allgemeine Typinformationen gelöscht werden.

Kompiliert nicht, da beim Löschen des Typs die Laufzeitreferenz entfernt wird. Der folgende Code wird jedoch kompiliert:

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

4

Betrachten Sie die folgende Situation. Angenommen, Sie möchten überprüfen, ob Typ A eine Superklasse des Typs obj ist, dann können Sie auch gehen

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

ODER

... obj Instanz von A ...

Für die isAssignableFrom-Lösung muss jedoch der Typ des Objekts hier sichtbar sein. Wenn dies nicht der Fall ist (z. B. könnte der Typ von obj einer privaten inneren Klasse angehören), ist diese Option deaktiviert. Die Instanz der Lösung würde jedoch immer funktionieren.


2
Das ist nicht wahr. Bitte sehen Sie "Adam Rosenfield" Kommentar stackoverflow.com/questions/496928/…
Maxim Veksler

1
Könnten Sie näher erläutern "Das ist nicht wahr"? Der Kommentar, auf den Sie sich beziehen, hat nichts mit dem Szenario in meinem Beitrag zu tun. Ich habe einen Testcode, der meine Erklärung stützt.
Algebra

Wenn Sie einen Nicht-Null-Verweis auf eine Objektinstanz ( objin diesem Beispiel) eines beliebigen Typs haben , können Sie die öffentliche getClass()Methode aufrufen , um die Reflektionsmetadaten für die implementierende Klasse abzurufen. Dies gilt auch dann, wenn dieser implementierende Klassentyp zur Kompilierungszeit an diesem Speicherort rechtlich nicht sichtbar wäre. Es ist OK zur Laufzeit , da für Sie halten objReferenz, einige Codepfad , die letztlich tat müssen die Klasse legalen Zugang erstellt ein und gab (durchgesickert?) Es Ihnen.
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))

Der obige Pseudocode ist eine Definition von, wenn Referenzen des Typs / der Klasse A aus Referenzen des Typs / der Klasse B zuweisbar sind. Es handelt sich um eine rekursive Definition. Für einige mag es hilfreich sein, für andere mag es verwirrend sein. Ich füge es hinzu, falls jemand es nützlich finden sollte. Dies ist nur ein Versuch, mein Verständnis zu erfassen, es ist nicht die offizielle Definition. Es wird in einer bestimmten Java VM-Implementierung verwendet und funktioniert für viele Beispielprogramme. Daher kann ich nicht garantieren, dass es alle Aspekte von isAssignableFrom erfasst, aber es ist nicht vollständig deaktiviert.


2
Bitte erläutern Sie, was dieser Code bewirkt und wie er die Frage beantwortet.
Fund Monica Klage

0

Apropos Leistung "2" (mit 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();
}
}

Es gibt:

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

Damit wir schließen können: Instanz von so schnell wie isInstance () und isAssignableFrom () nicht weit entfernt (+ 0,9% Ausführungszeit). Also kein wirklicher Unterschied, was auch immer Sie wählen


0

Wie wäre es mit einigen Beispielen, um es in Aktion zu zeigen ...

@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

Einige Tests, die wir in unserem Team durchgeführt haben, zeigen, dass dies A.class.isAssignableFrom(B.getClass())schneller funktioniert als B instanceof A. Dies kann sehr nützlich sein, wenn Sie dies bei einer großen Anzahl von Elementen überprüfen müssen.


13
Hm, wenn Sie einen Engpass haben instanceof, glaube ich, dass Sie ernsthafte Designprobleme haben ...
Sleske

1
Die Antwort von JBE enthält eine Hypothese, die sich von Ihrer Hypothese unterscheidet.
Alastor Moody
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.