Class类
获取 Class 实例
//方法一:直接通过一个class的静态变量class获取
Class cls = String.class;
//方法二:如果我们有一个实例变量,可以通过该实例变量提供的getClass()方法获取
String s = "Hello";
Class cls = s.getClass();
//方法三:如果知道一个class的完整类名,可以通过静态方法Class.forName()获取
Class cls = Class.forName("java.lang.String");
Class 实例在JVM中是唯一的
Class cls1 = String.class;
String s = "Hello";
Class cls2 = s.getClass();
boolean sameClass = cls1 == cls2; // true
Class 实例比较和 instanceof 的差别
Integer n = new Integer(123);
boolean b1 = n instanceof Integer; // true,因为n是Integer类型
boolean b2 = n instanceof Number; // true,因为n是Number类型的子类
boolean b3 = n.getClass() == Integer.class; // true,因为n.getClass()返回Integer.class
boolean b4 = n.getClass() == Number.class; // false,因为Integer.class!=Number.class
Class 实例的基本信息
package com.zc;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Person implements ISay {
public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {
printClassInfo(Person.class);
}
static void printClassInfo(Class<?> cls) throws NoSuchFieldException, NoSuchMethodException {
String name = cls.getName(); //com.zc.Person
Package pkg = cls.getPackage(); //package com.zc
Boolean isInterface = cls.isInterface(); //false
Boolean isEnum = cls.isEnum(); //false
Boolean isArray = cls.isArray(); //false
Boolean isPrimitive = cls.isPrimitive(); //false
//获取父类
Class<?> superclass = cls.getSuperclass(); //class java.lang.Object
//获取实现的接口
Class<?>[] interfaces = cls.getInterfaces(); //数组个数为1
//获取字段
Field[] declaredFields = cls.getDeclaredFields(); //获取该类所有的声明字段(不包括父类)
Field[] fields = cls.getFields(); //获取所有public字段(包括父类)
Field noExistDeclaredField = cls.getDeclaredField("noExistField");
Field noExistField = cls.getField("noExistField");
//获取方法
Method[] declaredMethods = cls.getDeclaredMethods();
Method[] methods = cls.getMethods();
Method m1 = cls.getDeclaredMethod("printClassInfo", Class.class);
Method m2 = cls.getMethod("main");
}
}
interface ISay {
}
判断继承关系
判断一个实例是否是某个类型时,使用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)JVM在执行Java程序的时候,并不是一次性把所有用到的class全部加载到内存,而是第一次需要用到class时才加载。
Field类
获取Field对象
Class 类提供了以下几个方法来获取字段:
Field getField(name)
:根据字段名获取某个public的field(包括父类)Field getDeclaredField(name)
:根据字段名获取当前类的某个field(不包括父类)Field[] getFields()
:获取所有public的field(包括父类)Field[] getDeclaredFields()
:获取当前类的所有field(不包括父类)
查询字段信息
以String
类的value
字段为例,它的定义是:
public final class String {
private final char[] value;
}
我们用反射获取该字段的信息,代码如下:
Field f = String.class.getDeclaredField("value");
f.getName(); // "value"
f.getType(); // class [C,表示char[]类型
int m = f.getModifiers();
Modifier.isFinal(m); // true
Modifier.isPublic(m); // false
Modifier.isProtected(m); // false
Modifier.isPrivate(m); // true
Modifier.isStatic(m); // false
获取字段值
class Person {
private String name;
public Person(String name) {
this.name = name;
}
}
Object p = new Person("Tom");
Class<?> c = p.getClass();
Field f = c.getDeclaredField("name");
f.setAccessible(true);
Object value = f.get(p);
System.out.println(value);
设置字段值
设置字段值是通过Field.set(Object, Object)
实现的,其中第一个Object
参数是指定的实例,第二个Object
参数是待修改的值。
Method类
获取Method对象
Class 类提供了以下几个方法来获取 Method:
Method getMethod(name, Class...)
:获取某个 public 的 Method(包括父类)Method getDeclaredMethod(name, Class...)
:获取当前类的某个 Method(不包括父类)Method[] getMethods()
:获取所有 public 的 Method(包括父类)Method[] getDeclaredMethods()
:获取当前类的所有 Method(不包括父类)
查询方法信息
一个Method
对象包含一个方法的所有信息:
getName()
:返回方法名称,例如:"getScore"
;getReturnType()
:返回方法返回值类型,也是一个Class实例,例如:String.class
;getParameterTypes()
:返回方法的参数类型,是一个Class数组,例如:{String.class, int.class}
;getModifiers()
:返回方法的修饰符,它是一个int
,不同的bit表示不同的含义。
调用方法
以下面的代码为例:
String s = "Hello world";
String r = s.substring(6); // "world"
如果用反射来调用substring
方法,需要以下代码:
public class Main {
public static void main(String[] args) throws Exception {
// String对象:
String s = "Hello world";
// 获取String substring(int)方法,参数为int:
Method m = String.class.getMethod("substring", int.class);
// 在s对象上调用该方法并获取结果:
String r = (String) m.invoke(s, 6);
// 打印调用结果:
System.out.println(r);
}
}
调用静态方法
调用静态方法时,由于无需指定实例对象,所以invoke
方法传入的第一个参数永远为null
。
public class Main {
public static void main(String[] args) throws Exception {
// 获取Integer.parseInt(String)方法,参数为String:
Method m = Integer.class.getMethod("parseInt", String.class);
// 调用该静态方法并获取结果:
Integer n = (Integer) m.invoke(null, "12345");
// 打印调用结果:
System.out.println(n);
}
}
调用非public方法
和Field类似,对于非public方法,我们虽然可以通过Class.getDeclaredMethod()
获取该方法实例,但直接对其调用将得到一个IllegalAccessException
。为了调用非public方法,我们通过Method.setAccessible(true)
允许其调用:
public class Main {
public static void main(String[] args) throws Exception {
Person p = new Person();
Method m = p.getClass().getDeclaredMethod("setName", String.class);
m.setAccessible(true);
m.invoke(p, "Bob");
System.out.println(p.name);
}
}
class Person {
String name;
private void setName(String name) {
this.name = name;
}
}
Constructor类
(1)如果通过反射来创建新的实例,可以调用Class提供的newInstance()方法:
Person p = Person.class.newInstance();
调用Class.newInstance()的局限是,它只能调用该类的public无参数构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。
(2)为了调用任意的构造方法,Java的反射API提供了Constructor对象,它包含一个构造方法的所有信息,可以创建一个实例。
// 获取构造方法Integer(int)
Constructor<Integer> cons1 = Integer.class.getConstructor(int.class);
// 调用构造方法
Integer n1 = cons1.newInstance(123);
// 获取构造方法Integer(String)
Constructor<Integer> cons2 = Integer.class.getConstructor(String.class);
Integer n2 = cons2.newInstance("123");
通过Class实例获取Constructor的方法如下:
getConstructor(Class...)
:获取某个public
的Constructor
;getDeclaredConstructor(Class...)
:获取某个Constructor
;getConstructors()
:获取所有public
的Constructor
;getDeclaredConstructors()
:获取所有Constructor
。
注意Constructor
是当前类定义的构造方法,和父类无关,因此不存在多态的问题。
调用非public
的Constructor
时,必须首先通过setAccessible(true)
设置允许访问。setAccessible(true)
可能会失败。
动态代理
概念
动态代码:没有实现类但是在运行期动态创建了一个接口对象的方式。
动态代理:JDK提供的动态创建接口对象的方式。
动态代理步骤
在运行期动态创建一个interface
实例的方法如下:
- 定义一个
InvocationHandler
实例,它负责实现接口的方法调用; - 通过
Proxy.newProxyInstance()
创建interface
实例,它需要3个参数:- 使用的
ClassLoader
,通常就是接口类的ClassLoader
; - 需要实现的接口数组,至少需要传入一个接口进去;
- 用来处理接口方法调用的
InvocationHandler
实例。
- 使用的
- 将返回的
Object
强制转型为接口。
例子
一个最简单的动态代理实现如下:
// 准备 handler,它负责实现接口的方法调用
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;
}
};
// 创建 interface 实例
Hello hello = (Hello) Proxy.newProxyInstance(
Hello.class.getClassLoader(), // 传入ClassLoader
new Class[]{Hello.class}, // 传入要实现的接口
handler); // 传入处理调用方法的InvocationHandler
// 正常使用 interface 实例
hello.morning("Bob");
上面是简写过程,正规过程如下:
// 准备 handler,它负责实现接口的方法调用
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;
}
};
// 获取代理类
Class<?> proxyClass = Proxy.getProxyClass(Hello.class.getClassLoader(), Hello.class);
// 获取代理类的构造函数
Constructor<?> cons = proxyClass.getConstructor(InvocationHandler.class);
// 调用构造函数,生成实例
Hello hello = (Hello) cons.newInstance(handler);
// 正常使用 interface 实例
hello.morning("Bob");
动态代理实际上是JVM在运行期动态创建class字节码并加载的过程,其实就是JVM帮我们自动编写了一个上述类(不需要源码,可以直接生成字节码):
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", String.class),
new Object[] { name });
}
}