Чим відрізняється instanceof від Class.isAssignableFrom (…)?


457

Що з наступного краще?

a instanceof B

або

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

Єдина відмінність, про яку я знаю, полягає в тому, що коли 'a' є недійсним, перший повертає помилкове значення, а другий видає виняток. Окрім цього, чи завжди вони дають однаковий результат?


17
Для записів isInstance () - це найзручніший метод перевірити, чи можна об'єкт переводити на тип класу (детальніше див.: Tshikatshikaaa.blogspot.nl/2012/07/… )
Jérôme Verstrynge

Відповіді:


497

Під час використання instanceofпотрібно знати клас класу Bпід час компіляції. При використанні isAssignableFrom()він може бути динамічним і змінюватися під час виконання.


12
я не розумію, будь ласка, детальніше поясніть, чому ми не можемо писати a instanceof Bref.getClass(). як це може бути прийнятою відповіддю з таким невеликим поясненням (або його відсутністю)?
Еліран Малька

65
Синтаксис - a instanceof Brefні a instanceof Bref.class. Другий аргумент оператору instanceof - це ім'я класу, а не вираз, що вирішує екземпляр об'єкта класу.
Брендон Блум

2
так "динамічно" само собою зрозуміло :) Окрім продуктивності, це справжня різниця.
peterk

2
@EliranMalka, можливо, у вас може бути клас, сформований під час виконання. Як об’єкти проксі.
Вагнер Цухія

Отже, в B.class.isAssignableFrom(a.getClass()), B відомо, і a instanceof Bкраще. Правильно?
Флоріан F

208

instanceofможе використовуватися лише з еталонними типами, а не з примітивними типами. isAssignableFrom()може використовуватися з будь-якими об’єктами класу:

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

Подивитися http://java.sun.com/javase/6/docs/api/java/lang/Class.html#isAssignableFrom(java.lang.Class) .


10
Я не бачу сенсу використовувати instanceof / isAssignableFrom з примітивними типами.
Джиммі Т.

116

Якщо говорити про продуктивність:

TL; DR

Використовуйте isInstance або instanceof, які мають аналогічні показники. Визначення від цього трохи повільніше.

Сортування за продуктивністю:

  1. isInstance
  2. instanceof (+ 0,5%)
  3. єдоступним від (+ 2,7%)

Заснований на еталоні 2000 ітерацій на JAVA 8 Windows x64, з 20 ітераціями розминки.

Теоретично

Використовуючи програмний перегляд байт-коду, ми можемо перевести кожного оператора в байт-код.

В контексті:

package foo;

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

  ...

}

JAVA:

b instanceof A;

Байт-код:

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

JAVA:

A.class.isInstance(b);

Байт-код:

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

Байт-код:

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

Виміряючи, скільки інструкцій байтового коду використовується кожним оператором, можна очікувати, що instanceof та isInstance будуть швидшими, ніж isAssignableFrom . Однак фактична продуктивність визначається НЕ байт-кодом, а машинним кодом (який залежить від платформи). Давайте зробимо мікро-орієнтир для кожного з операторів.

Орієнтир

Кредит: Як радив @ aleksandr-dubinsky, і завдяки @yura за надання базового коду, ось орієнтир JMH (див. Цей посібник з настройки ):

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

Дайте такі результати (оцінка - це кількість операцій у одиниці часу , тому чим більша оцінка, тим краще):

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

Увага

  • еталоном є JVM і залежать від платформи. Оскільки між кожною операцією немає суттєвих відмінностей, можливо, можна отримати інший результат (а може бути і інший порядок!) На іншій версії JAVA та / або платформах, таких як Solaris, Mac або Linux.
  • тест порівнює ефективність "є B екземпляр A", коли "B поширює A" безпосередньо. Якщо ієрархія класів глибша і складніша (як, наприклад, B подовжує X, яка Y, яка Z, яка A, A), результати можуть бути різними.
  • Зазвичай рекомендується написати код, вибираючи одного з операторів (найзручніший), а потім профілюючи свій код, щоб перевірити, чи є вузькі місця. Можливо, цей оператор є мізерним у контексті вашого коду, а може ...
  • стосовно попереднього пункту, instanceofв контексті вашого коду можна оптимізувати простіше, ніж isInstanceнаприклад ...

Щоб навести приклад, візьміть таку петлю:

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 код оптимізується в якийсь момент, і ми отримуємо:

  • instanceof: 6ms
  • isInstance: 12ms
  • Доступно від: 15 мс

Примітка

Спочатку ця публікація робила свій власний орієнтир, використовуючи цикл for raw в JAVA, який дав недостовірні результати, оскільки певна оптимізація, як Just In Time, може усунути цикл. Так що в основному вимірювання , скільки часу зробив взяття JIT компілятор для оптимізації циклу: см тест продуктивності залежить від числа ітерацій для отримання більш докладної інформації

Пов'язані питання


6
Так, instanceofбайт-код, який використовує по суті ту ж логіку, що і checkcast(байт-код за кастингом). Це по суті буде швидше, ніж інші варіанти, незалежно від ступеня оптимізації JITC.
Гарячі лизання

1
Що має сенс, як isAssignableFrom()і динамічний.
Матьє

так, результати JMH абсолютно різні (однакова швидкість для всіх).
Юра

Привіт, хороший орієнтир, щойно натрапив на ситуацію, коли тисячу разів називали "AssignableFrom", змінюючись на instanceof, справді змінився. Ця відповідь вартувала б десь в блозі ...;)
Мартін

33

Більш прямий еквівалент a instanceof Bє

B.class.isInstance(a)

Це працює (повертає брехня) , якщо aце nullзанадто.


Класно, але це не відповідає на запитання, і це мав бути коментарем.
Madbreaks

23

Крім базових відмінностей, згаданих вище, існує суттєва різниця між методом instance оператора та методом AssignableFrom у класі.

Читайте instanceofяк "це це (ліва частина) екземпляр цього або будь-якого підкласу цього (права частина)" та читайте x.getClass().isAssignableFrom(Y.class)як "Чи можу я написати X x = new Y()". Іншими словами, оператор instanceof перевіряє, чи є лівий об'єкт однаковим, або підклас правого класу, в той час як isAssignableFromперевіряє, чи можемо ми призначити об'єкт класу параметрів (від) до посилання класу, на який називається метод.
Зауважте, що обидва вони вважають фактичний екземпляр не еталонним типом.

Розглянемо приклад 3 класу A, B і C, де C поширюється на B, а на B розширюється 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еквівалентно A.class.isAssignableFrom(b.getClass())(як зауважив ОП). Ваш приклад правильний, але не має значення.
Кару

Оскільки це new Y()може бути не законно, якщо Yвін абстрактний або без публічного конструктора за замовчуванням, ви можете сказати, що X x = (Y)nullце законно, якщо і лише тоді, якщо x.getClass().isAssignableFrom(Y.class)це правда.
Земляний двигун

2
Чому "b.getClass (). IsAssignableFrom (A.class)" у цьому прикладі? Я припускаю, що прикладом має бути зворотний A.class.isAssignableFrom (b.getClass ()).
loshad vtapkah

14

Є ще одна відмінність:

null instanceof X не falseмає значення, що таке X

null.getClass (). isAssignableFrom (X) передасть NullPointerException


4
-1, неправильно: null instanceof X(де X - якийсь клас, відомий під час компіляції), завжди повернеться false.
Каспар

4
@Caspar, хоча ти маєш рацію, основна ідея була вдалою справою. Я відредагував пост, щоб він був правильним.
еріксон

1
це корисно, крайній край завжди важливий :).
трильйони

Щоб бути рівнозначним першому рядку, другий рядок повинен бути, X.class.isAssignableFrom(null.getClass())чи не так? Але так, виклик getClass()нульової посилання призведе до NPE.
Вільям Прайс

Ця відповідь не вистачає пункту - нульове відмінність не має значення, оскільки збій трапляється поза операцією (завжди потрібно перевірити наявність нуля, перш ніж використовувати подібне посилання). Взагалі, в першу чергу getClass()не слід використовувати isAssignableFrom- операція призначена для ситуації, коли немає об'єктів. Якщо у вас є посилання на об'єкт a, використовувати a instanceof SomeClass(якщо ви робите знати тип SomeClass) або someObject.getClass().isInstance(a)(якщо ви НЕ знаєте тип someObject).
AndrewF

12

Є ще одна відмінність. Якщо тип (Class) для тестування є динамічним, наприклад передається як параметр методу, то instanceof не буде вирізати його для вас.

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

але ви можете:

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

На жаль, я бачу, що ця відповідь вже висвітлена. Можливо, цей приклад комусь корисний.


3
насправді жодна відповідь не є дійсно правильною є AssignableЗ роботи з класами, Class.isInstance є аналогом 'instanceof'
bestsss

Щоб помістити правильний коментар @ bestsss в конкретний код: Тому що у вас є об'єкт ( this), clazz.isInstance(this)було б краще у вашому прикладі.
AndrewF

7

Цей потік дав мені деяке розуміння того, instanceofчим відрізняється від isAssignableFrom, тому я подумав, що поділюсь чимось своїм.

Я виявив, що використання isAssignableFromє єдиним (мабуть, не єдиним, але, можливо, найпростішим) способом запитати себе, чи може посилання одного класу мати випадки іншого, коли порівняння не має жодного класу для порівняння.

Отже, я не знайшов використання instanceofоператора для порівняння присвоюваності як хорошої ідеї, коли все, що у мене було, були заняттями, якщо тільки я не задумався створити екземпляр з одного з класів; Я думав, що це буде неохайно.


5

instanceof також не можна використовувати з примітивними типами або родовими типами. Як у наступному коді:

//Define Class< T > type ... 

Object e = new Object();

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

Помилка: Неможливо виконати примірник перевірки параметру типу T. Замість цього скористайтеся об'єктом стирання, оскільки подальша загальна інформація про тип буде стерта під час виконання.

Не компілюється через стирання типу, видаляючи посилання на час виконання. Однак, наступний код буде складати:

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

4

Розглянемо наступну ситуацію. Припустимо, ви хочете перевірити, чи тип A є суперкласом типу obj, ви можете перейти будь-який

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

АБО

... obj instanceof A ...

Але рішення isAssignableFrom вимагає, щоб тут видно тип obj. Якщо це не так (наприклад, тип obj може бути приватного внутрішнього класу), ця опція вимкнена. Однак рішення instanceof завжди працюватиме.


2
Це не правда. Дивіться коментар "Адама Розенфілда" stackoverflow.com/questions/496928/…
Максим Векслер

1
Не могли б ви розробити "Це неправда"? Коментар, на який ви посилаєтесь, не має нічого спільного зі сценарієм у моєму дописі. У мене є якийсь тестовий код, який резервує моє пояснення.
алгебра

Якщо у вас є ненульова посилання на екземпляр об'єкта ( objу цьому прикладі) будь-якого типу, ви можете викликати на ньому відкритий getClass()метод, щоб отримати метадані відображення для класу реалізації. Це справедливо навіть у тому випадку, якщо той тип класу, що реалізує, не був би юридично помітний у цьому місці під час компіляції. Це нормально , тому що під час виконання, для вас , щоб тримати objпосилання, деякий код шлях , який в кінцевому рахунку зробив мати законний доступ до класу створив один і дав (витік?) Його вам.
Вільям Прайс

0
isAssignableFrom(A, B) =

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

Наведений вище псевдокод - це визначення, якщо посилання типу / класу A призначаються за посиланнями типу / класу B. Це рекурсивне визначення. Для деяких це може бути корисно, для інших це може заплутати. Я додаю його на випадок, якщо хтось вважатиме це корисним. Це лише спроба захопити моє розуміння, це не офіційне визначення. Він використовується в певній реалізації Java VM і працює для багатьох прикладних програм, тому, хоча я не можу гарантувати, що він фіксує всі аспекти isAssignableFrom, він не повністю вимкнений.


2
Поясніть, будь ласка, що робить цей код і як він відповідає на питання.
Фонд позову Моніки

0

Розмова щодо продуктивності "2" (з 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();
}
}

Це дає:

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

Так що ми можемо зробити висновок: instanceof так швидко, як isInstance () і isAssignableFrom () недалеко (+ 0,9% часу виконання). Тож жодної реальної різниці, що б ви не вибрали


0

Як щодо деяких прикладів, щоб показати це в дії ...

@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

деякі тести, які ми зробили в нашій команді, показують, що A.class.isAssignableFrom(B.getClass())працює швидше, ніж B instanceof A. це може бути дуже корисно, якщо вам потрібно перевірити це на великій кількості елементів.


13
Гм, якщо у вас є вузьке місце instanceof, я вважаю, що у вас є серйозні проблеми із дизайном ...
sleske

1
Відповідь JBE представляє гіпотезу, яка відрізняється від вашої гіпотези.
Аластор Муді
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.