1. Class对象
类是程序的一部分,每个类都有一个Class对象,而反射所依赖的就是这个Class对象。
在JVM中,类都是动态加载的,当程序创建第一个对类的静态成员的引用时,就会加载这个类,这个可以证明构造函数也是类的静态方法,即使构造方法没有使用static关键字。
类加载器首先检查在这个类的Class对象是否已经加载,如果尚未加载,默认的类加载器就会根据类名查找 .class 文件,这个类的所有对象都是依据这个类的 .class对象 来创建的。下面举个例子:
package com.ga;
class Candy{
static{
System.out.println("Loading Candy");
}
}
class Gum{
static{
System.out.println("Loading Gum");
}
}
class Cookie{
static{
System.out.println("Loading Cookie");
}
}
public class SweetShop {
public static void main(String[] args) {
System.out.println("inside main");
new Candy();
System.out.println("After creating Candy");
try{
Class.forName("Gum"); //获取Class对象的引用的一种方法
}catch (ClassNotFoundException e){
System.out.println("找不到Gum");;
}
System.out.println("After Class.forName(\"Gum\")");
new Cookie();
System.out.println("After creating Cookie");
}
}
输出结果:
inside main
Loading Candy
After creating Candy
Loading Gum
After Class.forName("Gum")
Loading Cookie
After creating Cookie
上面就用了两种方式来加载类,第一种直接 new 某个类,第二种就是调用静态函数 forName() ,根据类名进行加载。
常用的获取Class对象的方法有:
Class.forName(); //这里调用的是Class类的静态函数
class.getClass(); //这里是调用对象的 getClass() 方法
Class.getInterfaces(); //查询接口,第一个 Class 是某个类的类名
Class.getSuperclass(); //查询基类,第一个 Class 是某个类的类名
Class.class; //类字面常量,第一个 Class 是某个类的类名
注:推荐使用类字面常量。如果使用 forName() 或者 getClass() ,有可能会因为没找到类(类名打错)而在运行时报错,使用类字面常量的话,他在编写代码时就受到检查,如果没有导入这个类,就会提示错误。
2. 反射
反射就是在程序运行时,获取某个类的Class对象,并根据这个Class对象新建一个实例对象,或者获得这个类所拥有的方法,属性,来实现一些通用的方法。大部分框架的实现就是使用了反射。
举个例子,获取User的属性和方法:
class User{
private String name;
private String password;
public User(String name, String password) {
this.name = name;
this.password = password;
}
public User(String name){
this.name = name;
}
public User() {
}
//get/set省略
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", password='" + password + '\'' +
'}';
}
public static void main(String[] args) {
Field[] fields = User.class.getDeclaredFields(); //获取所有字段
Method[] methods = User.class.getDeclaredMethods(); //获取所有方法
for (Field field: fields) {
System.out.println(field.toString());
}
for (Method method: methods) {
System.out.println(method.toString());
}
}
}
输出结果:
private java.lang.String com.qianfeng.User.name
private java.lang.String com.qianfeng.User.password
public java.lang.String com.qianfeng.User.getName()
public void com.qianfeng.User.setName(java.lang.String)
public java.lang.String com.qianfeng.User.getPassword()
public void com.qianfeng.User.setPassword(java.lang.String)
注:getConstructors(),getDeclaredFields(),getDeclaredMethods()等返回数组的方法,数组内容的顺序是随机的。(如要保证顺序,可参照Java反射,顺序输出类中的方法)
接下来列举一些常用方法(有 Declared 这个词的方法,获得到任意访问权限的属性或方法,没有的话只能访问公有即 public 的属性或方法):
获取构造器
public static void main(String[] args) {
try {
//获取User类中的一个公有构造方法,为空就是无参构造器
Constructor constructor1 = User.class.getConstructor();
//根据参数类型的类对象获取对应的一个构造方法,这个方法就会获取只有一个参数,且参数类型为String类型的构造器
Constructor constructor2 = User.class.getConstructor(String.class);
//获取User类中所有的公有构造器
Constructor[] constructor3 = User.class.getConstructors();
//获取User类中的任意权限的一个构造器,用法同上面的获取公有构造器的方法
Constructor constructor4 = User.class.getDeclaredConstructor();
//获取User类中的所有构造器
Constructor[] constructor5 = User.class.getDeclaredConstructors();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
获取成员变量
public static void main(String[] args) {
try {
//根据属性名获取对应的公有属性
Field field = User.class.getField("name");
//获取所有公有属性
Field[] fields = User.class.getFields();
//根据属性名获取对应的任意权限的属性
Field field1 = User.class.getDeclaredField("password");
//获取所有权限的属性
Field[] fields1 = User.class.getDeclaredFields();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
获取成员方法
先给User添加两个方法
public User Create(String name, String password){
return new User(name, password);
}
public User Create(String name){
return new User(name);
}
public static void main(String[] args) {
try {
//根据函数名和参数类型获取对应的公有成员方法
Method method1 = User.class.getMethod("Create", String.class);
//获取所有的公有成员方法
Method[] method2 = User.class.getMethods();
//根据函数名和参数类型获取对应的所有权限的成员方法
Method method3 = User.class.getDeclaredMethod("Create", String.class, String.class);
//获取所有权限的成员方法
Method[] method4 = User.class.getDeclaredMethods();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
使用技巧
获取了类的属性和方法后,就可以根据这些来修改该类的实例对象的属性的值,或者调用方法,比如:
public static void main(String[] args) {
User user = null;
try {
//通过反射创建实例,
user = User.class.newInstance();
//通过反射获取 name 字段,并将对象 user 中的 name 字段的值设为 “user”
User.class.getField("name").setAccessible(true); ////解除私有限定
User.class.getField("name").set(user, "user");
//通过反射获取 password 字段,并将对象 user 中的 password 字段的值设为 “password”
User.class.getField("password").setAccessible(true);
User.class.getField("password").set(user, "password");
System.out.println(user);
//通过反射获取只有一个参数,且参数类型为 String 类型的 setName 方法,通过invoke调用,invoke()中,第一个参数为要调用该方法的对象实例,第二个为参数的值
Object object = user;
User.class.getDeclaredMethod("setName", String.class).invoke(object, "user1");
User.class.getDeclaredMethod("setPassword", String.class).invoke(object, "password1");
System.out.println(user);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
输出结果
User{name='user', password='password'}
User{name='user1', password='password1'}
由于反射类中的 Field , Method 和 Constructor 继承自 AccessibleObject ,因此,通过在这些类上调用 setAccessible() 方法,可以修改访问权限,从而实现对这些字段的操作。
也可以用下面的方式解除操作权限,第一个参数为字段的对象,第二个为权限:
AccessibleObject.setAccessible(fields, true);