VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > Java教程 >
  • 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("一天被气得睡不着");
            }
        
        }
        复制代码

        效果图如下:

 

 

    • 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("一天被气得睡不着");
            }
        }
        复制代码

         

        效果图如下:

 

 

    • (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 );

    }
}
复制代码

 

 

 

效果如下:

 

 

 

扩展

 

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

 

 

 还是用代码举个例子:

 

复制代码
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("这个无参构造");
    }
}
复制代码

 

 

 

 

效果如下:

画个图分析一下:

  •  

     

  • 2)、类什么时候会被初始化?  ———— 类的主动引用会导致初始化
    • 当虚拟机启动的时候,会先初始化main()方法
    • new 一个类的对象
    • 调用一个类的静态成员 ( 含静态方法、静态属性【 但是:调用final修饰的常量不会触发初始化 】 )
    • 利用java.lang.reflect包下的方法 进行 反射调用
    • 多提一嘴:当在初始化一个类时,发现其父类没有初始化,那么就会先初始化父类

 

  • 3)、类在什么情况下不会初始化? ———— 类的被动引用不会导致初始化
    • 当访问一个静态域 ( 一块静态元素区 )时,只有声明这个域的类才会被初始化,反之:另外类访问这个静态域,那么:这里说的另外类就不会初始化,如:子类引用父类的静态常量,则:子类不会被初始化
    • 通过数组定义类引用 ( 即:这个数组类型是一个类类型 ),不会导致这个类被初始化
    • 调用一个类的静态常量时,不会导致这个类被初始化 ( 因为:常量在链接阶段就已经在常量池中了 )

 

 

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
              */
          }
      }
      复制代码
      效果图如下:
      •  

         

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

 

 

 

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

 

 

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

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

作者:紫邪情

出  处:
https://www.cnblogs.com/xiegongzi/p/15154820.html



相关教程