Jaka jest różnica między instanceof i Class.isAssignableFrom (…)?


457

Które z poniższych jest lepsze?

a instanceof B

lub

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

Jedyną różnicą, o której wiem, jest to, że gdy „a” jest zerowe, pierwsze zwraca false, a drugie zgłasza wyjątek. Poza tym, czy zawsze dają taki sam wynik?


17
Dla rekordów, isinstance () jest najbardziej wygodny sposób, by sprawdzić, czy obiekt może być lanego do typu klasy (więcej szczegółów, patrz: tshikatshikaaa.blogspot.nl/2012/07/... )
Jérôme Verstrynge

Odpowiedzi:


497

Podczas korzystania instanceofmusisz znać klasę Bw czasie kompilacji. Podczas korzystania isAssignableFrom()może być dynamiczny i zmieniać się w czasie wykonywania.


12
nie rozumiem - wyjaśnij, dlaczego nie możemy pisać a instanceof Bref.getClass(). jak może to być zaakceptowana odpowiedź z tak małym wyjaśnieniem (lub jej brakiem)?
Eliran Malka

65
Składnia a instanceof Brefnie jest a instanceof Bref.class. Drugim argumentem dla operatora instanceof jest nazwa klasy, a nie wyrażenie rozstrzygające instancję obiektu klasy.
Brandon Bloom

2
tak „dynamiczny” jest oczywisty :) Poza wydajnością, to prawdziwa różnica.
peterk

2
@EliranMalka może możesz mieć klasę wygenerowaną w czasie wykonywania. Jak obiekty proxy.
Wagner Tsuchiya

Tak więc w B.class.isAssignableFrom(a.getClass())B jest znane i a instanceof Bjest lepsze. Dobrze?
Florian F,


116

Mówienie w kategoriach wydajności:

TL; DR

Użyj isInstance lub instanceof, które mają podobną wydajność. isAssignableFrom jest nieco wolniejszy.

Posortowane według wydajności:

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

Oparty na teście 2000 iteracji na JAVA 8 Windows x64, z 20 iteracjami rozgrzewania.

W teorii

Korzystając z miękkiej przeglądarki kodu bajtowego , możemy przetłumaczyć każdego operatora na kod bajtowy.

W kontekście:

package foo;

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

  ...

}

JAWA:

b instanceof A;

Kod bajtowy:

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

JAWA:

A.class.isInstance(b);

Kod bajtowy:

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

JAWA:

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

Kod bajtowy:

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

Mierząc liczbę instrukcji kodu bajtowego używanych przez każdego operatora, można oczekiwać, że instanceof i isInstance będą szybsze niż isAssignableFrom . Rzeczywista wydajność NIE jest jednak określana przez kod bajtowy, ale przez kod maszynowy (zależny od platformy). Zróbmy mikro test porównawczy dla każdego operatora.

Benchmark

Credit: Zgodnie z zaleceniami @ aleksandr-dubinsky i dzięki @yura za dostarczenie kodu podstawowego, oto test porównawczy JMH (zobacz ten przewodnik strojenia ):

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

Dał następujące wyniki (wynik to liczba operacji w jednostce czasu , więc im wyższy wynik, tym lepiej):

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

Ostrzeżenie

  • Benchmark jest zależny od JVM i platformy. Ponieważ nie ma znaczących różnic między każdą operacją, może być możliwe uzyskanie innego wyniku (a może innej kolejności!) Na innej wersji JAVA i / lub platformach takich jak Solaris, Mac lub Linux.
  • test porównawczy porównuje działanie „jest B, instancja A”, gdy „B rozszerza A” bezpośrednio. Jeśli hierarchia klas jest głębsza i bardziej złożona (jak B rozszerza X, który rozszerza Y, który rozszerza Z, który rozszerza A), wyniki mogą być inne.
  • zwykle zaleca się, aby najpierw napisać kod, wybierając jednego z operatorów (najwygodniejszy), a następnie profilować kod, aby sprawdzić, czy występuje wąskie gardło wydajności. Może ten operator jest nieistotny w kontekście twojego kodu, a może ...
  • w odniesieniu do poprzedniego punktu, instanceofw kontekście twojego kodu może zostać zoptymalizowany łatwiej niż isInstancena przykład ...

Aby podać przykład, wykonaj następującą pętlę:

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;

Dzięki JIT kod jest w pewnym momencie optymalizowany i otrzymujemy:

  • instanceof: 6ms
  • isInstance: 12ms
  • isAssignableFrom: 15ms

Uwaga

Pierwotnie ten post przeprowadzał własny test porównawczy przy użyciu pętli for w surowej JAVA, co dawało niewiarygodne wyniki, ponieważ niektóre optymalizacje, takie jak Just In Time, mogą wyeliminować pętlę. Więc głównie mierzyło to, ile czasu zajął kompilatorowi JIT, aby zoptymalizować pętlę: zobacz Test wydajności niezależny od liczby iteracji, aby uzyskać więcej szczegółów

Powiązane pytania


6
Tak, instanceofto kod bajtowy, który używa zasadniczo tej samej logiki co checkcast(kod bajtowy za rzutowaniem). Z natury będzie szybszy niż inne opcje, niezależnie od stopnia optymalizacji JITC.
Hot Licks

1
Co ma sens, podobnie jak isAssignableFrom()dynamiczne.
Matthieu,

tak, z wynikami JMH są zupełnie inne (ta sama prędkość dla wszystkich).
Yura,

Cześć, niezły benchmark, właśnie natknąłem się na sytuację, w której isAssignableFrom był nazywany tysiące razy, zmiana na instanceof naprawdę zrobiła różnicę. Ta odpowiedź byłaby warta wpisu na blogu gdzieś ...;)
Martin

33

Bardziej bezpośredni odpowiednik a instanceof Bto

B.class.isInstance(a)

To działa (zwraca false), gdy ajest nullzbyt.


Fajnie, ale to nie odpowiada na pytanie i powinno być komentarzem.
Madbreaks

23

Poza podstawowymi różnicami wspomnianymi powyżej, istnieje zasadnicza subtelna różnica między instancją operatora a metodą isAssignableFrom w klasie.

Czyta się instanceofjako „czy to (lewa część) jest wystąpieniem tej lub dowolnej podklasy tego (prawa część)” i czyta się x.getClass().isAssignableFrom(Y.class)jako „Czy mogę pisać X x = new Y()”. Innymi słowy, operator instanceof sprawdza, czy lewy obiekt jest taki sam lub podklasę prawej klasy, podczas gdy isAssignableFromsprawdza, czy możemy przypisać obiekt klasy parametru (od) do odwołania do klasy, na której wywoływana jest metoda.
Zauważ, że oba rozważają rzeczywistą instancję, a nie typ odwołania.

Rozważ przykład 3 klas A, B i C, w których C rozszerza B, a B rozszerza 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 Ajest równoważne A.class.isAssignableFrom(b.getClass())(jak zauważył PO). Twój przykład jest poprawny, ale nieistotny.
Karu

Ponieważ new Y()może nie być legalne, jeśli Yjest abstrakcyjne lub bez publicznego domyślnego konstruktora, możesz powiedzieć, że X x = (Y)nulljest legalne, jeśli i tylko jeśli x.getClass().isAssignableFrom(Y.class)jest prawdziwe.
Earth Engine

2
Dlaczego „b.getClass (). IsAssignableFrom (A.class)” w tym przykładzie? Myślę, że przykładem powinna być odwrotna A.class.isAssignableFrom (b.getClass ()).
loshad vtapkah

14

Jest jeszcze jedna różnica:

zerowa instancja X falsenie ma znaczenia, czym jest X

null.getClass (). isAssignableFrom (X) zgłosi wyjątek NullPointerException


4
-1, niepoprawne: null instanceof X(gdzie X jest klasą znaną w czasie kompilacji) zawsze zwróci false.
Caspar

4
@Caspar, dopóki masz rację, podstawowa idea była dobra. Zredagowałem post, aby był poprawny.
erickson,

1
jest to pomocne, skrzynia krawędzi jest zawsze ważna :).
tryliony

Aby być równoważnym z pierwszą linią, druga linia powinna być X.class.isAssignableFrom(null.getClass()), prawda? Ale tak, wywołanie getClass()zerowego odwołania spowoduje NPE.
William Price,

Ta odpowiedź nie ma sensu - dereferencja zerowa nie ma znaczenia, ponieważ awaria zdarza się poza operacją (zawsze musisz sprawdzić, czy nie ma wartości zerowej przed użyciem takiego odwołania). Zasadniczo getClass()nie należy go używać z isAssignableFrom- operacja jest przeznaczona do sytuacji, gdy nie ma żadnych obiektów. Jeśli masz odwołanie do obiektu a, wykorzystywania a instanceof SomeClass(jeśli zrobić znać typ SomeClass) lub someObject.getClass().isInstance(a)(jeśli nie wiesz, rodzaj someObject).
AndrewF

12

Jest jeszcze jedna różnica. Jeśli typ (klasa) do testowania jest dynamiczny, np. Przekazany jako parametr metody, instanceof nie wycina go za ciebie.

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

ale możesz zrobić:

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

Ups, widzę, że ta odpowiedź jest już omówiona. Może ten przykład jest komuś pomocny.


3
właściwie żadna odpowiedź nie jest naprawdę poprawna isAssignableFrom pracy z klasami, Class.isInstance jest analogią do „instanceof”
bestsss

Aby umieścić poprawny komentarz @ bestsss w konkretnym kodzie: Ponieważ masz obiekt ( this), clazz.isInstance(this)byłoby lepiej w twoim przykładzie.
AndrewF

7

Ten wątek dał mi wgląd w to, czym się instanceofróżnił isAssignableFrom, więc pomyślałem, że podzielę się czymś własnym.

Odkryłem, że używanie isAssignableFromjest jedynym (prawdopodobnie nie jedynym, ale być może najłatwiejszym) sposobem, aby zapytać siebie, czy odwołanie do jednej klasy może mieć instancje innej, gdy nie ma instancji żadnej klasy do porównania.

Dlatego też nie znalazłem używania instanceofoperatora do porównywania możliwości przypisania jako dobrego pomysłu, gdy wszystko, co miałem, to klasy, chyba że rozważałem utworzenie instancji z jednej z klas; Myślałem, że to będzie niechlujne.


5

instanceof nie może być używany z typami pierwotnymi ani typami ogólnymi. Jak w poniższym kodzie:

//Define Class< T > type ... 

Object e = new Object();

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

Błąd jest następujący: Nie można wykonać instancji sprawdzenia względem parametru typu T. Zamiast tego należy użyć obiektu Erasure Object, ponieważ dalsze ogólne informacje o typie zostaną usunięte w czasie wykonywania.

Nie kompiluje się z powodu usunięcia typu usuwającego odwołanie do środowiska wykonawczego. Jednak poniższy kod zostanie skompilowany:

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

4

Rozważ następującą sytuację. Załóżmy, że chcesz sprawdzić, czy typ A jest superklasą typu obj, możesz przejść albo

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

LUB

... obj instanceof A ...

Ale rozwiązanie isAssignableFrom wymaga, aby tutaj był widoczny typ obj. Jeśli tak nie jest (np. Typ obj może być prywatnej klasy wewnętrznej), ta opcja jest niedostępna. Jednak rozwiązanie instanceof zawsze będzie działać.


2
To nie jest prawda. Zobacz komentarz „Adam Rosenfield” stackoverflow.com/questions/496928/…
Maxim Veksler,

1
Czy mógłbyś rozwinąć „To nieprawda”? Komentarz, do którego się odwołujesz, nie ma nic wspólnego ze scenariuszem w moim poście. Mam kod testowy, który stanowi kopię zapasową mojego wyjaśnienia.
algebra

Jeśli masz niepuste odwołanie do instancji obiektu ( objw tym przykładzie) dowolnego typu , możesz wywołać publiczną getClass()metodę, aby uzyskać metadane odbicia dla klasy implementującej. Jest to prawdą, nawet jeśli ten typ klasy implementującej nie byłby prawnie widoczny w tym miejscu podczas kompilacji. Jest OK przy starcie, ponieważ, aby utrzymać objodniesienia, niektóre ścieżki kodu, który ostatecznie nie mają legalny dostęp do klasy stworzył jedną i dał (wyciekły?) Go do ciebie.
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))

Powyższy pseudo kod jest definicją, jeżeli referencje typu / klasy A można przypisać z referencji typu / klasy B. Jest to definicja rekurencyjna. Dla niektórych może to być pomocne, dla innych może być mylące. Dodaję to na wypadek, gdyby ktoś uznał to za przydatne. To tylko próba uchwycenia mojego zrozumienia, to nie jest oficjalna definicja. Jest używany w pewnej implementacji Java VM i działa dla wielu przykładowych programów, więc chociaż nie mogę zagwarantować, że przechwytuje wszystkie aspekty isAssignableFrom, nie jest całkowicie wyłączony.


2
Proszę wyjaśnić, co robi ten kod i jak odpowiada na pytanie.
Pozew funduszu Moniki z

0

Mówiąc w kategoriach wydajności „2” (z 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();
}
}

To daje:

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

Abyśmy mogli dojść do wniosku: instanceof tak szybko, jak isInstance () i isAssignableFrom () nie daleko (+ 0,9% czasu wykonania). Więc nie ma żadnej różnicy, którą wybierzesz


0

Co powiesz na kilka przykładów pokazujących to w akcji ...

@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

niektóre testy, które przeprowadziliśmy w naszym zespole, pokazują, że A.class.isAssignableFrom(B.getClass())działa szybciej niż B instanceof A. może to być bardzo przydatne, jeśli chcesz to sprawdzić na dużej liczbie elementów.


13
Hm, jeśli masz wąskie gardło instanceof, uważam, że masz poważne problemy projektowe ...
sleske

1
Odpowiedź JBE przedstawia hipotezę, która różni się od twojej hipotezy.
Alastor Moody
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.