目录
在之前学习JDBC+Servlet+JSP时,连接数据库时写了一段加载配置文件的代码
其中就涉及到了类加载机制和反射机制
一、反射概念
反射(Reflection)是程序的自我分析能力,通过反射可以确定类有哪些方法、有哪些构造方法以及有哪些成员变量;对于任意一个对象,都能够调用它的任意一个方法和属性;Java提供了反射机制,通过反射机制能够动态读取一个类的信息;能够在运行时动态加载类,而不是在编译器。反射可以应用于框架开发,它能够从配置文件中读取配置信息动态加载类、并创建对象。
我们都知道Java面向对象,所以在Java中万物皆为对象(静态的成员、普通数据类型除外)。也就是说类也是对象!类是java.lang.Class类的实例对象。
反射就是把java类中的各种成分映射成一个个的Java对象
比如一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把这些部分映射成一个个对象。(其实:一个类中这些成员方法、构造方法、在加入类中都有一个类来描述)
加载过程:Class对象的由来是将class文件读入内存,并为之创建一个Class对象。
其中获取Class对象涉及到java.lang.object包
获取构造方法成员变量、成员方法等涉及到java.lang.reflect包
二、获取Class类对象
Class类比较特殊。Class 类的实例表示正在运行的 Java 应用程序中的类和接口。也就是jvm中有N多的实例每个类都有该Class对象(包括基本数据类型)。
Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的。也就是这不需要我们自己去处理创建,JVM已经帮我们创建好了。
获取Class对象有三种方法:
1)Object.getClass();
2)任何数据类型(包括基本数据类型)都有一个“静态”的class属性
3)通过Class类的静态方法:forName (String className) (常用)
import com.hyxy.entity.User;
public class Reflection {
public static void main(String[] args) {
//方法一
User user1=new User();//此时产生了一个User对象,以及一个Class对象
Class userClass=user1.getClass();
System.out.println(userClass.getName());
//方法二
Class userClass1=User.class;
System.out.println(userClass==userClass1);
//方法三
try {
Class userClass2 = Class.forName("com.hyxy.entity.User");
System.out.println(userClass2==userClass);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
运行结果:
可见,在运行期间,一个类,只有一个Class对象产生。
第一种方法没什么意义,对象都有了也就不需要反射了;
第二种方法需要导入类的包,依赖太强,不导包就抛编译错误;
一般都第三种方法获取Class对象,可以传入字符串也可写在配置文件中等多种方法。
三、获取构造方法
通过Class对象可以获取某个类中的:构造方法、成员变量、成员方法;并访问成员;
1、获取构造方法:
1)批量的方法:
public Constructor[] getConstructors():所有"公有的"构造方法
public Constructor[] getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有)
2).获取单个的方法,并调用:
public Constructor getConstructor(Class... parameterTypes):获取单个的"公有的"构造方法:
public Constructor getDeclaredConstructor(Class... parameterTypes):获取"某个构造方法"可以是私有的,或受保护、默认、公有;
调用构造方法: Constructor类的newInstance(Object... initargs)方法,它的返回值是T类型,所以newInstance是创建了一个构造方法的声明类的新实例对象。并为之调用。
User类
package com.hyxy.entity;
public class User {
public String name;
public int age;
//默认的构造方法
User(char c){
System.out.println("默认的构造方法 c="+c);
}
//无参构造方法
public User(){
System.out.println("调用了无参构造方法");
}
//一个参数的构造方法
public User(String name){
System.out.println("姓名"+name);
}
//多个参数的构造方法
public User(String name,int age){
System.out.println("姓名"+name+" 年龄"+age);
}
//受保护的构造方法
protected User(int age){
System.out.println("受保护的构造方法 age="+age);
}
//私有构造方法
private User(boolean n){
System.out.println("私有的构造方法 n="+n);
}
}
测试类:
import java.lang.reflect.Constructor;
public class Reflection1 {
public static void main(String[] args) throws Exception {
//1、加载Class对象
Class clazz=Class.forName("com.hyxy.entity.User");
System.out.println("----------所有公共构造方法----------");
Constructor[] conArr=clazz.getConstructors();
for(Constructor c:conArr){
System.out.println(c);
}
System.out.println("----------所有构造方法----------");
conArr=clazz.getDeclaredConstructors();
for(Constructor c:conArr){
System.out.println(c);
}
System.out.println("----------获取无参构造方法----------");
Constructor con=clazz.getConstructor(null);
//因为是无参的构造方法所以类型是一个null,不写也可以.返回的是描述这个无参构造函数的类对象.
System.out.println("con="+con);
Object obj=con.newInstance();
System.out.println("----------获取私有构造方法并调用----------");
con=clazz.getDeclaredConstructor(boolean.class);
System.out.println(con);
con.setAccessible(true);//忽略访问修饰符访问
obj=con.newInstance(true);
}
}
运行结果:
四、获取成员变量
1、批量的
1)Field[] getFields():获取所有的"公有字段"
2)Field[] getDeclaredFields():获取所有字段,包括:私有、受保护、默认、公有;
2、获取单个的
1)public Field getField(String fieldName):获取某个"公有的"字段;
2)public Field getDeclaredField(String fieldName):获取某个字段(可以是私有的)
设置字段的值:Field 类的 public void set(Object obj,Object value) 方法
obj:要设置的字段所在的对象;value:要为字段设置的值;
User类:
package com.hyxy.entity;
public class User {
public User(){}
public String name;
protected int age;
char sex;
private String uid;
public String toString() {
return "User [name=" + name + ", age=" + age + ", sex=" + sex + ", uid=" + uid + "]";
}
}
测试类:
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import com.hyxy.entity.User;
public class Reflection2 {
public static void main(String[] args) throws Exception {
//1、加载Class对象
Class clazz=Class.forName("com.hyxy.entity.User");
System.out.println("----------获取所有公有的字段----------");
Field[] fieldArray = clazz.getFields();
for(Field f : fieldArray){
System.out.println(f);
}
System.out.println("----------获取所有字段----------");
fieldArray =clazz.getDeclaredFields();
for(Field f : fieldArray){
System.out.println(f);
}
System.out.println("----------获取公有字段并调用----------");
Field f = clazz.getField("name");
System.out.println(f);
//获取一个对象
Object obj = clazz.getConstructor().newInstance();//相当于User user=new User();
//为字段设置值
f.set(obj, "joda");//为User对象中的name属性赋值,相当于user.name = "joda";
//验证
User user = (User)obj;
System.out.println("姓名:" + user.name);
System.out.println("----------获取私有字段并调用----------");
f = clazz.getDeclaredField("uid");
System.out.println(f);
f.setAccessible(true);//暴力反射,解除私有限定
f.set(obj, "123123");
System.out.println(user);
}
}
运行结果:
五、获取成员方法
1、批量的:
public Method[] getMethods():获取所有"公有方法";(包含了父类的方法也包含Object类)
public Method[] getDeclaredMethods():获取所有的成员方法,包括私有的(不包括继承的)
2、获取单个的:
public Method getMethod(String name,Class<?>... parameterTypes); name : 方法名;Class ... : 形参的Class类型对象
public Method getDeclaredMethod(String name,Class<?>... parameterTypes)
调用方法: Method --> public Object invoke(Object obj,Object... args):
obj :要调用方法的对象;args:调用方式时所传递的实参
User类:
package com.hyxy.entity;
public class User {
public void method1(String s){
System.out.println("调用了公有的,String参数的method1(): s = " + s);
}
protected void method2(){
System.out.println("调用了受保护的,无参的method2()");
}
void method3(){
System.out.println("调用了默认的,无参的method3()");
}
private String method4(int i){
System.out.println("调用了私有的,并且返回类型为String,int参数的method4(): i = " + i);
return "method4 return";
}
}
测试类:
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import com.hyxy.entity.User;
public class Reflection2 {
public static void main(String[] args) throws Exception {
//1、加载Class对象
Class clazz=Class.forName("com.hyxy.entity.User");
System.out.println("----------获取所有的公有方法----------");
clazz.getMethods();
Method[] methodArray = clazz.getMethods();
for(Method m : methodArray){
System.out.println(m);
}
System.out.println("----------获取所有的方法----------");
methodArray = clazz.getDeclaredMethods();
for(Method m : methodArray){
System.out.println(m);
}
System.out.println("----------获取公有的method1()方法----------");
Method m = clazz.getMethod("method1", String.class);
System.out.println(m);
//实例化一个User对象
Object obj = clazz.getConstructor().newInstance();
m.invoke(obj, "joda");
System.out.println("----------获取私有的method4()方法----------");
m = clazz.getDeclaredMethod("method4", int.class);
System.out.println(m);
m.setAccessible(true);//解除私有限定
Object result = m.invoke(obj, 20);
System.out.println("返回值:" + result);
}
}
invoke方法其实就相当于通过setVar()来设置字段值,不过同时还会调用该方法
运行结果:
六、反射main方法
User类:
package com.hyxy.entity;
public class User {
public static void main(String[] args) {
System.out.println("main方法执行了");
}
}
注意:反射的是User的main方法
测试类:
import java.lang.reflect.Method;
import com.hyxy.entity.User;
public class Reflection2 {
public static void main(String[] args) {
try {
//1、获取Student对象的字节码
Class clazz = Class.forName("com.hyxy.entity.User");
//2、获取main方法
Method methodMain = clazz.getMethod("main", String[].class);//第一个参数:方法名称,第二个参数:方法形参的类型,
//3、调用main方法
// methodMain.invoke(null, new String[]{"a","b","c"});
//第一个参数,对象类型,因为方法是static静态的,所以为null可以,第二个参数是String数组,这里要注意在jdk1.4时是数组,jdk1.5之后是可变参数
//这里拆的时候将 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();
}
}
}
七、通过反射运行配置文件内容
user类:
package com.hyxy.entity;
public class User {
public void show(){
System.out.println("show()");
}
}
配置文件propertities.txt:
className = com.hyxy.entity.User
methodName=show
测试类:
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Properties;
import com.hyxy.entity.User;
public class Reflection3 {
public static void main(String[] args) throws Exception {
//通过反射获取Class对象
Class stuClass = Class.forName(getValue("className"));
//2获取show()方法
Method m = stuClass.getMethod(getValue("methodName"));
//3.调用show()方法
m.invoke(stuClass.getConstructor().newInstance());
}
//此方法接收一个key,在配置文件中获取相应的value
public static String getValue(String key) throws IOException{
Properties pro = new Properties();//获取配置文件的对象
FileReader in = new FileReader("propertites.txt");//获取输入流
pro.load(in);//将流加载到配置文件对象中
in.close();
return pro.getProperty(key);//返回根据key获取的value值
}
}
当我们升级这个系统时,不需要User类了,而需要新写一个User2类时,这时只需要更改propertities.txt的文件内容就可以了,测试类的代码就不需要改动了。
八、通过反射越过泛型检查
泛型用在编译期,编译过后泛型擦除(消失掉)。所以是可以通过反射越过泛型检查的
例如有一个String泛型的集合,可以利用这个特点向集合中添加一个Integer类型的值
import java.lang.reflect.Method;
import java.util.ArrayList;
import com.hyxy.entity.User;
public class Reflection4 {
public static void main(String[] args) throws Exception {
ArrayList<String> strList = new ArrayList<>();
strList.add("a");
strList.add("b");
// strList.add(1); 编译错误
//获取ArrayList的Class对象,反向的调用add()方法,添加数据
Class listClass = strList.getClass(); //得到 strList 对象的字节码 对象
//获取add()方法
Method m = listClass.getMethod("add", Object.class);
//调用add()方法
m.invoke(strList, 1);
//遍历集合
for(Object obj : strList){
System.out.println(obj+",类型:"+obj.getClass().getName());
}
}
}
运行结果:
是不是很神奇:D
参考资料:
1、https://edu.csdn.net/course/play/6025/113181
2、https://blog.csdn.net/sinat_38259539/article/details/71799078
3、https://blog.csdn.net/weixin_36586120/article/details/80398583