javaSE高级篇4 — 反射机制( 含类加载器 ) — 更新完毕

时间:2023-03-08 17:27:07
javaSE高级篇4 — 反射机制( 含类加载器 ) — 更新完毕

反射机制

1、反射机制是什么?————英文单词是:reflect。在java.lang包下———这才是java最牛逼的技术

  • 首先提前知道一句话————在java中,有了对象,于是有了类,那么有了类之后,也就有了反射
    • 因此:学反射机制,需要做到一件事————把思想从以前的类中跳出来,将思想再拔高一个层次——站在类的头上思考问题

 

  • 那么到底什么是反射机制?
    • 就是用来动态的操作类( 含静态操作类本身【 名字、修饰符、注解... 】、动态操作类中的成员信息【 属性、方法 ....】 )
  • 为什么需要学习反射?
    • 1、因为这是javaSE中最重要的一门技术,可以说前面说的所有东西,甚至后面的javaSE知识部分,都是为了这门技术服务
    • 2、因为这是后面学java框架的必备技术,在框架的底层中就是应用了反射机制(  如:为什么配置一些诸如xml的文件,系统就可以帮忙去加载相应的东西,就是应用了反射这门技术 )
      • 多说一嘴:在javaSE中最核心的知识便是————反射、枚举、【 注解 】( 其他的知识都是为了这些知识服务 )————这些东西是学框架必会的技术( 不然看源码都看不懂,这样也就只能限于用框架而不懂框架了 )

2、反射机制中有什么?

  • Class ———— 用来描述类本身( 注:是大写的C,不是小写的c【 小写是关键字 】 )————这也是最重要的一个( 因为它包含了后面说的那些东西 )
    • 类本身有什么?
      • 修饰符( 权限、特征 )、类名、继承关系、实现关系、类成员( 属性、一般方法、构造方法 )
  • Package ———— 用来描述类所属的包
  • Field ———— 用来描述类中的属性
  • Method ———— 用来描述类中的方法
  • Constructor ———— 用来描述类中的构造方法
  • Annotation ———— 用来描述类中的注解 ( 这个在后续会讲解 )

1、扯了这么多卵球烦的东西,还是来实操吧( 不再单独列出有哪些方法,而是直接上代码 )

  • 学了Class,其他的也就没问题了
    • 1)、对于类本身的操作
      • 在这里需要解释一个东西
        • Class是获取类对象,那么以前 new 类名() 的方式创建的类的对象( 即:类的实例 ),怎么理解这两个?
          • 反射不是在类之上吗,所以Class获取的是:类这个模板的对象( 是整体的对象 );而 new 类名() 它创建的是类这种类型的某一个对象 ( 是类中的一个 个体 )————测试一下嘛:看Class创建的多个类对象一不一样  和 new创建的类的多个对象一不一样
            • 在这里就不展示了,自己动手做( 答案是:Class创建的类对象是一样的 【 注意:用的是同一个类啊,不是同一个类,如:Person和Teacher类,然后用Class创建类对象,这一样个锤子 】——而:new创建出来的类的对象肯定不一样,这在前面阶段就已经知道原因了( new是在堆内存中 )
      • package cn.xieGongZi.Class.playClassOneSelf;
        
        // 对类本身进行操作
        public class Demo { public static void main(String[] args) { // 获取反射对象( 即:类对象 )
        // 1、类名.class 最常用( 这个class是关键字,小写的那个 )
        Class<Teacher> tc = Teacher.class;  // 这里为什么需要用泛型?在下面揭晓 // 2、类的对象.getClass
        // Class<? extends Teacher> tc = new Teacher().getClass(); // 3、Class.forName( " 全路径包名.类名 " ) ———— 要处理异常,不然万一()里面的内容手贱写错了,找不到这个类呢
        // Class<?> tc = Class.forName("cn.xieGongZi.Class.Teacher"); // 获取了这个对象之后可以干的事情
        // 1、获取类本身的东西
        System.out.println("对类本身进行操作");

        String name = tc.getName();
        System.out.println( "获取类的全路径名( 含包名 ):" + name ); // 获取这个类单纯的名字,不要什么包名之类的
        String simpleName = tc.getSimpleName();
        System.out.println( "获取类的简单名字 ( 即:没有什么包名之类的 ): " + simpleName); // 2、获取类的修饰符
        int modifiers = tc.getModifiers(); // 注意:结果是一个整型哦
        // 0 表示默认不写
        // 1 表示public
        // 2 表示private
        // 3 表示protected——————以上表示权限修饰符的,特征修饰符的( 如:4 表示static )就不展示了
        System.out.println( "获取类的修饰符:" + modifiers); // 3、获取这个类实现的接口是哪些 ———— 注意:返回值是一个Class类型的数组
        Class<?>[] classImpl = tc.getInterfaces();
        for (Class<?> interfaceName : classImpl) {
        System.out.println( "获取类实现的接口名( 但:这是全路径名[ 简单名还需要用getSimpleName() ] ): " + interfaceName.getSimpleName());
        } // 4、获取类继承了哪些类
        Class<? super Teacher> superclass = tc.getSuperclass();
        System.out.println("获取类继承的类名:" + superclass.getSimpleName());
        // 对于类本身需要玩的东西,好像就没了——————其他的方法就是字面意思( 如:判断类是不是接口啊、是不是枚举啊之类的 ) // 5、给类创建对象
        try {
        Teacher teacher = tc.newInstance();  
        // 在这里创建类的对象的时候,如果在前面创建类对象的时候,没有用泛型
           // 那么这里创建类的对象时就需要进行造型了
        // 所以:这就是创建类对象的时候采用泛型的原因
        System.out.println("这个对象是通过反射机制帮忙创建出来的:" + teacher);
        } catch (InstantiationException e) {
        e.printStackTrace();
        } catch (IllegalAccessException e) {
        e.printStackTrace();
        }
        }
        } class Person{ private String name;
        private char sex;
        public int age;
        String address; public void study() { System.out.println("是人类学个锤子,快来嗨");
        } public void heiHei() { System.out.println("是人类就要你说嗯来,我说嘿");
        }
        } interface Fly{ } interface Run{ }
        class Teacher extends Person implements Fly,Run { public String phone;
        public String school; private boolean isCool;
        private String nature; public void eat(){ System.out.println("人类的儿子 —— 老师吃得贼球好");
        } public void sleep(){ System.out.println("一天被气得睡不着");
        } }

        效果图如下:

        • javaSE高级篇4 — 反射机制( 含类加载器 ) — 更新完毕
      • 2)、对于类中的成员进行操作
        •  (1)、属性的获取、属性的修改 
        • package cn.xieGongZi.Class.playClassInMember;
          
          import java.lang.reflect.Field;
          
          // 对类中的成员进行操作
          public class Play { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException { System.out.println( "对类中的成员进行操作 ");
          System.out.println(); // 获取类对象
          Class<Teacher> tc = Teacher.class; System.out.println("对类的属性进行操作:查看有哪些属性 及 修改属性值");
          // 1、获取类属性的名字————只获取 类本身的、继承过来的 public修饰的 属性
          Field[] fields = tc.getFields();
          for (Field field : fields) {
          System.out.println( "这个方法获取的是:类本身以及继承过来的 公有的( public ) 属性名字:" + field.getName());
          }
          System.out.println(); // 获取 类本身的 所有属性( 含私有的 )
          Field[] allField = tc.getDeclaredFields();
          for (Field field : allField) {
          System.out.println( "这个方法是获取 类本身的 所有属性( 含private修饰的 ):" + field.getName() );
          } // 那想要获取父类中的属性呢?( 含private修饰的 )
          Field[] parentFields = tc.getSuperclass().getDeclaredFields();
          for (Field parentField : parentFields) {
          System.out.println( "这是Teacher的父类属性:" + parentField .getName() );
          }
          System.out.println();


          System.out.println("对类中的属性进行操作:赋值");
          Field phone = tc.getField("phone"); // getField()会抛一个异常:NoSuchFieldException
          // 没有这样的属性( 怕输错了找不到嘛 ) Teacher teacher = tc.newInstance(); // 创建类的对象,因为:利用set()方法给属性赋值时 需要 类的对象来做参数
          phone.set( teacher,"邪公子"); // newInstance()会抛两个异常————IllegalAccessException, InstantiationException
          // IllegalAccessException————指赋的这个值,类不能接收。为什么不可以接收,马上揭晓
          // InstantiationException————对象实例化异常
          System.out.println( "现在类中的phone属性就有值了:" + teacher.phone );
          // 这是public修饰的属性嘛,所以能改也没什么好奇怪的


          // 可是要搞事情啊 ———— 修改类中private修饰的属性
          // 老规矩:先拿到属性涩————可是要拿的属性包含private修饰的属性( 不然咋体现反射的魅力呢 )
          Field nature = tc.getDeclaredField("nature");
          // 这个方法不是可以获取 类本身的 所有属性吗( private修饰的不就都包含进来了 )
          // 这是它的重载方法而已


          // 现在就可以进行修改属性了
          nature.setAccessible(true); // 提供修改属性的权限,这一步很重要,修改private修饰的属性就必须要这一步
          nature.set( teacher,"这是一个帅气、又欠揍的人" );
          // 但是这样直接修改属性会抛异常:即 IllegalAccessException ———— 因为这是私有属性
          // 因此:这里还需要另一个方法:setAccessible( boolean b ) ———— 这个方法是:提供修改这个属性的权限
          System.out.println("这是通过反射机制修改的private属性:" + nature.get(teacher) );
          // nature.get(teacher) 相当于:teacher.getNature ———— 反着过来的而已
          // 只是在这里:主要是为了玩反射,所以Teacher中并没有设置get和set方法
          // 这个get()方法的意思就是:返回 该字段( 调它的字段 ) 在给定对象中的值 // 以上便是反射对类属性的查看和修改 }
          } class Person{ private String name;
          private char sex;
          public int age;
          String address; public void study() { System.out.println("是人类学个锤子,快来嗨");
          } public void heiHei() { System.out.println("是人类就要你说嗯来,我说嘿");
          }
          } interface Fly{ } interface Run{ } class Teacher extends Person implements Fly,Run{ public String phone;
          public String school; private boolean isCool;
          private String nature; public void eat() { System.out.println("人类的儿子——老师吃得贼球好");
          } public void sleep() { System.out.println("一天被气得睡不着");
          }
          }

          效果图如下:

          • javaSE高级篇4 — 反射机制( 含类加载器 ) — 更新完毕
        • (2)、通过反射对类中的方法进行操作
          • package cn.xieGongZi.Class.playClassinMethod;
            
            import java.lang.reflect.InvocationTargetException;
            import java.lang.reflect.Method; // 通过反射玩儿类中的方法
            public class Play { public static void main(String[] args) { System.out.println("通过反射玩儿类中的方法"); Class<Teacher> tc = Teacher.class; // 1、查看类有哪些方法
            Method[] methods = tc.getMethods();
            for (Method method : methods) {
            System.out.println("这是查看 类本身的、继承过来的 public修饰的方法:" + method.getName());
            }
            System.out.println();


            // 2、查看 类本身的 所有方法 ( 含private修饰的 )
            Method[] allMethods = tc.getDeclaredMethods();
            for (Method method : allMethods) {
            System.out.println( "这是查看 类本身的 所有方法 ( 含 private修饰的 ):" + method.getName());
            }
            System.out.println(); // 3、让类中的方法给我运行起来( 这是运行public修饰的方法 )
            try {
            Teacher teacher = tc.newInstance();
            System.out.println("通过反射运行了类中public修饰的方法");
            teacher.eat();
            teacher.study(); // 以上这些都是运行类中的public方法 ———— 但太low,体现不出反射的精髓 } catch (InstantiationException e) {
            e.printStackTrace();
            } catch (IllegalAccessException e) {
            e.printStackTrace();
            }
            System.out.println();


            // 4、如果想要让private修饰的方法也运行呢?
            try { Method sleepMethod = tc.getDeclaredMethod("sleep", null);
            // getDeclaredMethod( “sleep",null )方法的参数说明:
            // 第一个 类中方法的名字 第二个 类中方法的参数类型
            // 对第二个参数的补充:
            // 没参数是null,如果有参数,则:需要通过参数类型.class进行说明
            // 如:int就是int.class、float就是float.class————支持多个参数

            sleepMethod.setAccessible(true); // 给操作private修饰的方法提供可以修改的权限 System.out.println("运行了类中private修饰的方法");
            sleepMethod.invoke(tc.newInstance(), null);
            // 虽然可以运行,但是这个违背了java的设计原则
            // 即:private封装之后的方法只能在本类中调用
            // 因此:不建议使用这种反射
            // 在这里只是为了说明反射可以做到而已

            } catch (InstantiationException e) {
            e.printStackTrace();
            } catch (IllegalAccessException e) {
            e.printStackTrace();
            } catch (NoSuchMethodException e) {
            e.printStackTrace();
            } catch (InvocationTargetException e) {
            e.printStackTrace();
            }
            }
            } class Person{ public void study() { System.out.println("是人类学个锤子,快来嗨");
            } private void heiHei() { // 这里改成private来进行测试 System.out.println("是人类就要你说嗯来,我说嘿");
            }
            } interface Fly{ } interface Run{ }
            class Teacher extends Person implements Fly, Run { public void eat() { System.out.println("人类的儿子 —— 老师吃得贼球好");
            } private void sleep() { // 改成私有的方法 System.out.println("一天被气得睡不着");
            }
            }

2、哪些类型可以有类对象?

  • 1)、基本数据类型对应的包装类
  • 2)、String类型
  • 3)、注解类型
  • 4)、数组( 含一维和二维 )
  • 5)、枚举类型
  • 6)、类
  • 7)、接口
  • 8)、Void类型
package cn.xieGongZi.haveClassType;

import java.lang.annotation.RetentionPolicy;

public class Demo {

    public static void main(String[] args) {

        // 包装类类型
Class<Integer> c1 = Integer.class; // String类型
Class<String> c2 = String.class; // 数组
Class<Integer[]> c3 = Integer[].class;
Class<Byte[][]> c4 = Byte[][].class; // 枚举类型
Class<RetentionPolicy> c5 = RetentionPolicy.class; // 这个枚举类在注解中会见到 // 接口类型
Class<Comparable> c6 = Comparable.class; // void类型
Class<Void> c7 = void.class; // 注解类型
Class<Override> c8 = Override.class; // 类
Class<Demo> c9 = Demo.class; // 当前测试的这个类嘛 // 测试看一下嘛
System.out.println( c1 );
System.out.println( c2 );
System.out.println( c3 );
System.out.println( c4 );
System.out.println( c5 );
System.out.println( c6 );
System.out.println( c7 );
System.out.println( c8 );
System.out.println( c9 ); }
}

效果如下:

  • javaSE高级篇4 — 反射机制( 含类加载器 ) — 更新完毕

扩展

3、类加载过程( 即:ClassLoader ) ———— 了解即可

javaSE高级篇4 — 反射机制( 含类加载器 ) — 更新完毕

 还是用代码举个例子:

package cn.xieGongZi.classLoader;

public class Test {

    public static void main(String[] args) {

        // 建一个A类的对象
new A(); }
} // 假如有一个类 A
class A{ // 有一个变量
static int a; // 有一个静态代码块
static { System.out.println("静态代码块");
a = 20;
} // 第二个静态代码块
static { a = 50; System.out.println( a );
} // 来个无参构造 public A() { System.out.println("这个无参构造");
}
}

效果如下:

  • javaSE高级篇4 — 反射机制( 含类加载器 ) — 更新完毕

画个图分析一下:

  • javaSE高级篇4 — 反射机制( 含类加载器 ) — 更新完毕
  • 2)、类什么时候会被初始化?  ———— 类的主动引用会导致初始化
    • 当虚拟机启动的时候,会先初始化main()方法
    • new 一个类的对象
    • 调用一个类的静态成员 ( 含静态方法、静态属性【 但是:调用final修饰的常量不会触发初始化 】 )
    • 利用java.lang.reflect包下的方法 进行 反射调用
    • 多提一嘴:当在初始化一个类时,发现其父类没有初始化,那么就会先初始化父类
  • 3)、类在什么情况下不会初始化? ———— 类的被动引用不会导致初始化
    • 当访问一个静态域 ( 一块静态元素区 )时,只有声明这个域的类才会被初始化,反之:另外类访问这个静态域,那么:这里说的另外类就不会初始化,如:子类引用父类的静态常量,则:子类不会被初始化
    • 通过数组定义类引用 ( 即:这个数组类型是一个类类型 ),不会导致这个类被初始化
    • 调用一个类的静态常量时,不会导致这个类被初始化 ( 因为:常量在链接阶段就已经在常量池中了 )

4、类加载器

javaSE高级篇4 — 反射机制( 含类加载器 ) — 更新完毕

  • 类加载器的作用
    • 就是为了把class文件加载到内存中,从而把静态数据弄成方法区中运行时的数据结构,同时在堆空间中生成一个java.lang.Class类对象,作为方法区中的数据访问入口
    • 多说一嘴:类缓存
      • 指的是:标准的JavaSE类加载器,可以根据要求查找类,如果发现这个类已经在类加载器中去了,那么:这个类就会再加载 ( 缓存 )一段时间
        • 但是:GC回收机制是可以回收这些Class对象的
  • 玩玩儿类加载器
    • package cn.xieGongZi.classLoader.thinkClassLoader;
      
      public class Demo {
      
          public static void main(String[] args) {
      
              // 查看当前类是谁加载的
      ClassLoader thisClass = Demo.class.getClassLoader();
      System.out.println( "当前类是 " + thisClass + " 加载的" ); // 查看这个类加载器的父类加载器 ———— 即:扩展类加载器
      ClassLoader fatherClassLoader = thisClass.getParent();
      System.out.println( "当前类的父类加载器是 " + fatherClassLoader ); // 查看这个加载器的父类的父类加载器 ———— 即:根加载器( 注意:根加载器是C++写的,获取不到 ,即为null )
      ClassLoader grandFather = fatherClassLoader.getParent();
      System.out.println( "这个类加载的祖类加载器( 根加载器 )是 " + grandFather ); // 看看JDK内置的类是谁加载的 ———— 举个例子:Object类
      ClassLoader JDKClassLoader = Object.class.getClassLoader();
      System.out.println( "JDK内置的类是 " + JDKClassLoader + "加载的" );
      System.out.println(); // 来查看一下系统类加载器可以加载的路径是哪些?
      String classLoadPath = System.getProperty("java.class.path");
      System.out.println( classLoadPath );
      /*
      类加载可以加载的路径如下( 反之:也可以得出,类需要在以下路径中才可以被加载 )
      D:\IntallationList\JDK IntallationList\jre\lib\charsets.jar;
      D:\IntallationList\JDK IntallationList\jre\lib\deploy.jar;
      D:\IntallationList\JDK IntallationList\jre\lib\ext\access-bridge-64.jar;
      D:\IntallationList\JDK IntallationList\jre\lib\ext\cldrdata.jar;
      D:\IntallationList\JDK IntallationList\jre\lib\ext\dnsns.jar;
      D:\IntallationList\JDK IntallationList\jre\lib\ext\jaccess.jar;
      D:\IntallationList\JDK IntallationList\jre\lib\ext\jfxrt.jar;
      D:\IntallationList\JDK IntallationList\jre\lib\ext\localedata.jar;
      D:\IntallationList\JDK IntallationList\jre\lib\ext\nashorn.jar;
      D:\IntallationList\JDK IntallationList\jre\lib\ext\sunec.jar;
      D:\IntallationList\JDK IntallationList\jre\lib\ext\sunjce_provider.jar;
      D:\IntallationList\JDK IntallationList\jre\lib\ext\sunmscapi.jar;
      D:\IntallationList\JDK IntallationList\jre\lib\ext\sunpkcs11.jar;
      D:\IntallationList\JDK IntallationList\jre\lib\ext\zipfs.jar;
      D:\IntallationList\JDK IntallationList\jre\lib\javaws.jar;
      D:\IntallationList\JDK IntallationList\jre\lib\jce.jar;
      D:\IntallationList\JDK IntallationList\jre\lib\jfr.jar;
      D:\IntallationList\JDK IntallationList\jre\lib\jfxswt.jar;
      D:\IntallationList\JDK IntallationList\jre\lib\jsse.jar;
      D:\IntallationList\JDK IntallationList\jre\lib\management-agent.jar;
      D:\IntallationList\JDK IntallationList\jre\lib\plugin.jar;
      D:\IntallationList\JDK IntallationList\jre\lib\resources.jar;
      D:\IntallationList\JDK IntallationList\jre\lib\rt.jar;
      D:\JavaTrainStudy\javaTrainStudy\out\production\study-09-reflect;   // 自己写的类也被加载到了
      D:\IntallationList\IdeaIntallationList\IntelliJ IDEA 2020.3\lib\idea_rt.jar
      */
      }
      }

      效果图如下:

      • javaSE高级篇4 — 反射机制( 含类加载器 ) — 更新完毕
    • 这里顺便补充一下:什么叫双亲委派机制
      • 就是从类加载器 自底向上依次查看类加载器中有没有加载特定类的包
        • 如:java.lang.String,系统类加载器先看有没有加载这个包,没有的话就会去找扩展类加载器看有没有加载,还没有的话,就会去根加载器中查看
          • 所以这里就引申出另一个答案:为什么取类名的时候,不能和java中现有的类名重复
            • 就是因为这个双亲委派机制,它会依次这样去找,发现自己就有,所以自己定义的那个 和 java本身有的类 重名的类就不会生效

反射机制也就这么回事儿,也没什么好说的了,主要的就是这个Class,那些什么Package、Constuctor......都是包含在Class中的,因此:只要通过 类对象.get 就出来相应的东西

最后:有兴趣的可以利用反射玩一下其他的,如:集合中不是支持泛型吗,那这样的向集合中添加元素就只能采用泛型的类型了

但是:可以使用反射机制对这个方法进行修改,即:添加元素时参数类型不是泛型规定的类型,照样可以做到把元素添加进去