【韩顺平讲Java】Java反射专题(一)

资料

课程地址:https://www.bilibili.com/video/BV1g84y1F7df?p=1
课程资料:链接:https://pan.baidu.com/s/1l_RepifmluABNgevGVn5pw
提取码:piy0

反射快速入门

反射是框架的底层基石,对阅读框架底层源码有帮助
在这里插入图片描述
先提出一个需求:
1.根据配置文件 re.properties指定信息,创建对象并调用方法
classfullpath=com.hspedu.Cat
method=hi
●老韩思考:使用现有技术,你能做的吗?

2.这样的需求在学习框架时特别多,即通过外部文件配置,在不修改源码情况下,来控制程序,也符合设计模式的ocp原则(开闭原则,即不修改源码,来扩展功能)
3.快速入门com.hspedu.reflection.question
ReflectionQuestion.java

根据这个需求,我们创建一个配置文件
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

在配置文件里面写上下面这段代码,类路径和方法

classfullpath=reflect.getProperty
method=hi

我们的目的就是获得re.properties里面的类名和方法,来创建对象以及调用方法
我们先创建getProperty类

package reflect;

/**
 * Created by 此生辽阔 on 2021/7/4 10:13
 */
public class getProperty {
    private String name;
    public void hi(){
        System.out.println("hi");
    }
}


我们知道,Properties类可以读取配置文件,所以写出下面的代码

package reflect;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Properties;

/**
 * Created by 此生辽阔 on 2021/7/4 10:17
 */
public class reflectionQuestion {
    public static void main(String[] args) throws IOException {
        //Properties类可以读写配置文件
        Properties properties = new Properties();
      //  properties.load(new FileInputStream("src\\re.properties"));//报错,src\\re.properties不存在
      //  properties.load(new FileInputStream("re.properties"));//报错,re.properties不存在
      //  properties.load(new FileInputStream("javaCode\\src\\re.properties"));//可以读取
        properties.load(new FileInputStream("D:\\project\\JavaTest\\javaCode\\src\\re.properties"));
       String classfullpath= properties.get("classfullpath").toString();
        String method= properties.get("method").toString();
        System.out.println("classfullpath="+classfullpath);
        System.out.println("method="+method);
    }
}

获取re.properties的路径可以通过下面的方法
在这里插入图片描述

读取结果
在这里插入图片描述

还有一点就是可以看到代码中有警告,我们可以通过加上注解去掉警告
在这里插入图片描述

@SuppressWarnings({"all"})

在这里插入图片描述

但是我们获取到的是字符串,是不能直接创建对象的
那么怎么创建对象呢?
这就需要用到反射了

 //使用反射创建对象
        //(1)加载类,返回一个Class类型的对象
        Class cls = Class.forName(classfullpath);
        //(2)通过cls得到你加载的类reflect.getProperty的对象实例
         Object o= cls.newInstance();
        //得到类的运行类型
        System.out.println("o的运行类型"+o.getClass());

在这里插入图片描述

     //(3)通过cls得到你加载的类的method对应的Method对象
        //即在反射中可以把方法视为对象
        Method method1 = cls.getMethod(method);
         //(4)通过method1调用方法,即通过方法对象来实现调用方法
         method1.invoke(o);
         //反射可以说是java的核心吧,没有反射就没有框架,java的地位也会降很多
        //传统的方法:对象.方法()  反射机制  方法.invoke(对象);

在这里插入图片描述

那么反射的价值到底体现在哪里?
我们前面说过,反射可以在不修改源码的情况下,扩展程序的功能,我们的反射是基于配置文件读取类名以及需要调用的方法,现在假如getProperties类有另外一个方法cry,而我们需要调用cry而不是调用hi,那怎么办?,可以直接修改配置文件

在这里插入图片描述

那么我们的反射再读取配置文件的时候,读取到的需要执行的方法就是cry了。
线面的错误是因为我还没有给getProperties类写cry()方法
在这里插入图片描述
写cry()方法之后
在这里插入图片描述

反射原理图

反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息(比如成员变量,构造器,成员方法等等),并能操作对象的属性及方法。

反射在设计模式和框架底层都会用到,加载完类之后,在堆中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,形象的称之为:反射

java反射机制原理示意图

在这里插入图片描述

解释:
java程序运行分为3个阶段 编译成字节码阶段 类加载阶段 和运行阶段
编译阶段
比如我们编写了一个cat类,它的属性 构造器 方法 这些 ,源代码就会通过javac被编译成字节码文件cat.class

字节码文件里面有什么呢?
属性 构造器 成员变量,泛型 注解这些

加载阶段

new一个对象的时候会触发类的加载,之后类加载器把字节码文件加载到堆中生成一个Class类对象 ,通过反射在底层会把成员变量 成员方法,构造器这些当成一种对象来对待,如下图
在这里插入图片描述

Runtime运行阶段
new一个对象的时候会触发类的加载
类加载完就生成一个对象了,对象在堆里面,该对象知道它是属于哪个Class对象的

得到Class对象后,可以创建对象以及调用对象方法,操作它的属性等

反射相关类

反射可以做哪些事情?

1.在运行时判断任意一个对象所属的类
2.在运行时构造任意一个类的对象
3.在运行时得到任意一个类所具有的成员变量和方法
4.在运行时调用任意一个对象的成员变量和方法
5.生成动态代理

反射相关的类

  1. java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象
  2. java.lang.reflect.Method:代表类的方法 Method对象表示某个类的方法
  3. java.lang.reflect.Field:代表类的成员变量. Field对象表示某个类的成员变量
  4. java.lang.reflect.Constructor:代表类的构造方法 Constructor对象表示构造器

测试Field和Constructor

 //java.lang.reflect.Field: 代表类的成员变量, Field对象表示某个类的成员变量
        //得到name字段
        //getField不能得到私有的属性
        Field nameField = cls.getField("age"); //
        System.out.println(nameField.get(o)); // 传统写法 对象.成员变量 , 反射 :  成员变量对象.get(对象)

        //java.lang.reflect.Constructor: 代表类的构造方法, Constructor对象表示构造器
        Constructor constructor = cls.getConstructor(); //()中可以指定构造器参数类型, 返回无参构造器
        System.out.println(constructor);//Cat()
package com.hspedu;

/**
 * @author 韩顺平
 * @version 1.0
 */
public class Cat {

    private String name = "招财猫";
    public int age = 10; //public的

    public Cat() {} //无参构造器

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

    public void hi() { //常用方法
        //System.out.println("hi " + name);
    }
    public void cry() { //常用方法
        System.out.println(name + " 喵喵叫..");
    }

}

测试Field和Constructor得到的结果
在这里插入图片描述

获得有参数的构造器

     Constructor constructor2 = cls.getConstructor(String.class); //这里老师传入的 String.class 就是String类的Class对象
        System.out.println(constructor2);//Cat(String name)

在这里插入图片描述

发射调用优化

反射的优点和缺点

1.优点:可以动态的创建和使用对象(也是框架底层核心),使用灵活,没有反射机制,框架技术就失去底层支撑。
2.缺点:使用反射基本是解释执行,对执行速度有影响.

反射调用优化-关闭访问检查

  1. Method和Field、Constructor对象都有setAccessible()方法
  2. setAccessible作用是启动和禁用访问安全检查的开关
  3. 参数值为true表示反射的对象在使用时取消访问检查,提高反射的效率。参数值为false则表示反射的对象执行访问检查
package reflect;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * Created by 此生辽阔 on 2021/7/4 15:31
 */
public class Reflection02 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {

        //Field
        //Method
        //Constructor
        m1();
        m2();
        m3();
    }

    //传统方法来调用hi
    public static void m1() {

        Cat cat = new Cat();
        long start = System.currentTimeMillis();
        for (int i = 0; i < 900000000; i++) {
            cat.hi();
        }
        long end = System.currentTimeMillis();
        System.out.println("m1() 耗时=" + (end - start));
    }

    //反射机制调用方法hi
    public static void m2() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {

        Class cls = Class.forName("reflect.Cat");
        Object o = cls.newInstance();
        Method hi = cls.getMethod("hi");
        long start = System.currentTimeMillis();
        for (int i = 0; i < 900000000; i++) {
            hi.invoke(o);//反射调用方法
        }
        long end = System.currentTimeMillis();
        System.out.println("m2() 耗时=" + (end - start));
    }

    //反射调用优化 + 关闭访问检查

    public static void m3() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {

        Class cls = Class.forName("reflect.Cat");
        Object o = cls.newInstance();
        Method hi = cls.getMethod("hi");
        hi.setAccessible(true);//在反射调用方法时,取消访问检查
        long start = System.currentTimeMillis();
        for (int i = 0; i < 900000000; i++) {
            hi.invoke(o);//反射调用方法
        }
        long end = System.currentTimeMillis();
        System.out.println("m3() 耗时=" + (end - start));
    }
}


在这里插入图片描述

Class类分析

  1. Class也是类,因此也继承Object类
  2. Class类对象不是new出来的,而是系统创建的
  3. 对于某个类的Class类对象,在内存中只有一份,因为类只加载一次
  4. 每个类的实例都会记得自己是由哪个Class 实例所生成
  5. 通过Class对象可以完整地得到一个类的完整结构,通过一系列API
  6. Class对象是存放在堆的
  7. 类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据(包括方法代码,
    变量名,方法名,访问权限等等)https://www.zhihu.com/question/38496907

Class也是类,因此也继承Object类

查看类图
在这里插入图片描述

然后点钟Class,按住ctrl+lat+p可以查看父类

在这里插入图片描述

Class类对象不是new出来的,而是系统创建的

类加载器通过loadClass()方法完成了类的加载,然后生成了某个类对应的类对象

我们来打断点看一下创建对象的过程
在这里插入图片描述

用force step Into

step into会进入你自己写的方法。
而force step into能够进入所有的方法,比如jdk的方法。

在这里插入图片描述

直接跳到这一步了,可以看到使用了classLoader();
在这里插入图片描述

我们再来看看使用反射的方式创建对象的过程

在这里插入图片描述

在这里插入图片描述

对于某个类的Class类对象,在内存中只有一份,因为类只加载一次

测试一下类只加载一次

  Class aClass = Class.forName("reflect.Cat");
        Class aClass2 = Class.forName("reflect.Cat");
        System.out.println(aClass.hashCode());
        System.out.println(aClass2.hashCode());

hashCode一样,说明这两个类是一样的,hashCode不一样,说明加载的类不一样
在这里插入图片描述

    Class aClass = Class.forName("reflect.Cat");
        Class aClass2 = Class.forName("reflect.Cat");
        Class aClass3 = Class.forName("reflect.Dog");
        System.out.println(aClass.hashCode());
        System.out.println(aClass2.hashCode());
        System.out.println(aClass3.hashCode());

我们添加一个Dog类进行测试,发现打印出来的hashCode不一样,说明堆内存中同样加载了一个Dog的Class类对象,且只加载一次
在这里插入图片描述

通过Class对象可以完整地得到一个类的完整结构,通过一系列API

在这里插入图片描述

类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据(包括方法代码,变量名,方法名,访问权限等等)

可以理解为类在加载的时候,会在堆中创建该类的Class类对象,同时在方法区生成字节码的二进制数据
在这里插入图片描述

Class常用方法

package reflect;

/**
 * Created by 此生辽阔 on 2021/7/4 18:26
 */
public class Car {
    public String brand="宝马";
    public double price=10000;
    public int year=2020;

    @Override
    public String toString() {
        return "Car{" +
                "brand='" + brand + '\'' +
                ", price=" + price +
                ", year=" + year +
                '}';
    }
}

package reflect;

import java.lang.reflect.Field;

/**
 * Created by 此生辽阔 on 2021/7/4 18:22
 */
public class Class02 {

    public static void main(String[] args) throws Exception {
        //定义类路径
         String fullclasspath="reflect.Car";
        //获得Car类对应的Class类对象
        Class<?> cls = Class.forName(fullclasspath);
        //输出cls
        System.out.println(cls);//显示cls对象, 是哪个类的Class对象reflect.Car
        System.out.println(cls.getClass());//输出cls运行类型 java.lang.Class
        //得到包名
        System.out.println(cls.getPackage().getName());

        //得到全类名
        System.out.println(cls.getName());
        //通过cls创建实例
        Object o = cls.newInstance();
        System.out.println(o);
        //通过反射获得属性brand
         Field brand=cls.getField("brand");
        System.out.println(brand);//public java.lang.String reflect.Car.brand
        System.out.println(brand.get(o));
        //通过反射给属性赋值
        brand.set(o,"奔驰");
        System.out.println(brand.get(o));
        //得到所有的属性(字段)并遍历
        Field[] fields = cls.getFields();
        for(Field f:fields)
        {
            System.out.println(f);
            System.out.println(f.getName());
        }
    }
}



在这里插入图片描述

获取Class类对象的六种方式

GetClass_.java
1.前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法
forName()获取,可能抛出ClassNotFoundException,实例:Class cls1 =Class.forName( “java.lang.Cat”);
应用场景:多用于配置文件,读取类全路径,加载类.
2.前提:若已知具体的类,通过类的class获取,该方式最为安全可靠,程序性能
最高实例: Class cls2 = Cat.class;
应用场景:多用于参数传递,比如通过反射得到对应构造器对象.

下面这个图的意思是在哪个阶段可以使用哪种获得Class类对象的方法

比如在编译阶段可以使用Class.forName获得Class类对象
在类加载阶段可以通过类.class获得Class类对象
在Runtime运行阶段可以通过对象.getClass()的方式获得Class类对象
也可以通过获得类加载器来获取Class类对象

在这里插入图片描述

在Java底层,基本类型和包装类型是自动装箱和拆箱的,通过反射过来的对象就能进行自动装箱和拆箱,所以获得的类对象是同一个,可以验证hashCode一样

package reflect;

/**
 * Created by 此生辽阔 on 2021/7/4 19:33
 */

public class GetClass_ {
    public static void main(String[] args) throws ClassNotFoundException {

        //1. Class.forName
        String classAllPath = "reflect.Car"; //通过读取配置文件获取
        Class<?> cls1 = Class.forName(classAllPath);
        System.out.println(cls1);

        //2. 类名.class , 应用场景: 用于参数传递
        Class cls2 = Car.class;
        System.out.println(cls2);

        //3. 对象.getClass(), 应用场景,有对象实例
        Car car = new Car();
        Class cls3 = car.getClass();
        System.out.println(cls3);

        //4. 通过类加载器【4种】来获取到类的Class对象
        //(1)先得到类加载器 car
        ClassLoader classLoader = car.getClass().getClassLoader();
        //(2)通过类加载器得到Class对象
        Class cls4 = classLoader.loadClass(classAllPath);
        System.out.println(cls4);

        //cls1 , cls2 , cls3 , cls4 其实是同一个对象
        System.out.println(cls1.hashCode());
        System.out.println(cls2.hashCode());
        System.out.println(cls3.hashCode());
        System.out.println(cls4.hashCode());

        //5. 基本数据(int, char,boolean,float,double,byte,long,short) 按如下方式得到Class类对象
        Class<Integer> integerClass = int.class;
        Class<Character> characterClass = char.class;
        Class<Boolean> booleanClass = boolean.class;
        System.out.println(integerClass);//int

        //6. 基本数据类型对应的包装类,可以通过 .TYPE 得到Class类对象
        Class<Integer> type1 = Integer.TYPE;
        Class<Character> type2 = Character.TYPE; //其它包装类BOOLEAN, DOUBLE, LONG,BYTE等待
        System.out.println(type1);

        System.out.println(integerClass.hashCode());//?
        System.out.println(type1.hashCode());//?
    }
}

在这里插入图片描述

哪些类型有Class对象

1.外部类,成员内部类,静态内部类,局部内部类,匿名内部类
2. interfac:接口
3.数组
4.enum:枚举
3. annotation:注解
4. 6.基本数据类型
5. void

package com.hspedu.reflection.class_;

import java.io.Serializable;

/**
 * @author 韩顺平
 * @version 1.0
 * 演示哪些类型有Class对象
 */
public class AllTypeClass {
    public static void main(String[] args) {

        Class<String> cls1 = String.class;//外部类
        Class<Serializable> cls2 = Serializable.class;//接口
        Class<Integer[]> cls3 = Integer[].class;//数组
        Class<float[][]> cls4 = float[][].class;//二维数组
        Class<Deprecated> cls5 = Deprecated.class;//注解
        //枚举
        Class<Thread.State> cls6 = Thread.State.class;
        Class<Long> cls7 = long.class;//基本数据类型
        Class<Void> cls8 = void.class;//void数据类型
        Class<Class> cls9 = Class.class;//

        System.out.println(cls1);
        System.out.println(cls2);
        System.out.println(cls3);
        System.out.println(cls4);
        System.out.println(cls5);
        System.out.println(cls6);
        System.out.println(cls7);
        System.out.println(cls8);
        System.out.println(cls9);
    }
}

在这里插入图片描述

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值