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

  1. package reflectTest;
  2. public class Test01 {
  3. public static void main (String[] args) throws Exception {
  4. // 通过Class.forName静态方法获取类名
  5. // 要求,参数中的路径必须带有包名
  6. Class c1 = Class.forName("myClass.myClass");
  7. Class c2 = Class.forName("java.lang.String");
  8. System.out.println(c1);
  9. System.out.println(c2);
  10. }
  11. }

第二种方法,java中任何一个对象都有一个方法:getClass

  1. String s = "abc";
  2. Class c3 = s.getClass();
  3. System.out.println(c3);
  4. Integer num = new Integer(43);
  5. Class c4 = num.getClass();
  6. System.out.println(c4);

第三种方法,java语言中任意一种属性,包括基本类型数据都有class属性。

  1. Class c5 = int.class;
  2. System.out.println(c5);
  3. Class c6 = Thread.class;
  4. System.out.println(c6);

这三种方法实际上就是通过类,通过类方法,通过类属性来获取类的方法

  1. // 获取两种类名
  2. Class c1 = Class.forName("myClass.myClass");
  3. Class c2 = Class.forName("java.lang.String");
  4. // 这种方法获得的是完整的带包名的类型
  5. System.out.println(c1.getName());
  6. System.out.println(c2.getName());
  7. // myClass.myClass
  8. // java.lang.String
  9. // 这种方法获得的是简单类名
  10. System.out.println(c1.getSimpleName());
  11. System.out.println(c2.getSimpleName());
  12. // myClass
  13. // String

通过反射机制,获取类,再通过类实例化对象**

  1. // 通过反射机制获取类并实例化对象
  2. // 注意用的是.而不是/
  3. Class c1 = Class.forName("myClass.myClass");
  4. // 此时forName的方法执行会导致类加载
  5. // 会先执行类中的静态代码块
  6. // 返回一个obj
  7. Object o = c1.newInstance();
  8. System.out.println(o);
  9. // 实际上底层会调用的无参构造方法,然后才实例化的对象

注:涉及到了类加载问题,newInstance调用了类的无参构造,要保证类的无参构造存在。

反射机制的灵活就体现出来了,我们只需要修改forName方法中的参数,就可以创建不同类型的对象。

通过IO流读取properties文件

  1. package reflectTest;
  2. import java.io.FileReader;
  3. import java.util.Properties;
  4. public class Test03 {
  5. public static void main(String[] args) throws Exception {
  6. // 创建一个字符读取流
  7. FileReader reader = new FileReader("src/class.properties");
  8. //Properties类表示一组持久的属性。
  9. // Properties可以保存到流中或从流中加载。
  10. // 属性列表中的每个键及其对应的值都是一个字符串。
  11. Properties pro = new Properties();
  12. // 参数可以传InputStream类型的
  13. // 参数也可以传Reader类型的
  14. pro.load(reader);
  15. // 关闭流
  16. reader.close();
  17. //使用此属性列表中指定的键搜索属性。
  18. String className = pro.getProperty("className");
  19. System.out.println(className);
  20. }
  21. }

灵活性体现在只需要操作配置文件而不是重写源码实现代码的重用。

我们properties文件的路径是在根目录下的src中的…文件,不具有通用性,我们希望有一段通用的代码可以得到src中的文件

  1. String path = null;
  2. path = Thread.currentThread().getContextClassLoader().getResource("class.properties").getPath();
  3. System.out.println(path);
  4. // 拿到了文件的绝对路径/D:/CODE/java/reflect/out/production/reflect/class.properties

但要注意的是,文件必须放在src的根目录下。

获取了这个path之后,就可以创建流,创建properties对象,加载….

实际上,java为我们提供了一种操作,可以直接获取到proterties文件:资源绑定器。
资源绑定器只能绑定properties文件,而且这个文件必须在类目录下。

  1. // 这是一个静态方法
  2. ResourceBundle bundle = ResourceBundle.getBundle("class");
  3. String s = bundle.getString("className");
  4. System.out.println(s);

3.类加载器

JDK中自带了三种类加载器
1.启动类加载器 : rt.jar
2.扩展类加载器 : ext/*.jar
3.应用类加载器 : classpath

代码在执行之前会把所需要的所有类加载到JVM之中。

java中为了保证类加载的安全,使用了双亲委派机制,优先从启动类加载器加载类,然后是扩展类加载器,如果这两个双亲都加载不到类,那么会从应用类加载器中加载类,知道加载到为止。

4.获取类中的成员

获取类中的所有field

  1. package reflectTest;
  2. import java.lang.reflect.Field;
  3. public class Test06 {
  4. public static void main(String[] args) throws Exception{
  5. Class c = Class.forName("myClass.myClass");
  6. // 获取类中的fields
  7. // 这是Class类中的方法,返回一个field数组
  8. Field [] fields1 = c.getFields();
  9. for (Field f:fields1){
  10. // 获取这个属性的名字
  11. System.out.println(f.getName());
  12. }
  13. // 实际上这种方法只能够获取到public修饰的属性
  14. // 若想获得所有属性,就要用到这个方法
  15. // 获取都有已经声明过的属性
  16. Field [] fields2 = c.getDeclaredFields();
  17. for (Field f:fields2){
  18. String s = f.getName();
  19. System.out.println(s);
  20. }
  21. }
  22. }

获取field的修饰符和类型

  1. Field [] fields2 = c.getDeclaredFields();
  2. for (Field f:fields2){
  3. // 这个方法返回的是不同修饰如对应的数字
  4. // 然后获取修饰符的字符串形式
  5. int i = f.getModifiers();
  6. // Modifier重写了toString方法
  7. String s = Modifier.toString(i);
  8. System.out.println(s);
  9. // 返回的是不同修饰符对应的类
  10. Class type = f.getType();
  11. // 打印类的名字
  12. System.out.println(type.getSimpleName());
  13. System.out.println(f.getName());
  14. System.out.println();
  15. }

5.通过反射机制访问一个对象的属性

  1. package reflectTest;
  2. import java.lang.reflect.Field;
  3. public class Test07 {
  4. public static void main(String[] args) throws Exception {
  5. // 首先获取到类
  6. Class c = Class.forName("myClass.myClass");
  7. Object o = c.newInstance();
  8. // 然后获取到属性
  9. Field f = c.getDeclaredField("no");
  10. // 操作这个属性
  11. // set方法在Filed类中,第一个参数写类的实例对性,第二个参数写属性值
  12. f.set(o,100);
  13. // get方法获得属性的值,单数写类的实例对象,返回一个Object类型的引用
  14. Object o2 = f.get(o);
  15. System.out.println(o2);
  16. // 单上面的方式只能够访问public修饰的属性,如果要访问其他修饰符的属性,就要打破封装了
  17. // Field类中的这个方法,可以打破封装
  18. f.setAccessible(true);
  19. }
  20. }

6.通过反射机制访问一个对象的方法

引:关于可变长参数

在java中方法的参数可以是一个可变长度的在参数列表中写 数据类型… args就可以实现了。

  1. package reflectTest;
  2. public class Test08 {
  3. public static void main(String[] args) {
  4. // 不传参数可以
  5. m();
  6. // 传一个参数也可以
  7. m(1);
  8. // 传一个数组也可以
  9. int [] arr = new int[10];
  10. m(arr);
  11. }
  12. public static void m(int... args){
  13. // 此时args有length属性,说明在底层args是一个int数组
  14. for (int i=0 ; i<args.length ; i++){
  15. }
  16. }
  17. }

注意:只能传一种可变长参数,如果传入可变长参数的同时还要传进去其他参数,那么可变长参数必须写在参数列表的最后。

通过反射机制获取类中的方法信息并调用方法
获取类中的方法信息和获取类中的属性流程类似。

  1. package reflectTest;
  2. import java.lang.reflect.Method;
  3. import java.lang.reflect.Modifier;
  4. public class Test09 {
  5. public static void main(String[] args) throws Exception {
  6. Class c = Class.forName("myClass.myClass");
  7. Object o = c.newInstance();
  8. // 获取方法
  9. Method [] methods = c.getDeclaredMethods();
  10. for (Method method:methods){
  11. // 获取方法的修饰符
  12. System.out.println(Modifier.toString(method.getModifiers()));
  13. // 获取方法的返回值
  14. System.out.println(method.getReturnType().getSimpleName());
  15. // 获取方法的方法名
  16. System.out.println(method.getName());
  17. // 获取方法的参数列表
  18. Class [] cc = method.getParameterTypes();
  19. for (Class ccc:cc){
  20. System.out.println(ccc.getSimpleName());
  21. }
  22. System.out.println("======");
  23. }
  24. }
  25. }

通过反射调用类中的方法

  1. // 获取到m1方法
  2. Method m1 = c.getDeclaredMethod("m1", int.class);
  3. // 返回值是一个obj类型的,用到m1的invoke方法,第一个参数写类的实例化对象,第二个参数写参数列表
  4. Object obj_m1 = m1.invoke(o,12);
  5. System.out.println(obj_m1);
  6. Method m2 = c.getDeclaredMethod("m2",int.class,String.class,boolean.class);
  7. m2.invoke(o,13,"abv",true);

要注意的是,首先要获得一个准确的方法(怎么获得:方法名,参数列表)传给Method类的引用m1,然后调用m1的invoke方法,参数是一个类对象和参数列表,返回值是Obj类型的,用于存储方法的返回值。

7.通过反射机制调用构造方法

通过反射机制调用构造方法和调用成员方法类似。

  1. package reflectTest;
  2. import java.lang.reflect.Constructor;
  3. public class Test10 {
  4. public static void main(String[] args) throws Exception {
  5. Class c = Class.forName("myClass.myClass");
  6. // 这种方式实际上已经调用了无参构造方法
  7. // Object o = c.newInstance();
  8. // 调用类的getDeclaredConstructor方法
  9. // 参数列表里面写你想调用哪一个构造方法
  10. Constructor con = c.getDeclaredConstructor(int.class,String.class);
  11. // 用Constructor类对象的newInstance方法,返回一个obj类型的对象
  12. // 实际上这是一个创建对象的方法
  13. Object o = con.newInstance(100,"zjl");
  14. System.out.println(o);
  15. }
  16. }

8.获取类的父类和实现的接口

  1. package reflectTest;
  2. public class Test11 {
  3. public static void main(String[] args) throws Exception {
  4. Class c = Class.forName("java.lang.String");
  5. // 这种方法可以获得继承的父类,只有一个
  6. Class superclass = c.getSuperclass();
  7. System.out.println(superclass.getName());
  8. // 这种方式可以获得实现的接口,很多
  9. Class [] interfaces = c.getInterfaces();
  10. for (Class in : interfaces){
  11. System.out.println(in.getName());
  12. }
  13. }
  14. }