Java反射学习记录
Java反射产生的背景:Java一开始是静态语言,静态语言就是程序在运行时,可以根据某些条件改变自身结构。Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
加载完类之后,在堆内存的方法区中就产生了一个Class类型(这个也叫运行时类)的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。
Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。Java的动态性让编程的时候更加灵活!
1.使用反射访问对象的属性,方法,创建对象。
Class clazz = Person.class;//这个就叫运行时类,每个类只有一个运行时类 //1.通过反射,创建Person类的对象 Constructor cons = clazz.getConstructor(String.class,int.class); Object obj = cons.newInstance("Tom", 12);
Person p = (Person) obj;
//上面两步可以合并为一步,如下
//Person person = (Person)clazz.getConstructor(String.class, int.class).newInstance("Tom", 12);
//上面是通过带参构造器使用反射创建的对象,对于无参构造器,如下:
Person person = (Person)clazz.getConstructor().newInstance();//该方法要求实体类中必须存在无参构造器
System.out.println(p.toString());
//2.通过反射,调用对象指定的属性、方法 //调用属性 Field age = clazz.getDeclaredField("age"); age.set(p,10); System.out.println(p.toString()); //调用方法 Method show = clazz.getDeclaredMethod("show"); show.invoke(p); System.out.println("*******************************"); //通过反射,可以调用Person类的私有结构的。比如:私有的构造器、方法、属性 //调用私有的构造器 Constructor cons1 = clazz.getDeclaredConstructor(String.class); cons1.setAccessible(true); Person p1 = (Person) cons1.newInstance("Jerry"); System.out.println(p1); //调用私有的属性 Field name = clazz.getDeclaredField("name"); name.setAccessible(true); name.set(p1,"HanMeimei"); System.out.println(p1); //调用私有的方法 Method showNation = clazz.getDeclaredMethod("showNation", String.class); showNation.setAccessible(true); String nation = (String) showNation.invoke(p1,"中国");//相当于String nation = p1.showNation("中国") System.out.println(nation);
//调用私有属性,方法等需要设置访问权限为true
//调用方法:首先用反射得到一个Method对象,用该方法调用invoke(),如果有参数就把参数放入invoke()中,但是invoke()方法第一个参数必须是对象
2.理解Class类
Object类中有一个方法public final Class getClass();该方法返回一个Class类的对象,Class类是Java反射的源头。通过对象反射求出类的名称。
对于每个类而言,JRE 都为其保留一个不变的Class 类型的对象。一个Class 对象包含了特定某个结构(class/interface/enum/annotation/primitive type/void/[])的有关信息。
Class本身也是一个类,Class对象只能由系统建立对象,一个加载的类在JVM 中只会有一个Class实例,每个类的实例都会记得自己是由哪个Class 实例所生成,通过Class可以完整地得到一个类中的所有被加载的结构,
Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象。
Class类的常用方法:
static Class forName(String name):返回指定类名name的Class对象
Object newInstance():调用无参构造器,返回该Class对象的一个实例
getName():返回此Class对象所表示的实体(类,接口,数组类,基本类型或void)名称
Class getSuperClass():返回当前Class对象的父类的Class对象
Class[] getInterfaces():获取当前Class对象的接口
ClassLoader getClassLoader():返回该类的类加载器
Class getSuperclass():返回表示此Class所表示的实体的超类的Class。
Constructor[] getConstructors():返回一个包含某些Constructor对象的数组
Constructor getConstrucetor(Class..clazz):返回指定参数类型的构造器
Field[] getDeclaredFields():获取当前运行时类声明的所有属性,不包括父类中声明的属性
Field[] getFields();获取当前运行时类及其父类中声明为public访问权限的属性
Field getDeclaredField(String name):返回指定name的属性
Method[] getMethods(String name, Class... paramType):返回一个Method对象,此对象的形参类型为paramType
Method getDeclaredMethod(String name, Class ... paramType);获取指定名称及参数的方法
3.获取Class类的实例
1).Class clazz = String.class;
2).Class clazz = "www.atguigu.com".getClass();
3).Class clazz = Class.forName("www.atguigu.com");
4).Class clazz = this.getClass().getClassLoader().loadClass("类的全类名");
4.类的加载过程
程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。
接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例。
换句话说,Class的实例就对应着一个运行时类。
加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。
通过反射创建对应的运行时类的对象
newInstance():调用此方法,创建对应的运行时类的对象,内部调用了运行时类的空参的构造器。
要想此方法正常的创建运行时类的对象,要求:
(1)运行时类必须提供空参的构造器
(2)空参的构造器的访问权限得够,通常设置为public
在javabean中要求提供一个public的空参构造器。原因:
(1)便于通过反色和,创建运行时类的对象
(2)便于子类继承此运行时类时,默认调用super()时,保证弗雷有此构造器
ClassLoader
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化。
加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。这个加载的过程需要类加载器参与。
类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。
类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。
类加载器作用是用来把类(class)装载进内存的。JVM 规范定义了如下类型的类的加载器。
//对于自定义类,使用系统类加载器进行加载
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader);
//调用系统类加载器的getParent():获取扩展类加载器
ClassLoader classLoader1 = classLoader.getParent();
System.out.println(classLoader1);
//调用扩展类加载器的getParent():无法获取引导类加载器
//引导类加载器主要负责加载java的核心类库,无法加载自定义类的。
ClassLoader classLoader2 = classLoader1.getParent();
System.out.println(classLoader2);
//即使使用核心类库,可以获得引导类加载器,但输出值为null
ClassLoader classLoader3 = String.class.getClassLoader();
System.out.println(classLoader3);
链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。
- 验证:确保加载的类信息符合JVM规范,例如:以cafe开头,没有安全方面的问题
- 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
初始化:
- 执行类构造器<clinit>()方法的过程。类构造器<clinit>()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。
- 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
- 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步。
5.创建运行时类的对象
6.获取运行时类的完整结构
(1).获取运行时类指定的属性
Field name = clazz.getDeclaredField("name");
name.setAccessible(true);//保证当前属性是可访问的
name.set(p, "Tom");//获取指定对象的此属性值
name.get(p);//获取指定对象的此属性值
(2).获取运行时类的指定方法
Method show = clazz.getDeclaredMethod("show", String.class);//参数1:指明获取的方法的名称;参数2:指明获取的方法的形参列表
show.setAccessible(true);//保证当前方法是可用的
Object returnValue = show.invoke(p,"CHN"); //invoke()方法,参数1:方法的调用者;参数2:给方法形参赋值的实参,返回值即为对应类中的方法的返回值;
(3).获取运行时类中指定的构造器
Constructor constructor = clazz.getDeclaredConstructor(String.class);//参数:指明构造器的参数列表
constructor.setAccessible(true);//保证此构造器是可访问的
Person per = (Person) constructor.newInstance("Tom");//调用此构造器创建运行时类的对象
(4).获取当前运行时类及其父类中声明为public的所有属性
Field[] fields = clazz.getFields();当前运行时类及其父类中声明为public访问权限的属性
Field[] declaredFields = clazz.getDeclaredFields();//获取当前运行时类中声明的所有属性。(不包含父类中声明的属性)
(5).属性的权限修饰符 数据类型 变量名
Field[] declaredFields = clazz.getDeclaredFields();//获取当前运行时类所有的属性
for(Field f : declaredFields){
int modifier = f.getModifiers();//1.权限修饰符
System.out.print(Modifier.toString(modifier) + "\t");
Class type = f.getType(); //2.数据类型
System.out.print(type.getName() + "\t");
String fName = f.getName();//3.变量名
System.out.print(fName);
}
(6).获取运行时类的方法结构
Method[] methods = clazz.getMethods();//获取当前运行时类及其所有父类中声明为public权限的方法
Method[] declaredMethods = clazz.getDeclaredMethods();//获取当前运行时类中声明的所有方法。(不包含父类中声明的方法)
@Xxxx 权限修饰符 返回值类型 方法名(参数类型1 形参名1,...) throws XxxException{}
Method[] declaredMethods = clazz.getDeclaredMethods();
for(Method m : declaredMethods){
Annotation[] annos = m.getAnnotations();//1.获取方法声明的注解
for(Annotation a : annos){
System.out.println(a);
}
System.out.print(Modifier.toString(m.getModifiers()) + "\t"); //2.权限修饰符
System.out.print(m.getReturnType().getName() + "\t");//3.返回值类型
System.out.print(m.getName());//4.方法名
System.out.print("(");
//5.形参列表
Class[] parameterTypes = m.getParameterTypes();
if(!(parameterTypes == null && parameterTypes.length == 0)){
for(int i = 0;i < parameterTypes.length;i++){
if(i == parameterTypes.length - 1){
System.out.print(parameterTypes[i].getName() + " args_" + i);
break;
}
System.out.print(parameterTypes[i].getName() + " args_" + i + ",");
}
}
System.out.print(")");
//6.抛出的异常
Class[] exceptionTypes = m.getExceptionTypes();
if(exceptionTypes.length > 0){
System.out.print("throws ");
for(int i = 0;i < exceptionTypes.length;i++){
if(i == exceptionTypes.length - 1){
System.out.print(exceptionTypes[i].getName());
break;
}
System.out.print(exceptionTypes[i].getName() + ",");
}
}
(7)获取构造器结构
Constructor[] constructors = clazz.getConstructors();//获取当前运行时类中声明为public的构造器
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();//获取当前运行时类中声明的所有的构造器
获取运行时类的父类
Class clazz = Person.class;
Class superclass = clazz.getSuperclass();
获取运行时类的带泛型的父类
Class clazz = Person.class;
Type genericSuperclass = clazz.getGenericSuperclass();
获取运行时类的带泛型的父类的泛型
Class clazz = Person.class;
Type genericSuperclass = clazz.getGenericSuperclass();//带泛型的父类
ParameterizedType paramType = (ParameterizedType) genericSuperclass;//向下转型为参数类型
Type[] actualTypeArguments = paramType.getActualTypeArguments();//获取泛型类型
//System.out.println(actualTypeArguments[0].getTypeName());
System.out.println(((Class)actualTypeArguments[0]).getName());
获取运行时类实现的接口
Class clazz = Person.class;
Class[] interfaces = clazz.getInterfaces();
for(Class c : interfaces){
System.out.println(c);
}
//获取运行时类的父类实现的接口
Class[] interfaces1 = clazz.getSuperclass().getInterfaces();
for(Class c : interfaces1){
System.out.println(c);
}
获取运行时类所在的包
Class clazz = Person.class;
Package pack = clazz.getPackage();
System.out.println(pack);
获取运行时类声明的注解
Class clazz = Person.class;
Annotation[] annotations = clazz.getAnnotations();
for(Annotation annos : annotations){
System.out.println(annos);
}
总结:
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 intgetModifiers()取得修饰符
public Class<?>[] getExceptionTypes()取得异常信息
5.全部的Field
public Field[] getFields()
返回此Class对象所表示的类或接口的public的Field。
public Field[] getDeclaredFields()
返回此Class对象所表示的类或接口的全部Field。
Field方法中:
public intgetModifiers() 以整数形式返回此Field的修饰符
public Class<?> getType() 得到Field的属性类型
public String getName() 返回Field的名称。
6.6. Annotation相关
get Annotation(Class<T> annotationClass)
getDeclaredAnnotations()
7.泛型相关
获取父类泛型类型:Type getGenericSuperclass()
泛型类型:ParameterizedType
获取实际的泛型类型参数数组:getActualTypeArguments()
8.类所在的包
Package getPackage()
7.反射的应用:动态代理
动态代理的举例 interface Human{ String getBelief(); void eat(String food); } //被代理类 class SuperMan implements Human{ @Override public String getBelief() { return "I believe I can fly!"; } @Override public void eat(String food) { System.out.println("我喜欢吃" + food); } } class HumanUtil{ public void method1(){ System.out.println("====================通用方法一===================="); } public void method2(){ System.out.println("====================通用方法二===================="); } } 要想实现动态代理,需要解决的问题? 问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。 问题二:当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法a。 class ProxyFactory{ //调用此方法,返回一个代理类的对象。解决问题一 public static Object getProxyInstance(Object obj){//obj:被代理类的对象 MyInvocationHandler handler = new MyInvocationHandler(); handler.bind(obj); return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler); } } class MyInvocationHandler implements InvocationHandler{ private Object obj;//需要使用被代理类的对象进行赋值 public void bind(Object obj){ this.obj = obj; } //当我们通过代理类的对象,调用方法a时,就会自动的调用如下的方法:invoke() //将被代理类要执行的方法a的功能就声明在invoke()中 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { HumanUtil util = new HumanUtil(); util.method1(); //method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法 //obj:被代理类的对象 Object returnValue = method.invoke(obj,args); util.method2(); //上述方法的返回值就作为当前类中的invoke()的返回值。 return returnValue; } } public class ProxyTest { public static void main(String[] args) { SuperMan superMan = new SuperMan(); //proxyInstance:代理类的对象 Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan); //当通过代理类对象调用方法时,会自动的调用被代理类中同名的方法 String belief = proxyInstance.getBelief(); System.out.println(belief); proxyInstance.eat("四川麻辣烫"); System.out.println("*****************************"); NikeClothFactory nikeClothFactory = new NikeClothFactory(); ClothFactory proxyClothFactory = (ClothFactory) ProxyFactory.getProxyInstance(nikeClothFactory); proxyClothFactory.produceCloth(); } }