反射的学习

反射的思想及作用

在学习反射之前,先来了解正射是什么。我们平常用的最多的 new 方式实例化对象的方式就是一种正射的体现。假如我需要实例化一个HashMap,代码就会是这样子

Map<Integer, Integer> map = new HashMap<>();
map.put(1, 1);

某一天发现,该段程序不适合用 HashMap 存储键值对,更倾向于用LinkedHashMap存储。重新编写代码后变成下面这个样子

Map<Integer, Integer> map = new LinkedHashMap<>();
map.put(1, 1);

假如又有一天,发现数据还是适合用 HashMap来存储,难道又要重新修改源码吗?

发现问题了吗?我们每次改变一种需求,都要去重新修改源码,然后对代码进行编译,打包,再到 JVM 上重启项目。这么些步骤下来,效率非常低。

 对于这种需求频繁变更但变更不大的场景,频繁地更改源码肯定是一种不允许的操作,我们可以使用一个开关,判断什么时候使用哪一种数据结构。

public Map<Integer, Integer> getMap(String param) {
    Map<Integer, Integer> map = null;
    if (param.equals("HashMap")) {
        map = new HashMap<>();
    } else if (param.equals("LinkedHashMap")) {
        map = new LinkedHashMap<>();
    } else if (param.equals("WeakHashMap")) {
        map = new WeakHashMap<>();
    }
    return map;
}

通过传入参数param决定使用哪一种数据结构,可以在项目运行时,通过动态传入参数决定使用哪一个数据结构。

如果某一天还想用TreeMap,还是避免不了修改源码,重新编译执行的弊端。这个时候,反射就派上用场了。

在代码运行之前,我们不确定将来会使用哪一种数据结构,只有在程序运行时才决定使用哪一个数据类,而反射可以在程序运行过程中动态获取类信息调用类方法。通过反射构造类实例,代码会演变成下面这样。

public Map<Integer, Integer> getMap(String className) {
    Class clazz = Class.forName(className);
    Consructor con = clazz.getConstructor();
    return (Map<Integer, Integer>) con.newInstance();
}

无论使用什么 Map,只要实现了Map接口,就可以使用全类名路径传入到方法中,获得对应的 Map 实例。例如java.util.HashMap / java.util.LinkedHashMap····如果要创建其它类例如WeakHashMap,我也不需要修改上面这段源码

我们来回顾一下如何从 new 一个对象引出使用反射的。

  • 在不使用反射时,构造对象使用 new 方式实现,这种方式在编译期就可以把对象的类型确定下来。
  • 如果需求发生变更,需要构造另一个对象,则需要修改源码,非常不优雅,所以我们通过使用开关,在程序运行时判断需要构造哪一个对象,在运行时可以变更开关来实例化不同的数据结构。
  • 如果还有其它扩展的类有可能被使用,就会创建出非常多的分支,且在编码时不知道有什么其他的类被使用到,假如日后Map接口下多了一个集合类是xxxHashMap,还得创建分支,此时引出了反射

总结:

  • 反射的思想在程序运行过程中确定和解析数据类的类型。
  • 反射的作用:对于在编译期无法确定使用哪个数据类的场景,通过反射可以在程序运行时构造出不同的数据类实例

Java反射机制

        Java 反射机制在程序运行时,可以判断一个对象所属的类,可以构造任意一个类的对象,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种 动态的获取信息 以及 动态调用对象的方法 的功能称为 java 的反射机制

        反射机制很重要的一点就是“运行时”,其使得我们可以在程序运行时加载、探索以及使用编译期间完全未知的 .class 文件。换句话说,Java 程序可以加载一个运行时才得知名称的 .class 文件,然后获悉其完整构造,并生成其对象实体、或对其 fields(变量)设值、或调用其 methods(方法)

反射的基本使用

  1. java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象
  2. java.lang.reflect.Method:代表类的方法,内部包含了该方法的所有信息,与Constructor类似,不同之处是 Method 拥有返回值类型信息,因为构造方法是没有返回值的。
  3. java.lang.reflect.Field:代表类的属性,内部包含了构造方法的所有信息,例如参数类型,参数名字,访问修饰符。Field对象代表某个类的成员变量
  4. java.lang.reflect.Constructor:代表类的构造方法,内部包含了构造方法的所有信息,例如参数类型,参数名字,访问修饰符,Constructor对象代表某个类的构造方法

 获取类的 Class 对象

  1. 前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundExecption,应用实例
String classPath = "com.neutech.eneity.Car";
//1.获取Car类 对应的Class对象
Class aClass = Class.forName(classPath);

应用场景:多用于配置文件,读取类全路径,加载类 

  2.前提:诺已知具体的类,通过类的class获取,该方式最为安全可靠,程序性能最高。实例

Class aClass1 = Car.class;

    应用场景:多用于参数传递,比如通过反射得到对应构造器对象

3.前提:已知某个类的实例对象,调用该实例的getClass()方法获取Class对象,实例Class clazz = 对象.getClass()

Car car = new Car();
Class aClass2 = car.getClass();  //运行时实例

4.其他方式  

        ClassLoader classLoader = 对象.getClass().getClassLoader();

        Class aClass3 = classLoader.loadClass("类的全类名");

//4.通过类的加载器来获取类的Class对象
//(1)先得到类加载器
ClassLoader classLoader = car.getClass().getClassLoader();
//(2)通过类加载器得到Class对象
Class aClass3 = classLoader.loadClass("com.neutech.eneity.Car");
System.out.println(aClass3);

5.基本数据类型(int,short,boolan等基本数据类型)按如下方式得到Class类对象

        Class aclass = 基本数据类型.class

Class<Integer> integerClass = int.class;

6.基本数据类型对应的包装类,可以通过.type得到Class类对象

        Class aclass = 包装类.TYPE

Class<Integer> type = Integer.TYPE;

构造类的实例化对象

通过反射构造一个类的实例方式有2种:

  • Class对象调用newInstance()方法
//创建Class类对象
Class aClass = Class.forName("com.neutech.refleaction.demo06.User");
//1.通过public的无参构造器创建实例  User实体类必须有无参构造方法
Object user = aClass.newInstance();
  • Constructor 构造器调用newInstance()方法
//2.通过public的有参构造器创建实例
/* Constructor对象就是
 * public User(String name) {
         this.name = name;
    }
 */
Constructor constructor =   aClass.getConstructor(String.class);
Object user1 = constructor.newInstance("媛");
System.out.println(user1);

//3.通过无参构造器创建实例
//Constructor constructor1 =   aClass.getConstructor(Integer.class,String.class);//因为getConstructor()只能获取public修饰的构造方法
Constructor constructor1 = aClass.getDeclaredConstructor(int.class,String.class); //aClass.getDeclaredConstructor获取所有的构造方法
//暴力破解 使用反射可以访问private构造器。反射面前都是纸老虎
constructor1.setAccessible(true);
Object user2 = constructor1.newInstance(12, "媛1");

通过Class对象调用 newInstance() 会走默认无参构造方法,如果想通过显式构造方法构造实例,需要提前从Class中调用getConstructor()方法获取对应的构造器,通过构造器去实例化对象。

获取一个类的所有信息

Class 对象中包含了该类的所有信息,在编译期我们能看到的信息就是该类的变量、方法、构造器,在运行时最常被获取的也是这些信息。

获取类中的变量(Field)

  • Field[] getFields():获取本类及父类中所有被public修饰的所有变量
  • Field getField(String name):根据变量名获取类中的一个变量,该变量必须被public修饰
  • Field[] getDeclaredFields():获取类中所有的变量,但无法获取继承下来的变量
  • Field getDeclaredField(String name):根据姓名获取类中的某个变量,无法获取继承下来的变量

获取类中的方法(Method)

  • Method[] getMethods():获取本类及父类中被public修饰的所有方法
  • Method getMethod(String name, Class... paramTypes):根据名字和参数类型获取对应方法,该方法必须被public修饰
  • Method[] getDeclaredMethods():获取所有方法,但无法获取继承下来的方法
  • Method getDeclaredMethod(String name, Class... paramTypes):根据名字和参数类型获取对应方法,无法获取继承下来的方法 获取类的构造器(Constructor)

操作实例

import java.lang.reflect.Method;

public class ReflectMethod  {


    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {

        Class clazz = Class.forName("reflect.Circle");

        //根据参数获取public的Method,包含继承自父类的方法
        Method method = clazz.getMethod("draw",int.class,String.class);

        System.out.println("method:"+method);

        //获取所有public的方法:
        Method[] methods =clazz.getMethods();
        for (Method m:methods){
            System.out.println("m::"+m);
        }

        System.out.println("=========================================");

        //获取当前类的方法包含private,该方法无法获取继承自父类的method
        Method method1 = clazz.getDeclaredMethod("drawCircle");
        System.out.println("method1::"+method1);
        //获取当前类的所有方法包含private,该方法无法获取继承自父类的method
        Method[] methods1=clazz.getDeclaredMethods();
        for (Method m:methods1){
            System.out.println("m1::"+m);
        }
    }

/**
     输出结果:
     method:public void reflect.Shape.draw(int,java.lang.String)

     m::public int reflect.Circle.getAllCount()
     m::public void reflect.Shape.draw()
     m::public void reflect.Shape.draw(int,java.lang.String)
     m::public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
     m::public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
     m::public final void java.lang.Object.wait() throws java.lang.InterruptedException
     m::public boolean java.lang.Object.equals(java.lang.Object)
     m::public java.lang.String java.lang.Object.toString()
     m::public native int java.lang.Object.hashCode()
     m::public final native java.lang.Class java.lang.Object.getClass()
     m::public final native void java.lang.Object.notify()
     m::public final native void java.lang.Object.notifyAll()

     =========================================
     method1::private void reflect.Circle.drawCircle()

     m1::public int reflect.Circle.getAllCount()
     m1::private void reflect.Circle.drawCircle()
     */
}

class Shape {
    public void draw(){
        System.out.println("draw");
    }

    public void draw(int count , String name){
        System.out.println("draw "+ name +",count="+count);
    }

}
class Circle extends Shape{

    private void drawCircle(){
        System.out.println("drawCircle");
    }
    public int getAllCount(){
        return 100;
    }
}

获取类中的构造器(Constuctor)

  • Constuctor[] getConstructors():获取本类中所有被public修饰的构造器
  • Constructor getConstructor(Class... paramTypes):根据参数类型获取类中某个构造器,该构造器必须被public修饰
  • Constructor[] getDeclaredConstructors():获取本类中所有构造器
  • Constructor getDeclaredConstructor(class... paramTypes):根据参数类型获取对应的构造器

操作实例:

public static void main(String[] args) throws Exception {
        //创建Class对象
        Class aClass = Class.forName("com.neutech.refleaction.demo06.User");
        //1.通过public的无参构造器创建实例
        Object user = aClass.newInstance();
        System.out.println(user);

        //2.通过public的有参构造器创建实例

        /* Constructor对象就是
         * public User(String name) {
                 this.name = name;
            }
         */
        Constructor constructor =   aClass.getConstructor(String.class);
        Object user1 = constructor.newInstance("媛");
        System.out.println(user1);

        //3.通过无参构造器创建实例
        //Constructor constructor1 =   aClass.getConstructor(Integer.class,String.class);//因为getConstructor()只能获取public修饰的构造方法
        Constructor constructor1 = aClass.getDeclaredConstructor(int.class,String.class); //aClass.getDeclaredConstructor获取所有的构造方法
        //暴力破解 使用反射可以访问private构造器。反射面前都是纸老虎
        constructor1.setAccessible(true);
        Object user2 = constructor1.newInstance(12, "媛1");
        System.out.println(user2);


    }
}

class User{
    private int age = 10;
    private String name  = "亮";

    public User() {
    }

    public User(String name) {
        this.name = name;
    }

    private User(int age, String name) {
        this.age = age;
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

每种功能内部以 Declared 细分为2类:

Declared修饰的方法:可以获取该类内部包含的所有变量、方法和构造器,但是无法获取继承下来的信息

Declared修饰的方法:可以获取该类中public修饰的变量、方法,可获取继承下来的信息,构造器只能获取本类的

通过反射调用方法

通过反射获取到某个 Method 类对象后,可以通过调用invoke方法执行。

  • invoke(Oject obj, Object... args):参数``1指定调用该方法的**对象**,参数2`是方法的参数列表值。

如果调用的方法是静态方法,参数1只需要传入null,因为静态方法不与某个对象有关,只与某个类有关。

可以像下面这种做法,通过反射实例化一个对象,然后获取Method方法对象,调用invoke()指定SmallPineapplegetInfo()方法。

Class clazz = Class.forName("reflect.Circle");
//创建对象
Circle circle = (Circle) clazz.newInstance();

//获取指定参数的方法对象Method
Method method = clazz.getMethod("draw",int.class,String.class);

//通过Method对象的invoke(Object obj,Object... args)方法调用
method.invoke(circle,15,"圈圈");

//对私有无参方法的操作
Method method1 = clazz.getDeclaredMethod("drawCircle");
//修改私有方法的访问标识
method1.setAccessible(true);
method1.invoke(circle);

//对有返回值得方法操作
Method method2 =clazz.getDeclaredMethod("getAllCount");
Integer count = (Integer) method2.invoke(circle);
System.out.println("count:"+count);

/**
    输出结果:
    draw 圈圈,count=15
    drawCircle
    count:100
*/

通过反射操作属性(暴力破解私有属性)

public class Test1 {
    public static void main(String[] args) throws Exception {
        //1.得到Student对应的Class对象
        Class<?> stuClass = Class.forName("com.neutech.refleaction.demo06.Student");
        //2.创建对象
        Object o = stuClass.newInstance(); //运行时类型 class com.neutech.refleaction.demo06.Student
        System.out.println(o.getClass());

        //使用反射得到age属性对象
        Field ageField = stuClass.getField("age");
        //通过反射操作属性
        ageField.set(o,88);
        System.out.println(o);

        //通过反射得到name属性对象 name私有静态
        Field nameField = stuClass.getDeclaredField("name");
        //爆破
        nameField.setAccessible(true);
        //通过反射操作属性 给属性赋值
        nameField.set(o,"liang");
        nameField.set(null,"liang--"); //因为name是static属性,因此o也可以写成null
        System.out.println(o);
        //获取属性的值
        System.out.println(nameField.get(o)); //liang--

    }
}

class Student{
    public int age;

    private static String name;

    public Student(){}

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                "name=" + name +
                '}';
    }
}

反射的应用场景

反射常见的应用场景这里介绍2个:

  • Spring 实例化对象:当程序启动时,Spring 会读取配置文件applicationContext.xml并解析出里面所有的 标签实例化到IOC容器中。
  • JDBC连接数据库:使用JDBC连接数据库时,指定连接数据库的驱动类时用到反射加载驱动类

Spring 的 IOC 容器

在 Spring 中,经常会编写一个上下文配置文件applicationContext.xml,里面就是关于bean的配置,程序启动时会读取该 xml 文件,解析出所有的 <bean>标签,并实例化对象放入IOC容器中。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="smallpineapple" class="com.bean.SmallPineapple">
        <constructor-arg type="java.lang.String" value="小菠萝"/>
        <constructor-arg type="int" value="21"/>
    </bean>
</beans>

在定义好上面的文件后,通过ClassPathXmlApplicationContext加载该配置文件,程序启动时,Spring 会将该配置文件中的所有bean都实例化,放入 IOC 容器中,IOC 容器本质上就是一个工厂,通过该工厂传入 <bean> 标签的id属性获取到对应的实例。

public class Main {
    public static void main(String[] args) {
        ApplicationContext ac =
                new ClassPathXmlApplicationContext("applicationContext.xml");
        SmallPineapple smallPineapple = (SmallPineapple) ac.getBean("smallpineapple");
        smallPineapple.getInfo(); // [小菠萝的年龄是:21]
    }
}

Spring 在实例化对象的过程经过简化之后,可以理解为反射实例化对象的步骤:

  • 获取Class对象的构造器
  • 通过构造器调用 newInstance() 实例化对象

JDBC 加载数据库驱动类

在导入第三方库时,JVM不会主动去加载外部导入的类,而是等到真正使用时,才去加载需要的类,正是如此,我们可以在获取数据库连接时传入驱动类的全限定名,交给 JVM 加载该类。

public class DBConnectionUtil {
    /** 指定数据库的驱动类 */
    private static final String DRIVER_CLASS_NAME = "com.mysql.jdbc.Driver";
    
    public static Connection getConnection() {
        Connection conn = null;
        // 加载驱动类
        Class.forName(DRIVER_CLASS_NAME);
        // 获取数据库连接对象
        conn = DriverManager.getConnection("jdbc:mysql://···", "root", "root");
        return conn;
    }
}

在我们开发 SpringBoot 项目时,会经常遇到这个类,但是可能习惯成自然了,就没多大在乎,我在这里给你们看看常见的application.yml中的数据库配置。

这里的 driver-class-name,和我们一开始加载的类是不是觉得很相似,这是因为MySQL版本不同引起的驱动类不同,这体现使用反射的好处:不需要修改源码,仅加载配置文件就可以完成驱动类的替换

反射优点和缺点

优点:可以动态的创建和使用对象(框架底层核心),使用灵活,没有反射机制,框架底层失去支撑,增加程序的灵活性:面对需求变更时,可以灵活地实例化不同对象

缺点:破坏类的封装性:可以强制访问 private 修饰的信息,性能损耗:反射相比直接实例化对象、调用方法、访问变量,中间需要非常多的检查步骤和解析步骤,JVM无法对它们优化。

反射调用优化

1.Method,Field和Constructor对象都有setAccessible()方法

2.setAccessible作用是启动和禁用访问安全检测的开关

3.参数值为true时,反射的对象在使用时取消访问检查,提高反射的效率,参数值为false则表示反射的对象执行访问检查

public static void main(String[] args) throws Exception {
    m1();
    m2();
    m3();
}

//new 对象 传统方法
public static void m1(){
    long start = System.currentTimeMillis();
    Cat cat = new Cat();
    for (int i = 0; i < 900000000; i++) {
        cat.eat();
    }
    long end = System.currentTimeMillis();
    System.out.println("传统方法=="+(end-start));
}

//反射调用方法
public static void m2() throws Exception {
    long start = System.currentTimeMillis();
    Class aClass = Class.forName("com.neutech.eneity.Cat");
    Object o = aClass.newInstance();
    Method eat = aClass.getMethod("eat");
    for (int i = 0; i < 900000000; i++) {
        eat.invoke(o);
    }
    long end = System.currentTimeMillis();
    System.out.println("反射调用=="+(end-start));
}
//反射优化
public static void m3() throws Exception {
    long start = System.currentTimeMillis();
    Class aClass = Class.forName("com.neutech.eneity.Cat");
    Object o = aClass.newInstance();
    Method eat = aClass.getMethod("eat");
    eat.setAccessible(true); //取消访问安全检查
    for (int i = 0; i < 900000000; i++) {
        eat.invoke(o);
    }
    long end = System.currentTimeMillis();
    System.out.println("反射优化=="+(end-start));
}
//输出结果
传统方法==3
反射调用==1172
反射优化==1138

 参考博文:

链接:https://juejin.cn/post/6844904005294882830

链接:https://juejin.cn/post/6864324335654404104

学习视频链接

【韩顺平讲Java】Java反射专题 -反射 反射机制 类加载 reflection Class 类结构 等_哔哩哔哩_bilibili

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值