Java反射笔记-韩顺平

本文详细介绍了Java反射机制,包括如何通过反射创建对象、调用方法,并展示了其在框架设计中的作用。通过示例代码展示了从配置文件读取信息动态创建并执行Cat对象的hi方法。此外,还讨论了反射的优缺点,如灵活性和性能影响。最后,深入探讨了Class类、类加载流程以及如何通过反射获取类的结构信息。
摘要由CSDN通过智能技术生成

反射(reflection)

引入:一个需求引出反射

  • 问题:
  1. 根据配置文件re.properties指定信息,创建Cat对象并调用方法hi

    re.properties文件

    classfullpath=com.hapedu.Cat
    method=hi
    
  2. 学习框架是使用特别多,通过外部文件配置,在不修改源码的情况下,来控制程序,符合设计模式的ocp原则(开闭原则:不修改源码,也能扩容功能)

  3. 快速入门

    Cat.java

    package com.hapedu;
    
    public class Cat {
    
        private String name="招财猫";
    
        public void hi(){
            System.out.println("hi   "+name);
        }
    
        public void say(){
            System.out.println(name+"  喵喵喵~");
        }
    
    }
    

    RelectionQuestion.java

    package com.hapedu.reflection.question;
    
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.util.Properties;
    
    public class ReflectionQuestion {
        public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
            //1、创建Properties类读取文件
            Properties properties = new Properties();
            properties.load(new FileInputStream("./re.properties"));
            String classfullpath = properties.get("classfullpath").toString();
            String methodName = properties.get("method").toString();
            //打印配置文件信息
            System.out.println("classfullpath="+classfullpath);
            System.out.println("methodName="+methodName);
    
            //2、使用反射机制实现
            //(1)   加载类,返回Class类型的对象
            Class aClass = Class.forName(classfullpath);
            //(2)   通过aClass得到加载类的对象实例
            Object o = aClass.newInstance();
            //(3)   通过aClass得到加载的类的方法对象     即:反射中可以把方法视为对象(万物皆对象)
            Method method = aClass.getMethod(methodName);
            //(4)   通过method1调用方法:即通过方法对象来实现调用方法   即:方法对象.invoke(类对象)
            method.invoke(o);
        }
    }
    

一、反射机制

1、Java Reflection

  1. 反射机制允许程序在执行期借助与Reflection API取得任何的内部信息(比如成员变量,构造器,成员方法等等),并能操作对象的属性及方法。反射在设计模式和框架底层都会用到
  2. 加载完类之后,在堆中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了嘞的完整结构信息。通过这个对象得到类的结构。这个对象就想一面精子,通过这个精子看到类的结构,形象的称之为:反射。

在这里插入图片描述

2、Java反射机制原理示意图(重点)

在这里插入图片描述

3、反射机制可以完成

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

4、反射相关的主要:

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

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;

public class Reflection01 {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchFieldException {
        //1、创建Properties获取配置文件
        Properties properties = new Properties();
        properties.load(new FileInputStream("./re.properties"));
        String classfullpath = properties.get("classfullpath").toString();
        String methodName = properties.get("method").toString();

        //2、通过反射机制实现
        //(1)   加载类,返回Class类型的对象
        Class<?> aClass = Class.forName(classfullpath);
        //(2)   通过aClass对象获取类的实例
        Object o = aClass.newInstance();
        //(3)   通过aClass对象获取类对象的方法对象
        Method method1 = aClass.getMethod(methodName);
        //(4)   通过方法对象调用方法
        method1.invoke(o);

        //java.lang.reflect.Field:代表类的成员变量,Field对象表示某个类的成员变量
        //通过getField()方法得到成员属性,但不能得到私有属性,通过成员属性对象.get(类的对象)进行调用
        Field age = aClass.getField("age");
        System.out.println(age.get(o));

        //java.lang.reflect.Constructor:代表类的构造方法,Constructor对象表示构造器
        //通过getConstructor()方法得到构造方法对象,不输入参数则是无参构造方法
        Constructor<?> constructor = aClass.getConstructor();
        System.out.println(constructor);
    }
}

5、反射优点和缺点

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

import com.hapedu.Cat;

import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;


public class Reflection02 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        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, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
        Class<?> aClass = Class.forName("com.hapedu.Cat");
        Object o = aClass.newInstance();
        Method hi = aClass.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));
    }

    //反射机制调用hi()方法优化(关闭访问检查)
    public static void m3() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
        Class<?> aClass = Class.forName("com.hapedu.Cat");
        Object o = aClass.newInstance();
        Method hi = aClass.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));
    }
}

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

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

二、Class类

1、基本介绍

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

2、Class类的常用方法

在这里插入图片描述

3、应用实例

创建Car类

package com.hapedu;

public class Car {
    public String brand ="宝马";
    public int price =500000;
    public String color ="白色";

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

创建Class01类完成实例

package com.hapedu.reflection.class_;

import java.lang.reflect.Field;

public class Class01 {

    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {
        String classAllPath="com.hapedu.Car";
        //1、获取到Car类对应的Class对象
        //<?>表示不确定的Class类型
        Class<?> aClass = Class.forName(classAllPath);
        //2、输出aClass
        System.out.println(aClass);
        System.out.println(aClass.getClass());
        //3、得到包名
        System.out.println(aClass.getPackage().getName());
        //4、得到全类名
        System.out.println(aClass.getName());
        //5、通过aClass创建对象实例
        Object car = aClass.newInstance();
        System.out.println(car);
        //6、通过反射获取属性brand
        Field brand = aClass.getField("brand");
        System.out.println(brand.get(car));
        //7、通过反射给属性赋值
        brand.set(car,"奔驰");
        System.out.println(brand.get(car));
        //8、得到所有属性的字段
        Field[] fields = aClass.getFields();
        for(Field field : fields){
            System.out.println(field.getName());
        }
    }
}

4、获取Class类对象(六种方法)

1、java.lang.Car

前提:已知一个类的全名称,且该类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException异常

实例:Class cls1=Class.forName(“java.lang.Cat”);

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

2、Car.class

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

实例:Class cls2=Car.class;

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

3、对象.getClass()

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

实例:Class cls3=对象.getClass();//运行类型

应用场景:通过创建好的对象,获取Class对象

4、其他方式

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

Class cls4 =cl.loadClass(“类的全类名”);

5、基本数据(int,char,boolean,float,double,byte,long,short)按如下方式得到Class类对象
Class cls = 基本数据类型.class
6、基本数据类型对应的包装类,可以通过.TYPE得到Class类对象
Class cls = 包装类.TYPE

5、有Class对象的类型

  1. 外部类,成员内部类,静态内部类,局部内部类,匿名内部类
  2. interface:接口
  3. 数组
  4. enum:枚举
  5. annotation:注解
  6. 基本数据类型
  7. void
package com.hapedu.reflection.class_;

import java.io.Serializable;

public class AllTpyeClass {
    public static void main(String[] args) {
        Class<String> aClass1 = String.class;//外部类
        Class<Serializable> aClass2 = Serializable.class;//接口
        Class<Integer[]> aClass3 = Integer[].class;//数组
        Class<Integer[][]> aClass4 = Integer[][].class;//二维数组
        Class<Deprecated> aClass5 = Deprecated.class;//注解
        Class<Thread.State> aClass6 = Thread.State.class;//枚举
        Class<Long> aClass7 = long.class;//基本数据类型
        Class<Void> aClass8 = void.class;//void数据类型
        Class<Class> aClass9 = Class.class;//Class类

        System.out.println(aClass1);
        System.out.println(aClass2);
        System.out.println(aClass3);
        System.out.println(aClass4);
        System.out.println(aClass5);
        System.out.println(aClass6);
        System.out.println(aClass7);
        System.out.println(aClass8);
        System.out.println(aClass9);
    }
}

三、类加载

1、静态加载和动态加载

  • 基本说明

    反射机制是java实现动态语言的关键,也就是通过反射实现类动态加载

    1. 静态加载:编译时加载相关的类,如果没有则报错

      Dog dog = new Dog();
      
    2. 动态加载:运行时加载需要的类,编译不报错,降低了依赖性

      Class cls = Class.forName("Person");
      Object o = cls.newInstance();
      Method method = cls.getMethod("hi");
      method.invoke(o);
      
  • 类加载时机

    1. 当创建对象时(new) //静态加载
    2. 当子类被加载时,父类也加载 //静态加载
    3. 调用类中的静态成员时 //静态加载
    4. 通过反射 //动态加载

2、类加载流程图

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

3、类加载的五个阶段

1、加载阶段

JVM在改阶段的主要目的是将字节码从不同的数据源(可能是class文件、也可能是jarbao,甚至网络)转化为二进制字节流加载到内存中,并生成一个代表该类的java.lang.Class对象

在这里插入图片描述

2、连接阶段----验证
  1. 目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
  2. 包括:文件格式验证(是否以魔数 oxcafebabe开头)、元数据验证、字节码验证和符号引用验证。
  3. 可以考虑使用 -Xverify:none参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间。
3、连接阶段----准备
  1. JVM会在改阶段对静态变量,分配内容并初始化(对应数据类型的默认初始值,如0、0L、null、false等)。这些变量所使用的内存都将在方法区中进行分配在这里插入图片描述
4、连接阶段----解析
  1. 虚拟机将常量池内的符号引用替换为直接引用的过程。
5、Initialization(初始化)
  1. 到初始化阶段,才真正开始执行类中定义的Java程序代码,此阶段是执行()方法的过程
  2. ()方法是有编译器按语句在源文件中出现的顺序,依次自动手机类中的所有静态变量的赋值动作和静态代码块中的语句,并进行合并。
  3. 虚拟机会保证一个类的()方法在多线程环境中被正确的加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的()方法,其他线程都需要阻塞等待,知道活动线程执行()方法完毕

四、通过反射获取类的结构信息

1、java.lang.Class类

  1. getName:获取全类名
  2. getSimpleName:获取简单类名
  3. getFields:获取所有public修饰的属性,包含本类以及父类的
  4. getDeclaredFields:获取本类中所有属性
  5. getMethods:获取所有public修饰的方法,包含本类以及父类的
  6. getDeclaredMethods:获取本类中所有方法
  7. getConstructors:获取所有public修饰的构造器,包含本类以及父类的
  8. getDeclaredConstructors:获取本类中所以构造器
  9. getPackage:以getPackage形式返回父类信息
  10. getSuperClass:以Class形式返回父类信息
  11. getInterfaces:以Class[]形式返回接口信息
  12. getAnnotations:以Annotation[]形式返回注解信息

2、java.lang.reflect.Field类

  1. getModifiers:以int形式返回修饰符
    • ​ 说明:默认修饰符是0,public是1,private是2,protected是4,static是8,final是16
  2. getType:以Class形式返回类型
  3. getName:返回属性名

3、java.lang.reflect.Mehod类

  1. getModifiers:以int形式返回修饰符
    • ​ 说明:默认修饰符是0,public是1,private是2,protected是4,static是8,final是16
  2. getReturnType:以Class形式获取返回类型
  3. getName:返回方法名
  4. getParameterTypes:以Class[]返回参数类型数组

4、java.lang.reflect.Constructor类

  1. getModifiers:以int形式返回修饰符
  2. getName:返回构造器名(全类名)
  3. getParameterTypes:以Class[]返回参数类型数组

5、Constructor类相关方法

  1. setAccessible:爆破(形参填入true,即可访问私有构造方法、变量、方法等)
  2. newInstance(Object…obj):调用构造器

6、通过反射访问类中的成员

  1. 获取Field对象
  2. 爆破
  3. 访问
    • field.set(o,值); //o表示对象,field表示Field对象
    • field.get(o); //o表示对象,field表示Field对象
  4. 注意:如果是静态属性,则set和get中的参数o,可以写成null

7、通过反射访问类中的成员

  1. 根据方法名和参数列表获取Method方法对象:Method m = cls.getDeclaredMethod(方法名,XX.class);
  2. 获取对象:Object o = cls.newInstance();
  3. 爆破
  4. 访问:m.invoke(o,实参列表);
  5. 注意:如果是静态方法,则invoke的参数o,可以写成null

数类型数组

5、Constructor类相关方法

  1. setAccessible:爆破(形参填入true,即可访问私有构造方法、变量、方法等)
  2. newInstance(Object…obj):调用构造器

6、通过反射访问类中的成员

  1. 获取Field对象
  2. 爆破
  3. 访问
    • field.set(o,值); //o表示对象,field表示Field对象
    • field.get(o); //o表示对象,field表示Field对象
  4. 注意:如果是静态属性,则set和get中的参数o,可以写成null

7、通过反射访问类中的成员

  1. 根据方法名和参数列表获取Method方法对象:Method m = cls.getDeclaredMethod(方法名,XX.class);
  2. 获取对象:Object o = cls.newInstance();
  3. 爆破
  4. 访问:m.invoke(o,实参列表);
  5. 注意:如果是静态方法,则invoke的参数o,可以写成null
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值