Poison

Class.isAssignableFrom

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* Determines if the class or interface represented by this
* {@code Class} object is either the same as, or is a superclass or
* superinterface of, the class or interface represented by the specified
* {@code Class} parameter. It returns {@code true} if so;
* otherwise it returns {@code false}. If this {@code Class}
* object represents a primitive type, this method returns
* {@code true} if the specified {@code Class} parameter is
* exactly this {@code Class} object; otherwise it returns
* {@code false}.
*
* <p> Specifically, this method tests whether the type represented by the
* specified {@code Class} parameter can be converted to the type
* represented by this {@code Class} object via an identity conversion
* or via a widening reference conversion. See <em>The Java Language
* Specification</em>, sections 5.1.1 and 5.1.4 , for details.
*
* @param cls the {@code Class} object to be checked
* @return the {@code boolean} value indicating whether objects of the
* type {@code cls} can be assigned to objects of this class
* @exception NullPointerException if the specified Class parameter is
* null.
* @since JDK1.1
*/
public native boolean isAssignableFrom(Class<?> cls);

native 方法在我开发 Java Agent 的过程中进行了调试,发现对于满足实现关系的类,如果不是由相同的类加载器加载,则会返回 false

比如以下代码:

1
2
3
4
package me.tianshuang;

public class Parent {
}
1
2
3
4
package me.tianshuang;

public class Sub extends Parent {
}
1
2
3
4
5
6
7
8
9
10
11
12
package me.tianshuang;

import java.net.URL;
import java.net.URLClassLoader;

public class CustomClassLoader extends URLClassLoader {

public CustomClassLoader(URL[] urls) {
super(urls, null);
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package me.tianshuang;

import java.net.MalformedURLException;
import java.net.URL;

public class Test {

public static void main(String[] args) throws ClassNotFoundException, MalformedURLException {
Parent parent = new Parent();
Sub sub = new Sub();
System.out.println(parent.getClass().isAssignableFrom(sub.getClass()));

String path = "file:///Users/tianshuang/IdeaProjects/test/target/test-classes/";
URL[] urls = {new URL(path)};
Class<?> parentClass = new CustomClassLoader(urls).loadClass("me.tianshuang.Parent");
Class<?> subClass = new CustomClassLoader(urls).loadClass("me.tianshuang.Sub");
System.out.println(parentClass.isAssignableFrom(subClass));
}

}

输出如下:

1
2
true
false

同理可知,如果我们对不是由同一类加载器加载的 Class 进行 java.lang.Class#asSubclass 方法调用,将会触发 ClassCastException

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* Casts this {@code Class} object to represent a subclass of the class
* represented by the specified class object. Checks that the cast
* is valid, and throws a {@code ClassCastException} if it is not. If
* this method succeeds, it always returns a reference to this class object.
*
* <p>This method is useful when a client needs to "narrow" the type of
* a {@code Class} object to pass it to an API that restricts the
* {@code Class} objects that it is willing to accept. A cast would
* generate a compile-time warning, as the correctness of the cast
* could not be checked at runtime (because generic types are implemented
* by erasure).
*
* @param <U> the type to cast this class object to
* @param clazz the class of the type to cast this class object to
* @return this {@code Class} object, cast to represent a subclass of
* the specified class object.
* @throws ClassCastException if this {@code Class} object does not
* represent a subclass of the specified class (here "subclass" includes
* the class itself).
* @since 1.5
*/
@SuppressWarnings("unchecked")
public <U> Class<? extends U> asSubclass(Class<U> clazz) {
if (clazz.isAssignableFrom(this))
return (Class<? extends U>) this;
else
throw new ClassCastException(this.toString());
}

执行以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package me.tianshuang;

import java.net.MalformedURLException;
import java.net.URL;

public class Test {

public static void main(String[] args) throws ClassNotFoundException, MalformedURLException {
Parent parent = new Parent();
Sub sub = new Sub();
System.out.println(parent.getClass().isAssignableFrom(sub.getClass()));

String path = "file:///Users/tianshuang/IdeaProjects/test/target/test-classes/";
URL[] urls = {new URL(path)};
Class<?> parentClass = new CustomClassLoader(urls).loadClass("me.tianshuang.Parent");
Class<?> subClass = new CustomClassLoader(urls).loadClass("me.tianshuang.Sub");
System.out.println(subClass.asSubclass(parentClass));
}

}

输出如下:

1
2
3
4
true
Exception in thread "main" java.lang.ClassCastException: class me.tianshuang.Sub
at java.lang.Class.asSubclass(Class.java:3404)
at me.tianshuang.Test.main(Test.java:17)

同理,受类加载器影响的还有 instanceof 关键字,比如如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package me.tianshuang;

import java.net.MalformedURLException;
import java.net.URL;

public class Test {

public static void main(String[] args) throws ClassNotFoundException, MalformedURLException, InstantiationException, IllegalAccessException {
Parent parent = new Parent();
System.out.println(parent instanceof Parent);

String path = "file:///Users/tianshuang/IdeaProjects/test/target/test-classes/";
URL[] urls = {new URL(path)};
Class<?> parentClass = new CustomClassLoader(urls).loadClass("me.tianshuang.Parent");

System.out.println(parentClass.newInstance() instanceof Parent);
}

}

输出如下:

1
2
true
false

最后,不得不提的是我认为 java.lang.Class#isAssignableFromjava.lang.Class#asSubclass 的方法名具有误导性,不太分得清操作的为参数还是调用方,StackOverflow 上也有用户提出,如果不详细查看方法注释的话确实不太分得清楚。

Reference

Generics and Class.asSubclass - Stack Overflow