1 什么是java语言反射机制?
反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取类的信息以及动态调用对象的方法的功能称为java语言的反射机制。是动态语言的关键,反射机制允许程序在运行期间借助于Reflect API取得类的内部信息,并能直接操作任意对象的内部属性和方法
反射就是通过获取到该类的字节码文件对象---->即:Class类对象,通过Class类对象获取该类里面的一些属性(成员变量)、构造方法、成员方法等----如何获取等
class文件简介及加载:点击打开链接
Java类的加载过程:点击打开链接,点击打开链接,点击打开链接,点击打开链接,点击打开链接
说明:两种理解方式均可
反射的反是如何理解的?(可能不太准确)
理解:要结合正常创建对象的过程→(1)引入需要的类的全路径→(2)new实例化→(3)取得实例化对象→(4)类的结构(调用)
反射方式:(1)先实例化对象→(2)getClass()方法→(3)得到完整的"包类"名称→(4)类的结构
2 Java反射机制的功能
(1)运行期间判断一个对象所属的类别;是Student类的对象还是Teacher类的对象
(2)运行期间构造任意类的对象;不管构造方法是否私有化
(3)在运行期间判断任意一个类所具有的成员变量和方法
(4)在运行期间调用任意一个对象的成员变量和方法
(5)生成动态代理
通俗的理解:通过反射,任意类都是完全暴露的,可以获取类的完整结构
编写的某个类→javac.exe 编译→.class文件→java.exe加载(JVM的类加载器)到内存中→此时.class文件就是一个运行时的类,存方在缓存区→这个运行类本身就是一个Class的实例,每个Class类的实例对应着一个运行时的类。每个运行时的类只加载一次!
3 面试题:如何获取类的字节码文件对象(Class类的实例)?并且有几种方式呢?
1)Object类中的getClass()方法
说明:通过运行时类的对象获取,调用其getClass()返回其运行时的类(打印可以看出)
2)数据类型的class属性 举例:String.class、Student.class;调用运行时类本身的.class属性
3)Class类中的特有方法:forName(String className)---(重点,数据库加载驱动:Drivers)---静态方法获得类名对应的Class对象
说明:开发中常使用的方式,因为第三种里面的参数是一个字符串;一个Class对象表示特定类的对象(属性)
注意:第三种方式的是类的全路径名称的字符串形式;第一种还需要创建对象,第二种可能导包导错(相同的类处于不同的包中),如果没有此类,编译时就报错
实例1
package demo;
public class Demo1 {
public static void main(String[] args) throws ClassNotFoundException {
Person person=new Person();//补充person与clazz等无法进行比较(不在一个层面上)
Class<Person> person1=Person.class;//得到Person.class字节码文件对象
Class person2=person.getClass();//获取的是类的字节码文件对象
Class<?> person3 = Class.forName("demo.Person");//加载并且获得Person.class字节码文件对象
//如何方式3找不到类的全限定名:出现java.lang.ClassNotFoundException异常
}
}
说明1:出现黄色警告线原因---Class类实际是一个泛型类,大多数情况可以忽略类型参数,而使用原始的Class类;
说明2:Person类的字节码文件在内存中只有一份,所以三种方式通过"=="判断都是true;
注意:一个Class对象实际是一个类型,而此类型未必是一个类;int不是类,但int.class是一个Class类型的对象
回顾:在反射之前,在启动程序时,包含main方法的类首先被加载,它会加载所有需要的类,被加载的类又会继续加载它需要的类,以此类推;对于大型应用程序来说,类的层层加载会消耗很多时间,用户不耐烦。
反射应用:给用户一个启动速度比较快的幻觉---首先保证包含main方法的类没有显示地引用其它的类,在显示一个启动画面,然后通过调用Class.forName手工地加载其它的类
通过三种方式可以获取运行时Class类的实例,那么有了Class类的实例以后,我们可以干啥呢?
(1)*创建对应的运行时类的对象
(2)获取对应运行时类的完整结构(构造器,属性、方法、内部类、包命、注解、异常等),但无法获取代码块
(3)*调用运行时类的指定结构(构造器、属性、方法)
(4)反射的应用:动态代理
Class类
在程序运行期间(前提),Java运行时系统始终为所有的对象维护着一个被称为运行时的标识类型,这个信息(标识类型的信息)跟踪着每个对象所属的类;虚拟机利用运行时类型信息选择相应的方法执行。
理解:虚拟机为每个类型管理一个Class对象;注意:不是每个对象,通过"=="运算符的比较也可以看出。
那么如何访问这些信息?通过专门的java类访问这些信息,保存这些信息的类被称为Class。
提前说明:JVM将类中的所有信息都进行封装,以相应类的形式进行封装
常用获取构造器的方法
(1)getName---返回类的全限定类名----补充的
(2)public Constructor<?>[] getConstructors()---返回的是所有公共构造方法所在数组
(3)public Constructor<?>[] getDeclaredConstructors():获取的是当前字节码文件对象中所有的(静态的,私有的,受保护的)构造方法数组
(4)public Constructor<T> getConstructor(Class<?>... parameterTypes):它反映此 Class 对象所表示的类的某个指定公共构造方法
参数说明:参数为对应构造方法的数据类型的字节码文件对象,形如String.class或int.class;可变参数类型
(5)public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes):表示获取类或者接口中的指定的(任意一个)构造方法
实例2
package demo;
import java.lang.reflect.Constructor;
public class Demo2 {
public static void main(String[] args) throws Exception{
//方式1---public Constructor<?>[] getConstructors(),返回的是一个构造方法所在数组
//加"s"获取所有的公共的(public修饰的)构造方法
Constructor<?>[] constructors = person.getConstructors();
//遍历构造方法
for(Constructor con:constructors){
System.out.println(con);
}
System.out.println("---------------------");
//方式2
Constructor<?>[] dc = person.getDeclaredConstructors();
for(Constructor con:dc){
System.out.println(con);
}
System.out.println("----------------------");
//方式3---获取某一个公共的构造方法
Constructor<?> con = person.getConstructor(String.class,String.class);
System.out.println(con);
//方式4---获取某个构造方法,我们就获取个私有的方法
Constructor<?> dcr = person.getDeclaredConstructor(String.class,int.class);
System.out.println(dcr);
//了解一下打印的内容:public demo.Person(java.lang.String,int)
}
}
动态的创建一个类的实例----不经过编译时期的检查(翻墙)
(6)public 类名 newInstance():
说明:可以直接通过Class的newInstance()的方法,特点:对无参数默认构造器初始化并创建对象,必须强转类型
除了Class的此方法,还有没有其它的途径创建对象?当然有
Constructor类
理解上:其实类似于将int类型封装成Integer类那样,将构造方法封装成Constructor类,即将类中的每个成分都封装成对象
(1)public T newInstance(Object... initargs)
说明:传递的是实际参数(可变参数),该方法等效于创建一个类的对象(动态)
实例3 创建对象动态和静态(公共的构造方法)
package demo;
import java.lang.reflect.Constructor;
public class Demo3 {
public static void main(String[] args) throws Exception {
//(1)静态
Person person = new Person();
//System.out.println(person);
//(2)动态
Person person2 = Person.class.newInstance();
System.out.println(person==person2);
//(3)动态
Class<?> person3 = Class.forName("demo.Person");
Constructor<?> dc = person3.getDeclaredConstructor();//向上转型--?的位置
Object obj = dc.newInstance();//最常用的,返回值也最特殊,注意使用时最好强转
//(4)动态
Class<? extends Person> class1 = person.getClass();
Constructor<? extends Person> dc1 = class1.getDeclaredConstructor();//无参--向下转型--?的位置
Person person4 = dc1.newInstance();
}
}
注意:(2)和(3)以及(4)的newInstance()方法的返回值
那么问题来了,私有的构造方法能否创建对象?
(2)public void setAccessible(boolean flag):在访问的时候取消java语言访问检查(强制性)---常常是私有的构造方法;常常"flag=true" ----在给构造创建实例对象之前就应该取消检查
实例4
package demo;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Demo4 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
//动态的创建类对象,并初始化(私有的和公共的)
//1:私有的方式
String str="demo.Person";
Class<?> c = Class.forName(str);
Constructor<?> dc = c.getDeclaredConstructor(String.class,int.class);
dc.setAccessible(true);//私有的构造方法不能直接访问
Object obj = dc.newInstance("小王",27);
System.out.println(obj);//调用Person类的toString()方法
//2:公共的方式
Constructor<?> con = c.getConstructor(String.class,String.class);
Object ne = con.newInstance("小王","0012");
System.out.println(ne);
}
}
说明:注意构造方法的两个用途--创建对象和成员变量初始化
Class类中的关于变量的方法
通过反射获取成员变量并使用:成员变量--->Field
(1)获取所有的公共的成员变量public Field[] getFields()
说明:所有的公共的可访问的字段,返回的是Field对象数组
(2)public Field[] getDeclaredFields()
说明:获取当前字节码文件对象中所有的(公共的或者私有等)的字段
(3)public Field getField(String name)
说明:获取公共的指定的字段,参数为当前成员变量名称的字符串形式
(4)public Field getDeclaredField(String name)
说明:获取类或接口中已经声明的指定的字段(一般私有的)
Field类中的方法
注意1:如何修改obj实例对象的成员变量?即将指定对象变量上此 Field对象表示的字段设置为指定的新值---修改成员变量?
(1)public void set(Object obj, Object value)
说明:给obj实例对象里面的成员变量设置一个实际参数;obj为动态的对象,非forName等其他形式创建的字节码文件对象
思考:为什么修改成员变量,除了要修改值,还得要指定对象?
原因:可能内存中有好多通过反射创建的对象,你是针对具体的对象中的变量进行修改的,JVM不可能那么智能
注意2:如果想对私有成员变量设置值,必须在设置值之前取消Java语言的访问检查:Field类的方法---setAccessible(true);
注意3:输出每个对象不是地址的原因:多态
实例5
package demo;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
/**
* 通过反射获取成员变量并使用
* 成员变量--->Field
*
* @author Orange
* @version 1.8
*/
public class Demo5 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, SecurityException, NoSuchMethodException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
//(1)创建Class对象
Class<?> person = Class.forName("demo.Person");
//(2)获取所有的公共的成员变量public Field[] getFields():所有的公共的可访问的字段,返回的是Field对象数组
Field[] fields = person.getFields();
//遍历
for(Field fie:fields){
System.out.println(fie);
}
System.out.println("--------------------------");
//(3)public Field[] getDeclaredFields():获取当前字节码文件对象中所有的公共的或者私有的字段
Field[] df = person.getDeclaredFields();
for(Field fie:df){
System.out.println(fie);
}
//(4)重点---获取单个公共的成员变量(Field类对象)
//public Field getField(String name):获取公共的指定的字段,参数为当前成员变量名称"address"
Field field = person.getField("age");
//(5)动态创建一个对象
Constructor<?> con = person.getConstructor();
Object obj = con.newInstance();
//有了Field对象,给当前obj实例对象设置一个参数
//将指定对象变量上此 Field对象表示的字段设置为指定的新值public void set(Object obj, Object value)
//给obj实例对象里面的成员变量设置一个实际参数---->value
field.set(obj, 27);
System.out.println(obj);
//(6)私有的成员变量
Field de = person.getDeclaredField("name");
de.setAccessible(true);
de.set(obj, "小王");
System.out.println(obj);
}
}
Class类中关于成员方法的方法
(1) public Method[] getDeclaredMethods():
说明:得到此类对象本身的所有方法(公共、受保护、默认、私有),但不包括继承,理解上:已经获取所有的了,继承的就算了吧
(2) public Method[] getMethods():获取当前该字节码文件对象(Person.class)中自己本身以及它父类中所有的公共成员方法
说明:除了自身的公共方法以外,还有继承的公共方法
(3) public Method getMethod(String name,Class<?>... parameterTypes):指定公共成员方法
参数1:表示方法名的字符串形式
参数2:参数类型数的class形式;例如:String.class(非实参),原因:由于可能出现重载的形式,只有方法名无法判断是哪个方法
说明:返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法(无继承的方法,自身类中特有的公共方法)。
(4) public Method getDeclaredMethod(String name,Class<?>... parameterTypes)
说明:得到该对象(无继承方法)反映此 Class 对象所表示的类或接口的指定已声明方法(可以私有化)
Method类
(1) public Object invoke(Object obj, Object... args)----对封装的方法进行调用→真正调用方法
参数1:表示当前针对哪个实例对象进行方法的调用----疑问静态方法不需要obj吧(回头查阅)!!!
参数2:当前调用该方法的时候里面传递的实际参数
说明:必须调用的动态对象,然后传递参数才能调用此对象的方法。
注意:如果是私有的方法,必须取消Java语言的访问检查---setAccessible(true)
补充一点:如果不知道该方法的修饰符,而想调用,就取消访问权限吧!!!
实例6 通过反射获取成员变量并使用,之前使用对象调用成员方法
package demo;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Demo6 {
/**
* 需求:
* 通过反射获取成员变量并使用,之前使用对象调用成员方法
* 类似:
* Person p = new Person() ;
* p.show() ;
*/
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
Class<?> person = Class.forName("demo.Person");
Constructor<?> con = person.getConstructor();
Object instance = con.newInstance();//动态的创建了一个对象
Method[] met = person.getMethods();
Method me = person.getMethod("method1");
Object inv = me.invoke(instance);
System.out.println(inv);
}
}
反射的应用
(1) 给你ArrayList<Integer>的一个对象,我想在这个集合中添加一个字符串数据,如何实现呢?
package reflect;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class UseReflect {
public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
//给你ArrayList<Integer>的一个对象,我想在这个集合中添加一个字符串数据,如何实现呢?
ArrayList<Integer> list = new ArrayList<>();
list.add(520);//自动拆装箱
list.add(521);
//list.add("hah");//编译时期不通过
Class<? extends ArrayList> clazz = list.getClass();
/**
* 不需要通过构造方法再创建一个对象
*
* 原因:因为我们是往同一个对象中添加的,此对象没有用
*/
//ArrayList arrayList = clazz.newInstance();
Method method = clazz.getDeclaredMethod("add", Object.class);
//注意:通过API看此add方法的参数类型,而不是写入自己添加数据的类型--String.class
method.setAccessible(true);//不管私有与否,取消检查
method.invoke(list,"你好");
//注意:此时遍历的类型变了,不是Integer了
for(Object lt:list){
System.out.println(lt);
}
}
}
分析:反射是在运行时完成一系列的动作,泛型只在编译时期检查,在运行时泛型已经擦除,因此可以添加其他内容
应用2:读取配置文件,在运行时动态的获取不同的类,进而创建不同的类对象。
(2) 动态代理(下一篇章介绍)
相关链接:
如何给面试官讲反射---点击打开链接
反射机制的应用场景:点击打开链接
反射详解:点击打开链接
框架中Java反射机制在Spring IOC中的应用:点击打开链接
深层:点击打开链接
补充类加载器:点击打开链接,点击打开链接,点击打开链接,点击打开链接,点击打开链接
未完待续。。。