通俗地说,反射机制就是可以把一个类,类的成员(函数,属性),当成一个对象来操作,希望读者能理解,也就是说,类,类的成员,我们在运行的时候还可以动态地去操作它们.
Class类
在面向对象对象的世界中,万事万物皆是对象。Java语言中,静态的成员和普通数据类型不是对象(有其相应的包装类)。
万事万物都可以抽象成类,而这些类也是_java.lang.Class_的实例对象。
测试类
public class ClassTest {
public static void main(String[] args) {
// Student类的实例对象
Student student = new Student(); // student就表示了Student类的实例对象
/*
* Student这个类本身也是一个实例对象,是Class类的实例对象
* 但是不能new出来(私有的无参构造器),只有Java的虚拟机能创建Class类的实例对象
*/
// 任意一个类都是Class类的实例对象,其表示(获得)方式有如下三种
// 1 (任何一个类都有一个隐含的静态成员变量class)
Class s1 = Student.class;
// 2 (已知该类的实例对象,通过getClass()方法)
Class s2 = student.getClass();
// 3 (Class.forName("com.sh2zqp.Student"))
Class s3 = null;
try {
s3 = Class.forName("com.sh2zqp.Student");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// s1,s2,s3都表示了Student类的类类型(class type),也就是Class类的实例对象
// 它们三者是等价的,一个类只可能有Class类的一种实例对象
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
System.out.println(s1 == s2); // true
System.out.println(s2 == s3); // true
// 我们可以通过Student类的类类型 s1 or s2 or s3 来创建Student类的实例对象
try {
Student s = (Student) s1.newInstance(); // Student需要有无参的构造器
//Student s = (Student) s2.newInstance();
//Student s = (Student) s3.newInstance();
s.play();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Student
public class Student {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void play() {
System.out.println("play......");
}
}
Java动态加载类
Class.forName(“类的全路径”)
上面的式子不仅可以获得类的类类型,还代表了一种类的加载技术,既是动态加载类。
编译:把.java文件变成.class文件
运行:由java虚拟机来运行.class文件
编译时加载的类就是静态加载类
运行时加载的类就是动态加载类
不适用IDE,而使用命令行来编译,运行程序
javac 编译
java 运行
class Office {
public static void main(String[] args) {
if ("Word".equals(args[0])) {
Word word = new Word();
word.start();
}
if ("Excel".equals(args[0])) {
Excel excel = new Excel();
excel.start();
}
}
}
此时编译此文件
javac Office.java
Word和Excel类找不到
新建一个Word类
class Word {
public void start() {
System.out.println("word...start...");
}
}
javac Word.java
编译得到Word.class文件,再次编译Office.java
和Word相关的报错消失
new创建对象 是静态加载类,在编译时就需要使用可能需要的类,如果我们目前只有Word类,而没有Excel类,就会一直无法使用该程序,所以我们可以通过动态加载类解决上面问题。
public class Office {
public static void main(String[] args) {
try {
// 动态加载类,在运行时加载
Class c = Class.forName(args[0]);
// 通过类类型创建类的实例对象
OfficeAble office = (OfficeAble) c.newInstance();
office.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public interface OfficeAble {
public void start();
}
public class Word implements OfficeAble {
@Override
public void start() {
System.out.println("Word...Start...");
}
}
动态加载运行,各种功能模块互不影响,对以后想添加功能也比较方便。
基本数据类型以及一些特殊的类类型
public class BaseDataTypeClassTest {
public static void main(String[] args) {
Class c1 = int.class; // int的类类型(int类的字节码)
Class c2 = float.class;
Class c3 = Double.class;
Class c4 = String.class;
Class c5 = void.class;
System.out.println(c1);
System.out.println(c2);
System.out.println(c3);
System.out.println(c4);
System.out.println(c5);
System.out.println(c1.getName());
System.out.println(c1.getSimpleName());
System.out.println(c3.getName());
System.out.println(c3.getSimpleName());
System.out.println(c5.getName());
System.out.println(c5.getSimpleName());
}
}
运行结果
Class类的基本API
Method相关
/**
* Method类,方法的对象
* 一个成员方法就是一个Method的实例对象
* c.getMethods()获取所有的public修饰的函数,包括父类继承来的方法
* c.getDeclaredMethods()获取所有该类自己声明的方法,不考虑访问权限
*/
Method[] methods = c.getMethods();
//Method[] declaredMethods = c.getDeclaredMethods();
for (Method m : methods) {
// 方法的返回值类型
Class returnValueType = m.getReturnType();
System.out.print(returnValueType.getSimpleName() + " ");
// 方法名
System.out.print(m.getName());
// 方法的参数类型
Class[] parameterTypes = m.getParameterTypes();
System.out.print("(");
for (Class cl : parameterTypes) {
}
for (int i = 0; i < parameterTypes.length; i++) {
if (i != (parameterTypes.length-1) ) {
System.out.print(parameterTypes[i].getSimpleName() + ",");
} else {
System.out.print(parameterTypes[i].getSimpleName());
}
}
System.out.println(")");
}
Field相关
// 要获取object对象类的信息,先获取object对象的所属类的类类型
Class c = object.getClass(); // 2
/**
* Field类,成员变量也是对象
* 一个成员变量就是一个Field的实例对象
* c.getFields()获取所有的public修饰的成员变量,包括父类继承来的成员变量
* c.getDeclaredFields()获取所有该类自己声明的成员变量,不考虑访问权限
*/
Field[] fields = c.getFields();
Field[] declaredFields = c.getDeclaredFields();
for (Field f : declaredFields) {
// 得到成员变量类的类类型的名称
Class fieldClassType = f.getType();
System.out.print(fieldClassType.getSimpleName() + " ");
// 得到成员变量的名称
System.out.println(f.getName());
}
构造函数相关
// 要获取object对象类的信息,先获取object对象的所属类的类类型
Class c = object.getClass(); // 2
/**
* Constructor类,构造函数也是对象
* 一个构造函数就是一个Constructor的实例对象
* c.getConstructors()获取所有的public修饰的构造函数
* c.getDeclaredFields()获取所有该类的构造函数
*/
//Constructor[] constructors = c.getConstructors();
Constructor[] declaredConstructors = c.getDeclaredConstructors();
for (Constructor constructor : declaredConstructors) {
// 方法名
System.out.print(constructor.getName());
// 方法的参数类型
Class[] parameterTypes = constructor.getParameterTypes();
System.out.print("(");
for (int i = 0; i < parameterTypes.length; i++) {
if (i != (parameterTypes.length-1) ) {
System.out.print(parameterTypes[i].getSimpleName() + ",");
} else {
System.out.print(parameterTypes[i].getSimpleName());
}
}
System.out.println(")");
}
其他等等,只要获得该类的类类型即可获得全面的类信息
Class中方法的反射操作
- 如何获取某个方法(方法的名称和方法的参数列表才能唯一的决定某个方法)
方法的反射操作
method.invoke(对象,参数列表)
public class MethodInvokeTest {
public static void main(String[] args) { Student student = new Student(); // 获取Student类的print(int , int)方法 // 1. 要获取一个类的方法,就是获取该类的类信息 Class c = student.getClass(); // 2. 获取方法的名称和参数列表来决定该方法是否是我们需要的 try { // 获得public方法 Method cMethod = c.getMethod("print", new Class[] {int.class, int.class}); //Method cMethod = c.getMethod("print", int.class, int.class); // 获得自己声明的任意方法(不考虑权限) //Method cDeclaredMethod = c.getDeclaredMethod(); // 方法的反射操作 student.print(11,22); // student对象操作方法print // 方法有返回值返回具体的返回值,无返回值则返回null Object o = cMethod.invoke(student, 11, 22); // print方法对象cMethod操作对象student //Object o = cMethod.invoke(student, new Object[] {11, 22}); System.out.println(o); // null System.out.println("====================="); Method cMethod1 = c.getMethod("print", String.class, String.class); cMethod1.invoke(student, "a", "b"); System.out.println("====================="); //Method cMethod2 = c.getMethod("print"); Method cMethod2 = c.getMethod("print", new Class[] {}); //cMethod2.invoke(student); cMethod2.invoke(student, new Object[] {}); /** * 用Java反射机制来调用private方法 * * AccessibleObject类是Field、Method、和Constructor对象的基类。 * 它提供了将反射的对象标记为在使用时取消默认Java语言访问控制检查的能力。 * 对于公共成员、默认(打包)访问成员、受保护成员和私有成员,在分别使用 * Field、Method和Constructor对象来设置或获得字段、调用方法, * 或者创建和初始化类的新实例的时候,会执行访问检查。 * 当反射对象的accessible标志设为true时,则表示反射的对象在使用时应该取消Java语言访问检查。 * 反之则检查。由于JDK的安全检查耗时较多,所以通过setAccessible(true)的方式关闭安全检查来提升反射速度。 */ System.out.println("====================="); // //getDeclaredMethod可以获取到所有方法,而getMethod只能获取public Method cMethodPrivate = c.getDeclaredMethod("privateMethod", new Class[] {}); cMethodPrivate.setAccessible(true);// //压制Java对访问修饰符的检查 cMethodPrivate.invoke(student, new Object[] {}); } catch (Exception e) { e.printStackTrace(); } }
}
反射在集合泛型中的应用(本质)
通过Class,Method来认识泛型的本质,什么是泛型(防止输入错误的数据类型),泛型什么时候有效
public class GenericClassTest {
public static void main(String[] args) {
ArrayList list1 = new ArrayList();
ArrayList<String> list2 = new ArrayList<>();
list2.add("aaaa");
// list2.add(20); // 错误
Class c1 = list1.getClass();
Class c2 = list2.getClass();
System.out.println(c1);
System.out.println(c2);
System.out.println(c1 == c2); // true
// 反射的操作都是编译后的操作,即变成字节码之后的操作(运行时)
// c1==c2说明编译之后的集合是去泛型化的
// Java中的泛型是防止错误输入的,只在编译阶段有效,绕过编译就无效了
// 验证:通过方法的反射来操作,绕过编译
try {
Method c2Method = c2.getMethod("add", Object.class);
c2Method.invoke(list2, 100);
System.out.println(list2.size());
System.out.println(list2); // [aaaa, 100],此时不能再用for循环遍历,会有类型转换错误
} catch (Exception e) {
e.printStackTrace();
}
}
}
反射的应用场景
- 工厂模式:Factory类中用反射的话,添加了一个新的类之后,就不需要再修改工厂类Factory了
- 数据库JDBC中通过Class.forName(Driver).来获得数据库连接驱动
- 分析类文件:毕竟能得到类中的方法等等
- 访问一些不能访问的变量或属性:破解别人代码
参考资料