第十五章:Java反射机制
2021年3月11日
11:30
lReflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。
通过反射,可以调用Person类的私有结构的。比如:私有的构造器、方法、属性
疑问1:通过直接new的方式或反射的方式都可以调用公共的结构,开发中到底用哪个?
//建议:用直接new的方式。
那么什么时候会使用反射的方式?
//这个关系到反射的特征:动态性,需要动态调用的时候用反射的方式。
疑问2:反射机制与面向对象中的封装性是不是矛盾的?如何看待两个技术?
//不矛盾。
//封装性是:建议去调什么,给出的一些提示,解决的是开发中调用哪个的问题。比如可以直接用公共的,就不要去调私有的。公共的可以解决问题,也可能写了很多处理的逻辑,更适合用。私有的是内部使用的,暴露出的公共的结构中调用了私有结构,方便直接使用,没必要单独再去调私有的。
//放到单例模式上是:一个对象就可以解决问题,再造一个对象也是一样用,跟原来的对象一样的用法。既然给你造好对象了,直接用就可以了。
//反射解决的是能不能调的问题。
关于java.lang.Class类的理解
1.类的加载过程:
程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。
接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件
加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此
运行时类,就作为Class的一个实例。
2.换句话说,Class的实例就对应着一个运行时类。
Ø Class 对象只能由系统建立对象
Ø Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象
3.加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。(获取到的是同一个,== 为true,见下面代码)
获取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(“类的全类名”);
//方式一:调用运行时类的属性:.class Class clazz1 = Person.class;
//方式二:通过运行时类的对象,调用getClass() Person person1 = new Person("Tom",12); Class clazz2 = person1.getClass();
//方式三:调用Class的静态方法:forName(String classPath) //通过全类名,使用频率最高 Class clazz3 = Class.forName("com.atguigu.java.Person");
//方式四:使用类的加载器:ClassLoader (了解) ClassLoader classLoader = ReflectionTest.class.getClassLoader(); Class clazz4 = classLoader.loadClass("com.atguigu.java.Person");
System.out.println(clazz1 == clazz2); //true System.out.println(clazz1 == clazz3); //true System.out.println(clazz1 == clazz4); //true |
哪些类型可以有Class对象?
(1)class:
外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
(2)interface:接口
(3)[]:数组
(4)enum:枚举
(5)annotation:注解@interface
(6)primitive type:基本数据类型
(7)void
Class c1 = Object.class; System.out.println(c1); //class java.lang.Object Class c2 = Comparable.class; System.out.println(c2); //interface java.lang.Comparable Class c3 = String[].class; System.out.println(c3); //class [Ljava.lang.String; Class c4 = int[][].class; System.out.println(c4); //class [[I Class c5 = ElementType.class; System.out.println(c5); //class java.lang.annotation.ElementType Class c6 = Override.class; System.out.println(c6); //interface java.lang.Override Class c7 = int.class; System.out.println(c7); //int Class c8 = void.class; System.out.println(c8); //void Class c9 = Class.class; System.out.println(c9); //class java.lang.Class
int[] a = new int[10]; int[] b = new int[100]; Class c10 = a.getClass(); Class c11 = b.getClass(); System.out.println(c10); //class [I System.out.println(c11); //class [I // 只要数组的元素类型与维度一样,就是同一个Class System.out.println(c10 == c11); //true |
获取实例时候加上泛型,以后不用再强转了
Class<Person> clazz0 = Person.class; Constructor<Person> constructor = clazz0.getConstructor(String.class, int.class); Person tomCat = constructor.newInstance("TomCat", 15); |
类加载器的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。
JVM 规范定义了如下类型的类的加载器:
引导类加载器:用C++编写的,是JVM自带的类加载器,负责Java平台核心库,用来装载核心类库。该加载器无法直接获取
扩展类加载器:负责jre/lib/ext目录下的jar包或 –D java.ext.dirs 指定目录下的jar包装入工作库
系统类加载器:负责java –classpath 或 –D java.class.path所指的目录下的类与jar包装入工作 ,是最常用的加载器
我们写的类是系统类加载器加载的
获取一个系统类加载器ClassLoader.getSystemClassLoader() ClassLoader classloader = ClassLoader.getSystemClassLoader(); System.out.println(classloader); //sun.misc.Launcher$AppClassLoader@18b4aac2 ClassLoader classLoader1 = ClassLoaderTest.class.getClassLoader(); System.out.println(classLoader1); //sun.misc.Launcher$AppClassLoader@18b4aac2 我们写的类是系统类加载器加载的 获取系统类加载器的父类加载器,即扩展类加载器 ClassLoader classLoader2 = classLoader1.getParent(); System.out.println(classLoader2); //sun.misc.Launcher$ExtClassLoader@28a418fc 获取扩展类加载器的父类加载器,即引导类加载器 获取不到,所以为null String作为Java平台核心库的类,也是由引导类加载器加载的 ClassLoader classLoader3 = classLoader2.getParent(); System.out.println(classLoader3); //null ClassLoader classLoader4 = String.class.getClassLoader(); System.out.println(classLoader4); //null |
关于类加载器的一个主要方法:getResourceAsStream(String str):获取类路径下的指定文件的输入流
Properties:用来读取配置文件的两种方式:
Properties pros = new Properties();
//读取配置文件的方式一:new一个FileInputStream //此时的文件默认在当前的module下。 //要读取src目录下的文件,需要加上src\\的路径 //FileInputStream fis = new FileInputStream("jdbc.properties"); //FileInputStream fis = new FileInputStream("src\\jdbc1.properties"); //pros.load(fis);
//读取配置文件的方式二: //使用ClassLoader的getResourceAsStream()生成InputStream //配置文件默认识别为:当前module的src下 ClassLoader classLoader = ClassLoaderTest.class.getClassLoader(); InputStream is = classLoader.getResourceAsStream("jdbc1.properties"); pros.load(is);
String user = pros.getProperty("user"); String password = pros.getProperty("password"); System.out.println("user = " + user + ",password = " + password); |
pros.load(InputStream is) 加载InputStream里的数据
pros.getProperty(String key) 获取对应的属性值
通过反射创建对应的运行时类的对象
Class对象的 newInstance() : 调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参的构造器。
Class<Person1> clazz = Person.class; Person p1 = clazz.newInstance(); |
默认返回Object类型的对象,用多态就不用强转类型了
要想此方法正常的创建运行时类的对象,要求:
1.运行时类必须提供空参的构造器(默认有的话也行)
2.空参的构造器的访问权限得够。通常,设置为public。
在javabean中要求提供一个public的空参构造器。原因:
1.便于通过反射,创建运行时类的对象
2.便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器
获取当前运行时类的属性结构
getFields():获取当前运行时类及其父类中声明为public访问权限的属性
getFileds只能获取public,跟在不在一个包无关(也就是即使在一个包也取不了默认属性的)
getDeclaredFields():获取当前运行时类中声明的所有属性。(不分权限。但不包含父类中声明的属性)
属性的 权限修饰符 数据类型 变量名 各项都可以获取到
权限修饰符:getModifiers();//返回int型,可以用Modifier.toString(intmod)转换
数据类型:getType();//返回Class实例,可用getName()方法获取全类名
变量名:getName();
获取运行时类的方法结构
getMethods():获取当前运行时类及其所有父类中声明为public权限的方法
getDeclaredMethods():获取当前运行时类中声明的所有方法。(不包含父类中声明的方法)
注解(有@符号):getAnnotations() //@com.atguigu.java1.MyAnnotation(value=hello)
权限修饰符:getModifiers() //static会包含在这里
Modifier.toString(modifiers)
返回值类型:getReturnType()
方法名:getName()
形参列表:getParameterTypes() //返回Class[]
抛出的异常:getExceptionTypes() //返回Class[],可以getName()
获取运行时类的其他结构
构造器:getConstructors() //获取当前运行时类中声明为public的构造器(没有父类的!)
getDeclaredConstructors() //获取当前运行时类中声明的所有的构造器
父类:getSuperclass() //getSuperclass() c小写
带泛型的父类:getGenericSuperclass() //com.atguigu.java1.Creature<java.lang.String> 返回类型是ParameterizedTypeImpl,实现了ParameterizedType接口。没有泛型的话,此方法直接调用getSuperclass()返回父类了,(ParameterizedType)会发生强转错误
获取运行时类的带泛型的父类的泛型
getGenericSuperclass() 获取带泛型的父类后,
(ParameterizedType) 强转成ParameterizedType
getActualTypeArguments() 调用方法返回Class类型的 Type[]
Class clazz = Person.class;
Type genericSuperclass = clazz.getGenericSuperclass(); //返回类型是ParameterizedTypeImpl,实现了ParameterizedType接口。没有泛型的话,此方法直接调用getSuperclass()返回父类了,下面会强转错误 ParameterizedType paramType = (ParameterizedType) genericSuperclass; //获取泛型类型 Type[] actualTypeArguments = paramType.getActualTypeArguments(); for(Type t : actualTypeArguments){ //返回的是Class类型的,可强转成Class,调用getName()。 //Type接口没有getName()方法。java.lang.String System.out.println(((Class)t).getName()); } |
Type接口没有getName()方法,可强转成Class,调用getName()
强转后调用方法,应该把强转和对象用括号括起来,然后再调用方法。!!
((Class)t).getName()
实现的接口:getInterfaces() //不包含父类实现的
//interface java.lang.Comparable
父类实现的接口:getSuperclass().getInterfaces()
所在的包:getPackage() //package com.atguigu.java1
类声明的注解:getAnnotations() //@com.atguigu.java1.MyAnnotation(value=hi)
调用运行时类中指定的结构:属性、方法、构造器
注意:getField()、getMethod()、getConstructor()方法要求运行时类中构造声明为public,所以通常不采用此系列方法
通用步骤:
1.获取指定的结构(属性、方法、构造器)
2.保证此结构是可访问的(不管是不是public的,都可以设置一下)
3.使用该结构
一、操作运行时类中的指定的属性
getDeclaredField(String fieldName): ①获取运行时类中指定变量名的属性
//NoSuchFieldException 找不到会报这个异常
//IllegalAccessException 获取到了,但是属于非法访问。set()和get()都不可以
setAccessible(true):②保证当前属性是可访问的
set(p1,1001): 参数1:指明设置哪个对象的属性参数 2:将此属性值设置为多少
get(p1): 参数:获取哪个对象的当前属性值
二、操作运行时类中的指定的方法
getDeclaredMethod("show",String.class) 参数1:指明获取的方法的名称 参数2:指明获取的方法的形参列表
setAccessible(true):保证当前方法是可访问的
Object chn = show.invoke(p,"CHN"); 调用方法的invoke():参数1:方法的调用者 参数2:给方法形参赋值的实参 其中invoke()的返回值即为对应类中调用的方法的返回值。
如果调用的运行时类中的方法没有返回值,则此invoke()返回null,而不是void对象
注意:invoke()实参列表不匹配的话,会报IllegalArgumentException 因为用不同的getDeclaredMethod()实参生成的是不同的Method对象,所以对应的只能匹配生成时的实参对应的重载方法 假如a对象用的是String,int两个参数,b对象用的是String一个参数。a对象调用invoke()只传入一个String会报错。 a和b是两个不同的对象,互不影响,并不会根据a的参数去自动匹配执行b方法。 //IllegalArgumentException: wrong number of arguments 数量不对 //IllegalArgumentException: argument type mismatch 类型不对 |
调用静态方法:静态方法调用时候第一个参数可以是任意对象,可以写个null
showNameAge.invoke(null,"古木苏",17); |
三、调用运行时类中的指定的构造器
getDeclaredConstructor(String.class) 参数:指明构造器的参数列表。也可以获取空参构造器,但是没必要,空参可以直接用Class对象newInstance()
setAccessible(true):保证当前构造器是可访问的
newInstance("Jerry"): 调用此构造器创建运行时类的对象
关于setAccessible方法的使用
l Method和Field、Constructor对象都有setAccessible()方法。
l setAccessible启动和禁用访问安全检查的开关。
l 参数值为true则指示反射的对象在使用时应该取消Java语言访问检查。
Ø 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true。
Ø 使得原本无法访问的私有成员也可以访问
l 参数值为false则指示反射的对象应该实施Java语言访问检查。
动态代理&AOP面向切面编程
interface Human{ String getBelief(); void eat(String food); }
class HumanUtil{ public void method1(){ System.out.println("=====通用方法一====="); } public void method2(){ System.out.println("=====通用方法二====="); } }
class SuperMan implements Human{
@Override public String getBelief() { System.out.println("I believe I can fly,and I can save the world!"); return "I believe I can fly,and I can save the world!"; }
@Override public void eat(String food) { System.out.println("SuperMan喜欢吃: " + food); } }
/* 要想实现动态代理,需要解决的问题? 问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。 问题二:当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法a。 */
class ProxyFactory{ //调用此方法,返回一个代理类的对象。解决问题一 public static Object getNewProxyInstance(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 { //AOP面向切面编程 //方法一和方法二之间动态插入其他方法 //在InvocationHandler中实现 HumanUtil humanUtil = new HumanUtil(); humanUtil.method1();
//method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法 //obj:被代理类的对象 Object returnValue = method.invoke(obj, args);
humanUtil.method2();
//上述方法的返回值就作为当前类中的invoke()的返回值。 return returnValue; } }
public class ProxyTest { public static void main(String[] args) { SuperMan superMan = new SuperMan(); //proxyInstance:代理类的对象 Human proxyInstance = (Human) ProxyFactory.getNewProxyInstance(superMan);
//当通过代理类对象调用方法时,会自动的调用被代理类中同名的方法 String belief = proxyInstance.getBelief(); System.out.println(belief); proxyInstance.eat("四川麻辣烫"); } } |
2021年3月12日17:13:35 完结。结尾拉到上面。
结尾
使用 Microsoft OneNote 2016 创建。 版权所有:古木苏