架构基础--保姆级详解Java反射机制原理+应用

本人大号:秃秃爱健身https://blog.csdn.net/Saintmm,系原创啦。

一、什么是反射?

1)Java反射机制的核心:是在程序运行时动态加载类并获取类的一些信息,从而操作类或对象的属性和方法。本质是在JVM得到class对象之后,通过class对象进行反编译,从而获取对象的各种信息。

2)Java属于先编译再运行的语言,程序中对象的类型在编译期就确定下来了,而当程序在运行时可能需要动态加载某些类,这些类因为之前用不到,所以没有被加载到JVM。通过反射,可以在运行时动态地创建对象并调用其属性,不需要提前在编译期知道运行的对象是谁。

二、反射的原理?

下图为简易的类加载流程和反射的本质:
在这里插入图片描述
关于类的详细加载流程大家可以参阅官方文档:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html
在这里插入图片描述

三、反射的优缺点

优点:

  1. 功能强大,在运行时能获得类的各种内容,然后进行反编译,这就可以让我们灵活的构建代码,使其进行运行时装配,并且无需在新增或者移除组件时进行源代码修改,即可以更容易的实现面向对象编程。

缺点:

  1. 大家都知道的反射会消耗一定的系统资源;因此在不需要动态代理对象的时候不建议使用反射。

(2)破坏封装性,导致安全问题,因为反射调用方法时可以忽略权限检查。

四、反射的用途

1、反编译:.class–>.java

2、通过反射机制访问java对象的属性,方法,构造方法等

3、IDE工具,比如Intellij IDEA,当我们输入一个对象或者类后,一按点号,编译器就会自动列出他的属性或者方法,这里就是用到反射。

4、反射最重要的用途就是开发各种通用框架。比如很多框架(Spring)都是配置化的(比如通过XML文件配置Bean),为了保证框架的通用性,它们会需要根据配置文件加载不同的类或者对象,调用不同的方法,这个时候就必须使用到反射了,运行时动态加载需要的加载的对象。

五、反射的用法

Java反射包:
https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/package-summary.html

0)反射机制常用的类:

  • Java.lang.Class;
  • Java.lang.reflect.Constructor;
  • Java.lang.reflect.Field;
  • Java.lang.reflect.Method;

官方文档参考:
https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html
在这里插入图片描述

1)class的基本使用

1、获取class对象的三种方法
  1. Object#getClass()方法
    https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html
    在这里插入图片描述
  2. 任何数据类型都有一个“静态”的class属性
  3. **通过class类的静态方法:forName(String className)
    https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html
    在这里插入图片描述

代码:

package com.saint.reflect;

/**
 * @author 周鑫(玖枭)
 */
public class ObtainClass {
   
    public static void main(String[] args) {
   
        
        // 这里的new操作会产生一个User对象和一个User.class对象
        User user = new User();
        // todo: 第一种方式获取Class对象
        Class<? extends User> userClass = user.getClass();
        System.out.println("user.getClass(): " + userClass.getName());

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

        // todo: 第二种方式获取Class对象
        Class<User> userCLass2 = User.class;
        System.out.println("User.class : " + userCLass2.getName());
        System.out.println(userCLass2 == userClass);

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

        // todo:第三种方式获取Class对象
        try {
   
            Class<?> userClass3 = Class.forName("com.saint.reflect.User");
            System.out.println("Class.forName() : " + userClass3.getName());
            System.out.println(userClass3 == userClass);
        } catch (ClassNotFoundException e) {
   
            e.printStackTrace();
        }
    }
}

控制台输出内容:

user.getClass(): com.saint.reflect.User
-----------------------
User.class : com.saint.reflect.User
true
-----------------------
Class.forName() : com.saint.reflect.User
true

从控制台输出来看,我们可以知道一个类的Class对象是唯一的,因为类加载器确保类只会被加载一次(此处不考虑热部署中不同类加载器加载类的特例)。
在上述三种方式中:第一种对象都有了还需要反射干嘛;第二种需要导入类包,依赖太强,不导包就抛编译错误;最常用的是Class.forName()方法,参数可以通过配置文件导入,比较灵活,并且可以指定不同的类加载器,比如在加载JDBC驱动时:

Class.forName("com.mysql.cj.jdbc.Driver").newInstance();
2、通过反射创建实例的两种方式
  1. 使用Class对象的newInstance()方法来创建Class对象对应类的实例。
    在这里插入图片描述
  2. 先通过Class对象获取指定的Constructor构造器对象,再调用Constructor对象的newInstance()方法来创建Class对应的实例;这种方式可以指定的构造器来构造类的实例。

代码:

(1)User类

@Getter
@Setter
@ToString
public class User {
   
    private long userId;

    private String userName;

    private String password;

    //无参构造方法
    public User() {
   
        System.out.println("调用了公有、无参构造方法执行了。。。");
    }

    //有多个参数的构造方法
    public User(long userId, String userName, String password) {
   
        this.userId = userId;
        this.userName = userName;
        this.password = password;
        System.out.println("编号:" + userId + "姓名:" + userName + "密码:" + password);
    }

    //有一个参数的构造方法
    public User(String userName) {
   
        this.userName = userName;
        System.out.println("姓名:" + userName);
    }

    //受保护的构造方法
    protected User(long userId) {
   
        System.out.println("受保护的构造方法 userId = " + userId);
    }

    //私有构造方法
    private User(long userId, String userName) {
   
        this.userId = userId;
        this.userName = userName;
        System.out.println("私有的构造方法   编号:" + userId + ", 姓名:" + userName);
    }
}

(2)创建实例的方式:

// todo: 第一种方式:使用Class.newInstance()方法
Class<User> clazz = User.class;
User newInstance = clazz.newInstance();
System.out.println("使用Class.newInstance()创建的实例: " + newInstance.toString());

// todo: 第二种方式:通过Class对象获取指定的Constructor构造器对象
Constructor constructor = clazz.getConstructor(String.class);
System.out.println("-----------------");

//根据构造器创建实例:
Object obj = constructor.newInstance("saint");
User newInstance2 = (User) obj;
System.out.println("使用构造器创建的实例: " + newInstance2.toString());

控制台输出:

调用了公有、无参构造方法执行了。。。
使用Class.newInstance()创建的实例: User(userId=0, userName=null, password=null)
-----------------
姓名:saint
使用构造器创建的实例: User(userId=0, userName=saint, password=null)
3、使用反射获取构造方法并使用

(1)批量获取的方法:

  • public Constructor[] getConstructors():获取Class所有"public(公用)"的构造方法;
  • public Constructor[] getDeclaredConstructors():获取Class所有的构造方法(包括private私有、protected受保护、public公有、默认);

(2)获取指定入参的单个构造器:

  • public Constructor getConstructor(Class… parameterTypes):获取指定入参的“public”(共有)构造方法,非public类型的获取不到。
  • public Constructor getDeclaredConstructor(Class… parameterTypes):不管构造方法是private私有的、protected受保护、默认、public公有的,只要存在我就可以根据入参获取到某个构造器。

在这里插入图片描述
在这里插入图片描述

(3)使用构造器创建实例(上面提到过):

  • Constructor#newInstance(Object… initargs),它的返回类型是泛型T

在这里插入图片描述
官方的解释为&

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值