1.反射机制怎么说
什么是反射机制?
Java不是动态语言,但是Java可以称为准动态语言,即Java有一定的动态性,可以利用反射机制获取类似动态语言的特性
反射机制有什么用?
通过java语言中的反射机制,可以操作字节码文件(比如class文件)。
后期要学习java的高级框架,高级框架中底层实现原理用的都是反射机制,学习反射机制有利于理解剖析框架底层的源代码。
反射机制相关的包和重要的类
包:java.lang.reflect.*
java.lang.Class:代表整个字节码,代表一个类型,代表一个类。
java.lang.reflect.Method:代表字节码中的方法字节码。
java.lang.reflect.Field:代表字节码中的属性字节码。
java.lang.reflect.Constructor:代表字节码中的构造方法字节码。
反射机制的优缺点
优点:反射机制具有强大的灵活性。
缺点,使用反射机制是一种解释操作,这类操作总是慢于直接执行相同的操作。
2.反射机制基本操作
java中有三种方式可以获取类
第一种方法,用过Class类中的静态方法forName
package reflectTest;public class Test01 {public static void main (String[] args) throws Exception {// 通过Class.forName静态方法获取类名// 要求,参数中的路径必须带有包名Class c1 = Class.forName("myClass.myClass");Class c2 = Class.forName("java.lang.String");System.out.println(c1);System.out.println(c2);}}
第二种方法,java中任何一个对象都有一个方法:getClass
String s = "abc";Class c3 = s.getClass();System.out.println(c3);Integer num = new Integer(43);Class c4 = num.getClass();System.out.println(c4);
第三种方法,java语言中任意一种属性,包括基本类型数据都有class属性。
Class c5 = int.class;System.out.println(c5);Class c6 = Thread.class;System.out.println(c6);
这三种方法实际上就是通过类,通过类方法,通过类属性来获取类的方法
// 获取两种类名Class c1 = Class.forName("myClass.myClass");Class c2 = Class.forName("java.lang.String");// 这种方法获得的是完整的带包名的类型System.out.println(c1.getName());System.out.println(c2.getName());// myClass.myClass// java.lang.String// 这种方法获得的是简单类名System.out.println(c1.getSimpleName());System.out.println(c2.getSimpleName());// myClass// String
通过反射机制,获取类,再通过类实例化对象**
// 通过反射机制获取类并实例化对象// 注意用的是.而不是/Class c1 = Class.forName("myClass.myClass");// 此时forName的方法执行会导致类加载// 会先执行类中的静态代码块// 返回一个objObject o = c1.newInstance();System.out.println(o);// 实际上底层会调用的无参构造方法,然后才实例化的对象
注:涉及到了类加载问题,newInstance调用了类的无参构造,要保证类的无参构造存在。
反射机制的灵活就体现出来了,我们只需要修改forName方法中的参数,就可以创建不同类型的对象。
通过IO流读取properties文件
package reflectTest;import java.io.FileReader;import java.util.Properties;public class Test03 {public static void main(String[] args) throws Exception {// 创建一个字符读取流FileReader reader = new FileReader("src/class.properties");//Properties类表示一组持久的属性。// Properties可以保存到流中或从流中加载。// 属性列表中的每个键及其对应的值都是一个字符串。Properties pro = new Properties();// 参数可以传InputStream类型的// 参数也可以传Reader类型的pro.load(reader);// 关闭流reader.close();//使用此属性列表中指定的键搜索属性。String className = pro.getProperty("className");System.out.println(className);}}
灵活性体现在只需要操作配置文件而不是重写源码实现代码的重用。
我们properties文件的路径是在根目录下的src中的…文件,不具有通用性,我们希望有一段通用的代码可以得到src中的文件
String path = null;path = Thread.currentThread().getContextClassLoader().getResource("class.properties").getPath();System.out.println(path);// 拿到了文件的绝对路径/D:/CODE/java/reflect/out/production/reflect/class.properties
但要注意的是,文件必须放在src的根目录下。
获取了这个path之后,就可以创建流,创建properties对象,加载….
实际上,java为我们提供了一种操作,可以直接获取到proterties文件:资源绑定器。
资源绑定器只能绑定properties文件,而且这个文件必须在类目录下。
// 这是一个静态方法ResourceBundle bundle = ResourceBundle.getBundle("class");String s = bundle.getString("className");System.out.println(s);
3.类加载器
JDK中自带了三种类加载器
1.启动类加载器 : rt.jar
2.扩展类加载器 : ext/*.jar
3.应用类加载器 : classpath
代码在执行之前会把所需要的所有类加载到JVM之中。
java中为了保证类加载的安全,使用了双亲委派机制,优先从启动类加载器加载类,然后是扩展类加载器,如果这两个双亲都加载不到类,那么会从应用类加载器中加载类,知道加载到为止。
4.获取类中的成员
获取类中的所有field
package reflectTest;import java.lang.reflect.Field;public class Test06 {public static void main(String[] args) throws Exception{Class c = Class.forName("myClass.myClass");// 获取类中的fields// 这是Class类中的方法,返回一个field数组Field [] fields1 = c.getFields();for (Field f:fields1){// 获取这个属性的名字System.out.println(f.getName());}// 实际上这种方法只能够获取到public修饰的属性// 若想获得所有属性,就要用到这个方法// 获取都有已经声明过的属性Field [] fields2 = c.getDeclaredFields();for (Field f:fields2){String s = f.getName();System.out.println(s);}}}
获取field的修饰符和类型
Field [] fields2 = c.getDeclaredFields();for (Field f:fields2){// 这个方法返回的是不同修饰如对应的数字// 然后获取修饰符的字符串形式int i = f.getModifiers();// Modifier重写了toString方法String s = Modifier.toString(i);System.out.println(s);// 返回的是不同修饰符对应的类Class type = f.getType();// 打印类的名字System.out.println(type.getSimpleName());System.out.println(f.getName());System.out.println();}
5.通过反射机制访问一个对象的属性
package reflectTest;import java.lang.reflect.Field;public class Test07 {public static void main(String[] args) throws Exception {// 首先获取到类Class c = Class.forName("myClass.myClass");Object o = c.newInstance();// 然后获取到属性Field f = c.getDeclaredField("no");// 操作这个属性// set方法在Filed类中,第一个参数写类的实例对性,第二个参数写属性值f.set(o,100);// get方法获得属性的值,单数写类的实例对象,返回一个Object类型的引用Object o2 = f.get(o);System.out.println(o2);// 单上面的方式只能够访问public修饰的属性,如果要访问其他修饰符的属性,就要打破封装了// Field类中的这个方法,可以打破封装f.setAccessible(true);}}
6.通过反射机制访问一个对象的方法
引:关于可变长参数
在java中方法的参数可以是一个可变长度的在参数列表中写 数据类型… args就可以实现了。
package reflectTest;public class Test08 {public static void main(String[] args) {// 不传参数可以m();// 传一个参数也可以m(1);// 传一个数组也可以int [] arr = new int[10];m(arr);}public static void m(int... args){// 此时args有length属性,说明在底层args是一个int数组for (int i=0 ; i<args.length ; i++){}}}
注意:只能传一种可变长参数,如果传入可变长参数的同时还要传进去其他参数,那么可变长参数必须写在参数列表的最后。
通过反射机制获取类中的方法信息并调用方法
获取类中的方法信息和获取类中的属性流程类似。
package reflectTest;import java.lang.reflect.Method;import java.lang.reflect.Modifier;public class Test09 {public static void main(String[] args) throws Exception {Class c = Class.forName("myClass.myClass");Object o = c.newInstance();// 获取方法Method [] methods = c.getDeclaredMethods();for (Method method:methods){// 获取方法的修饰符System.out.println(Modifier.toString(method.getModifiers()));// 获取方法的返回值System.out.println(method.getReturnType().getSimpleName());// 获取方法的方法名System.out.println(method.getName());// 获取方法的参数列表Class [] cc = method.getParameterTypes();for (Class ccc:cc){System.out.println(ccc.getSimpleName());}System.out.println("======");}}}
通过反射调用类中的方法
// 获取到m1方法Method m1 = c.getDeclaredMethod("m1", int.class);// 返回值是一个obj类型的,用到m1的invoke方法,第一个参数写类的实例化对象,第二个参数写参数列表Object obj_m1 = m1.invoke(o,12);System.out.println(obj_m1);Method m2 = c.getDeclaredMethod("m2",int.class,String.class,boolean.class);m2.invoke(o,13,"abv",true);
要注意的是,首先要获得一个准确的方法(怎么获得:方法名,参数列表)传给Method类的引用m1,然后调用m1的invoke方法,参数是一个类对象和参数列表,返回值是Obj类型的,用于存储方法的返回值。
7.通过反射机制调用构造方法
通过反射机制调用构造方法和调用成员方法类似。
package reflectTest;import java.lang.reflect.Constructor;public class Test10 {public static void main(String[] args) throws Exception {Class c = Class.forName("myClass.myClass");// 这种方式实际上已经调用了无参构造方法// Object o = c.newInstance();// 调用类的getDeclaredConstructor方法// 参数列表里面写你想调用哪一个构造方法Constructor con = c.getDeclaredConstructor(int.class,String.class);// 用Constructor类对象的newInstance方法,返回一个obj类型的对象// 实际上这是一个创建对象的方法Object o = con.newInstance(100,"zjl");System.out.println(o);}}
8.获取类的父类和实现的接口
package reflectTest;public class Test11 {public static void main(String[] args) throws Exception {Class c = Class.forName("java.lang.String");// 这种方法可以获得继承的父类,只有一个Class superclass = c.getSuperclass();System.out.println(superclass.getName());// 这种方式可以获得实现的接口,很多Class [] interfaces = c.getInterfaces();for (Class in : interfaces){System.out.println(in.getName());}}}
