Какое из следующего лучше?
a instanceof B
или
B.class.isAssignableFrom(a.getClass())
Единственное отличие, о котором я знаю, когда "a" равно null, первое возвращает false, а второе - исключение. Кроме этого, они всегда дают тот же результат?
Какое из следующего лучше?
a instanceof B
или
B.class.isAssignableFrom(a.getClass())
Единственное отличие, о котором я знаю, когда "a" равно null, первое возвращает false, а второе - исключение. Кроме этого, они всегда дают тот же результат?
При использовании 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 или экземпляр, которые имеют схожую производительность. isAssignableFrom немного медленнее.
Отсортировано по производительности:
На основе теста 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;
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);
Измеряя, сколько команд байт-кода используется каждым оператором, можно ожидать, что 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
Внимание
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 код оптимизируется в какой-то момент, и мы получаем:
Примечание
Первоначально этот пост выполнял свой собственный бенчмарк, используя цикл for в сыром JAVA, что давало ненадежные результаты, поскольку некоторая оптимизация, например Just In Time, может исключить цикл. Таким образом, в основном было измерено, как долго выполнялся компилятор JIT для оптимизации цикла: см. Тест производительности, не зависящий от количества итераций, для более подробной информации
Связанные вопросы
Более прямым эквивалентом a instanceof B
является
B.class.isInstance(a)
Это работает (возвращает false), когда a
равно null
.
Помимо основных различий, упомянутых выше, существует ключевая тонкая разница между экземпляром оператора и isAssignableFrom методом класса.
Прочитайте instanceof
как "это (левая часть) экземпляр этого или любого подкласса этого (правая часть)" и читайте x.getClass().isAssignableFrom(Y.class)
как "Могу ли я написать X x = new Y()
". Другими словами, оператор instanceof проверяет, является ли левый объект тем же или подклассом правого класса, тогда как isAssignableFrom
проверяет, можно ли присвоить объект класса параметра (from) ссылке на класс, на который вызывается метод. < ш > Обратите внимание, что оба они рассматривают фактический экземпляр, а не ссылочный тип.
Рассмотрим пример из 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.
Существует и другая разница:
null instanceof 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 instanceof A ...
Но решение isAssignableFrom требует, чтобы вид obj был видимым здесь. Если это не так (например, тип obj может быть частного внутреннего класса), этот параметр отсутствует. Тем не менее, решение instanceof всегда будет работать.
instanceof не может использоваться с примитивными типами или родовыми типами. Как и в следующем коде:
//Define Class< T > type ...
Object e = new Object();
if(e instanceof T) {
// Do something.
}
Ошибка: Невозможно выполнить проверку экземпляра с параметром типа Т. Используйте вместо этого объект стирания, так как дополнительная информация типа будет удалена во время выполнения.
Не компилируется из-за стирания типа, удаляющего ссылку на рабочую среду. Однако приведенный ниже код будет скомпилирован:
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
Итак, мы можем заключить: экземпляр так быстро, как isInstance() и isAssignableFrom() не за горами (+ 0,9% времени выполнения). Так что нет никакой реальной разницы, что бы вы ни выбрали
некоторые тесты, которые мы провели в нашей команде, показывают, что A.class.isAssignableFrom(B.getClass())
работает быстрее, чем B instanceof A
. это может быть очень полезно, если вам нужно проверить это на большом количестве элементов.