Java高级-反射

2021-05-27  本文已影响0人  ttiga

15.1.Java反射机制概述

15.2.理解Class类并获取Class实例(重点)

public class ReflectionTest {
    // 反射之前,对于Person类的操作
    @Test
    public void test1(){
        // 1.创建Person类的对象
        Person p1 = new Person("tom", 12);
        // 2.通过对象调用内部属性,方法
        p1.age = 10;
        System.out.println(p1.toString());
        p1.show();
        // 在Person类外部,不可以通过Person类的对象调用其内部私有结构.
        // 比如: name . showNation()以及私有的构造器
    }
    // 反射之后,对于Person类的操作
    // Person类在运行时候,把它称作运行时类,相当
    @Test
    public void test2() throws Exception {
        // 原来可以做到的
        // 1.通过反射,创建Person类的对象
        // Person类有个属性".class"作为Class的实例
        Class clazz = Person.class;
        // 获取clazz对象指定的构造器
        Constructor cons = clazz.getConstructor(String.class, int.class);
        // 通过构造器造Object类型对象
        Object obj = cons.newInstance("Tom", 12);// 本质上obj就是Person类型,多态形式
        Person p = (Person) obj;// 因此可以强转
        System.out.println(obj.toString());// 实际调用Person类重写的toString方法
        // 2.通过反射,调用对象指定的属性,方法
        // 获取到clazz对应的Person类里age属性
        // 调属性
        Field age = clazz.getDeclaredField("age");
        // 给p对象的age属性赋值为10
        age.set(p, 10);// 可以看成 p.setage
        System.out.println(p.toString());
        // 调方法,其中有可变形参为参数类型,没有也可不写
        Method show = clazz.getDeclaredMethod("show");// 空参的
        // 调用
        show.invoke(p);// 参数为实参
        System.out.println("=============================");
        //原来做不到的: 通过反射,可以调用Person类(运行时类)的私有结构的。比如:私有的构造器、方法、属性
        // 调用私有构造器
        // 创建String类型的构造器
        Constructor cons1 = clazz.getDeclaredConstructor(String.class);
        cons1.setAccessible(true); // 简单的说就是把private封装的变量和方法,强行解锁了,使之可以被访问,被调用
        // 通过构造器创建对象,p1就Person类的对象
        Person p1 = (Person) cons1.newInstance("Jerry");
        System.out.println(p1.toString());
        // 调用私有属性,属性名为name
        Field name = clazz.getDeclaredField("name");
        name.setAccessible(true);
        name.set(p1, "hanmeimei");// 直接通过对象操作属性
        System.out.println(p1.toString());
        // 调用私有方法
        Method showNation = clazz.getDeclaredMethod("showNation", String.class);
        showNation.setAccessible(true);
        // showNation是Method类型的对象,Java里永远都是对象调,只不过这个对象恰好是原来的方法,参数p1是原来的对象
        // 其实仍然是对象调属性,方法 invoke():默认返回Object类型
        String nation = (String) showNation.invoke(p1, "中国");// 相当于 String naton = p1.showNation("中国")
        System.out.println(nation.toString());
    }
}
//原来做不到的: 通过反射,可以调用Person类(运行时类)的私有结构的。比如:私有的构造器、方法、属性
        // 调用私有构造器
        // 创建String类型的构造器
        Constructor cons1 = clazz.getDeclaredConstructor(String.class);
        cons1.setAccessible(true); // 简单的说就是把private封装的变量和方法,强行解锁了,使之可以被访问,被调用
        // 通过构造器创建对象,p1就Person类的对象
        Person p1 = (Person) cons1.newInstance("Jerry");
        System.out.println(p1.toString());
        // 调用私有属性,属性名为name
        Field name = clazz.getDeclaredField("name");
        name.setAccessible(true);
        name.set(p1, "hanmeimei");// 直接通过对象操作属性
        System.out.println(p1.toString());
        // 调用私有方法
        Method showNation = clazz.getDeclaredMethod("showNation", String.class);
        showNation.setAccessible(true);
        // showNation是Method类型的对象,Java里永远都是对象调,只不过这个对象恰好是原来的方法,参数p1是原来的对象
        // 其实仍然是对象调属性,方法 invoke():默认返回Object类型
        String nation = (String) showNation.invoke(p1, "中国");// 相当于 String naton = p1.showNation("中国")
        System.out.println(nation.toString());

疑问1:通过直接new的方式或反射的方式都可以调用公共的结构,开发中到底用那个?
建议:直接new的方式。
什么时候会使用:编译的时候不确定要new哪个类的对象,就用反射的方式。反射的特征:动态性.因为有不同类型的构造方法,而到运行的时候才能决定需要怎样的对象
疑问2:反射机制与面向对象中的封装性是不是矛盾的?如何看待两个技术?
不矛盾; 封装性是有public的结构就不建议调private的结构;反射机制是可以调用private的结构,看具体需求

关于java.lang.Class类的理解
1.类的加载过程:
编译过程:程序经过javac.exe(编译)命令以后,会生成一个或多个字节码文件(.cLass结尾)。
解释运行,加载的过程: 接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中,这个过程就称为类的加载。
加载到内存中的类,称之为运行时类,此运行时类,就作为Class类的一个实例
2.换句话说, Class的实例就对应着一个运行时类, 而且该运行时类一旦加载到内存以后,实际上会缓存一段时间,下面的只是通过不同方式获取了内存当中唯一的运行时类
说想提供一个Class的实例而去new一个Class是错误的.Class实例就是用一个运行时类来进行赋值的
3.加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。
万事万物皆对象? 对象.xxx,File,URL,反射,前端,数据库操作

// 获取Class的实例方式: (前三种方式要掌握)
    @Test
    public void test3() throws ClassNotFoundException {
        // 方式一: 调用运行时类的属性: 这个属性叫做: .class   class是Class的属性,用class去描述Class
        // 编译时写死了,若类不存在,编译会报错
        // Class是对于类的通用描述,此时其实是Person给他赋的值,所以Class具体操作都是Person
        Class clazz1 = Person.class;// 可以加上泛型避免后面要强转
        System.out.println(clazz1);// 获取到Person类本身
        // 方式二: 通过运行时类的对象,调用getClass(); 先创建了类的对象不好用
        Person p1 = new Person();
        // 获取该对象是哪个类造的方法,任何一个对象都知道在哪个类造的,所以声明在Ojbect类中
        Class clazz2 = p1.getClass();// 返回Person类本身赋给另外一个clazz2
        System.out.println(clazz2);
        // (最常用)方式三: 调用Class的静态方法: forName(String classPath),参数是类的全路径
        // 最常用,因为通过反射能做的第一件事就是能创建对应的运行时类的对象,运行时才确定是否存在运行时类
        // 这种方式更好的体现了动态性,因为反射主要想体现的就是运行时的动态性,编译的时候先不去确定
        // 不管是什么类,这个类本身都作为Class的实例
        Class clazz3 = Class.forName("com.senior.Person");// 类的全类名(包含包的完整路径)
        System.out.println(clazz3);
        // 虽然获取类的方式不同,但时都是获取内存中同一个运行时类,因为用的同一个类加载器,类只加载一次
        System.out.println(clazz1 == clazz2);
        System.out.println(clazz1 == clazz3);
        // 方式四: 用类的加载器: ClassLoader
        // 调用测试类的getClassLoader方法,获得一个类加载器
        ClassLoader classLoader = ReflectionTest.class.getClassLoader();
        // 通过加载器显式的加载某个类
        Class clazz4 = classLoader.loadClass("com.senior.Person");
    }
// Class实例可以是哪些结构的说明: Class不仅可以表示为运行时类,还可以表示其他结构
    @Test
    public void test4(){
        Class c1 = Object.class;
        Class c2 = Comparable.class;
        Class c3 = String[].class;
        Class c4 = int[][].class;
        Class c5 = ElementType.class;// 枚举类
        Class c6 = int.class;
        Class c7 = Override.class;
        Class c8 = void.class;
        Class c9 = Class.class;
        // 只要数组元素类型和维度一样,就是同一个Class
        int[] a = new int[10];
        int[] b = new int[100];
        Class c10 = a.getClass();
        Class c11 = b.getClass();
        System.out.println(c10 == c11);// true
    }
image.png

15.3.类的加载与ClassLoader的理解

image.png image.png
// 自定义加载类是系统类加载器加载的
public class ClassLoaderTest {
    @Test
    public void test1(){
        // 先获得Class类的实例,再获取当前自定义类的类加载器
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        System.out.println(classLoader); // AppClassLoader: 系统类加载器
        // getParent(): 获取上一级加载器
        ClassLoader classLoader1 = classLoader.getParent();
        System.out.println(classLoader1);// ExtClassLoader: 扩展类加载器
        ClassLoader classLoader2 = classLoader1.getParent();
        // 引导类加载器显示null不是没有,而是获取不到,不能主动去加载自定义类,不能直接通过它做一些事
        // 引导类加载器主要负责加载java的核心类库,无法加载自定义类的。
        System.out.println(classLoader2);// null: 无法获取引导类加载器
        ClassLoader classLoader3 = String.class.getClassLoader();
        System.out.println(classLoader3);// null
    }
}

Properties: 用来读取配置文件
写web的时候properties文件写在src下,因为部署到Tomcat服务器是module下的配置文件就缺失了
为了保证文件的存在,要写在src下

@Test
    public void test2() throws IOException {
        // 创建配置文件的对象
        Properties pros = new Properties();
        // 把设置中File Encodings下的 Transparent native-to-ascii conversion 勾上防止乱码
        // 此时的配置文件默认在当前的module下
        // 读取配置文件方式一:
        // 造一个输入流对象,构造器参数传入一个配置文件,配置文件在当前module下new一个Resource Bundle
         FileInputStream fis = new FileInputStream("jdbc.properties");
         // 读取的配置文件还是当前module下,只是module下多了一层目录下的文件
        // FileInputStream fis = new FileInputStream("src\\jdbc1.properties");
        // 通过(pros)读取文件对象去调用加载方法,参数传入输入流对象
         pros.load(fis);// pros加载对应的文件输入流对象,其实主要操作对应的jdbc配置文件
        // 方式二: 用ClassLoader 类加载器
        // 配置文件默认识别为:当前module的src下
        // 获取当前自定义加载类的类加载器
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        // 通过系统类加载器以流的形式获取资源文件
        //  File类型获取的是当前module下的,类加载器获取的是源(src)文件下的
        InputStream is = classLoader.getResourceAsStream("jdbc1.properties");
        // 通过文件对象读取输入流的资源
        //pros.load(is);
        // getProperty(): 获取配置文件的键值对中的值: 都是String类型
        // 分别获取user和password
        String user = pros.getProperty("user");
        String password = pros.getProperty("password");
        System.out.println("user = " + user + " password = " + password);
    }

15.4.创建运行时类的对象(重点)

通过反射,创建运行时类的对象

通过反射创建对应的运行时类的对象
Class对应的是哪个运行时类,就只能创建那个类的对象

newInstance(): 调用此方法,创建对应的运行时类(Person)的实例(对象),内部调的是运行时类的空参构造器
造对象,都得是用构造器来造,只是形式上不一样
构造器私有,就是单例模式了,但也是从静态方法里调用构造器的
要想此方法正常的创建运行时类的对象,要求:
1.运行时类必须提供空参构造器
2.提供足够条件的空参构造器的权限修饰符.通常 设置为public
有可能在学框架的时候,框架底层要用到反射,造javabean的对象,通常通过反射造对象,普遍调空参构造器,要是有属性,后面再通过调属性方式给属性赋值
在javabean中要求提供一个public的空参构造器。原因:
1.便于通过反射,创建运行时类的对象
2.便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器

public class NewInstanceTest {
    // InstantiationException: 初始化异常, 运行时类没有空参构造器
    // IllegalAccessException: 非法访问异常,构造器权限修饰符私有化,访问权限不够
    @Test
    public void test1() throws InstantiationException, IllegalAccessException {
        // 先获取Class类的实例
        // clazz实例指向方法区中的Person.class这个类
        Class clazz = Person.class;
        Person obj = (Person) clazz.newInstance();// 调的是运行时类的空参构造器
        // 获取运行时类的对象
        // Object obj = clazz.getDeclaredConstructor().newInstance();
        System.out.println(obj);
    }
}
// 体会反射的动态性: 运行时才知道到底要造哪个类的对象
    @Test
    public void test2(){
        for (int i = 0; i < 100; i++) {
            int num = new Random().nextInt(3); // 0~2
            String classPath = "";
            switch (num) {
                case 0:
                    classPath = "java.util.Date";
                    break;
                case 1:
                    classPath = "java.lang.Object";
                    break;
                case 2:
                    classPath = "com.senior.Person";
                    break;
            }
            try {
                Object obj = getInstance(classPath);
                System.out.println(obj);
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }
    public Object getInstance(String classPath) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        Class clazz = Class.forName(classPath);
        return clazz.newInstance();
    } 

15.5.获取运行时类的完整结构

看看Person能不能拿到父类的泛型,并实现其他接口
把自定义注解加到类的相关结构上,不想用默认值可以自行修改,通过反射方式可以拿到注解

// 丰富结构都在Person里
public class Creature <T> implements Serializable {
    private char gender;
    public double weight;
    public void breath(){
        System.out.println("生物呼吸");
    }
    public void eat(){
        System.out.println("生物吃东西");
    }
}
public interface MyInterface {
    // 定义抽象方法
    public abstract void info();
}
// 两个元注解: 需要什么结构,生命周期
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
// 要想通过反射获取当前的注解,要把SOURCE改成RUNTIME,这时会加载到内存当中,只有加载到内存中的结构才能通过反射获取
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    // 创建一个数组 默认值为hello
    String value() default "hello";
}
@MyAnnotation(value = "hi")
public class Person extends Creature<String> implements Comparable<String>,MyInterface{
    private static String GENDER;
    private String name;
    int age;
    public int id;
    public Person(){

    }
@MyAnnotation(value = "abc")
    private Person(String name) {
        this.name = name;
    }

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    private static void showDesc(){
        System.out.println("我是个可爱的人");
    }
    @MyAnnotation
    private String show(String nation){
        System.out.println("我的国籍是: " + nation);
        return nation;
    }
    public String display(String interests){
        return interests;
    }
    @Override
    public void info() {
        System.out.println("我是个人");
    }

    @Override
    public int compareTo(String o) {
        return 0;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", id=" + id +
                '}';
    }
}
public class FieldTest {
    @Test
    public void test1(){
        Class clazz = Person.class;
        // 获取所有属性结构
        // getFields():获取当前运行时类及其父类中声明为public访问权限的属性
        Field[] fields = clazz.getFields();
        for (Field f : fields) {
            System.out.println(f);
        }
        System.out.println();
        // 获取声明过的属性
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field f : declaredFields) {
            System.out.println(f);
        }
    }
    // 获取属性里的具体某个结构(部分)
    //权限修饰符 数据类型 变量名
    @Test
    public void test2(){
        // 获取运行时类的对象
        Class clazz = Person.class;
        // 获取当前类里所有属性
        Field[] declaredFields = clazz.getDeclaredFields();
        // 遍历数组
        for (Field f : declaredFields){
            // 1.获取权限修饰符
            int modifier = f.getModifiers();
            // Modifier类中重写了toString方法,default默认为空
            System.out.print(Modifier.toString(modifier) + "\t");
            // 2.获取数据类型
            Class type = f.getType();
            // getName(): 省略class
            System.out.println(type.getName() + "\t");
            // 3.获取变量名
            String fName = f.getName();
            System.out.print(fName);
            System.out.println();
        }
    }
}
public class MethodTest {
    @Test
    public void test1(){
        Class clazz = Person.class;
        // getMethods():获取当前运行时类及其所有父类中声明为public权限的方法
        Method[] methods = clazz.getMethods();
        for (Method m : methods) {
            System.out.println(m);
        }
        //getDeclaredMethods ():获取当前运行时类中声明的所有方法。(不包含父类中声明的方法)
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for (Method m : declaredMethods) {
            System.out.println(m);
        }
    }
}
@Test
    public void test1(){
        Class clazz = Person.class;
        // getConstructors():获取当前运行时类中声明为public的构造器
        Constructor[] constructors = clazz.getConstructors();
        for (Constructor c : constructors) {
            System.out.println(c);
        }
        System.out.println();
        // getDeclaredconstructors():获取当前运行时类中声明的所有的构造器
        Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
        for (Constructor c : declaredConstructors) {
            System.out.println(c);
        }
    }
/*
    获取运行时类的父类
     */
    @Test
    public void test2(){
        Class clazz = Person.class;
        // 父类也是Class的实例
        Class superClass = clazz.getSuperclass();
        System.out.println(superClass);
    }
    /*
    获取运行时类的带泛型的父类
     */
    @Test
    public void test3(){
        Class clazz = Person.class;
        // 父类也是Class的实例
        // Class类实现了Type接口
        Type genericSuperclass = clazz.getGenericSuperclass();
        System.out.println(genericSuperclass);
    }
/*
    获取运行时类的带泛型的父类的泛型
     */
    @Test
    public void test4(){
        Class clazz = Person.class;
        // 父类也是Class的实例
        Type genericSuperclass = clazz.getGenericSuperclass();
        // 确定类型的参数
        ParameterizedType paramType = (ParameterizedType) genericSuperclass;
        // 获取实际上的类型参数,实际上就是获取泛型参数
        Type[] actualTypeArguments = paramType.getActualTypeArguments();
        System.out.println(actualTypeArguments[0]);// 返回泛型类
        // System.out.println(actualTypeArguments[0].getTypeName());// 省略class关键字
        // System.out.println(((Class)actualTypeArguments[0]).getName());// 同上
        System.out.println(paramType);
        System.out.println(genericSuperclass);
    }
/*
    获取运行时类实现的接口
     */
    @Test
    public void test5(){
        Class clazz = Person.class;
        // 因为可以多实现所以返回数组类型
        Class[] interfaces = clazz.getInterfaces();
        for (Class c : interfaces) {
            System.out.println(c);
        }
        System.out.println();
        // 获取运行时类的父类实现的接口
        Class[] interface1 = clazz.getSuperclass().getInterfaces();
        for (Class c : interface1) {
            System.out.println(c);
        }
    }
    /*
    获取运行时类所在的包
     */
    @Test
    public void test6(){
        Class clazz = Person.class;
        Package pack = clazz.getPackage();
        System.out.println(pack);
    }
    /*
    获取运行时类声明的注解
     */
    @Test
    public void test7(){
        Class clazz = Person.class;
        // 因为注解可能有多个,所以返回数组类型
        Annotation[] annotations = clazz.getAnnotations();
        for (Annotation annos : annotations) {
            System.out.println(annos);
        }
    }

15.6.调用运行时类的指定结构(重点)

/*
不需要掌握
调用运行时类的指定属性
 */
public class ReflectionTest {
    @Test
    public void testField() throws Exception{
        // 获取Class类的实例
        // 一个类只有唯一的一个运行时类,即大Class对象
        Class clazz = Person.class;
        // 调Person类的(空参)构造器创建运行时类的对象
        Person p = (Person) clazz.newInstance();
        // 获取运行时类的指定属性: 要求运行时类中属性声明为public
        // 通常不采用此方法
        Field id = clazz.getField("age");
        //id.setAccessible(true); // 不适用于getField()
        /*
         设置当前属性值
         set(): 参数1: 指明设置哪个对象的属性
         参数2: 将此属性值设置为多少
         */
        id.set(p, 1001);
        // 获取当前运行时类对象属性的值
        // get(): 参数1: 获取当前对象的属性值
        int pId = (int) id.get(p);
        System.out.println(pId);
    }
    /*
    如何操作运行时类中的指定的属性 -- 要掌握
     */
    @Test
    public void testField1() throws Exception {
        Class clazz = Person.class;
        Person p = (Person) clazz.newInstance();
        // 1.getDclaredField(String fieldName): 获取运行时类中指定变量名的属性
        Field name = clazz.getDeclaredField("name");
        // 2.保证当前属性是可访问的: 如果是私有的情况下
        name.setAccessible(true);
        // 3.获取,设置指定对象的此属性
        name.set(p, "tom");
        System.out.println(name.get(p));
    }
    /*
        调用运行时类的静态属性
         */
    @Test
    public void testField2() throws Exception{
        Class clazz = Person.class;
        // 获取运行时类的属性
        Field gender = clazz.getDeclaredField("GENDER");
        // 保证属性可访问
        gender.setAccessible(true);
        // 调用属性
        gender.set(null,"male");// 对象参数也可写当前运行时类
        System.out.println(gender.get(null));
    }
}
/*
    如何操作运行时类的指定的方法 -- 要掌握
    非静态的方法: 必须要有运行类的对象
     */
    @Test
    public void testMethod() throws Exception {
        // 获取运行时类
        Class clazz = Person.class;
        // 创建运行时类的对象
        Person p = (Person) clazz.newInstance();
        // 1.获取指定的某个方法
        // getDeclaredMethod(): 参数1: 指明获取的方法名称, 参数2: 指明获取的方法的形参列表
        Method show = clazz.getDeclaredMethod("show", String.class);// 因为show是Class类型的方法,所以参数类型也要是class类型
        // 2.保证方法可访问
        show.setAccessible(true);
        /*
        3.调用方法的invoke(): 参数1: 方法的调用者, 参数2: 给方法形参赋值的实参
        invoke()的返回值即为对应类中调用方法的返回值,默认是Object类型
         */
        Object returnValue = show.invoke(p, "CHN");// String nation = p.show("CHN");
        System.out.println(returnValue);
        /*
        如何调用静态方法
         */
        // 1.获取运行时类的方法
        Method showDesc = clazz.getDeclaredMethod("showDesc");
        // 2.保证方法可访问
        showDesc.setAccessible(true);
        // 3.方法调用invoke(): 如果调用的运行时类中的方法没有返回值,则此invoke()返回null
        // Object returnaVal = showDesc.invoke(clazz); 这样也可以
        Object returnaVal = showDesc.invoke(Person.class);
        // Object returnaVal = showDesc.invoke(null);// 因为静态方法都是共享的,所以调用不需要具体对象所以可以写null
        System.out.println(returnaVal);// null
    }
@Test
    public void testConstructor() throws Exception{
        Class clazz = Person.class;
        // 获取运行时类指定的构造器
        // getDeclaredConstructor(): 参数: 指明构造器的参数列表
        Constructor constructor = clazz.getDeclaredConstructor(String.class);
        // 保证构造器可访问的
        constructor.setAccessible(true);
        // 调用构造器的newInstance()造对象
        // 构造器调的newinstance()和运行类调的不同,运行时类的是空参的,构造器调的是带参数的重载构造器
        Person p = (Person) constructor.newInstance("Tom");// 形参对应运行时类构造器的参数类型
        System.out.println(p);
    }

15.7.反射的应用: 动态代理

上一篇下一篇

猜你喜欢

热点阅读