Java基础——反射难道可以这么学 室友一把英雄联盟的时间 就能快速入门反射学不会来打我(框架的灵魂)

Java基础——反射可以这么学 (框架的灵魂)

写在前面

昨天啊,小付在二刷JDBC源码的时候,源码中的反射又偷偷跑出来给俺当头一棒子,为了加深反射在框架中的熟悉程度堪比abandon那般熟悉,为此今天就对基础的反射技术再次加以学习,毕竟只要功夫深,铁杵磨成针,慢慢来 不慌~室友打了把英雄联盟的时间就够你学完本篇内容啦,话不多说 ,肘着~

反射入门知识

什么是反射?

什么是反射?

Java的反射机制是在运行状态中,对于任意的一个类,都能知道这个类的所有属性和方法,对于任意一个对象。都能随时调用它的任意一个方法和属性,这种动态获取类的相关信息,以及调用对象的方法称之为Java语言的反射机制。

要想剖析一个类,就必须获得该类的字节码文件对象,也就是咱们javac编译java程序粗来的xxx.class文件,所以我们需要先获得每个字节码对应的Class类型的对象。那么这里肯定要调用底层,native方法肯定少不了。

通俗易懂的说法

反射说白了就是把Java类中的各种成分映射成一个个动态的Java对象
在这里插入图片描述

图示讲解

当我们创建一个对象(类)的实例时,我们往往会用到new这个关键字,这就是被

称之为静态创建,而静态创建时的步骤就大致如上图分为三个部分:

1)当我们在静态创建这个实例的时候,JVM会第一时间去本地磁盘调用底层方法找到对应的xxx.class文件

2)找到相应的xxx.class文件后会将其加载到JVM当中,在程序运行时,会将xxx.class文件读入运行时内存。

3)此时如果当前内存中没有与之对应的Class对象,也就是唯一的模板时,JVM就会自动帮我们创建一个Class对象,用来对静态创建的实例进行初始化

如图是类的正常加载过程:反射的原理在与class对象。

熟悉一下加载的时候:Class对象的由来是将class文件读入内存,并为之创建一个Class对象

基本的反射技术

JDK8的开发文档:
在这里插入图片描述

重点理解

Class类的实例表示正在运行的Java应用程序中的类和接口,也就是JVM中有很多个实例,每个类都有与之对应的Class对象(以及八大基本数据类型

4个整型: byte、short、int、long

2个浮点型:float、double

1个字符型:char

1个布尔型:boolean

Class没有公共的构造方法,如下进行反射技术的基本使用.

1、三种获取类的Class对象的方法

三种获取Class对象的方法

  • 1、Object ——> getClass();
  • 2、任何数据类型都有一个静态的’class’属性
  • 3、通过Class类的静态方法:forName(String className)来创建(最常用)
/**
 * 功能描述
 * 获取Class对象的三种方法
 * @author Alascanfu
 * @date 2021/12/21
 */
public class Test {
    public static void main(String[] args) {
        //1、获取当前的类的Class对象 并且 输出Class对象的名称
        User user = new User();
        Class<? extends User> userClass1 = user.getClass();
        System.out.println(userClass1.getName());
    
        //2、通过对应的类.class也可以获得到当前的Class对象
        Class<? extends User> userClass2 = User.class;
        System.out.println(userClass1 == userClass2);//true
        
        /*3、通过Class类中的forName方法调用两个native底层方法
        private static native Class<?> forName0(String name, boolean initialize,
                                               ClassLoader loader,
                                                Class<?> caller)
        与 反射类中的 public static native Class<?> getCallerClass();两个底层方法进行获取Class对象
        */
        try {
            Class<?> userClass3 = Class.forName("com.alascanfu.ReflectTest.User");
            System.out.println(userClass3 == userClass1);//true
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

注意

当程序运行时,在运行时内存中,一个类只会自动产生一个Class对象。

2、所有的基本数据类型都有Type属性

Class<?> classByte = Byte.TYPE;
Class<?> classShort = Short.TYPE;
Class<?> classInteger = Integer.TYPE;
Class<?> classLong = Long.TYPE;

3、通过反射获取类的构造函数

User类:

package com.alascanfu.ReflectTest;

public class User {
    private long id ;
    private String name ;
    private String username;
    private String password;
    private String address;
    private String avatar;
    private String permission;
    
    
    User() {
    
    }
    
    protected User(String username){
        this.username = "admin";
    }
    
    private User(long id) {
        this.id = -1L;
    }
    
    public User(long id, String name, String username, String password, String address, String avatar, String permission) {
        this.id = id;
        this.name = name;
        this.username = username;
        this.password = password;
        this.address = address;
        this.avatar = avatar;
        this.permission = permission;
    }
    
    @Override
    public String toString() {
        return "User{" +
            "id=" + id +
            ", name='" + name + '\'' +
            ", username='" + username + '\'' +
            ", password='" + password + '\'' +
            ", address='" + address + '\'' +
            ", avatar='" + avatar + '\'' +
            ", permission='" + permission + '\'' +
            '}';
    }
}

四种方式调用:

public class ConstructorTest {
    public static void main(String[] args) throws Exception{
        //1、加载Class对象
        Class<?> userClass = Class.forName("com.alascanfu.ReflectTest.User");
        
    
        //2、获取所有公有构造方法
        System.out.println("**********************所有公有构造方法*********************************");
        Constructor[] conArrayPublicCon = userClass.getConstructors();
        for(Constructor c : conArrayPublicCon){
            System.out.println(c);
        }
        //3、获取所有的构造方法
        System.out.println("**********************所有构造方法*********************************");
        Constructor[] conArrayAllCon = userClass.getDeclaredConstructors();
        for(Constructor c : conArrayAllCon){
            System.out.println(c);
        }
        //4、获取公有的指定参数的构造方法
        System.out.println("*************从公有的构造方法中获得指定参数类型的公有构造方法******************");
        Constructor<?> constructorPublicDirectParams = userClass.getConstructor(Long.TYPE,String.class,String.class,String.class,String.class,String.class,String.class);
        System.out.println(constructorPublicDirectParams);
    
        //5、获取所有的且指定参数的构造方法
        System.out.println("************从所有构造函数中获取指定参数类型的构造方法***********************");
        Constructor<?> constructorAllDirectParams = userClass.getDeclaredConstructor(null);
        System.out.println(constructorAllDirectParams);
    }
}

4、通过反射调用构造方法

通过Class对象获得到构造函数再来创建实例的过程,也就是我们常说的通过反射调用构造方法

代码实现:

//6、通过上述获得的构造方法创建实例
        System.out.println("********************通过上述获得的构造方法创建实例***********************");
        Object obj1 = constructorPublicDirectParams.newInstance(1L,"Alascanfu","admin","123456","CQTGU", "XXX.IMG", "Administrator");
        System.out.println(obj1);
        Object obj2 = constructorAllDirectParams.newInstance();
        System.out.println(obj2);

以上是接着3续写,记得查看上下文,这里调用的是指定的构造函数,包括了指定的包含全部形参的公共构造函数以及指定的不包含任何参数的构造函数

输出结果:

********************通过上述获得的构造方法创建实例***********************
User{id=1, name='Alascanfu', username='admin', password='123456', address='CQTGU', avatar='XXX.IMG', permission='Administrator'}
User{id=0, name='null', username='null', password='null', address='null', avatar='null', permission='null'}

5、通过反射调用类中的方法

通过Class对象利用反射获取类中的方法,可以获取指定参数名的方法,获得到方法之后,还可以通过利用反射创建的实例来执行对应方法。

public Object invoke(Object obj, Object... args)函数是用来执行方法的 其中第一个参数 是实例对象,第二个参数是参数{},

通过method.invoke(instance,args[])来执行方法,但是方法是包含权限修饰符的,不是所有方法都可以直接执行的,这里可能会用到破除访问修饰符的利器setAccessible(true)方法,后面会说,这里先进行理解一下就好。

代码实现:

//7、通过反射调用类中的方法
        System.out.println("********************通过反射调用类中所有公有的方法***********************");
        Method[] publicMethods = userClass.getMethods();
        for (Method method :publicMethods){
            System.out.println(method);
        }
    
        System.out.println("********************通过反射调用类中指定的公有方法***********************");
        Method setId = userClass.getMethod("setId", long.class);
        Method getId = userClass.getMethod("getId");
        System.out.println(setId.invoke(obj1, 201901094106L));
        System.out.println(getId.invoke(obj1));
    
        System.out.println("********************通过反射调用类中所有的方法***********************");
        Method[] declaredMethods = userClass.getDeclaredMethods();
        for (Method method :declaredMethods){
            System.out.println(method);
        }
    
        System.out.println("********************通过反射调用类中指定的方法***********************");
        Method setPassword = userClass.getDeclaredMethod("setPassword", String.class);
        Method getPassword = userClass.getDeclaredMethod("getPassword");
        setPassword.setAccessible(true);
        getPassword.setAccessible(true);
        System.out.println(setPassword.invoke(obj1, "2021-12-22"));
        System.out.println(getPassword.invoke(obj1));

6、通过反射调用类中的属性

利用Class对象通过反射技术调用类中的属性,也可为属性进行对应的修改

这里也有用到破除权限修饰符的神奇能力

代码如下:

/**
          *  8、通过反射调用属性
          *  public int age = 18;
          *  public String sex = "unknown";
         */
        System.out.println("********************通过反射调用类中的所有公有属性***********************");
        Field[] publicFields = userClass.getFields();
        for (Field field:publicFields){
            System.out.println(field);
        }
        
        System.out.println("********************通过反射调用类中的指定公有属性***********************");
        Field age = userClass.getField("age");
        age.set(obj1,21);
        System.out.println(obj1);
    
        System.out.println("********************通过反射调用类中的所有属性***********************");
        Field[] allFields = userClass.getDeclaredFields();
        for (Field field:allFields){
            System.out.println(field);
        }
    
        System.out.println("********************通过反射调用类中所有属性中的指定属性***********************");
        Field password = userClass.getDeclaredField("password");
        password.setAccessible(true);
        password.set(obj1,"123456");
        System.out.println(obj1);

7、打破权限修饰符,这就是反射的可怕之处。setAccessible(true)

Java中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限

  • default (即默认,什么也不写): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。

  • private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)

  • public : 对所有类可见。使用对象:类、接口、变量、方法

  • protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。

而我们如果通过反射获得到非公有的方法之后,想要进行执行该方法或者说获得到非公有的构造函数,如单例模式下的万恶之源——反射创建的问题引出都和setAccessible(true)有关。

利用类对象通过反射技术获取得到的 构造函数,方法,属性都可以通过setAccessible(true)来破除访问控制符来进行修改以及获取,如果没有获取的话JVM就会报出一个IllegalAccessException:

Exception in thread "main" java.lang.IllegalAccessException: Class com.alascanfu.ReflectTest.ConstructorTest can not access a member of class com.alascanfu.ReflectTest.User with modifiers "private"
	at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
	at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
	at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
	at java.lang.reflect.Field.set(Field.java:761)
	at com.alascanfu.ReflectTest.ConstructorTest.main(ConstructorTest.java:94)

后面在JUC高并发编程的专题下涉及到JMM(Java Memory Model)与Volatile(可见性、不支持原子性、禁止指令重排)那块会再次提到单例模式,卖个关子,后面大家一起学习哦~

8、 通过反射技术运行配置文件——JDBC的应用

public class JDBCUtils {
    public static Connection getConnection() throws Exception{
        //1.加载配置文件
        InputStream resourceAsStream = JDBCUtils.class
            .getClassLoader()
            .getResourceAsStream("jdbc.properties");
        Properties properties = new Properties();
        properties.load(resourceAsStream);
        
        //2.读取配置信息
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String url = properties.getProperty("url");
        String driverClass = properties.getProperty("driverClass");
        
        //3.加载驱动
        Class.forName(driverClass);
        Connection conn = DriverManager.getConnection(url, user, password);
        return conn;
    }
    
    public static void main(String[] args) {
        Connection connection = null;
        try {
            connection = JDBCUtils.getConnection();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(connection);
    }
}

jdbc.properties 放在src的工程目录之下,学过JDBC的同学应该都知道哦,这里不再细讲了。

外部配置文件jdbc.properties

user=root
password=123456
url=jdbc:mysql://localhost:3306/test
driverClass=com.mysql.jdbc.Driver

控制台结果:

Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
com.mysql.cj.jdbc.ConnectionImpl@5d740a0f

写在后面

今天室友打了一把英雄联盟的时间总结了一下Java基础——反射的相关基础使用

说实话,反射会在很多底层框架中使用到,如果不会最基本的反射使用,

那很多底层源码都无法读懂,就更不要说理解原理了。

最后的最后 一定要记得实践,不实践 ,等于白学

我也坚信 这篇通俗易懂的文章也能获得你的认可~

加油 基础不牢地动山摇,一定要好好看看哦 一把游戏的时间足以

最后
每天进步点 每天收获点
愿诸君 事业有成 学有所获
如果觉得不错 别忘啦一键三连哦~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Alascanfu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值