反射概述
1.反射的理解和好处
2.Class类的理解和特点
3.通过反射获取类中的成员【了解】
4.通过反射创建对象【掌握】
5.通过反射调用类中的成员【掌握】
6.通过反射观察泛型的本质
1.反射的理解和好处
反射的理解
正常的程序:
类——>对象——>调用里面的成员
反射:
对象——>类——>调用里面的成员
反射的好处
案例1:
张三和李四共同完成一个项目
张三:设计类:Music类(√)、Word类(×)
李四:创建对象,并调用里面的方法
public void method(){
Music m = new Music();
m.work();
Word w = new Word();//编译错误
w.work();
}
//使用反射
public void method(Object o){
Object obj = o.getClass().newInstance();
o.getClass().getMethod("work").invoke();
}
静态加载:编译期加载,也就是说编译时加载所有类,如果该类不存在,则直接报编译错误,所以依赖性太强。
动态加载:运行期加载,也就是说运行时加载用到的类,降低了依赖性
案例2:
method("john");
public<T> void method(T t){
t.getClass();
}
Class类的理解和特点 ★
理解
问:自定义的类,是一个对象吗?
答:是
问:自定义的类,是哪个类型的对象?
答:java.lang.Class类
There is a class named Class;
class Person{
String name;
public void method(){}
}
class Student{
}
class Book{
}
class MyClass{
String className;
public String getName(){}
Field[] fields;
Method[] methods;
}
问:Class类的对象如何创建
答:不是new出来的,而是系统自动创建的,我们可以通过一定的方式获取对象的引用
特点
1.Class本身也是一个类
2.Class 对象只能由系统建立对象
3.一个类在 JVM 中只会有一个Class实例
4.一个Class对象对应的是一个加载到JVM中的一个.class文件
5.每个类的实例都会记得自己是由哪个 Class 实例所生成
6.通过Class可以完整地得到一个类中的完整结构
反射详解
一、理解
反射:对象————>类————>调用成员
示例:
public<T> T method(T t){
System.out.println(t.getClass().getName());
Class c = t.getClass();
}
二、好处
反射属于java实现动态语言的关键,也就是通过反射使用类相当于动态加载。
静态加载:编译期加载,编译时加载所有的类,如果没有则报错,依赖性太强
动态加载:运行期加载,运行时加载需要的类,如果运行时不用该类,则不报错,降低了依赖性
三、关于类的 加载
1、类的加载时机
①当创建对象时(new)
②当子类被加载时
③调用类中的静态成员时
④通过反射
Class.forName("java.lang.String");
2、了解类的加载过程
①装载
通过类的加载器将字节码文件装载到内存中,从而生成一个Class类的对象,保存信息
②连接
将字节码文件中的二进制合并到jre,进行校验
③初始化
将类中需要初始化的成员进行初始化,一般主要是指静态成员
3、了解类的加载器
功能:负责装载字节码文件到内存中,从而生成Class类的对象
分类:
启动类加载器
引导类加载器
扩展类加载器
系统类加载器
自定义类加载器
装载的机制:
先从下往上检测是否被加载过,如果都没有,则从上往下依次判断是否能被加载
Class类的特点
一、说明
Class 类属于java.lang包的一个类型,负责解剖某个具体类型的内部结构信息
二、特点
1、本身属于一种类型,继承Object类
2、Class类的对象不是new出来的,而是系统创建出来的
3、对于某个具体类的Class类对象,在内存中只有一份,因为类只加载一次
4、某个具体类的对象都能记得它所属的类型,也就是可以获取Class类的对象
5.通过Class类可以完整的得到某个类的结构信息
6、Class类的对象存放在方法区,普通类的对象存放在堆中
三、获取Class类对象
1.通过全类名字符串来获取
Class c = Class.forName(全类名);
2.通过对象获取
Class c = 对象.getClass();
3.通过类获取
Class c = 类名.class;
4.通过类加载器来获取【了解】
Class c = classLoader.loadClass("全类名");
通过反射获取类的结构
一、获取所有属性
1.通过调用方法获取所有属性
Field[] fields = c.getFields();//获取所有public修饰的属性,包含本类以及从父类继承来的,不限于直接父类
Field[] fields = c.getDeclaredFields();//获取本类中定义的所有的属性,不问修饰符
2、获取属性,获取属性信息
for(Field field:fields){
//修饰符
String modifier = Modifier.toString(field.getModifiers());
//类型
String type = field.getType().getSimpleName();
//名
String name = field.getName();
}
二、获取所有方法
1.通过调用方法获取所有属性
Method[] methods = c.getMethods();//获取所有public修饰的方法,包含本类以及从父类继承来的,不限于直接父类
Method[] methods = c.getDeclaredMethods();;//获取本类中定义的所有的方法,不问修饰符
2、遍历方法,获取每个方法信息
for(Method method:methods){
//修饰符
String modifier = Modifier.toString(method.getModifiers());
//类型
String type = method.getReturnType().getSimpleName();
//名
String name = method.getName();
//参数
Class[] paraTypes = method.getParameterTypes();
}
三、获取所有构造
1.通过调用方法获取所有属性
Constructor[] constructors = c.getConstructors();//获取所有public修饰的构造器,包含本类以及从父类继承来的,不限于直接父类
Constructor[] constructors = c.getDeclaredConstructors();;//获取本类中定义的所有的构造器,不问修饰符
2、遍历构造器,获取每个构造器信息
for(Constructor con:constructors){
//修饰符
String modifier = Modifier.toString(con.getModifiers());
//名
String name = con.getName();
//参数
Class[] paraTypes = con.getParameterTypes();
}
四、获取包
Package package=c.getPackage();
五、获取父类
Class parent = c.getSuperClass();
六、获取接口
Class[] interfaces = c.getInterfaces();
七、获取父类或接口泛型
Type type = c.getGenericSuperClass();
ParameterizedType pt = (ParameterizedType)type;
Type[] genericTypes =pt.getActualArumentTypes();//泛型部分
八、获取注解
提示:只能获取运行策略为 RUNTIME的注解
Annotation[] anns = c.getAnnotations();
for(Annotation a:anns){
Type t = a.annotationType();
}
通过反射创建类对象
一.获取Class类对象
Class c = Student.class;
二、通过反射创建对象
方式一:调用无参构造器【推荐】
Object o = c.newInstance();
方式二:调用有参构造
Constructor con = c.getDeclaredConstructor(String.class,int.class);
con.setAccessible(true);
Object o2 = con.newInstance("john",12);
通过反射访问类成员
一、访问特定属性
/*
参数1:具体的对象
参数2:属性名
参数3:要赋的值
*/
public void setFieldValue(Object o,String name,Object value){
//1.获取Field对象
Field field=o.getClass().getDeclaredField(name);
//2.暴破
field.setAccessible(true);
//3.赋值
field.set(o,value);
}
/*
参数1:具体的对象
参数2:属性名
*/
public Object returnFieldValue(Object o,String name){
//1.获取Field对象
Field field=o.getClass().getDeclaredField(name);
//2.暴破
field.setAccessible(true);
return field.get(o);
}
二、访问特定方法
/*
参数1:对象
参数2:要调用的方法
参数3:实参列表
*/
public Object myInvoke(Object o,Method method,Object...objs){
method.setAccessible(true);
return method.invoke(o,objs);
}
示例:
1.获取Class类的对象
Class c = 对象.getClass();
2.获取指定的方法
Method method = c.getDeclaredMethod(方法名,XX.class);
method.setAccessible(true);
3.调用该方法
method.invoke(对象,实参列表);
通过反射观察泛型本质
面试题1:
List<Integer> list1 = new ArrayList<>();
List list2 = new ArrayList<>();
syso(list1.getClass()==list2.getClass());true
面试题2:通过反射调用list1的add方法
Object o = list1.getClass().getMethod("add",Object.class).invoke(list1,12345);
图解Java反射
Java Reflection
Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
Java反射机制提供的功能
在运行时判断任意一个对象所属的类
在运行时构造任意一个类的对象
在运行时判断任意一个类所具有的成员变量和方法
在运行时调用任意一个对象的成员变量和方法
生成动态代理
Java反射机制研究及应用
反射相关的主要API:
java.lang.Class:代表一个类
java.lang.reflect.Method:代表类的方法
java.lang.reflect.Field:代表类的成员变量
java.lang.reflect.Constructor:代表类的构造方法
… …
Class 类
在Object类中定义了以下的方法,此方法将被所有子类继承:
● public final Class getClass()
以上的方法返回值的类型是一个Class类,此类是Java反射的源头,实际上所谓反射从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名称。
对象照镜子后可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接口。对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个类的有关信息。
Class本身也是一个类
Class 对象只能由系统建立对象
一个类在 JVM 中只会有一个Class实例
一个Class对象对应的是一个加载到JVM中的一个.class文件
每个类的实例都会记得自己是由哪个 Class 实例所生成
通过Class可以完整地得到一个类中的完整结构
Class类的常用方法
反射的应用举例
String str = “test4.Person”;
Class clazz = Class.forName(str);
Object obj = clazz.newInstance();
Field field = clazz.getField(“name”);
field.set(obj, “Peter”);
Object obj2 = field.get(obj);
System.out.println(obj2);
实例化Class类对象(四种方法)
1)前提:若已知具体的类,通过类的class属性获取,该方法
最为安全可靠,程序性能最高
实例:
Class clazz = String.class;
2)前提:已知某个类的实例,调用该实例的getClass()方法获
取Class对象
实例:
Class clazz = “www.atguigu.com”.getClass();
3)前提:已知一个类的全类名,且该类在类路径下,可通过
Class类的静态方法forName()获取,可能抛出ClassNotFoundException
实例:
Class clazz = Class.forName(“java.lang.String”);
4)其他方式(不做要求)
ClassLoader cl = this.getClass().getClassLoader();
Class clazz4 = cl.loadClass(“类的全类名”);
类的加载过程
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化。
ClassLoader
类加载器是用来把类(class)装载进内存的。JVM 规范定义了两种类型的类加载器:启动类加载器(bootstrap)和用户自定义加载器(user-defined class loader)。 JVM在运行时会产生3个类加载器组成的初始化加载器层次结构 ,如下图所示:
//1.获取一个系统类加载器
ClassLoader classloader = ClassLoader.getSystemClassLoader();
System.out.println(classloader);
//2.获取系统类加载器的父类加载器,即扩展类加载器
classloader = classloader.getParent();
System.out.println(classloader);
//3.获取扩展类加载器的父类加载器,即引导类加载器
classloader = classloader.getParent();
System.out.println(classloader);
//4.测试当前类由哪个类加载器进行加载
classloader =
Class.forName("exer2.ClassloaderDemo").getClassLoader();
System.out.println(classloader);
//5.测试JDK提供的Object类由哪个类加载器加载
classloader =
Class.forName("java.lang.Object").getClassLoader();
System.out.println(classloader);
//*6.关于类加载器的一个主要方法:getResourceAsStream(String str):获取类路径下的指定文件的输入流
InputStream in = null;
in = this.getClass().getClassLoader().getResourceAsStream("exer2\\test.properties");
System.out.println(in);
创建运行时类的对象
有了Class对象,能做什么?
创建类的对象:调用Class对象的newInstance()方法
要 求:
1)类必须有一个无参数的构造器。
2)类的构造器的访问权限需要足够。
难道没有无参的构造器就不能创建对象了吗?
不是!只要在操作的时候明确的调用类中的构造器,并将参数传递进去之后,才可以实例化操作。步骤如下:
1)通过Class类的getDeclaredConstructor(Class … parameterTypes)取得本类的指定形参类型的构造器
2)向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数。
3)通过Constructor实例化对象。
以上是反射机制应用最多的地方。
//1.根据全类名获取对应的Class对象
String name = “atguigu.java.Person";
Class clazz = null;
clazz = Class.forName(name);
//2.调用指定参数结构的构造器,生成Constructor的实例
Constructor con = clazz.getConstructor(String.class,Integer.class);
//3.通过Constructor的实例创建对应类的对象,并初始化类属性
Person p2 = (Person) con.newInstance("Peter",20);
System.out.println(p2);
通过反射获取运行时类的完整结构
Field、Method、Constructor、Superclass、Interface、Annotation
实现的全部接口
所继承的父类
全部的构造器
全部的方法
全部的Field
使用反射可以取得:
1.实现的全部接口
public Class<?>[] getInterfaces()
确定此对象所表示的类或接口实现的接口。
2.所继承的父类
public Class<? Super T> getSuperclass()
返回表示此 Class 所表示的实体(类、接口、基本类型)的父类的 Class。
3.全部的构造器
public Constructor<T>[] getConstructors()
返回此 Class 对象所表示的类的所有public构造方法。
public Constructor<T>[] getDeclaredConstructors()
返回此 Class 对象表示的类声明的所有构造方法。
Constructor类中:
取得修饰符: public int getModifiers();
取得方法名称: public String getName();
取得参数的类型:public Class<?>[] getParameterTypes();
4.全部的方法
public Method[] getDeclaredMethods()
返回此Class对象所表示的类或接口的全部方法
public Method[] getMethods()
返回此Class对象所表示的类或接口的public的方法
Method类中:
public Class<?> getReturnType()取得全部的返回值
public Class<?>[] getParameterTypes()取得全部的参数
public int getModifiers()取得修饰符
public Class<?>[] getExceptionTypes()取得异常信息
5.全部的Field
public Field[] getFields()
返回此Class对象所表示的类或接口的public的Field。
public Field[] getDeclaredFields()
返回此Class对象所表示的类或接口的全部Field。
Field方法中:
public int getModifiers() 以整数形式返回此Field的修饰符
public Class<?> getType() 得到Field的属性类型
public String getName() 返回Field的名称。
6.Annotation相关
get Annotation(Class<T> annotationClass)
getDeclaredAnnotations()
7.泛型相关
获取父类泛型类型:Type getGenericSuperclass()
泛型类型:ParameterizedType
获取实际的泛型类型参数数组:getActualTypeArguments()
8.类所在的包 Package getPackage()
小 结:
1.在实际的操作中,取得类的信息的操作代码,并不会经常开发。
2.一定要熟悉java.lang.reflect包的作用,反射机制。
3.如何取得属性、方法、构造器的名称,修饰符等。
通过反射调用类中的指定方法、指定属性
1.调用指定方法
通过反射,调用类中的方法,通过Method类完成。步骤:
1.通过Class类的getMethod(String name,Class…parameterTypes)方法取得一个Method对象,并设置此方法操作时所需要的参数类型。
2.之后使用Object invoke(Object obj, Object[] args)进行调用,并向方法中传递要设置的obj对象的参数信息。
Object invoke(Object obj, Object … args)
说明:
1.Object 对应原方法的返回值,若原方法无返回值,此时返回null
2.若原方法若为静态方法,此时形参Object obj可为null
3.若原方法形参列表为空,则Object[] args为null
4.若原方法声明为private,则需要在调用此invoke()方法前,显式调用方法对象的setAccessible(true)方法,将可访问private的方法。
2.调用指定属性
在反射机制中,可以直接通过Field类操作类中的属性,通过Field类提供的set()和get()方法就可以完成设置和取得属性内容的操作。
public Field getField(String name) 返回此Class对象表示的类或接口的指定的public的Field。
public Field getDeclaredField(String name)返回此Class对象表示的类或接口的指定的Field。
在Field中:
public Object get(Object obj) 取得指定对象obj上此Field的属性内容
public void set(Object obj,Object value) 设置指定对象obj上此Field的属性内容
注:在类中属性都设置为private的前提下,在使用set()和get()方法时,首先要使用Field类中的setAccessible(true)方法将需要操作的属性设置为可以被外部访问。
public void setAccessible(true)访问私有属性时,让这个属性可见。