反射的学习

反射

http://liulei.wiki/%e5%8f%8d%e5%b0%84/ 由于一些图片没有导入,详情可前往个人博客查询

什么是反射,我就用自己的话来叙述一遍吧。

首先,在我们进行类的初始化时,首先会进行类加载,将.class的字节码文件加载到堆中,生成一个Class对象,由于Class对象是加了线程同步锁的,所以,每个类有且只有一个Class类,而我们创建的对象就会和它的Class类产生引用的关系。

就像照镜子一样,Class类,和它的实例相互映射,我们可以通过Class类中提供的方法获取到类中的所有属性。

而反射的概念就是将Class类中的所有成员当做对象来获取。

我们可以使用反射来创建对象,并且把(属性、方法、构造器...)当做对象来获取。(万物皆对象)

由此引出了另一个问题,我们为什么不直接用创建对象,然后用对象.属性的方法获取成员呢?

这就要思考反射的意义了,反射究竟有什么好处?

查阅了一些资料,最后其实就是

灵活性!!!

就以我前面所述的类的加载来看,我们正常创建一个对象实例,采用的是new 关键字,而这种方法虽然简单,但太僵硬了,程序按照固定的流程去转换成字节码文件,进行类的加载,对象的初始化,一系列流程下来。而当我们在使用程序的时候,想要使用某个类,但却没有new出来,那我们是不是需要去修改源代码,再去new一个类,然后再进行编译。(小程序似乎还可以容忍,数据量大的时候再编译一边,我怕我脑壳被打爆)

先不提设计原则之源码闭合原则,单单这个流程,不觉得太复杂了吗?而且new作为静态加载,当我们代码中使用到一个类的时候,如果不创建实例,那么编译会发生错误,但如果我们全部都实例化的话,当我们程序中并没有使用到,那么是不是产生了资源的浪费呢?

由以上所述,我再说说反射的好处

首先是动态加载,由于还没学到框架,和xml配置文件,我大概理解了一下意思

就我的理解而言,反射的好处在于其是动态加载的。

什么是动态加载呢?简单来说,就是我没用到的时候就不加载,也不会报错,用到的时候才开始加载,虽然我代码写在上面,但和new创建不一样啊,我不用先进行类的加载,不会占用内存空间。

不仅如此,当我需要使用某个类的时候或者修改、获取某个类属性,我们并不需要在源代码中修改,而只要在配置文件中修改即可,并不需要修改源代码,这样看来,是不是比new要灵活很多?

缺点

当然,说了这么多,反射是不是也有缺点呢?

当然有,而且也挺多

1.性能

使用反射,首当其冲的是性能问题,我电脑不咋样,运行如下代码

public class Reflection02 {
    public static void main(String[] args) throws Exception {
        m1();
        m2();
    }
    //传统方法调用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 Exception {
        Class cls = Class.forName("com.liulei.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));
    }
}

运行结果如下

是不是挺夸张的。(电脑烂不能怪我)

当然,反射也可以优化,但是优化的速度,emmm,算了,上代码

public class Reflection02 {
    public static void main(String[] args) throws Exception {
        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 Exception {
        Class cls = Class.forName("com.liulei.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 Exception {
        Class cls = Class.forName("com.liulei.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));
    }
}

结果如下

聊胜于无吧。

所以反射一般用于系统框架,普通程序不建议使用

2.内部暴露问题

什么是内部暴露问题呢,我一开始学的是,类的私有属性和方法无法被访问,只能通过类中公有的方法去调用私有的属性和方法。看上去的确没毛病,但学完反射,好家伙,我不给你开门,你直接给我爆破一波,直接把门给我炸了。

使用反射可以允许代码执行一些在正常情况下无法进行的操作,例如上面所说的,直接访问私有的成员属性和方法,这样便破坏了抽象性,可能导致一些意料之外的错误,至于什么错误,等我学到框架在回来唠唠。

然后就是爆破的代码

方法.setAccessible(true); //暴破【暴力破解】,使用反射可以访问private构造器,反射面前都是纸老虎
属性.setAccessible(true); //暴破【暴力破解】,使用反射可以访问private构造器,反射面前都是纸老虎
构造器.setAccessible(true); //暴破【暴力破解】,使用反射可以访问private构造器,反射面前都是纸老虎

3.安全

使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,那就有问题了。

反射的缺点就这些了。

以下就是有关反射的一些说明

Class类

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

        //1.Class也是类,因此也继承了Object类
        //2.Class类对象不是new出来的,而是系统创建的
        //(1)传统new 对象,debug
//        public Class<?> loadClass(String name) throws ClassNotFoundException {
//            return loadClass(name, false);
//        }
        Cat cat = new Cat();
        //(2)反射的方式
//        public Class<?> loadClass(String name) throws ClassNotFoundException {
//            return loadClass(name, false);
//        }
        Class cls1 = Class.forName("com.liulei.Cat");
        //3.对于某个类的Class类对象,在内存中只有一份,因为类只加载一次
        Class cls2 = Class.forName("com.liulei.Cat");
        System.out.println(cls1.hashCode());//hashcode:93122545
        System.out.println(cls2.hashCode());//hashcode:93122545
        //4.每个类的实例都会记得自己是由哪个Class实例所生成
        //5.通过Class对象可以完整的得到一个类的完整结构,通过一系列API
        //6.Class对象是存放在堆中的
        //7.类的字节码二进制数据,是放在方法区的,有的地方称之为类的元数据(包括方法代码,变量名,方法名,访问权限等等)
    }

Class类的一些常用方法

public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        String classAllPath = "com.liulei.Car";
        //1.获取到Car类,对应的Class对象
        //<?> 表示不确定的java类型
        Class<?> cls = Class.forName(classAllPath);
        //2.输出cls
        System.out.println(cls);//显示cls对象是哪个类的Class对象:com.liulei.Car
        System.out.println(cls.getClass()); //显示cls对象的运行类型:java.lang.Class
        //3.得到包名
        System.out.println(cls.getPackage().getName());//com.liulei
        //4.得到全类名
        System.out.println(cls.getName());//com.liulei.Car
        //5.生成对象实例
        Car car = (Car) cls.newInstance();
        System.out.println(car);
        //6.通过反射获取属性
        Field brand = cls.getField("brand");
        System.out.println("brand="+ brand.get(car) );
        //7.通过反射给属性赋值
        brand.set(car,"奔驰");
        System.out.println("brand="+ brand.get(car) );
        //8.得到所有的属性
        Field[] fields = cls.getFields();
        for (Field f :fields) {
            System.out.println(f.getName() + " = " + f.get(car));
        }

    }

获取Class对象的六种方法

/**
 * @version 1.0
 * 演示得到Class对象的各种方式
 */
public class GetClass_ {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        //1.已知全一个类的全类名 Class.forName()
        String classAllPath = "com.liulei.Car";
        Class<?> cls1 = Class.forName(classAllPath);
        System.out.println(cls1);

        //2.如果处于类加载状态中 通过 类名.class 获取,用于参数的传递,通过反射得到对应构造器对象
        Class cls2 = Car.class;
        System.out.println(cls2);

        //3.已知某个对象实例,调用该实例的getClass()方法获取Class对象
        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.通过基本数据类型得到Class对象
        Class<Integer> integerClass = int.class;
        Class<Character> characterClass = char.class;
        Class<Boolean> booleanClass = boolean.class;
        System.out.println(integerClass);
        System.out.println(characterClass);
        System.out.println(booleanClass);

        //6.通过包装类得到Class对象
        Class<Integer> type = Integer.TYPE;
        Class<Character> type1 = Character.TYPE;
        Class<Boolean> type2 = Boolean.TYPE;
        System.out.println(type);
        System.out.println(type1);
        System.out.println(type2);

        //通过基本数据得到的Class对象和包装类得到的Class对象是同一个
        System.out.println(integerClass.hashCode());
        System.out.println(type.hashCode());

    }
}

类加载!!!

正如我前面所说,new是静态加载,反射是动态加载

反射机制原理图

其中的类加载器的过程,在下图中

类加载过程图

反射机制流程

1.  .java文件通过javac编译形成.class文件
2.  .class字节码文件通过类加载器ClassLoader进入加载(Loading)状态
      将class文件读入内存,并在堆中创建一个java.lang.Class对象
3.进入连接(Linking)第一状态,验证(Verification):目的就是确保class文件的字节流中包含的信息符合虚拟机的要求,不能             
    危害虚拟机自身安全。验证阶段主要包括四个检验过程:文件格式验证、元数据验证、字节码验证和符号引用验证。
4.进入连接(Linking)第二状态,准备(Preparation):准备阶段就是为类变量(静态变量)分配初始值(初始值就是数据类型的
    默认值)的阶段,注意是类变量,不是实例变量。而且如果是static final修饰的话,分配的就不是初始值了,是程序设定的 
    值,并且一旦赋值,无法改变。
5.进入连接(Linking)第三阶段,解析(Resolution): 解析阶段是虚拟机常量池的符号引用替换为直接引用的过程。
6.进入初始化阶段,真正开始执行类中定义的java程序代码,此阶段会执行clinit()方法,<clinit>()方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并进行合并。
    虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。这就是为什么一个类只会有一个Class对象。
7.进入运行状态。

通过反射获取类的结构信息的一些方法

package com.liulei.reflection;

import org.junit.jupiter.api.Test;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * @version 1.0
 * 演示如何通过反射获取类的结构信息
 */
@SuppressWarnings("all")
public class ReflectionUtils {
    public static void main(String[] args) {

    }
    //第二组API
    @Test
    public void api_02() throws ClassNotFoundException {
        //得到 Class 对象
        Class<?> personCls = Class.forName("com.liulei.reflection.Person");
        //1.getDeclaredFields:获取本类中所有属性
        //2.getModifiers()规定 说明: 默认修饰符 是 0 , public 是 1 ,private 是 2 ,protected 是 4 , static 是 8 ,final 是 16
        //3.返回属性的数据类型getType()
        Field[] declaredFields = personCls.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println("本类中的所有属性 = "+ declaredField.getName()
            + "  该属性的修饰符的值 = " + declaredField.getModifiers() +
                    "该属性的类型 = " + declaredField.getType());
        }
        //getDeclaredMethods:获取本类中所有方法
        Method[] declaredMethods = personCls.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            System.out.println("本类中所有方法=" + declaredMethod.getName()
                    + " 该方法的访问修饰符值=" + declaredMethod.getModifiers()
                    + " 该方法返回类型" + declaredMethod.getReturnType());
        }
        //getDeclaredMethods:获取本类中所有方法
        //输出当前这个方法的形参数组情况
    }

    //第一组API
    @Test
    public void api_01() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        //得到 Class 对象
        Class<?> personCls = Class.forName("com.liulei.reflection.Person");
        Object o = personCls.newInstance();
        System.out.println(o.getClass());
        //getName:获取全类名
        System.out.println(personCls.getName());
        //getSimpleName:获取简单类名
        System.out.println(personCls.getSimpleName());
        //getFields:获取所有 public 修饰的属性,包含本类以及父类的
        Field[] fields = personCls.getFields();
        for (Field field : fields) {
            System.out.println("本类及父类的属性 = " + field.getName());
        }
        //getDeclaredFields:获取本类中所有属性
        System.out.println("==============");
        Field[] declaredFields = personCls.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println("本类中的所有属性 = " + declaredField.getName());
        }
        //getMethods:获取所有 public 修饰的方法,包含本类以及父类的
        System.out.println("==============");
        Method[] methods = personCls.getMethods();
        for (Method method : methods) {
            System.out.println("本类及父类的方法 = " + method.getName());
        }
        //getDeclaredMethods:获取本类中所有方法
        System.out.println("==============");
        Method[] declaredMethods = personCls.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            System.out.println("本类的所有方法 = " + declaredMethod );
        }
        //getConstructors: 获取所有 public 修饰的构造器,包含本类
        System.out.println("================");
        Constructor<?>[] constructors = personCls.getConstructors();
        for (Constructor<?> constructor : constructors) {
            System.out.println("本类的构造器 = " + constructor);
        }
        //getDeclaredConstructors:获取本类中所有构造器
        System.out.println("===============");
        Constructor<?>[] declaredConstructors = personCls.getDeclaredConstructors();
        for (Constructor<?> declaredConstructor : declaredConstructors) {
            System.out.println("本类的所有构造器 = " + declaredConstructor);
        }
        //getPackage:以 Package 形式返回 包信息
        System.out.println("===============");
        Package aPackage = personCls.getPackage();
        System.out.println("包信息 = " + aPackage);
        //getSuperClass:以 Class 形式返回父类信息
        System.out.println("===============");
        Class<?> superclass = personCls.getSuperclass();
        System.out.println("返回父类信息 = " + superclass);
        //getInterfaces:以 Class[]形式返回接口信息
        System.out.println("===============");
        Class<?>[] interfaces = personCls.getInterfaces();
        for (Class<?> anInterface : interfaces) {
            System.out.println("本类的接口信息 = " + anInterface);
        }
        //getAnnotations:以 Annotation[] 形式返回注解信息
        System.out.println("===============");
        Annotation[] annotations = personCls.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println("本类的注解信息 = " + annotation);
        }
    }
}

class A{
    public String hobby;
    public void hi(){

    }
    public A(){

    }
}

interface B {
    void m1();
}

@Deprecated
class Person extends A implements B{
    public Person(){

    }
    public Person(String name){
        this.name = name;
    }
    private Person(int Age){
        this.age = age;
    }
    //属性
    public String name = "jack";
    protected static int age = 19;
    String job = "上厕所";
    private double sal = 1000.0;
    //方法
    public void m1(){

    }

    protected void m2(){

    }

    void m3(){

    }
}

通过反射来创建对象

package com.liulei.reflection;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * @version 1.0
 * 演示通过反射机制创建实例
 */
public class ReflectCreateInstance {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        //1.获取到User类的Class对象
        Class<?> userCls = Class.forName("com.liulei.reflection.User");
        //2.通过public的无参构造器创建实例
        Object o = userCls.newInstance();
        System.out.println(o);
        //3.通过public的有参构造器创建实例
        Constructor<?> constructor = userCls.getConstructor(String.class);
        Object mary = constructor.newInstance("mary");
        System.out.println(mary);
        //4.通过非public的有参构造器创建实例
        Constructor<?> declaredConstructor = userCls.getDeclaredConstructor(int.class, String.class);
        declaredConstructor.setAccessible(true); //暴破【暴力破解】,使用反射可以访问private构造器,反射面前都是纸老虎
        Object tom = declaredConstructor.newInstance(10, "tom");
        System.out.println(tom);

    }
}

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

    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 + '\'' +
                '}';
    }
}

反射的学习到这就告一段落,加油!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值