繁体   English   中英

instanceof 和 Class.isAssignableFrom(...) 有什么区别?

[英]What is the difference between instanceof and Class.isAssignableFrom(...)?

以下哪个更好?

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; 博士

使用具有相似性能的isInstanceinstanceof isAssignableFrom稍慢。

按性能排序:

  1. isInstance
  2. 实例(+ 0.5%)
  3. isAssignableFrom (+ 2.7%)

基于在 JAVA 8 Windows x64 上进行 2000 次迭代的基准测试,其中包含 20 次预热迭代。

理论上

使用像字节码查看器这样的软件,我们可以将每个运算符转换为字节码。

在以下情况下:

package foo;

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

  ...

}

爪哇:

b instanceof A;

字节码:

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

爪哇:

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

爪哇:

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

测量每个运算符使用多少字节码指令,我们可以预期instanceofisInstanceisAssignableFrom更快。 但是,实际性能不是由字节码决定的,而是由机器代码(取决于平台)决定的。 让我们为每个运营商做一个微基准测试。

基准

信用:根据@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),结果可能会有所不同。
  • 通常建议先选择一个运算符(最方便)编写代码,然后分析您的代码以检查是否存在性能瓶颈。 也许这个运算符在你的代码上下文中可以忽略不计,或者......
  • 关于上一点, instanceof ,代码上下文中的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,代码在某个时候得到了优化,我们得到:

  • 实例:6ms
  • isInstance:12ms
  • isAssignableFrom : 15ms

注意

最初,这篇文章是在原始 JAVA 中使用for循环进行自己的基准测试,结果不可靠,因为像 Just In Time 这样的优化可以消除循环。 所以它主要是测量 JIT 编译器优化循环需要多长时间:有关更多详细信息,请参阅与迭代次数无关的性能测试

相关问题

一个更直接的等价于a instanceof B

B.class.isInstance(a)

a也为null时,这也有效(返回 false)。

除了上面提到的基本差异之外,实例化运算符和类中的 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.

还有一个区别:

无论 X 是什么,null instanceof X 都是false

null.getClass().isAssignableFrom(X) 将抛出 NullPointerException

还有另一个区别。 如果要测试的类型 (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
}

哎呀,我看到这个答案已经涵盖了。 也许这个例子对某人有帮助。

这个线程让我深入了解instanceofisAssignableFrom不同之处,所以我想我会分享一些我自己的东西。

我发现使用isAssignableFrom是唯一的(可能不是唯一的,但可能是最简单的)方法来询问自己一个类的引用是否可以引用另一个类的实例,当一个类的实例都没有进行比较时。

因此,当我只有类时,我没有发现使用instanceof运算符来比较可分配性是一个好主意,除非我考虑从其中一个类创建一个实例; 我以为这会很草率。

instanceof 也不能与原始类型或泛型类型一起使用。 如以下代码所示:

//Define Class< T > type ... 

Object e = new Object();

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

错误是:无法对类型参数 T 执行 instanceof 检查。改用它的擦除对象,因为进一步的泛型类型信息将在运行时被擦除。

由于类型擦除删除了运行时引用,因此无法编译。 但是,下面的代码将编译:

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

考虑以下情况。 假设你要检查类型A是否是obj类型的超类,你可以去

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

... obj instanceof A ...

但是 isAssignableFrom 解决方案要求 obj 的类型在这里可见。 如果不是这种情况(例如,obj 的类型可能是私有内部类),则此选项无效。 但是,instanceof 解决方案始终有效。

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

这样我们就可以得出结论: instanceofisInstance()isAssignableFrom()一样快(+0.9% 执行时间)。 所以无论你选择什么都没有真正的区别

举一些例子来展示它的实际效果吧...

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

这一切都取决于您在代码中可用的内容。 如果您使用的是实际的 object,我不建议您使用isAssignableFrom — 有更好的选择。 以下是根据您现有的推荐排名列表:

  • 如果您有 object a并且您在编译时知道类型B

    a instanceof B

  • 如果你有一个 object a并且你不知道类型B ,但是你有一个 object b

    b.getClass().isInstance(a)

  • If you have an object a and you don't have a type at compile time OR an instantiated object, but you do have a class object Class<?> someBClass :

    someBClass.isInstance(a)

  • 如果您没有实例化对象,但您有两个Class<?>对象:

    someBClass.isAssignableFrom(someAClass)

假设您从列表顶部开始并向下工作,其中每一个都是实现目标的最简单方法,并且根据我自己的研究,我相信您也将获得最佳性能。

我们在团队中进行的一些测试表明A.class.isAssignableFrom(B.getClass())B instanceof A工作得更快。 如果您需要对大量元素进行检查,这将非常有用。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM