Java Reflect(反射)入门学习
反射机制指在程序运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能即Java的反射机制。
Java反射机制主要提供了以下功能:
- 在运行时判断任意一个对象所属的类;
- 在运行时构造任意一个类的对象;
- 在运行时判断任意一个类所具有的成员变量和方法;
- 在运行时调用任意一个对象的方法;
- 生成动态代理。
Java反射机制相关的类主要有java.lang包中的Class类和java.lang.reflect包中的Constructor类、Field类、Method类和Parameter类。
1、Class对象
Class类是一个比较特殊的类,它是反射机制的基础,Class类的对象表示正在运行的Java程序中的类或接口,也就是任何一个类被加载时,即将类的.class文件(字节码文件)读入内存的同时,都自动为之创建一个java.lang.Class对象。Class类没有公共构造方法,其对象是JVM在加载类时通过调用类加载器中的defineClass()方法创建的,因此不能显式地创建一个Class对象。通过这个Class对象,才可以获得该对象的其他信息。
Java在将.class字节码文件载入时,JVM将产生一个java.lang.Class对象代表该.class字节码文件,从该Class对象中可以获得类的许多基本信息,这就是反射机制。所以要想完成反射操作,就必须首先认识Class类。
1.1、获取Class对象的三种方式
1.1.1、Object —> getClass();
在Java的Object类中,有一个 获取类信息的本地方法: public final native Class<?> getClass(); 对象可通过此方法获取其类的Class对象
//第一种方式获取Class对象
Student stu1 = new Student();//这一new 产生一个Student对象,一个Class对象。
Class stuClass = stu1.getClass();//获取Class对象
System.out.println(stuClass.getName());//main.basics.reflect.Student
1.1.2、静态class属性
从Class类的源码注释:
/*
* Instances of the class {@code Class} represent classes and
* interfaces in a running Java application. An enum is a kind of
* class and an annotation is a kind of interface. Every array also
* belongs to a class that is reflected as a {@code Class} object
* that is shared by all arrays with the same element type and number
* of dimensions. The primitive Java types ({@code boolean},
* {@code byte}, {@code char}, {@code short},
* {@code int}, {@code long}, {@code float}, and
* {@code double}), and the keyword {@code void} are also
* represented as {@code Class} objects.
*/
可知,任何数据类型(包括基本数据类型)以及void关键字,在JVM中,都是Class对象,而Java中的对象都有一个“静态”的class属性。
/第二种方式获取Class对象
Class stuClass2 = Student.class;
System.out.println(stuClass == stuClass2);//判断第一种方式获取的Class对象和第二种方式获取的是否是同一个
System.out.println(void.class);// void
System.out.println(int.class);// int
1.1.3、静态方法(常用)
通过Class类的静态方法Class.forName(String className) 可以由字符串获取一个Class对象。
//第三种方式获取Class对象
try {
Class stuClass3 = Class.forName("main.basics.reflect.Student");//注意此字符串必须是类的全路径,就是带包名的类路径,包名.类名
System.out.println(stuClass3 == stuClass2);//判断三种方式是否获取的是同一个Class对象
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
小结:
- 在运行期间,一个类,只有一个Class对象产生。
- 三种方式常用第三种。对于第一种,已经有了对象了,就不需要反射了。第二种需要导入类的包,依赖太强,不导包就抛编译错误,而第三种,一个字符串可以传入也可写在配置文件中等多种方法,非常灵活。
2、动态获取Class对象的信息
通过Class对象可以获取某个类中的:构造方法、成员变量、成员方法;并访问成员;
2.1、构造方法
2.1.1、批量获取
public Constructor[] getConstructors():所有"公有的"构造方法
public Constructor[] getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有)
2.1.2、获取单个
public Constructor getConstructor(Class... parameterTypes):获取单个的"公有的"构造方法:
public Constructor getDeclaredConstructor(Class... parameterTypes):获取"某个构造方法"可以是私有的,或受保护、默认、公有;
2.1.3、调用构造方法
Constructor-->newInstance(Object... initargs)
//1.加载Class对象
Class clazz = Class.forName("main.basics.reflect.Student");
//2.获取所有公有构造方法
System.out.println("**********************所有公有构造方法*********************************");
Constructor[] conArray = clazz.getConstructors();
for(Constructor c : conArray){
System.out.println(c);
}
System.out.println("************所有的构造方法(包括:私有、受保护、默认、公有)***************");
conArray = clazz.getDeclaredConstructors();
for(Constructor c : conArray){
System.out.println(c);
}
System.out.println("*****************获取公有、无参的构造方法*******************************");
Constructor con = clazz.getConstructor(null);
//1、因为是无参的构造方法所以类型是一个null,不写也可以:这里需要的是一个参数的类型,切记是类型
//2、返回的是描述这个无参构造函数的类对象。
System.out.println("con = " + con);
//调用构造方法
Object obj = con.newInstance();
// System.out.println("obj = " + obj);
// Student stu = (Student)obj;
System.out.println("******************获取私有构造方法,并调用*******************************");
con = clazz.getDeclaredConstructor(char.class);
System.out.println(con);
//调用构造方法
con.setAccessible(true);//暴力访问(忽略掉访问修饰符)
obj = con.newInstance('男');
2.2、成员变量
2.2.1、批量获取
public Field[] getFields():获取所有的"公有字段"
public Field[] getDeclaredFields():获取所有字段,包括 私有、受保护、默认、公有;
2.2.2、获取单个
public Field getField(String fieldName):获取某个"公有的"字段;
public Field getDeclaredField(String fieldName):获取某个字段(可以是私有的)
2.2.3、设置字段的值:
Field --> public void set(Object obj,Object value):
参数说明:
obj:要设置的字段所在的对象;
value:要为字段设置的值;
//1.获取Class对象
Class stuClass = Class.forName("main.basics.reflect.Student");
//2.获取字段
System.out.println("************获取所有公有的字段********************");
Field[] fieldArray = stuClass.getFields();
for(Field f : fieldArray){
System.out.println(f);
}
System.out.println("************获取所有的字段(包括私有、受保护、默认的)********************");
fieldArray = stuClass.getDeclaredFields();
for(Field f : fieldArray){
System.out.println(f);
}
System.out.println("*************获取公有字段**并调用***********************************");
Field f = stuClass.getField("name");
System.out.println(f);
//获取一个对象
Object obj = stuClass.getConstructor().newInstance();//产生Student对象--》Student stu = new Student();
//为字段设置值
f.set(obj, "刘德华");//为Student对象中的name属性赋值--》stu.name = "刘德华"
//验证
Student stu = (Student)obj;
System.out.println("验证姓名:" + stu.name);
System.out.println("**************获取私有字段****并调用********************************");
f = stuClass.getDeclaredField("phoneNum");
System.out.println(f);
f.setAccessible(true);//暴力反射,解除私有限定
f.set(obj, "18888889999");
System.out.println("验证电话:" + stu);
2.3、成员方法
2.3.1、批量获取
public Method[] getMethods():获取所有"公有方法";(包含了父类的方法也包含Object类)
public Method[] getDeclaredMethods():获取所有的成员方法,包括私有的(不包括继承的)
2.3.2、获取单个
public Method getMethod(String name,Class... parameterTypes)
public Method getDeclaredMethod(String name,Class... parameterTypes)
参数:
name : 方法名;
Class ... : 形参的Class类型对象
2.3.3、调用方法
Method --> public Object invoke(Object obj,Object... args):
参数说明:
obj : 要调用方法的对象;
args:调用方式时所传递的实参;
//1.获取Class对象
Class stuClass = Class.forName("main.basics.reflect.Student");
//2.获取所有公有方法
System.out.println("***************获取所有的”公有“方法*******************");
stuClass.getMethods();
Method[] methodArray = stuClass.getMethods();
for(Method m : methodArray){
System.out.println(m);
}
System.out.println("***************获取所有的方法,包括私有的*******************");
methodArray = stuClass.getDeclaredMethods();
for(Method m : methodArray){
System.out.println(m);
}
System.out.println("***************获取公有的show1()方法*******************");
Method m = stuClass.getMethod("show1", String.class);
System.out.println(m);
//实例化一个Student对象
Object obj = stuClass.getConstructor().newInstance();
m.invoke(obj, "刘德华");
System.out.println("***************获取私有的show4()方法******************");
m = stuClass.getDeclaredMethod("show4", int.class);
System.out.println(m);
m.setAccessible(true);//解除私有限定
Object result = m.invoke(obj, 20);//需要两个参数,一个是要调用的对象(获取有反射),一个是实参
System.out.println("返回值:" + result);
2.4、获取main方法
try {
//1、获取Student对象的字节码
Class clazz = Class.forName("main.basics.reflect.Student");
//2、获取main方法
Method methodMain = clazz.getMethod("main", String[].class);//第一个参数:方法名称,第二个参数:方法形参的类型,
//3、调用main方法
// methodMain.invoke(null, new String[]{"a","b","c"});//java.lang.IllegalArgumentException: argument type mismatch
//第一个参数,对象类型,因为方法是static静态的,所以为null可以,第二个参数是String数组,
/*启动Java程序的main方法的参数是一个字符串数组,
按照jdk1.5的语法,整个数组是一个参数,而按照jdk1.4的语法,数组中的每一个元素对应一个参数,
当把一个字符串数组作为参数传递给invoke方法时,javac会按照jdk1.4的语法进行处理,
因为jdk1.5肯定要兼容jdk1.4的语法,也就是把数组打散成若干个单独的参数,
所以也就会出现上面的异常了。
既然字符串数组会拆包成一个个的对象参数,那么我们就在这个字符串的外面再套上一层外衣,
当拆包的时候只是拆掉外面的那层,里面的字符串数组就可以作为一个单独的参数进行传递了*/
//这里拆的时候将 new String[]{"a","b","c"} 拆成3个对象。。。所以需要将它强转。
methodMain.invoke(null, (Object)new String[]{"a","b","c"});//方式一
// methodMain.invoke(null, new Object[]{new String[]{"a","b","c"}});//方式二
} catch (Exception e) {
e.printStackTrace();
}
3、反射使用示例
3.1、变换执行
通过配置文件,灵活的切换程序执行路线。
className = main.basics.reflect.Student
#className = main.basics.reflect.Student2
methodName = show
#methodName = show2
Properties pro = new Properties();//获取配置文件的对象
try {
FileReader in = new FileReader("HelloJava\\resources\\pro.properties");//获取输入流
pro.load(in);//将流加载到配置文件对象中
in.close();
//通过反射获取Class对象
Class stuClass = Class.forName(pro.getProperty("className"));
//2获取show()方法
Method m = stuClass.getMethod(pro.getProperty("methodName"));
//3.调用show()方法
m.invoke(stuClass.getConstructor().newInstance());
/**
* 需求:
* 当我们升级这个系统时,不要Student类,而需要新写一个Student2的类时,
* 这时只需要更改pro.properties的文件内容就可以了。代码一点都不用改动
*/
} catch (Exception e) {
e.printStackTrace();
}
3.2、破坏泛型(慎用)
ArrayList<String> strList = new ArrayList<>();
strList.add("aaa");
strList.add("bbb");
// strList.add(100);
//获取ArrayList的Class对象,反向的调用add()方法,添加数据
Class listClass = strList.getClass(); //得到 strList 对象的字节码 对象
try {
Method m = null;
//获取add()方法
m = listClass.getMethod("add", Object.class);
//调用add()方法
m.invoke(strList, 100);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
//遍历集合
for(Object obj : strList){
System.out.println(obj);
}
3.3、破坏单例及单例防止破坏
普通的单例类:
package main.basics.reflect;
public class SingletonStudent {
private static SingletonStudent ourInstance = new SingletonStudent();
public static SingletonStudent getInstance() {
return ourInstance;
}
private SingletonStudent() {
}
}
防止破坏的单例类:
package main.basics.reflect;
public class SingletonStudent2 {
private static int count = 0;
private static SingletonStudent2 ourInstance = null;
public static SingletonStudent2 getInstance() {
if (null == ourInstance) {
ourInstance = new SingletonStudent2();
}
return ourInstance;
}
private SingletonStudent2() {
synchronized (SingletonStudent2.class) {
if (count > 0) {
throw new RuntimeException("无法创建实例");
}
count++;
}
}
}
SingletonStudent singletonStudent = SingletonStudent.getInstance();
SingletonStudent2 singletonStudent_2 = SingletonStudent2.getInstance();
try {
Constructor constructor = singletonStudent.getClass().getDeclaredConstructor();
constructor.setAccessible(true);
SingletonStudent singletonStudent1 = (SingletonStudent) constructor.newInstance();
SingletonStudent singletonStudent2 = (SingletonStudent) constructor.newInstance();
System.out.println(singletonStudent.hashCode());
System.out.println(singletonStudent1.hashCode());
System.out.println("----------------------------------");
Constructor constructor2 = singletonStudent_2.getClass().getDeclaredConstructor();
constructor2.setAccessible(true);
SingletonStudent2 singletonStudent_21 = (SingletonStudent2) constructor2.newInstance();
SingletonStudent2 singletonStudent_22 = (SingletonStudent2) constructor2.newInstance();
System.out.println(singletonStudent_22.hashCode());
System.out.println(singletonStudent_21.hashCode());
} catch (Exception e) {
e.printStackTrace();
}
4、附录:源码
4.1、Student
package main.basics.reflect;
public class Student {
//---------------构造方法-------------------
//(默认的构造方法)
Student(String str){
System.out.println("(默认)的构造方法 s = " + str);
}
//无参构造方法
public Student(){
System.out.println("调用了公有、无参构造方法执行了。。。");
}
//有一个参数的构造方法
public Student(char name){
System.out.println("姓名:" + name);
}
//有多个参数的构造方法
public Student(String name ,int age){
System.out.println("姓名:"+name+"年龄:"+ age);//这的执行效率有问题,以后解决。
}
//受保护的构造方法
protected Student(boolean n){
System.out.println("受保护的构造方法 n = " + n);
}
//私有构造方法
private Student(int age){
System.out.println("私有的构造方法 年龄:"+ age);
}
//**********字段*************//
public String name;
protected int age;
char sex;
private String phoneNum;
//**************成员方法***************//
public void show(){
System.out.println("is show()");
}
public void show1(String s){
System.out.println("调用了:公有的,String参数的show1(): s = " + s);
}
protected void show2(){
System.out.println("调用了:受保护的,无参的show2()");
}
void show3(){
System.out.println("调用了:默认的,无参的show3()");
}
private String show4(int age){
System.out.println("调用了,私有的,并且有返回值的,int参数的show4(): age = " + age);
return "abcd";
}
public String getPhoneNum() {
return phoneNum;
}
public void setPhoneNum(String phoneNum) {
this.phoneNum = phoneNum;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + ", sex=" + sex
+ ", phoneNum=" + phoneNum + "]";
}
//**************main方法***************//
public static void main(String[] args) {
System.out.println("main方法执行了。。。");
}
}
4.2、Student2
package main.basics.reflect;
public class Student2 {
public void show2(){
System.out.println("is show2()");
}
}