开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第13天,点击查看活动详情
1.1、获取父类的Class
有了Class实例,我们还可以获取它的父类的Class:
public class Demo1 {
public static void main(String[] args) throws Exception{
Class<Integer> aClass = Integer.class;
Class<? super Integer> superclass = aClass.getSuperclass();
System.out.println(superclass);
Class<? super Integer> superclass1 = superclass.getSuperclass();
System.out.println(superclass1);
System.out.println(superclass1.getSuperclass());
}
}
运行上述代码,可以看到,Integer的父类类型是Number,Number的父类是Object,Object的父类是null。除Object外,其他任何非interface的Class都必定存在一个父类类型。
1.2、获取interface
由于一个类可能实现一个或多个接口,通过Class我们就可以查询到实现的接口类型。例如,查询Integer实现的接口:
public class Demo1 {
public static void main(String[] args) throws Exception{
Class<Integer> aClass = Integer.class;
Class<?>[] interfaces = aClass.getInterfaces();
for (Class i : interfaces) {
System.out.println(i);
}
}
}
运行上述代码可知,Integer实现的接口有:
- java.lang.Comparable
- java.lang.constant.Constable
- java.lang.constant.ConstantDesc
要特别注意:getInterfaces()只返回当前类直接实现的接口类型,并不包括其父类实现的接口类型:
public class Demo1 {
public static void main(String[] args) throws Exception{
Class<? super Integer> aClass = Integer.class.getSuperclass();
Class<?>[] interfaces = aClass.getInterfaces();
for (Class i : interfaces) {
System.out.println(i);
}
}
}
Integer的父类是Number,Number实现的接口是java.io.Serializable。
此外,对所有interface的Class调用getSuperclass()返回的是null,获取接口的父接口要用getInterfaces():
System.out.println(java.io.DataInputStream.class.getSuperclass()); // java.io.FilterInputStream,因为DataInputStream继承自FilterInputStream
System.out.println(java.io.Closeable.class.getSuperclass()); // null,对接口调用getSuperclass()总是返回null,获取接口的父接口要用getInterfaces()
如果一个类没有实现任何interface,那么getInterfaces()返回空数组。
1.3、继承关系
当我们判断一个实例是否是某个类型时,正常情况下,使用instanceof操作符:
Object n = Integer.valueOf(123);
boolean isDouble = n instanceof Double; // false
boolean isInteger = n instanceof Integer; // true
boolean isNumber = n instanceof Number; // true
boolean isSerializable = n instanceof java.io.Serializable; // true
如果是两个Class实例,要判断一个向上转型是否成立,可以调用isAssignableFrom():
// Integer i = ?
Integer.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Integer
// Number n = ?
Number.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Number
// Object o = ?
Object.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Object
// Integer i = ?
Integer.class.isAssignableFrom(Number.class); // false,因为Number不能赋值给Integer
1.4、小结
通过Class对象可以获取继承关系:
Class getSuperclass():获取父类类型;Class[] getInterfaces():获取当前类实现的所有接口。
通过Class对象的isAssignableFrom()方法可以判断一个向上转型是否可以实现。
2、动态代理
我们来比较Java的class和interface的区别:
- 可以实例化
class(非abstract); - 不能实例化
interface。
所有interface类型的变量总是通过向上转型并指向某个实例的:
CharSequence cs = new StringBuilder();
有没有可能不编写实现类,直接在运行期创建某个interface的实例呢?
这是可能的,因为Java标准库提供了一种动态代理(Dynamic Proxy)的机制:可以在运行期动态创建某个interface的实例。
什么叫运行期动态创建?听起来好像很复杂。所谓动态代理,是和静态相对应的。我们来看静态代码怎么写:
定义接口:
public interface Hello {
void morning(String name);
}
编写实现类:
public class HelloWorld implements Hello {
public void morning(String name) {
System.out.println("Good morning, " + name);
}
}
创建实例,转型为接口并调用:
Hello hello = new HelloWorld();
hello.morning("Bob");
这种方式就是我们通常编写代码的方式。
还有一种方式是动态代码,我们仍然先定义了接口Hello,但是我们并不去编写实现类,而是直接通过JDK提供的一个Proxy.newProxyInstance()创建了一个Hello接口对象。这种没有实现类但是在运行期动态创建了一个接口对象的方式,我们称为动态代码。JDK提供的动态创建接口对象的方式,就叫动态代理。
一个最简单的动态代理实现如下:
public class Demo1 {
public static void main(String[] args) throws Exception{
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method);
if (method.getName().equals("morning")) {
System.out.println("Good morning, " + args[0]);
}
return null;
}
};
Hello hello = (Hello) Proxy.newProxyInstance(
Hello.class.getClassLoader(), //传入ClassLoader
new Class[] {Hello.class}, //传入要实现的接口
handler); //传入处理调用方法的InvocationHandler
hello.morning("Bob");
}
}
interface Hello {
void morning(String name);
}
在运行期动态创建一个interface实例的方法如下:
-
定义一个
InvocationHandler实例,它负责实现接口的方法调用; -
通过
Proxy.newProxyInstance()创建interface实例,它需要3个参数:- 使用的
ClassLoader,通常就是接口类的ClassLoader; - 需要实现的接口数组,至少需要传入一个接口进去;
- 用来处理接口方法调用的
InvocationHandler实例。
- 使用的
-
将返回的
Object强制转型为接口。动态代理实际上是JDK在运行期动态创建class字节码并加载的过程,它并没有什么黑魔法,把上面的动态代理改写为静态实现类大概长这样:
public class HelloDynamicProxy implements Hello {
InvocationHandler handler;
public HelloDynamicProxy(InvocationHandler handler) {
this.handler = handler;
}
public void morning(String name) {
handler.invoke(
this,
Hello.class.getMethod("morning"),
new Object[] { name });
}
}
其实就是JDK帮我们自动编写了一个上述类(不需要源码,可以直接生成字节码),并不存在可以直接实例化接口的黑魔法。
小结:
Java标准库提供了动态代理功能,允许在运行期动态创建一个接口的实例;
动态代理是通过Proxy创建代理对象,然后将接口方法“代理”给InvocationHandler完成的。