İnstanceof ve Class.isAssignableFrom (…) arasındaki fark nedir?


458

Aşağıdakilerden hangisi daha iyidir?

a instanceof B

veya

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

Bildiğim tek fark, 'a' boş olduğunda, ilki yanlış döndürürken ikincisi bir istisna atar. Bunun dışında hep aynı sonucu veriyorlar mı?


17
Kayıtlar için, bir nesnenin bir sınıf türüne dökülüp dökülemeyeceğini kontrol etmek için en uygun yöntemdir (daha fazla bilgi için, bkz: tshikatshikaaa.blogspot.nl/2012/07/… )
Jérôme Verstrynge

Yanıtlar:


498

Kullanırken instanceof, Bderleme zamanında sınıfını bilmeniz gerekir . Kullanırken isAssignableFrom()dinamik olabilir ve çalışma sırasında değişebilir.


12
Anlamıyorum - lütfen neden yazamadığımızı açıklayın a instanceof Bref.getClass(). nasıl bu kadar az açıklamayla (ya da eksikliği) kabul edilen cevap nasıl olabilir?
Eliran Malka

65
Sözdizimi a instanceof Brefdeğil a instanceof Bref.class. İnstanceof operatörünün ikinci argümanı, sınıf nesnesi örneğine çözümlenen bir ifade değil, bir sınıf adıdır.
Brandon Bloom

2
evet "dinamik" söylemeye gerek yok :) Performans dışında, bu gerçek bir fark.
peterk

2
@EliranMalka belki çalışma zamanında oluşturulan bir sınıfa sahip olabilirsiniz. Proxy nesneler gibi.
Wagner Tsuchiya

Yani, içinde B.class.isAssignableFrom(a.getClass()), B bilinir ve a instanceof Bdaha iyidir. Sağ?
Florian F


116

Performans açısından konuşmak:

TL; DR

Benzer performansa sahip isInstance veya instanceof kullanın . isAssignableFrom biraz daha yavaş.

Performansa göre sıralandı:

  1. isinstance
  2. anlık (+% 0,5)
  3. isAssignableFrom (+% 2,7)

20 ısınma yinelemesi ile JAVA 8 Windows x64 üzerinde 2000 yinelemesinin bir karşılaştırmasına dayanmaktadır.

Teoride

Yumuşak bir bayt kodu görüntüleyici kullanarak her bir operatörü bayt koduna çevirebiliriz.

Bağlamında:

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

Her bir operatör tarafından kaç bayt kod talimatı kullanıldığını ölçerek , instanceof ve isInstance öğelerinin isAssignableFrom'dan daha hızlı olmasını bekleyebiliriz . Ancak, gerçek performans bayt koduyla değil, makine koduyla (platforma bağımlıdır) belirlenir. Her operatör için bir mikro ölçüt yapalım.

Kıyaslamak

Kredi: @ aleksandr-dubinsky tarafından önerildiği gibi ve @yura'ya temel kodu sağladığı için teşekkürler, burada bir JMH karşılaştırması bulunmaktadır (bu ayarlama kılavuzuna bakın ):

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

Aşağıdaki sonuçları verdi (puan bir zaman birimindeki bir dizi işlemdir , bu nedenle puan ne kadar yüksek olursa o kadar iyidir):

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

Uyarı

  • Benchmark JVM ve platforma bağlıdır. Her işlem arasında önemli bir fark olmadığından, farklı bir JAVA sürümü ve / veya Solaris, Mac veya Linux gibi platformlarda farklı bir sonuç (ve belki de farklı bir düzen!) Elde etmek mümkün olabilir.
  • karşılaştırmalı değerlendirme, "B A'yı doğrudan uzattığında" "B'nin A örneğidir" performansını karşılaştırır. Sınıf hiyerarşisi daha derin ve daha karmaşıksa (B, A'yı genişleten Z'yi genişleten Y'yi genişleten X gibi), sonuçlar farklı olabilir.
  • genellikle ilk olarak operatörlerden birini seçerek (en uygun olan) kodu yazmanız ve ardından performans darboğazının olup olmadığını kontrol etmek için kodunuzu biçimlendirmeniz önerilir. Belki bu operatör kodunuz bağlamında ihmal edilebilir, ya da belki ...
  • önceki nokta instanceofile ilgili olarak, kod bağlamında isInstanceörneğin daha kolay optimize edilebilir ...

Size bir örnek vermek için aşağıdaki döngüyü uygulayın:

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;

JIT sayesinde kod bir noktada optimize edilmiştir ve şunu elde ederiz:

  • instanceof: 6ms
  • Enstance: 12ms
  • Gönderen: 15ms

Not

Başlangıçta bu yazı, Ham JAVA'da bir for döngüsü kullanarak kendi karşılaştırmasını yapıyor , bu da Just In Time gibi bazı optimizasyon döngüyü ortadan kaldırabileceğinden güvenilir sonuçlar vermedi. Bu yüzden çoğunlukla JIT derleyicisinin döngüyü optimize etmek için ne kadar sürdüğünü ölçüyordu: daha fazla ayrıntı için yineleme sayısından bağımsız olarak Performans testine bakın

İlgili sorular


6
Evet, instanceofesas olarak aynı mantığı kullanan bir bayt kodudur checkcast(dökümün arkasındaki bayt kodu). JITC optimizasyon derecesi ne olursa olsun, diğer seçeneklerden daha hızlı olacaktır.
Hot Licks

1
isAssignableFrom()Dinamik olduğu gibi mantıklı .
Matthieu

Evet, JMH sonuçları tamamen farklıdır (herkes için aynı hızda).
Yura

Merhaba, güzel bir kıyaslama, isAssignableFrom'un binlerce kez çağrıldığı bir duruma girdi, instanceof'a geçmek gerçekten fark yarattı. Bu cevap bir yerde bir blog yazısı değerinde olurdu ...;)
Martin

33

A daha doğrudan eşdeğer a instanceof BDİR

B.class.isInstance(a)

Bu da çalışır (false adeğerini döndürür) null.


Güzel, ama bu soruya cevap vermiyor ve bir yorum olmalıydı.
Madbreaks

23

Yukarıda belirtilen temel farklılıkların yanı sıra, instanceof operatörü ve Sınıfta isAssignableFrom yöntemi arasında temel bir küçük fark vardır.

Oku instanceof“Bu (sol kısmı) bu örneğini veya bu alt sınıflarının (sağ kısım)” olarak ve okumak x.getClass().isAssignableFrom(Y.class)“Can I yazma olarak X x = new Y()”. Başka bir deyişle, instanceof operatörü, sol nesnenin aynı veya sağ sınıfın alt sınıfında olup isAssignableFromolmadığını denetlerken, yöntem sınıfının (from) nesnesini yöntemin çağrıldığı sınıfın referansına atayabilip atamadığımızı denetler.
Her ikisinin de gerçek örneği referans türü olarak görmediğini unutmayın.

C'nin B ve B'nin A'yı genişlettiği 3 A, B ve C sınıfı örneğini ele alalım.

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 Aeşdeğerdir A.class.isAssignableFrom(b.getClass())(OP'nin fark ettiği gibi). Örneğin doğru ama alakasız.
Karu

Yana new Y()eğer yasal olmayabilir Ysoyut veya kamu varsayılan yapıcı olmayan, sen söyleyebiliriz X x = (Y)nullancak ve ancak yasal x.getClass().isAssignableFrom(Y.class)doğrudur.
Earth Engine

2
Neden bu örnekte 'b.getClass (). İsAssignableFrom (A.class)'? Sanırım örnek ters A.class.isAssignableFrom (b.getClass ()) olmalıdır.
loshad vtapkah

14

Başka bir fark daha var:

X'in değeri falsene olursa olsun , X'in sıfır örneği

null.getClass (). isAssignableFrom (X) bir NullPointerException kurar


4
-1, yanlış: null instanceof X(burada X, derleme zamanında bilinen bir sınıftır) her zaman geri döner false.
Caspar

4
@Caspar, haklıyken temel fikir iyi bir nokta oldu. Gönderiyi doğru olacak şekilde düzenledim.
erickson

1
Bu yardımcı olur, kenar kasa her zaman önemlidir :).
trilyonlar

İlk satıra eşdeğer olmak için ikinci satır olmamalı X.class.isAssignableFrom(null.getClass())mı? Ancak evet, getClass()null referansı çağırmak NPE ile sonuçlanacaktır.
William Price

Bu yanıt noktayı kaçırıyor - boş bir işlem ilgili değildir, çünkü hata işlemin dışında gerçekleşir (böyle bir referans kullanmadan önce her zaman null olup olmadığını kontrol etmeniz gerekir). Genel getClass()olarak isAssignableFromilk etapta kullanılmamalıdır - operasyon hiçbir nesnenin olmadığı durum içindir. Eğer nesne referansı varsa a, kullanımı a instanceof SomeClass(eğer varsa yapmak türünü bilmek SomeClass) ya da someObject.getClass().isInstance(a)(eğer varsa yok türünü bilmek someObject).
AndrewF

12

Başka bir fark daha var. Test edilecek tür (Sınıf) dinamikse, örneğin bir yöntem parametresi olarak iletildiyse, instanceof bunu sizin için kesmez.

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

ama şunları yapabilirsiniz:

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

Hata! Bu cevabın zaten kapsantığını görüyorum. Belki bu örnek birisi için yararlıdır.


3
aslında hiçbir cevap gerçekten doğru değilSınıflar w / çalışma, Class.isInstance 'instanceof'
bestsss

@ Bestsss'nin doğru yorumunu somut koda koymak için: Bir nesneniz ( this) olduğundan, clazz.isInstance(this)örneğinizde daha iyi olur.
AndrewF

7

Bu konu bana nasıl instanceoffarklı isAssignableFromolduğuna dair bir fikir verdi , bu yüzden kendime ait bir şey paylaşacağımı düşündüm.

isAssignableFromBir karşılaştırma yapmak için her iki sınıfın örnekleri olduğunda, bir sınıfın bir referans başka bir örneği alabilir, kendi kendine sormak için tek (muhtemelen tek, ama muhtemelen en kolay) yolu olarak bulduk .

Bu nedenle, instanceofsınıflardan birinden bir örnek oluşturmayı düşünmediğim sürece, sahip olduğum tek şey sınıflarken iyi bir fikir olarak karşılaştırılabilirliği karşılaştırmak için operatörü kullanmadım ; Bunun özensiz olacağını düşündüm.


5

instanceof, ilkel tiplerle veya jenerik tiplerle de kullanılamaz. Aşağıdaki kodda olduğu gibi:

//Define Class< T > type ... 

Object e = new Object();

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

Hata: T tür parametresine karşı instanceof kontrolü gerçekleştirilemiyor. Çalışma sırasında daha fazla genel tür bilgisi silineceğinden, bunun silme Nesnesini kullanın.

Çalışma zamanı başvurusunu kaldırarak tür silme nedeniyle derlenmez. Ancak, aşağıdaki kod derlenecektir:

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

4

Aşağıdaki durumu göz önünde bulundurun. Diyelim ki A türünün obj türünün süper bir sınıfı olup olmadığını kontrol etmek istiyorsunuz, ya

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

VEYA

... obj A örneği ...

Ancak isAssignableFrom çözümü, obj türünün burada görünür olmasını gerektirir. Durum böyle değilse (örneğin, obj türü özel bir iç sınıftan olabilir), bu seçenek devre dışıdır. Ancak, çözüm örneği her zaman işe yarayacaktır.


2
Bu doğru değil. Lütfen "Adam Rosenfield" yorumuna bakın stackoverflow.com/questions/496928/…
Maxim Veksler

1
"Bu doğru değil" diyebilir misiniz? Bahsettiğiniz yorumun, yazımdaki senaryo ile ilgisi yok. Açıklamamı destekleyen bazı test kodlarım var.
cebir

Herhangi bir türdeki nesne örneğine ( objbu örnekte) null olmayan bir başvurunuz varsa, uygulayıcı sınıfın yansıma meta verilerini edinmek için genel yöntemi çağırabilirsiniz . Sınıf uygulayan sınıf türü derleme zamanında bu konumda yasal olarak görünmese bile bu doğrudur. Zamanında 's Tamam tutmak için, çünkü referansı, sonuçta bazı kod yolu yaptılar sınıfına yasal erişime sahip birini yaratan ve verdi (sızdırılmış?) Onu sana. getClass()obj
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))

Yukarıdaki sözde kod, A tipi / A sınıfı referanslar B tipi / sınıf referanslarından atanabilirse, bir özyinelemeli tanımdır. Bazıları için yararlı olabilir, bazıları için kafa karıştırıcı olabilir. Birisinin faydalı bulması ihtimaline karşı ekliyorum. Bu sadece anlayışımı yakalama çabasıdır, resmi tanım değildir. Belirli bir Java VM uygulamasında kullanılır ve birçok örnek program için çalışır, bu yüzden isAssignableFrom'un tüm yönlerini yakaladığından emin olamamam da tamamen kapalı değildir.


2
Lütfen bu kodun ne yaptığını ve soruyu nasıl yanıtladığını açıklayın.
Monica'nın Davası

0

"2" performansı açısından konuşmak (JMH ile):

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

O verir:

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

Böylece şu sonuca varabiliriz: instanceof isInstance () ve isAssignableFrom () kadar hızlı değil (+% 0,9 yürütme süresi). Yani ne seçerseniz seçin gerçek bir fark yok


0

Eylemde göstermek için bazı örneklere ne dersiniz ...

@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

ekibimizde yaptığımız bazı testler bunun A.class.isAssignableFrom(B.getClass())daha hızlı çalıştığını gösteriyor B instanceof A. çok sayıda öğe üzerinde kontrol etmeniz gerekirse bu çok yararlı olabilir.


13
Hm, bir darboğazınız instanceofvarsa, ciddi tasarım sorunlarınız olduğuna inanıyorum ...
sleske

1
JBE'nin cevabı, hipotezinizden farklı bir hipotez sunar.
Alastor Moody
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.