java类加载的知识点

1.类加载器

1.1类加载

  • 类加载的描述

    • 当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过类的加载,类的连接,类的初始化这三个步骤来对类进行初始化。如果不出现意外情况,JVM将会连续完成这三个步骤,所以有时也把这三个步骤统称为类加载或者类初始化
  • 类的加载

    • 就是指将class文件读入内存,并为之创建一个 java.lang.Class 对象
    • 任何类被使用时,系统都会为之建立一个 java.lang.Class 对象
  • 类的连接

    • 验证阶段:用于检验被加载的类是否有正确的内部结构,并和其他类协调一致
    • 准备阶段:负责为类的类变量分配内存,并设置默认初始化值
    • 解析阶段:将类的二进制数据中的符号引用替换为直接引用
  • 类的初始化

    在该阶段,主要就是对类变量进行初始化

    • 类的初始化步骤
      • 假如类还未被加载和连接,则程序先加载并连接该类
      • 假如该类的直接父类还未被初始化,则先初始化其直接父类
      • 假如类中有初始化语句,则系统依次执行这些初始化语句
      • 注意:在执行第2个步骤的时候,系统对直接父类的初始化步骤也遵循初始化步骤1-3(所以java最先初始化的,永远都是java.lang.Object类)
    • 类的初始化时机(每次运行的时候,只有这个类首次进入jvm虚拟机才会进行初始化)
      • 创建类的实例
      • 调用类的类方法
      • 访问类或者接口的类变量,或者为该类变量赋值
      • 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
      • 初始化某个类的子类
      • 直接使用java.exe命令来运行某个主类

1.2类加载器

1.2.1类加载器的作用
  • 负责将.class文件加载到内存中,并为之生成对应的 java.lang.Class 对象。虽然我们不用过分关心类加载机制,但是了解这个机制我们就能更好的理解程序的运行!
1.2.2JVM的类加载机制
  • 全盘负责:就是当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入
  • 父类委托:就是当一个类加载器负责加载某个Class时,先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类
  • 缓存机制:保证所有加载过的Class都会被缓存,当程序需要使用某个Class对象时,类加载器先从缓存区中搜索该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存储到缓存区
1.2.3ClassLoader类

ClassLoader类是一个抽象类,这个类是Object的子类。它就是类加载器类,负责加载类对象。下面三个内置的类加载器和ClassLoader没有继承的关系。三个内置的类加载器只是类加载器的一种。ClassLoader这个类加载器类只是封装了一些方法。

1.2.4Java中的内置类加载器
  • Bootstrap class loader:它是虚拟机的内置类加载器,通常表示为null ,并且没有它的父类加载器,它java内置类加载器的祖宗。
  • Platform class loader:它是平台类加载器,平台类加载器可以看到所有平台类 ,平台类包括由平台类加载器或其祖先定义的Java SE平台API,其实现类和JDK特定的运行时类
  • System class loader:它被成为系统类加载器,它也被称为应用程序类加载器 ,与平台类加载器不同。 系统类加载器通常用于定义应用程序类路径,模块路径和JDK特定工具上的类
  • 类加载器的继承关系:System class loader的父加载器为Platform class loader,而Platform class loader的父加载器为Bootstrap class loader
1.2.5ClassLoader 中的两个方法
  • 方法分类

    方法名说明
    static ClassLoader getSystemClassLoader()返回用于委派的系统类加载器
    ClassLoader getParent()返回父类加载器进行委派
  • 示例代码

    package com.liudashuai;
    
    public class Demo {
        public static void main(String[] args) {
            //static ClassLoader getSystemClassLoader():返回用于委派的系统类加载器。这是一个静态的方法,用于返回这个类加载委派的系统类加载器
            ClassLoader c = ClassLoader.getSystemClassLoader();
            System.out.println(c); //AppClassLoader。打印:sun.misc.Launcher$AppClassLoader@18b4aac2
    
            //ClassLoader getParent():返回父类加载器进行委派
            ClassLoader c2 = c.getParent();//返回这个c类加载器对象的父类对象。
            System.out.println(c2); //打印:sun.misc.Launcher$ExtClassLoader@677327b6。因为我用的java8。java9模块化之后对classloader有所改造其中一点就是将extclassloader改为platformclassloader。所以老师显示的是platformclassloader但是我显示的是extclassloader。extclassloader叫扩展类加载器,和platformclassloader一样,也是AppClassLoader的父类,BootstrapClassLoader的儿子。
    
            ClassLoader c3 = c2.getParent();//看c2的类加载器对象
            System.out.println(c3); //打印:null。因为PlatformClassLoader(JDK9)、extclassloader(JDK8)的父类是BootstrapClassLoader,然后这个BootstrapClassLoader一般都是null,所以打印了null。
        }
    }
    

2.反射

2.1反射的概述

  • 反射:是指在运行时去获取一个类的变量和方法信息。然后通过获取到的信息来创建对象,调用方法的一种机制。由于这种动态性,可以极大的增强程序的灵活性,程序不用在编译期就完成确定,在运行期仍然可以扩展。

  • 常识:

    • .class文件:是使用javac命令编译对应的.java文件生成的类文件。你在idea中你新建了一个.java的文件,就会自动生成.class文件,而且你修改你的java文件,它的.class文件会对应的更新内容。

    • 加载过程:当运行Java程序是,首先运行JVM(Java虚拟机),然后再把class文件加载到JVM的方法区里,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构,并向程序员提供访问方法区内的数据结构的接口。

      过程:

      1. 在加载class文件的时候,JVM会先加载类中的所有静态成员( 静态方法,静态变量,静态代码块 )都加载到方法区class文件的所处静态区中

      2. 当把所有的静态成员加载完成之后,开始给类中的所有静态成员变量进行默认初始化

      3. 当类中的所有静态成员变量默认初始化之后,接着开始给所有静态成员变量显示赋值。

      4. 当类中所有的静态成员变量显示赋值结束之后,静态代码块才会运行。

      5. 当静态代码块执行结束之后,才表示class文件加载完成

        注意:因为静态成员是属于类的,而非静态成员是属于类的对象,所以在加载的时候会先加载静态成员,只有将类实例化之后才会加载非静态成员

        所以下面代码的结果是这样的:

        package com.liudashuai;
        
        public class Demo1{
            public static int a=12;
            static {//静态代码块
                a=24;
                System.out.println(a);
            }
            public static void f(){//不是静态方法块
                a=100;
            }
        
            public static void main(String[] args) {
                System.out.println(Demo1.a);//Demo1类加载的过程是:a=0-->a=12-->a=24结束。所以这里打印出来a是24.静态方法块是类加载的时候就执行的,不是构造方法执行的时候才执行。还有就是类加载的时候,静态方法会执行,静态方法只是在类加载的时候被放到了方法区,但是类加载的时候不会自己运行。静态方法只有被调用的时候才会执行。
                Demo1 demo1=new Demo1();
            }
        }
        //显示:
        24
        24
        
  • 你一个类运行的时候生成对应的.class文件,这个.class文件可以通过类加载器把.class文件加载到内存,生成一个Class躯体(映像)。生成的这个Class也是一个对象,我们可以通过Class对象使用Student、Teacher等类的方法和属性,这个Class对象相当于Student、Teacher相当于类文件的模板,然后我们创建对象实际上是用这个模板来创建一个Student等对象的。我们通过这个Class对象获取某个类的变量和方法信息,然后使用这个信息去操作某个对象的方法或属性的过程就叫反射。反射的示意图如下。

    在这里插入图片描述

2.2获取Class类对象的三种方式

我们想用反射去是使用一个类,那么我们首先需要拿到的就是那个类的字节码文件对象。所以需要学这个获取Class类对象的三种方式。

2.2.1三种方式分类
  • 类名.class属性。比如使用Student.class就会返回Student类对应的Class对象
  • 对象名.getClass()方法。注意:这个getClass()方法是属于Object类中的方法,所以所有的java对象都可以用这个方法。
  • Class.forName(全类名)方法。注意:这个forName(String className)方法是Class类中的静态方法(java中就有一个类叫Class类,在java.lang包下,且这个Class类是一个泛型类)。这个方法需要一个字符串的参数,这个String参数是某类的全路径名,即完整包名+类名。完整包名可以看那个类的第一行package XX;那个XX就是完整包名了。注意本模块下的类是不能访问其他模块的类的。
2.2.2示例代码
public class ReflectDemo {
    public static void main(String[] args) throws ClassNotFoundException {
        //使用类的class属性来获取该类对应的Class对象
        Class<Student> c1 = Student.class;//这种方式可以知道泛型是什么。能接受这个Student.class返回的变量至少是Class<Student>类型的,你用Class<?>和Class来接收都行。下面两种方式就得用 Class<? extends Student>或Class<?>。
        System.out.println(c1);

        Class<Student> c2 = Student.class;
        System.out.println(c1 == c2);//返回是true
        System.out.println("--------");

        //调用对象的getClass()方法,返回该对象所属类对应的Class对象
        Student s = new Student();
        Class<? extends Student> c3 = s.getClass();//注意这个方法接收的变量至少得是Class<? extends Student>以上,所以Class<?> c3 = s.getClass();或者Class c3 = s.getClass();都行。这个是因为知道s表面是什么类的,但是不一定是Student类,因为编译器只看得到表面,所以不能用一个Class<Student>来接收,因为就像Class<Number>不能接收Class<Integer>一样。所以它用了一个万能类,一个限定上限的万能类来接收。限定上限的万能类可以接受Class<Student>和Class<Student子类>的类型。Class<? extends Number> c3 = s.getClass();你看这样就不行。
        System.out.println(c1 == c3);//true
        System.out.println("--------");

        //使用Class类中的静态方法forName(String className)
        Class<?> c4 = Class.forName("com.itheima_02.Student");  //注意这个泛型的写法,因为这个forName(String className)在编译的时候并不知道里面的字符串是属于什么类,所有用contrl+alt+v自动生成的是这样的。这个接受的变量的类型至少得是Class<?>以上,所以Class也可以接收Class c4 = Class.forName("com.itheima_02.Student");这样也是对的。因为编译器不知道这个字符串是什么类,甚至连它是不是类都不知道,所以只能用Class<?>来接收。
        System.out.println(c1 == c4);//true
    }
}

上面三种方式得到的对象都是一个对象。每个类加载到JVM中的时候都会生成一个Class对象,且只要内存里有了那个Class对象就不会再生成同一个类的Class对象,上面三个方法只是让某个变量能指向它罢了,并没有生成对象,这个字节码对象是类加载的时候生成的,且一个类只能生成一个字节码对象,不同类可以生成不同的字节码对象。类的加载是用类加载器(ClassLoader)来加载的。

类在什么时候会被加载?

  1. 定义了main的类,启动main方法时该类会被加载

  2. 创建类的实例,即new对象的时候。或创建子类的实例的时候,因为在创建子类实例的时候,在加载子类之前会优先加载其父类。

  3. 访问类的静态方法

  4. 访问类的静态变量

  5. 反射

    注意:在初始化某个类时,如果这个类的静态代码块、静态方法或静态变量引用到了另一个类,则那个类也会被加载。

    总之,只有用到的类才会被加载。

2.3用反射获取对应类的构造方法并使用

2.3.1Class类获取构造方法对象的方法

Class类的实例也叫XX类的字节码文件对象

  • 方法分类

    方法名说明
    Constructor<?>[] getConstructors()返回所有公共构造方法对象的数组
    Constructor<?>[] getDeclaredConstructors()返回所有构造方法对象的数组
    Constructor getConstructor(Class<?>… parameterTypes)返回单个公共构造方法对象
    Constructor getDeclaredConstructor(Class<?>… parameterTypes)返回单个构造方法对象
  • 示例代码

    Student类长这样:

    package com.liudashuai;
    
    public class Student {
        //成员变量:一个私有,一个默认,一个公共
        private String name;
        int age;
        public String address;
    
        //构造方法:一个私有,一个默认,两个公共
        public Student() {
        }
    
        private Student(String name) {
            this.name = name;
        }
    
        Student(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public Student(String name, int age, String address) {
            this.name = name;
            this.age = age;
            this.address = address;
        }
    
        //成员方法:一个私有,四个公共
        private void function() {
            System.out.println("function");
        }
    
        public void method1() {
            System.out.println("method");
        }
    
        public void method2(String s) {
            System.out.println("method:" + s);
        }
    
        public String method3(String s, int i) {
            return s + "," + i;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", address='" + address + '\'' +
                    '}';
        }
    }
    

    测试getConstructors()方法

    package com.liudashuai;
    
    import java.lang.reflect.Constructor;
    
    public class Demo {
        public static void main(String[] args) throws ClassNotFoundException{
            //获取Class对象
            Class<?> c = Class.forName("com.liudashuai.Student");//得到com.liudashuai.Student的字节码文件对象c
    
            //Constructor<?>[] getConstructors() 返回一个包含 Constructor对象的数组, Constructor对象反映了由该 Class对象表示的类的所有公共构造函数,注意返回类型
            Constructor<?>[] cons = c.getConstructors();
            for(Constructor con : cons) {     //这里也可以用for(Constructor<?> con : cons) {……},声明为Constructor<?>也没有关系的,也可以用方法的,可能部分方法不能用罢了,比如List<?> a=new ArrayList<String>();a.remove("aaa");可以,但是,a.add("abc");不行。可能是add方法内部设置的一个判断吧。所以用for(Constructor<?> con : cons) {……}也是没有什么关系的。
                System.out.println(con);//注意他们的toString方法都是java已经重写过了的
            }
        }
    }
    //打印:
    public com.liudashuai.Student()
    public com.liudashuai.Student(java.lang.String,java.lang.String,int)
    

    测试getDeclaredConstructors()方法

    package com.liudashuai;
    
    import java.lang.reflect.Constructor;
    
    public class Demo {
        public static void main(String[] args) throws ClassNotFoundException{
            //获取Class对象
            Class<?> c = Class.forName("com.liudashuai.Student");
    
            //Constructor<?>[] getDeclaredConstructors() 返回反映由该 Class对象表示的类声明的所有构造函数的 Constructor对象的数组
            Constructor<?>[] cons = c.getDeclaredConstructors();
            for(Constructor con : cons) {
                System.out.println(con);
            }
        }
    }
    //打印
    public com.liudashuai.Student(java.lang.String,int,java.lang.String)
    com.liudashuai.Student(java.lang.String,int)
    private com.liudashuai.Student(java.lang.String)
    public com.liudashuai.Student()
    

    测试getConstructor(Class<?>… parameterTypes)方法创建对象

    package com.liudashuai;
    
    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationTargetException;
    
    public class Demo {
        public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
            //获取Class对象
            Class<?> c = Class.forName("com.liudashuai.Student");
            //Constructor<T> getConstructor(Class<?>... parameterTypes) 返回一个 Constructor对象,该对象反映由该 Class对象表示的类的指定公共构造函数
            //Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) 返回一个 Constructor对象,该对象反映由此 Class对象表示的类或接口的指定构造函数
            //参数要与你要获取的那个类的构造方法的参数列表匹配,且这里写的是字节码对象,比如原来构造方法那个位置是int的,就用int.class
            
            //获取公共无参构造
            Constructor<?> con = c.getConstructor();//这样写就是返回c的那个无参的构造方法
    
            //Constructor提供了一个类的单个构造函数的信息和访问权限
            //T newInstance(Object... initargs) 使用由此 Constructor对象表示的构造函数,使用指定的初始化参数来创建和初始化构造函数的声明类的新实例
    
            Object obj = con.newInstance();//相当于Student s = new Student();但是这里是用反射的来创建对象的,不是直接创建的对象的,这里就是为了讲解怎么通过反射创建对象
            System.out.println(obj);
    
            /*
            //获取到有参构造,错误,因为这个getConstructor(Class<?>... parameterTypes)方法是只能是获取公共的构造方法
            Constructor con1 =c.getConstructor(String.class,int.class);
            Object lb = con1.newInstance("李白", 30);
            System.out.println(lb);
             */
    
            //获取到公共有参构造;
            Constructor con1 =c.getConstructor(String.class,int.class,String.class);
            Object lb = con1.newInstance("李白", 30,"浙江");
            System.out.println(lb);
        }
    }
    //打印:因为我们重写Student的toString方法
    Student{name='null', age=0, address='null'}
    Student{name='李白', age=30, address='浙江'}
    
2.3.2Constructor类用于创建对象的方法
方法名说明
T newInstance(Object…initargs)根据指定的构造方法创建对象(使用例子在上面有,注意:定义可变参数的方法,也包含了定义newInstance()的),这个newInstance(XX)里面的参数要也调用者的生成是的getConstructor(XX)方法的参数匹配

2.4反射获取构造方法并使用练习1

下面例子主要展示了:怎么使用带参数的公共构造方法。

  • 案例需求

    • 通过反射获取公共的构造方法并创建对象
  • 代码实现

    • 学生类

      package com.liudashuai;
      public class Student {
          //成员变量:一个私有,一个默认,一个公共
          private String name;
          int age;
          public String address;
      
          //构造方法:一个私有,一个默认,两个公共
          public Student() {
          }
      
          private Student(String name) {
              this.name = name;
          }
      
          Student(String name, int age) {
              this.name = name;
              this.age = age;
          }
      
          public Student(String name, int age, String address) {
              this.name = name;
              this.age = age;
              this.address = address;
          }
      
          //成员方法:一个私有,四个公共
          private void function() {
              System.out.println("function");
          }
      
          public void method1() {
              System.out.println("method");
          }
      
          public void method2(String s) {
              System.out.println("method:" + s);
          }
      
          public String method3(String s, int i) {
              return s + "," + i;
          }
      
          @Override
          public String toString() {
              return "Student{" +
                      "name='" + name + '\'' +
                      ", age=" + age +
                      ", address='" + address + '\'' +
                      '}';
          }
      }
      
    • 测试类

      package com.liudashuai;
      
      import java.lang.reflect.Constructor;
      import java.lang.reflect.InvocationTargetException;
      
      public class Demo {
          public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
              //获取Class对象
              Class<?> c = Class.forName("com.liudashuai.Student");
      
              //public Student(String name, int age, String address)
              //Constructor<T> getConstructor(Class<?>... parameterTypes)
              Constructor<?> con = c.getConstructor(String.class, int.class, String.class);
              //基本数据类型也可以通过.class得到对应的Class类型
      
              //T newInstance(Object... initargs)
              Object obj = con.newInstance("林青霞", 30, "西安");
              System.out.println(obj);
          }
      }
      //显示:
      Student{name='林青霞', age=30, address='西安'}
      

2.5反射获取构造方法并使用练习2

下面例子主要展示了:1.setAccessible(boolean flag)这个方法可以取消访问检查。2.怎么用反射使用非public的构造方法(这个反式private的方法也是可以使用的)

  • 案例需求

    • 通过反射获取私有构造方法并创建对象
  • 代码实现

    • 学生类:参见上方学生类

    • 测试类

      package com.liudashuai;
      
      import java.lang.reflect.Constructor;
      import java.lang.reflect.InvocationTargetException;
      
      public class Demo {
          public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
              //获取Class对象
              Class<?> c = Class.forName("com.liudashuai.Student");
      
              //private Student(String name)
              //Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)这个方法可以获取本调用者的构造方法,包括非public的
              Constructor<?> con = c.getDeclaredConstructor(String.class);
      
              //暴力反射
              //public void setAccessible(boolean flag):值为true,取消访问检查
              con.setAccessible(true);//因为con是那个private Student(String name) {……}构造方法,所以要是不用这个方法取消访问检查,下面的Object obj = con.newInstance("林青霞");调用的时候就会出错,虽然编译器没有提示你错误,但是执行的时候会认为它是private的然后回抛出异常。
      
              Object obj = con.newInstance("林青霞");
              System.out.println(obj);
          }
      }
      //显示:
      Student{name='林青霞', age=0, address='null'}
      

2.6反射获取成员变量并使用

2.6.1Class类获取成员变量对象的方法

这里主要展示了:1.getField(String name)获取对象的公共成员变量并使用的做法 2.展示了getFields()、getDeclaredFields()的功能。

  • 方法分类

    方法名说明
    Field[] getFields()返回所有公共成员变量对象的数组(成员变量也叫字段,字段叫Field),注意他返回的是Field的对象,这个对象重写了toString方法,这个Field是java.lang.reflect包下的
    Field[] getDeclaredFields()返回所有成员变量对象的数组(Declared"声明了的"的意思)
    Field getField(String name)返回单个公共成员变量对象
    Field getDeclaredField(String name)返回单个成员变量对象(包括非公共的)
  • 示例代码(Student还是用前面的Student)

    测试getFieds()方法的使用和效果:

    package com.liudashuai;
    
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    
    public class Demo {
        public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
            //获取Class对象
            Class<?> c = Class.forName("com.liudashuai.Student");
    
            //Field[] getFields() 返回一个包含 Field对象的数组, Field对象反映由该 Class对象表示的类或接口的所有可访问的公共字段
    
            Field[] fields = c.getFields();
            for(Field field : fields) {
                System.out.println(field);
            }
        }
    }
    //显示:public java.lang.String com.liudashuai.Student.address
    

    测试getDeclaredFields()方法的使用和效果:

    package com.liudashuai;
    
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    
    public class Demo {
        public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
            //获取Class对象
            Class<?> c = Class.forName("com.liudashuai.Student");
    
            //Field[] getFields() 返回一个包含 Field对象的数组, Field对象反映由该 Class对象表示的类或接口的所有可访问的公共字段
    
            Field[] fields = c.getDeclaredFields();
            for(Field field : fields) {
                System.out.println(field);
            }
        }
    }
    //显示:
    private java.lang.String com.liudashuai.Student.name
    int com.liudashuai.Student.age
    public java.lang.String com.liudashuai.Student.address
    

    测试getField()方法,并使用它修改成员变量的值

    package com.liudashuai;
    
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    
    public class Demo {
        public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
            //获取Class对象
            Class<?> c = Class.forName("com.liudashuai.Student");
    
            //Field getField(String name) 返回一个 Field对象,该对象反映由该 Class对象表示的类或接口的指定公共成员字段
            Field addressField = c.getField("address");//找到这个c对象中叫address名字的成员变量,注意返回是Field类型的。
            //获取无参构造方法创建对象
            Constructor<?> con = c.getConstructor();
            Object obj = con.newInstance();
            //Field类的介绍:提供有关类或接口的单个字段的信息和动态访问
            //Field类的这个方法void set(Object obj, Object value)将指定的对象参数中由此Field对象表示的字段设置为指定的新值
            addressField.set(obj,"西安"); //给obj对象的addressField表示的成员变量赋值为西安
            System.out.println(obj);
    
            //上面那块语句相当于做了下面的事情
    //        Student s = new Student();
    //        s.address = "西安";
    //        System.out.println(s);
        }
    }
    
2.6.2Field类用于给成员变量赋值的方法
方法名说明
void set(Object obj,Object value)给obj对象的成员变量赋值为value

2.7反射获取成员变量并使用练习

这里展示了:1.获取非公共的成员变量的getDeclaredField(String)方法的使用

  • 案例需求

    • 通过反射获取成员变量并赋值

    • 用反射达到这样的效果:

      Student s = new Student();

      s.name = “林青霞”;

      s.age = 30;

      s.address = “西安”;

      System.out.println(s);

  • 代码实现

    • 学生类:参见上方学生类

    • 测试类

      package com.liudashuai;
      
      import java.lang.reflect.Constructor;
      import java.lang.reflect.Field;
      
      public class Demo {
          public static void main(String[] args) throws Exception {
              //获取Class对象
              Class<?> c = Class.forName("com.liudashuai.Student");
      
              //Student s = new Student();
              Constructor<?> con = c.getConstructor();//c这个字节码文件的获取构造方法
              Object obj = con.newInstance();//用拿到的构造方法创建对象
      
              //s.name = "林青霞";
      //        Field nameField = c.getField("name"); //这句语句执行会抛出NoSuchFieldException: name,因为name这个字段在上面那个Student中是private修饰的,这个getField(Stirng)方法只能拿到公共字段
              Field nameField = c.getDeclaredField("name");
              nameField.setAccessible(true);//取消访问检查
              nameField.set(obj, "林青霞");
      
              //s.age = 30;
              Field ageField = c.getDeclaredField("age");
              ageField.setAccessible(true);
              ageField.set(obj,30);
      
              //s.address = "西安";
              Field addressField = c.getDeclaredField("address");
              addressField.setAccessible(true);
              addressField.set(obj,"西安");
              System.out.println(obj);
          }
      }
      //显示:
      Student{name='林青霞', age=30, address='西安'}
      

2.8反射获取成员方法并使用

这里主要展示了:1.getMethods()和getDeclaredMethods()方法的功能 2.getMethod(String name, Class<?>… parameterTypes)如何获取公共无参方法并使用。

2.8.1Class类获取成员方法对象的方法
  • 方法分类

    方法名说明
    Method[] getMethods()返回所有公共成员方法对象的数组,包括继承的方法
    Method[] getDeclaredMethods()返回所有成员方法对象的数组,不包括继承的
    Method getMethod(String name, Class<?>… parameterTypes)返回单个公共成员方法对象,参数第一个是那个方法的方法名,另一个是那个方法的参数对应的Class
    Method getDeclaredMethod(String name, Class<?>… parameterTypes)返回单个成员方法对象,参数第一个是那个方法的方法名,另一个是那个方法的参数对应的Class
  • 示例代码

    Student类还是和前面一样的。

    测试getMethods()方法

    package com.liudashuai;
    
    import java.lang.reflect.Method;
    
    public class Demo {
        public static void main(String[] args) throws Exception {
            //获取Class对象
            Class<?> c = Class.forName("com.liudashuai.Student");
    
            //Method[] getMethods() 返回一个包含 方法对象的数组, 方法对象反映由该 Class对象表示的类或接口的所有公共方法,包括由类或接口声明的对象以及从超类和超级接口继承的类
            Method[] methods = c.getMethods();
            for(Method method : methods) {
                System.out.println(method);
            }
        }
    }
    //显示:
    public java.lang.String com.liudashuai.Student.toString()
    public java.lang.String com.liudashuai.Student.method3(java.lang.String,int)
    public void com.liudashuai.Student.method2(java.lang.String)
    public void com.liudashuai.Student.method1()
    public final void java.lang.Object.wait() throws java.lang.InterruptedException
    public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
    public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
    public boolean java.lang.Object.equals(java.lang.Object)
    public native int java.lang.Object.hashCode()
    public final native java.lang.Class java.lang.Object.getClass()
    public final native void java.lang.Object.notify()
    public final native void java.lang.Object.notifyAll()
    

    测试getDeclaredMethods()方法

    package com.liudashuai;
    
    import java.lang.reflect.Method;
    
    public class Demo {
        public static void main(String[] args) throws Exception {
            //获取Class对象
            Class<?> c = Class.forName("com.liudashuai.Student");
    
            //Method[] getDeclaredMethods() 返回一个包含 方法对象的数组, 方法对象反映由 Class对象表示的类或接口的所有声明方法,包括public,protected,default(package)访问和私有方法,但不包括继承方法
            Method[] methods = c.getDeclaredMethods();
            for(Method method : methods) {
                System.out.println(method);
            }
        }
    }
    //显示
    public java.lang.String com.liudashuai.Student.toString()
    private void com.liudashuai.Student.function()
    public void com.liudashuai.Student.method2(java.lang.String)
    public void com.liudashuai.Student.method1()
    public java.lang.String com.liudashuai.Student.method3(java.lang.String,int)
    

    测试getMethod(String name, Class<?>… parameterTypes)方法,获取公共无参方法并使用。

    package com.liudashuai;
    
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Method;
    
    public class Demo {
        public static void main(String[] args) throws Exception {
            //获取Class对象
            Class<?> c = Class.forName("com.liudashuai.Student");
            //Method getMethod(String name, Class<?>... parameterTypes) 返回一个 方法对象,该对象反映由该 Class对象表示的类或接口的指定公共成员方法
            Method m = c.getMethod("method1");
            //获取无参构造方法创建对象
            Constructor<?> con = c.getConstructor();
            Object obj = con.newInstance();
            //Method类的介绍:它在类或接口上提供有关单一方法的信息和访问权限
            //Method类的方法Object invoke(Object obj, Object... args) 在具有指定参数的指定对象上调用此 方法对象表示的基础方法
            //Object:返回值类型
            //obj:调用方法的对象
            //args:方法需要的参数
            m.invoke(obj);//因为我们现在用的是无参的方法,method1()在Student中长这样public void method1() {……},所以直接不写后面的那个参数就行了,可变参数是允许是空的
    
            //上面的这一块语句相当于下面的这些语句
    //        Student s = new Student();
    //        s.method1();
        }
    }
    //显示:(因为那个Student的method1方法里面的语句就是System.out.println("method");)
    method
    
2.8.2Method类用于执行方法的方法
方法名说明
Object invoke(Object obj,Object… args)调用obj对象的成员方法,参数是args,返回值是Object类型

2.9反射获取成员方法并使用练习

这里主要展示了:1.反射调用公共无参方法、调用公共带参方法、调用有返回值的方法、调用私有的方法

  • 案例需求

    • 通过反射获取成员方法并调用

    • 实现下面这些语句的效果

      Student s = new Student();

      s.method1();

      s.method2(“林青霞”);

      String ss = s.method3(“林青霞”,30);

      System.out.println(ss);

      s.function();

  • 代码实现

    • 学生类:参见上方学生类

    • 测试类

      package com.liudashuai;
      
      import java.lang.reflect.Constructor;
      import java.lang.reflect.Method;
      
      public class Demo {
          public static void main(String[] args) throws Exception {
              //获取Class对象
              Class<?> c = Class.forName("com.liudashuai.Student");
      
              //Student s = new Student();
              Constructor<?> con = c.getConstructor();
              Object obj = con.newInstance();
      
              //s.method1();
              Method m1 = c.getMethod("method1");
              m1.invoke(obj);
      
              //s.method2("林青霞");
              Method m2 = c.getMethod("method2", String.class);
              m2.invoke(obj,"林青霞");
      
      //        String ss = s.method3("林青霞",30);
      //        System.out.println(ss);
              Method m3 = c.getMethod("method3", String.class, int.class);
              Object o = m3.invoke(obj, "林青霞", 30);
              System.out.println(o);
      
              //s.function();
      //        Method m4 = c.getMethod("function"); //抛出NoSuchMethodException: com.itheima_02.Student.function(),因为这个方法不是公共的,所以不能用getMethod(Stirng)方法来得到。
              Method m4 = c.getDeclaredMethod("function");
              m4.setAccessible(true);
              m4.invoke(obj);
          }
      }
      //显示:
      method
      method:林青霞
      林青霞,30
      function
      

2.10反射的案例

2.10.1反射练习之越过泛型检查
  • 案例需求

    • 通过反射技术,向一个泛型为Integer的集合中添加一些字符串数据

      即:有一个ArrayList集合,现在想在集合里面添加字符串数据,应该如何实现?以前的话,我们实现不了,但是现在可以用反射来实现。

  • 代码实现

    package com.liudashuai;
    
    import java.lang.reflect.Method;
    import java.util.ArrayList;
    
    public class Demo {
        public static void main(String[] args) throws Exception {
            //创建集合
            ArrayList<Integer> array = new ArrayList<Integer>();
    
    //        array.add(10);//可以
    //        array.add(20);//可以
    //        array.add("hello");//不可以
    
            //下面我们通过反射来实现,添加集合数据
            Class<? extends ArrayList> c = array.getClass();//我们知道对象,可以用getClass方法来得到它的字节码对象。得到字节码对象有三种方式嘛,前面讲过。
            Method m = c.getMethod("add", Object.class);//因为泛型在字节码文件里面是把泛型都变为Object的,所以这里Oject.class,因为泛型只是在编译的时候有一个检查作用而已,限制你输入,变成字节码文件都是Object
    
            m.invoke(array,10);
            m.invoke(array,"world");
            m.invoke(array,"java");
    
            System.out.println(array);
        }
    }
    //显示
    [10, world, java]
    
2.10.2运行配置文件中指定类的指定方法
  • 案例需求

    • 通过反射运行配置文件(即一个文件,一般用”键=值“的格式)中指定类的指定方法。
  • 代码实现

    Student类

    package com.liudashuai;
    public class Student {
        public void study(){
            System.out.println("好好学习天天向上");
        }
    }
    

    测试类

    package com.liudashuai;
    
    import java.io.FileReader;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Method;
    import java.util.Properties;
    
    public class Demo {
        public static void main(String[] args) throws Exception {
            //加载数据
            Properties prop = new Properties();
            FileReader fr = new FileReader("idea_test\\a.txt");
            prop.load(fr);//把a.txt文件里面的数据放到prop集合里面
            fr.close();
    
            /*假设a.txt文件里面存的内容是这两句
                className=com.liudashuai.Student
                methodName=study
             */
            String className = prop.getProperty("className");//这样className字符串内容就是com.liudashuai.Student
            String methodName = prop.getProperty("methodName");//这样methodName字符串内容就是study
    
            //通过反射来使用
            Class<?> c = Class.forName(className);//拿到com.liudashuai.Student的字节码对象
    
            Constructor<?> con = c.getConstructor();//获取无参的构造方法
            Object obj = con.newInstance();//创建对象
    
            Method m = c.getMethod(methodName);//拿到study()那个无参方法
            m.invoke(obj);//用obj对象调用study()方法
        }
    }
    //显示
    好好学习天天向上
    

    a.txt文件的内容

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I7x64oDN-1661396910708)(第十五次任务-类加载器和反射/image-20220824110127174.png)]

  • 然后要是有一个Teacher类是这样的

    package com.liudashuai;
    
    public class Teacher {
        public void teach(){
            System.out.println("用爱成就学员");
        }
    }
    

    你要是想执行Teacher对象的teach()的方法,就可以不用改测试类的代码,只改变配置文件的值

    package com.liudashuai;
    
    import java.io.FileReader;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Method;
    import java.util.Properties;
    
    public class Demo {
        public static void main(String[] args) throws Exception {
            //加载数据
            Properties prop = new Properties();
            FileReader fr = new FileReader("idea_test\\a.txt");
            prop.load(fr);
            fr.close();
    
            String className = prop.getProperty("className");
            String methodName = prop.getProperty("methodName");
    
            //通过反射来使用
            Class<?> c = Class.forName(className);
    
            Constructor<?> con = c.getConstructor();
            Object obj = con.newInstance();
    
            Method m = c.getMethod(methodName);
            m.invoke(obj);
        }
    }
    

    a.txt如下

    在这里插入图片描述

    结果显示”用爱成就学员“,即Teacher对象的teach()方法被执行。所以这样的好处是:你可以不改变程序代码,直接改变配置文件的值,可以让程序执行另一个对象的另一个方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值