Java基础—反射

一、概念

1、反射是什么

  • 作用:反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息,包括成员变量、构造器、成员方法等,并能操作对象的属性及方法。
  • 功能:动态获取信息以及动态调用对象方法
  • 用途:在设计模式或者框架底层都会用到
  • 个人理解:之前都是通过new关键字创建对象,进而使用方法。现在是直接通过大Class实例来完成对象创建、属性复制、方法调用这一系列过程

2、绘制 内存模型图 来解释 反射机制

在这里插入图片描述

3、反射机制 相关的类

  • java.lang.Class:Class代表类,由启动类加载器进行加载;某个类被类加载器加载后,将在堆中生成一个Class对象,该对象和方法区中的二进制字节码文件存在联系
  • java.lang.reflect.Method:代表类的方法,Method对象代表类中某个方法
  • java.lang.reflect.Field:代表类的成员变量,Field对象代表类中某个属性
  • java.lang.reflect.Constructctor:代表类的构造方法,Constructctor对象代表类中某个构造器

4、反射的优点和缺点

  • 优点:可以动态的创建和使用对象,其应用于框架底层,如果没有反射机制,框架技术就失去底层支撑
  • 缺点:使用反射基本是解释执行,对执行速度有影响(对于普通代码来说,现在JVM编辑器采用热点代码即时编译 + 解释执行两种方式并行的方案)

证明反射缺点:

import java.lang.reflect.Method;

public class Test {
    public static void main(String[] args) throws Exception {
        int num = 100000000;
        System.out.println("执行方法次数:" + num);
        m1(num);
        m2(num);
    }

    /**
     * 通过普通方式执行方法
     * 
     * @author 明快de玄米61
     * @date   2024/7/22 23:10
     * @param  num 执行次数
     **/
    public static void m1(int num) {
        // 开始时间
        long startTime = System.currentTimeMillis();

        // 执行代码
        Cat cat = new Cat();
        for (int i = 0; i < num; i++) {
            cat.said();
        }

        // 结束时间
        long endTime = System.currentTimeMillis();

        // 耗时
        System.out.println("普通方式耗时:" + (endTime - startTime) + "毫秒");
    }

    /**
     * 通过反射方式执行方法
     *
     * @author 明快de玄米61
     * @date   2024/7/22 21:45
     * @param  num 执行次数
     **/
    public static void m2(int num) throws Exception {
        // 开始时间
        long startTime = System.currentTimeMillis();

        // 执行代码
        Class<?> cls = Class.forName("com.atguigu.reflect.Cat");
        Object o = cls.newInstance();
        Method method = cls.getMethod("said");
        for (int i = 0; i < num; i++) {
            method.invoke(o);
        }

        // 结束时间
        long endTime = System.currentTimeMillis();

        // 耗时
        System.out.println("反射方式耗时:" + (endTime - startTime) + "毫秒");
    }
}

class Cat {
    public void said() {
        // 控制台会出现太多打印结果,所以注释掉吧!
//        System.out.println("喵喵喵~");
    }
}

证明反射缺点的结果:

执行方法次数:100000000
普通方式耗时:9毫秒
反射方式耗时:775毫秒

5、反射调用优化—关闭访问检查

Method、Field、Constructor对象都有setAccessible()方法

该方法作用是禁用访问安全检查的开关,默认是关闭的。即:默认开启访问检查

如果参数设置为true,表示反射的对象在使用时取消访问检查,提高反射的效率

如果参数设置为false(默认值),表示反射的对象将执行访问检查

在这里插入图片描述

举例说明:

import java.lang.reflect.Method;

public class Test {
    public static void main(String[] args) throws Exception {
        int num = 100000000;
        System.out.println("执行方法次数:" + num);
        m2(num);
        m3(num);
    }

    /**
     * 通过反射方式执行方法(执行访问检查:默认值)
     *
     * @author 明快de玄米61
     * @date   2024/7/22 21:45
     * @param  num 执行次数
     **/
    public static void m2(int num) throws Exception {
        // 开始时间
        long startTime = System.currentTimeMillis();

        // 执行代码
        Class<?> cls = Class.forName("com.atguigu.reflect.Cat");
        Object o = cls.newInstance();
        Method method = cls.getMethod("said");
        for (int i = 0; i < num; i++) {
            method.invoke(o);
        }

        // 结束时间
        long endTime = System.currentTimeMillis();

        // 耗时
        System.out.println("在反射时,执行访问检查的耗时:" + (endTime - startTime) + "毫秒");
    }

    /**
     * 通过反射方式执行方法(不执行访问检查:手动设置)
     *
     * @author 明快de玄米61
     * @date   2024/7/22 21:45
     * @param  num 执行次数
     **/
    public static void m3(int num) throws Exception {
        // 开始时间
        long startTime = System.currentTimeMillis();

        // 执行代码
        Class<?> cls = Class.forName("com.atguigu.reflect.Cat");
        Object o = cls.newInstance();
        Method method = cls.getMethod("said");
        // 在反射调用方法时,取消访问检查
        method.setAccessible(true);
        for (int i = 0; i < num; i++) {
            method.invoke(o);
        }

        // 结束时间
        long endTime = System.currentTimeMillis();

        // 耗时
        System.out.println("在反射时,取消访问检查的耗时:" + (endTime - startTime) + "毫秒");
    }
}

class Cat {
    public void said() {
        // 控制台会出现太多打印结果,所以注释掉吧!
//        System.out.println("喵喵喵~");
    }
}

结果:

执行方法次数:100000000
在反射时,执行访问检查的耗时:243毫秒
在反射时,取消访问检查的耗时:125毫秒

6、Class类 介绍

  • Class也是类,因此也继承Object类
  • Class类对象不是new出来的,而是由类加载器加载的,通过ClassLoader类以及子类的loadClass方法
  • 对于某个类的Class类对象,在内存中只有一份,根据双亲委派机制,所以类只加载一次
  • 每个类的实例都会记得自己是由哪个Class实例所生成
  • 通过Class可以完整地得到一个类的完整结构,可以通过一系列API得到属性、方法、构造器
  • Class对象存放在堆中
  • 类的字节码二进制数据存放在方法区中,有的地方称为类的元数据(包括方法代码、变量名、方法名、访问权限等等)

在这里插入图片描述

7、Class类—常用方法

在这里插入图片描述

8、哪些类型有Class对象

  • 类:外部类、成员内部类、静态内部类、局部内部类、匿名内部类
  • 接口
  • 枚举
  • 注解
  • 数组
  • 基本数据类型
  • void

示例代码如下:

// 外部类
Class<String> cls1 = String.class;

// 接口
Class<Serializable> cls2 = Serializable.class;

// 枚举
Class<Thread.State> cls3 = Thread.State.class;

// 注解
Class<Override> cls4 = Override.class;

// 数组
// 一维数组
Class<int[][]> cls5 = int[][].class;
// 二维数组
Class<Integer[]> cls6 = Integer[].class;

// 基本数组类型
Class<Integer> cls7 = int.class;

// void
Class<Void> cls8 = void.class;

二、使用方式

1、Class类

前提准备:

import java.lang.annotation.Annotation;

// 父类
class Animal {
}

// 子类
class Cat extends Animal {
    public void said() {
    }
}

示例:

// 1、获取Class对象
// 全类名
String classPath = "com.atguigu.reflect.Cat";
// 通过全类名获取Class类对象
Class<?> cls = Class.forName(classPath);

// 2、获取全类名,结果是:Cat
System.out.println(cls.getName());

// 3、获取简单类名,结果是:com.atguigu.reflect.Cat
System.out.println(cls.getSimpleName());

// 4、获取Class对象所属的Class类,承接以上代码,结果是:class java.lang.Class
System.out.println(cls.getClass().toString());

// 5、获取包名,承接以上代码,结果是:com.atguigu.reflect
System.out.println(cls.getPackage().getName());

// 6、返回父类全路径名称,承接以上代码,结果是:com.atguigu.reflect.Animal
System.out.println(cls.getSuperclass().getName());

// 7、获取Class对象的方式
// 方式1:知道类的全路径,多用于通过配置文件内容来加载类
Class<?> cls = Class.forName(classPath);
// 方式2:知道具体的类,多用于参数传递,比如通过反射得到类的构造器对象
Class<Cat> cls = Cat.class;
// 方式3:知道创建好的对象
Class<? extends Cat> cls = cat.getClass();
// 方法4:通过类加载器,但是两者需要使用同一个类加载器
ClassLoader classLoader = Test.class.getClassLoader();
Class<?> aClass = classLoader.loadClass("com.atguigu.reflect.Cat");
// 方式5:获取基本数据类型的Class对象(boolean、byte、char、short、int、long、float、double)
// 结果:int
Class<Integer> cls = int.class;
System.out.println(cls);
// 方式6:获取基本类型对应包装类的Class对象,其实方法5和方法6得到的对应类型的Class对象是同一个
// 结果:int
Class<Integer> cls = Integer.TYPE;
System.out.println(cls);

2、接口

代码:

import java.lang.annotation.Annotation;

public class Test {
    public static void main(String[] args) throws Exception {
	    // 返回直接实现的接口信息,不返回接口的父级接口信息
        Class<?> cls = Class.forName("com.atguigu.reflect.Cat");
        for (Class<?> ifs : cls.getInterfaces()) {
            System.out.println(ifs.getName());
        }
    }
}

// 父接口
interface Type {}

// 子接口
interface Type1 extends Type {}
interface Type2 extends Type {}

// 类
class Cat implements Type1, Type2 {
}

结果:

com.atguigu.reflect.Type1
com.atguigu.reflect.Type2

3、注解

代码:

import java.lang.annotation.Annotation;

public class Test {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("com.atguigu.reflect.Cat");
        for (Annotation annotation : cls.getAnnotations()) {
            System.out.println(annotation.toString());
        }
    }
}

// 类
@Deprecated
class Cat {
}

结果:

@java.lang.Deprecated()

4、字段

代码:

import java.lang.reflect.Field;

public class Test {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("com.atguigu.reflect.Cat");
        // 1、返回 “所有可访问的公共字段类对象” 数组
        // 其中包括:当前类 / 接口、超类、超级接口中的公共字段
        System.out.println("1、返回 “所有可访问的公共字段类对象” 数组");
        Field[] fields = cls.getFields();
        for (Field field : fields) {
            System.out.println(field.getName());
        }

        // 2、返回 “当前类或接口声明的所有字段类对象” 数组
        // 其中包括:公共,受保护,默认(包)访问和私有字段,但不包括继承或者实现的
        System.out.println("\n2、返回 “当前类或接口声明的所有字段类对象” 数组");
        Field[] declaredFields = cls.getDeclaredFields();
        for (Field field : declaredFields) {
            System.out.println(field.getName());
        }

        // 3、打印字段名称、字段修饰符、字段类型
        // 字段修饰符说明:默认修饰符(0)、public(1)、private(2)、protected(4)、static(8)、final(16),其中getModifiers()方法返回值是所有修饰符之和
        System.out.println("\n3、打印字段名称、字段修饰符、字段类型");
        for (Field field : declaredFields) {
            System.out.printf("字段名称:%s,字段修饰符:%s,字段类型:%s\n", field.getName(), field.getModifiers(), field.getType().getName());
        }

        // 4、设置公共字段的值
        System.out.println("\n4、设置公共字段的值");
        Cat cat = new Cat(1.0, 2.0, 3.0);
        // 使用getField方法获取字段Field对象,该方法仅支持公共(public)字段哦~
//        Field heightField = cls1.getField("height");
        // 使用getDeclaredField方法调用构造器,该方法支持所有构造器哦~,这两种方式都是可以的
        Field heightField = cls.getDeclaredField("height");
        heightField.set(cat, 2.5);
        System.out.println("height:" + cat.height);

        // 5、设置非公共字段的值
        System.out.println("\n5、设置非公共字段的值");
        Field weightField = cls.getDeclaredField("weight");
        // 如果不对私有属性字段进行爆破,直接调用set方法,将会抛出:
        // Exception in thread "main" java.lang.IllegalAccessException: Class com.atguigu.reflect.Test can not access a member of class com.atguigu.reflect.Cat with modifiers "private"
        weightField.setAccessible(true);
        weightField.set(cat, 0.5);
        System.out.println("weight:" + cat.getWeight());

        // 6、获取公共字段的值
        System.out.println("\n6、获取公共字段的值");
        System.out.println("height:" + heightField.get(cat));

        // 7、获取非公共字段的值
        System.out.println("\n7、获取非公共字段的值");
        // 如果方法没有提供获取值的方法(例如:getWeight方法),我们完全可以使用反射去完成获取字段值的流程
        // 在上面已经通过“weightField.setAccessible(true)”语句完成了字段爆破过程,所以这里可以直接使用,不用爆破了
        System.out.println("weight:" + weightField.get(cat));

        // 8、设置以及获取非公共静态字段的值
        System.out.println("\n8、设置以及获取非公共静态字段的值");
        // 如果方法没有提供获取值的方法(例如:getWeight方法),我们完全可以使用反射去完成获取字段值的流程
        Field lengthField = cls.getDeclaredField("length");
        // 对私有字段进行操作的时候,需要先使用 “setAccessible(true)” 方法进行爆破~
        lengthField.setAccessible(true);
        // 静态字段是属于类的,所以get()方法、set()方法都不需要指明操作对象
        lengthField.set(null, 3.5);
        System.out.println("length:" + lengthField.get(null));
    }
}

// 接口
interface Type {
    // 公共属性
    String belong = "动物科";
}

// 超类
class Animal {
    // 公共属性
    public String name;
    // 私有属性
    private int age;
}

// 子类
class Cat extends Animal implements Type {
    // 公共属性
    public double height;

    // 私有属性
    private double weight;

    // 私有静态属性
    private static double length;

    public Cat(double height, double weight, double length) {
        this.height = height;
        this.weight = weight;
        Cat.length = length;
    }

    public double getHeight() {
        return height;
    }

    public double getWeight() {
        return weight;
    }

    public static double getLength() {
        return length;
    }
}

结果:

1、返回 “所有可访问的公共字段类对象” 数组
height
belong
name

2、返回 “当前类或接口声明的所有字段类对象” 数组
height
weight
length

3、打印字段名称、字段修饰符、字段类型
字段名称:height,字段修饰符:1,字段类型:double
字段名称:weight,字段修饰符:2,字段类型:double
字段名称:length,字段修饰符:10,字段类型:double

4、设置公共字段的值
height:2.5

5、设置非公共字段的值
weight:0.5

6、获取公共字段的值
height:2.5

7、获取非公共字段的值
weight:0.5

8、获取非公共静态字段的值
length:3.5

5、方法

代码:

import java.lang.reflect.Method;

public class Test {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("com.atguigu.reflect.Cat");
        // 1、返回 “所有公共方法类对象” 数组
        // 其中包括:当前类 / 接口、超类、超级接口中的公共方法
        System.out.println("1、返回 “所有公共方法类对象” 数组");
        Method[] methods = cls.getMethods();
        for (Method method : methods) {
            System.out.println(method.getName());
        }

        // 2、返回 “当前类或接口声明的所有方法对象” 数组
        // 其中包括:公共,受保护,默认(包)访问和私有方法,但不包括继承或者实现的
        System.out.println("\n2、返回 “当前类或接口声明的所有方法对象” 数组");
        Method[] declaredMethods = cls.getDeclaredMethods();
        for (Method method : declaredMethods) {
            System.out.println(method.getName());
        }

        // 3、打印方法名称、方法修饰符、方法参数数组、方法返回类型
        // 方法修饰符说明:默认修饰符(0)、public(1)、private(2)、protected(4)、static(8)、final(16),其中getModifiers()方法返回值是所有修饰符之和
        System.out.println("\n3、打印方法名称、方法修饰符、方法参数数组、方法返回类型");
        for (Method method : declaredMethods) {
            System.out.printf("方法名称:%s,方法修饰符:%s,方法返回类型:%s\n", method.getName(), method.getModifiers(), method.getReturnType().getName());
            Class<?>[] parameterTypes = method.getParameterTypes();
            for (Class<?> parameterType : parameterTypes) {
                System.out.println(">>>>该方法的参数类型:" + parameterType.getName());
            }
        }

        // 4、调用无参公共方法
        System.out.println("\n4、调用无参公共方法");
        Cat cat = new Cat();
        // 使用getMethod方法获取Method对象,该方法仅支持公共(public)方法哦~
        Method m4 = cls.getMethod("m4");
        // 使用getDeclaredMethod方法获取Method对象,该方法支持所有方法哦~,这两种方式都是可以的
//        Method m4 = cls.getDeclaredMethod("m4");
        m4.invoke(cat);

        // 5、调用有参私有方法
        System.out.println("\n5、调用有参私有方法");
        Method m5 = cls.getDeclaredMethod("m5", String.class, int.class);
        // 如果不对私有方法进行爆破,直接调用invoke方法,将会抛出:
        // Exception in thread "main" java.lang.IllegalAccessException: Class com.atguigu.reflect.Test can not access a member of class com.atguigu.reflect.Cat with modifiers "private"
        m5.setAccessible(true);
        Object result = m5.invoke(cat, "世界", 1);
        System.out.println("方法调用结果:" + result);
        System.out.println("方法返回结果类型未改变:" + (result.getClass() == String.class));

        // 6、调用静态方法
        System.out.println("\n6、调用静态方法");
        Method m6 = cls.getDeclaredMethod("m6");
        // 静态方法属于类,所以不用指定具体对象
        m6.invoke(null);
    }
}

// 接口
interface Type {
    default void m1() {
    }
}

// 超类
class Animal {
    public void m2() {
    }

    private void m3() {
    }
}

// 子类
class Cat extends Animal implements Type {
    public void m4() {
        System.out.println("注意:方法m4()被调用了");
    }

    private String m5(String a, int b) {
        System.out.println("注意:方法m5(String a, int b)被调用了");
        return "hello world~";
    }

    public static void m6() {
        System.out.println("注意:静态方法m6()被调用了");
    }
}

结果:

1、返回 “所有公共方法类对象” 数组
m4
m6
m2
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll
m1

2、返回 “当前类或接口声明的所有方法对象” 数组
m4
m5
m6

3、打印方法名称、方法修饰符、方法参数数组、方法返回类型
方法名称:m4,方法修饰符:1,方法返回类型:void
方法名称:m5,方法修饰符:2,方法返回类型:java.lang.String
>>>>该方法的参数类型:java.lang.String
>>>>该方法的参数类型:int
方法名称:m6,方法修饰符:9,方法返回类型:void

4、调用无参公共方法
注意:方法m4()被调用了

5、调用有参私有方法
注意:方法m5(String a, int b)被调用了
方法调用结果:hello world~
方法返回结果类型未改变:true

6、调用静态方法
注意:静态方法m6()被调用了

6、构造器

代码:

import java.lang.reflect.Constructor;

public class Test {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("com.atguigu.reflect.Cat");
        // 1、返回 “类的所有公共构造函数” 数组
        System.out.println("1、返回 “类的所有公共构造函数” 数组");
        Constructor<?>[] constructors = cls.getConstructors();
        for (Constructor<?> constructor : constructors) {
            System.out.println(constructor.getName());
        }

        // 2、返回 “类的所有构造函数” 数组
        System.out.println("\n2、返回 “类的所有构造函数” 数组");
        Constructor<?>[] declaredConstructors = cls.getDeclaredConstructors();
        for (Constructor constructor : declaredConstructors) {
            System.out.println(constructor.getName());
        }

        // 3、打印构造器的参数数组
        System.out.println("\n3、打印构造器的参数数组");
        for (Constructor<?> constructor : declaredConstructors) {
            System.out.println(">>>构造器名称:" + constructor.getName());
            Class<?>[] parameterTypes = constructor.getParameterTypes();
            for (Class<?> parameterType : parameterTypes) {
                System.out.println(">>>>>>参数名称:" + parameterType.getName());
            }
        }

        // 4、创建无参构造对象
        // 前提:存在公共的无参构造方法
        System.out.println("\n4、创建公共无参构造对象");
        // 这种创建方式,快速方便快捷
        Object o = cls.newInstance();
        System.out.println(o);

        // 5、创建公共有参构造对象
        System.out.println("\n5、创建公共有参构造对象");
        // 使用getConstructor方法调用构造器,该方法仅支持公共(public)构造器哦~
//        Constructor<?> constructor1 = cls.getConstructor(double.class, double.class);
        // 使用getDeclaredConstructor方法调用构造器,该方法支持所有构造器哦~,这两种方式都是可以的
        Constructor<?> constructor1 = cls.getDeclaredConstructor(double.class, double.class);
        Object o1 = constructor1.newInstance(1.0, 2.0);
        System.out.println(o1);

        // 6、创建私有有参构造对象
        System.out.println("\n6、创建私有有参构造对象");
        // 必须使用getDeclaredConstructor()方法,才能获取私有构造器,而getConstructor方法仅支持公共(public)构造器哦~
        Constructor<?> constructor2 = cls.getDeclaredConstructor(double.class);
        // 如果不对私有构造器进行爆破,直接调用newInstance方法,将会抛出:
        // Exception in thread "main" java.lang.IllegalAccessException: Class com.atguigu.reflect.Test can not access a member of class com.atguigu.reflect.Cat with modifiers "private"
        constructor2.setAccessible(true);
        Object o2 = constructor2.newInstance(1.0);
        System.out.println(o2);
    }
}

// 普通类
class Cat {

    private double width;

    private double height;

    public Cat() {}

    public Cat(double width, double height) {
        this.width = width;
        this.height = height;
    }

    private Cat(double height) {
        this.height = height;
    }
}

结果:

1、返回 “类的所有公共构造函数” 数组
com.atguigu.reflect.Cat
com.atguigu.reflect.Cat

2、返回 “类的所有构造函数” 数组
com.atguigu.reflect.Cat
com.atguigu.reflect.Cat
com.atguigu.reflect.Cat

3、打印构造器的参数数组
>>>构造器名称:com.atguigu.reflect.Cat
>>>构造器名称:com.atguigu.reflect.Cat
>>>>>>参数名称:double
>>>>>>参数名称:double
>>>构造器名称:com.atguigu.reflect.Cat
>>>>>>参数名称:double

4、创建公共无参构造对象
com.atguigu.reflect.Cat@4517d9a3

5、创建公共有参构造对象
com.atguigu.reflect.Cat@372f7a8d

6、创建私有有参构造对象
com.atguigu.reflect.Cat@2f92e0f4

三、思考拓展

  • 针对构造器、方法、字段来说,普通方法可以获取当前类、父类、接口中的公共信息;而调用添加Declared的方法可以获取当前类/接口的所有信息,另外通过“setAccessible(true)”方法进行爆破之后,还能对私有静态构造器、方法、字段进行相关操作;普通方法以及添加Declared的方法各有千秋,可以根据不同场景使用不同的方法
  • 针对不懂的属性字段或者方法,大家可以去查阅官方文档,这里给大家提供jdk1.8的API文档
    链接:https://pan.baidu.com/s/1KlwQrBpAcmoZA-QZiC6gCw?pwd=gyg1
    提取码:gyg1
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值