В чем разница между экземпляром и классом.isAssignableFrom(...)?

что из следующего лучше?

a instanceof B

или

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

единственное различие, о котором я знаю, когда " a " равно null, первый возвращает false, а второй создает исключение. Кроме этого, они всегда дают один и тот же результат?

13 ответов


при использовании instanceof, вы должны знать класс B во время компиляции. При использовании isAssignableFrom() он может быть динамическим и меняться во время выполнения.


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


говорим с точки зрения производительности :

TL; DR

использовать isInstance или instanceof, которые имеют аналогичные характеристики. isAssignableFrom немного медленнее.

сортировка по производительности:

  1. isInstance
  2. instanceof (+ 0.5%)
  3. isAssignableFrom (+ 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 benchmark (см. Это руководство по настройке):

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 расширяется Непосредственным образом. Если иерархия классов глубже и сложнее (например, B расширяет X, который расширяет Y, который расширяет Z, который расширяет 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
  • isAssignableFrom: 15ms

Примечание

Первоначально этот пост делал свой собственный бенчмарк с использованием на цикл в raw JAVA, который дал ненадежные результаты, поскольку некоторая оптимизация, как раз вовремя, может устранить цикл. Таким образом, он в основном измерял, сколько времени занимает компилятор JIT для оптимизации цикла: см. тест производительности не зависит от количества итераций дополнительные детали

вопросы


более прямой эквивалент a instanceof B is

B.class.isInstance(a)

это работает (возвращает false), когда a и null тоже.


помимо основных различий, упомянутых выше, существует основная тонкая разница между оператором instanceof и методом isAssignableFrom в классе.

читать 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.

есть и другое отличие:

нулевой экземпляр X равен false независимо от того, что X

null.getClass().isAssignableFrom (X) выдаст исключение NullPointerException


есть еще одно отличие. Если тип (класс) для тестирования является динамическим, например, передается как параметр метода, то 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
}

Ой, я вижу, что этот ответ уже покрыт. Возможно, этот пример кому-то полезен.


эта тема дала мне некоторое представление о том, как instanceof отличается от isAssignableFrom, поэтому я решил поделиться чем-то своим.

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

следовательно, я не нашел с помощью instanceof оператор для сравнения уступки, чтобы быть хорошим идея, когда все, что у меня было, были классы, если я не собирался создавать экземпляр из одного из классов; я думал, что это будет небрежно.


рассмотрим следующую ситуацию. Предположим, вы хотите проверить, является ли тип A супер-классом типа obj, вы можете пойти либо

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

или

... в obj экземпляром ...

но решение isAssignableFrom требует, чтобы тип obj был виден здесь. Если это не так (например, тип obj может иметь частный внутренний класс), эта опция отключена. Однако решение instanceof всегда будет работать.


instanceof нельзя использовать с примитивными типами или универсальными типами. Как в следующем коде:

//Define Class< T > type ... 

Object e = new Object();

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

ошибка: невозможно выполнить проверку instanceof против параметра типа T. используйте вместо этого объект erasure, так как дополнительная информация о типе будет удалена во время выполнения.

не компилируется из-за стирания типа, удаляющего ссылку времени выполнения. Однако, приведенный ниже код будет компилироваться:

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

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 " (с 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% времени executon). Так что никакой реальной разницы, что бы вы ни выбрали


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