Class类
class是JVM在执行过程中动态加载的。每加载一种class,创建一个class类型(这个类型的名字就是class)的唯一对象(实例/instance),并关联起来,该实例保存着该class的所有信息,包括类名、报名、父类、实现的接口、所有方法、字段等,可以通过该实例获得class对应的所有信息,这就叫反射,实例反射出class信息。
获取class的class实例
- 通过一个class的静态变量class获取
Class cls = String.class;
- 通过实例变量提供的getClass()方法获取:
String s = "Hello";//String类的实例s
Class cls = s.getClass();
- 知道完整类名(包名.类名),使用静态方法Class。forName()获取
Class cls = Class.forName("java.lang.String");
JDBC常用此方法加载数据库驱动
Class.forName(driver);
注意一下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
instanceof不但匹配指定类型,还匹配该类型的父类。==判断class实例是精确判断数据类型,通常用instanceof判断数据类型,因为面向抽象编程就不关心具体子类型。
动态加载
JVM在执行Java程序
的时候,并不是一次性把所有用到的class全部加载到内存,而是第一次需要用到class时才加载。
小结
JVM为每个加载的class及interface创建了对应的Class实例来保存class及interface的所有信息;
获取一个class对应的Class实例后,就可以获取该class的所有信息;
通过Class实例获取class信息的方法称为反射(Reflection);
JVM总是动态加载class,可以在运行期根据条件来控制加载class。
访问字段
Class类提供了以下几个方法来获取字段:以String类的value字段为例
Field getField(name):根据字段名获取某个public的field(包括父类)
Field f = String.class.get
Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
Field[] getFields():获取所有public的field(包括父类)
Field[] getDeclaredFields():获取当前类的所有field(不包括父类)
publpublic class Main {
public static void main(String[] args) throws Exception {
Class stdClass = Student.class;
// 获取public字段"score":
System.out.println(stdClass.getField("score"));
// 获取继承的public字段"name":
System.out.println(stdClass.getField("name"));
// 获取private字段"grade":
System.out.println(stdClass.getDeclaredField("grade"));
}
}
class Student extends Person {
public int score;
private int grade;
}
class Person {
public String name;
}
/*运行结果
public int Student.score
public java.lang.String Person.name
private int Student.grade
*/
获取字段值
对于一个Person实例,我们可以先拿到name字段对应的Field,再获取这个实例的name字段的值:
// reflection
import java.lang.reflect.Field;
public class Main {
public static void main(String[] args) throws Exception {
Object p = new Person("Xiao Ming");
Class c = p.getClass();
Field f = c.getDeclaredField("name");
f.setAccessible(true);//因为main类无法访问Person的private字段,所以写这句
Object value = f.get(p);
System.out.println(value); // "Xiao Ming"
}
}
class Person {
private String name;
public Person(String name) {
this.name = name;
}
}
设置字段值
通过Field.set(Object, Object)实现
Person p = new Person("Xiao Ming");
Class c = p.getClass();
Field f = c.getDeclareField("name");
f.setAccessible(true);
f.set(p, "Xiao Hong");
调用方法
通过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表示不同的含义。
用反射来调用substring方法
import java.lang.reflect.Method;
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);//输出world
}
}
调用静态方法
调用静态方法无需指定实例对象,以Interger.parsenInt(String)为例
import java.lang.reflect.Method;
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");//第一个参数为null
// 打印调用结果:
System.out.println(n);
}
}
调用非public方法
和Field类似,对于非public方法,我们虽然可以通过Class.getDeclaredMethod()获取该方法实例,但直接对其调用将得到一个IllegalAccessException。为了调用非public方法,我们通过Method.setAccessible(true)允许其调用:
import java.lang.reflect.Method;
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;
}
}
调用构造方法
- 使用new创建新的实例
Person p = new Person(); - 通过反射创建新实例,Class提供的newInstance()方法(只能调用该类的public无参构造方法)
Person p = Person.class.newInstance(); - Constructor对象
通过Class实例获取Constructor的方法如下:
getConstructor(Class…):获取某个public的Constructor;
getDeclaredConstructor(Class…):获取某个Constructor;
getConstructors():获取所有public的Constructor;
getDeclaredConstructors():获取所有Constructor。
import java.lang.reflect.Constructor;
public class Main {
public static void main(String[] args) throws Exception {
// 获取构造方法Integer(int):
Constructor cons1 = Integer.class.getConstructor(int.class);
// 调用构造方法:
Integer n1 = (Integer) cons1.newInstance(123);
System.out.println(n1);
// 获取构造方法Integer(String)
Constructor cons2 = Integer.class.getConstructor(String.class);
Integer n2 = (Integer) cons2.newInstance("456");
System.out.println(n2);
}
}
Constructor对象封装了构造方法的所有信息;
通过Class实例的方法可以获取Constructor实例:getConstructor(),getConstructors(),getDeclaredConstructor(),getDeclaredConstructors();
通过Constructor实例可以创建一个实例对象:newInstance(Object… parameters); 通过设置setAccessible(true)来访问非public构造方法。
获取继承关系
获取父类的Class
Class i = Interger.class;
Class n = i.getSuperclass();
System.out.println(n);//class java.lang.Number
获取interface
通过Class可以查询类实现的接口(一个类可能实现一个或者多个接口),如查询Integer实现的接口:getInterfaces()只返回当前类实现的接口,不包括父类实现的接口类型
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) throws Exception {
Class s = Integer.class;
Class[] is = s.getInterfaces();
for (Class i : is) {
System.out.println(i);
}
}
}
/*结果
java.lang.Comparable
java.lang.constant.Constable
java.lang.constant.ConstantDesc
*/
获取接口的父接口要用getInterfaces()
通过Class对象可以获取继承关系:
Class getSuperclass():获取父类类型;
Class[] getInterfaces():获取当前类实现的所有接口。
通过Class对象的isAssignableFrom()方法可以判断一个向上转型是否可以实现。
动态代理(Dynamic Proxy)
有实现类但是在运行期动态创建了一个接口对象的方式,称为动态代码
一个动态代理实现如下:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
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强制转型为接口。