Java反射:反射机制 Class类 类加载 反射获取类结构 反射创建类 反射暴破...

目录

一,反射机制

1.反射机制概念

2.反射机制原理

3..反射机制功能

 4..反射机制相关的类

二,Class类

1.Class类的特点

2. 拥有Class对象的类型

3.获取Class对象的方式

 三,类加载

1.静态加载和动态加载

2.类加载的各个阶段

 (1)加载

(2)连接阶段-验证

(3)连接阶段-准备

(4)连接阶段-解析

(5)初始化

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

1.Class类

2.Field类

3.Method类

4.Constructor类

五,通过反射创建类的实例

六,通过反射调用属性及方法

1.通过反射调用属性

2.通过反射调用方法

七,反射的优化


一,反射机制

1.反射机制概念

        在运行状态中,对于任意一个类,能够知道其所有结构信息(如:成员属性,方法,构造器等),对于任意一个对象,都能够调用其任一个方法及属性。这种动态获取信息以及动态调用对象的方法的功能称为Java语言的反射机制。

2.反射机制原理

        首先说一下Java程序在计算机中的三个阶段:第一个阶段是编译阶段:这个阶段.java文件被javac编译成.class字节码文件,第二个阶段是类加载阶段:这个阶段.class文件中的信息(成员方法,成员属性,构造器)被类加载器加载为Class类对象放到堆中,第三个阶段是运行阶段:这个阶段便是我们可以操作的阶段,可以创建对象并调用对象方法。

        而反射机制便是在运行阶段,通过Class类对象,获取其结构信息,创建相应对象,调用方法,变量,构造器。

3..反射机制功能

Java反射机制可以完成:

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

 4..反射机制相关的类

        上面说过,在Java的反射机制中,成员属性,方法,构造器等均可被视为对象,在调用时通过相关对象进行调用。反射机制中相关的类有:

类名解释
Class代表一个类(类名就叫Class),Class对象表示某个类加载后在堆中的对象
Method代表类的方法,Method对象表示某个类的方法
Field代表类的成员属性,Field对象表示某个类的成员属性
Constructor代表类的构造方法,Constructor对象表示构造器

二,Class类

        Class类是Java反射机制中一个非常重要的类,获取类结构信息,实例化对象,调用方法等都要用过Class类来完成。

1.Class类的特点

  1. Class也是类,继承Object类;
  2. Class类对象不是new出来的,而是系统创建的,也就是上文说的在类加载过程由类加载器创建,Class类对象存放在堆中;
  3. 对于某个类的Class类对象,在内存中只有一份,因为类只加载一次,因此对于属于同一个类的两个对象,它们的Class类对象是相同的;
  4. 通过Class对象可以完整地得到一个类的完整结构,通过一系列的api;
  5. 类的.class字节码文件的二进制数据是放在方法区的,因为二进制数据不便于操作,便有了在堆中存放的Class类对象;

2. 拥有Class对象的类型

  1. 外部类,成员内部类,静态内部类,局部内部类,匿名内部类
  2. interface:接口
  3. 数组
  4. enum:枚举
  5. annotation:注解
  6. 基本数据类型(基本数据类型的Class类对象与包装器类是同一个)
  7. void

3.获取Class对象的方式

1.通过Class的静态方法forName:

String classPath = "Text_Reflection.Cat";
Class cls1 = Class.forName(classPath);
System.out.println(cls1);

2.通过类名.class,多用于参数传递:

Class cls2 = Cat.class;
System.out.println(cls2);

 3.有对象实例时,通过getClass获取:

Cat cat = new Cat();
Class cls3 = cat.getClass();
System.out.println(cls3);

 4.通过类加载器获取:

String className = "Text_Reflection.Cat";
ClassLoader classLoader = cat.getClass().getClassLoader();
Class cls4 = classLoader.loadClass(className);
System.out.println(cls4);

 三,类加载

1.静态加载和动态加载

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

静态加载:编译时加载相关的类,如果没有则报错,依赖性太强,因为有些类在运行时并不会被用到。

动态加载:运行时加载需要的类,如果运行时不用该类,即使不存在该类,也不会报错,降低了依赖性。

类静态加载的时机有:创建对象(new),当子类加载父类也加载时,调用类中的静态成员时。

类动态加载时机:通过反射

2.类加载的各个阶段

 (1)加载

        在这个阶段,计算机会将类的.class文件读入内存,并为之创建一个Class类对象,此过程由类加载器完成。

(2)连接阶段-验证

        确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全,包括:文件格式验证(是否以魔数 oxcafebabe开头),元数据验证,字节码验证和符号引用验证。

(3)连接阶段-准备

        JVM会在该阶段对静态变量分配内存并默认初始化(对应数据类型的默认初始值,如0, 0L, null, false等)。这些变量所使用的内存都将在方法区中进行分配。(static final类型的变量会直接初始化为设置的值,因为static final类型的变量只会初始化一次)。

(4)连接阶段-解析

        JVM将常量池内的符号引用替换为直接引用的过程。

(5)初始化

        到初始化阶段,才真正开始执行类中定义的Java程序代码,此阶段执行<clinit>()方法,<clinit>()方法是由编辑器按语句在源文件中出现的顺序,依次自动收集类中所有静态变量的赋值动作和静态代码块中的语句,并进行合并。虚拟机保证一个类的<clinit>()方法在多线程环境中被正确地加锁,同步。

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

1.Class类

(1)getName:获取全类名

//getName 获取全类名
System.out.println(cls.getName());

(2)getSimpleName:获取简单类名

//getsimpleName 获取简单类名 
System.out.println(cls.getSimpleName());

(3)getFields:获取所有public修饰的成员属性,包含本类及父类的

//getFields 获取所有public属性,包括父类的
Field[] fields = cls.getFields();
for (Field field : fields) {
     System.out.println("本类及父类属性:" + field.getName());
}

(4)getDeclaredFields:获取本类中所有的属性,不包括父类

//getDeclaredFields:获取本类所有属性 没有父类的
Field[] fields1 = cls.getDeclaredFields();
for (Field field : fields1) {
    System.out.println("所有属性:" + field.getName());
}

(5)getMethods:获取所有public修饰的方法,包含本类及父类的

//getMethods 获取所有public方法
Method[] methods = cls.getMethods();
for (Method method : methods) {
     System.out.println("public方法:" + method.getName());
}

(6)getDeclaredMethods:获取本类中所有的方法,不包括父类

//getDeclaredMethods 获取所有方法 自己的,不包括父类和超类
Method[] methods1 = cls.getDeclaredMethods();
for (Method method : methods1) {
    System.out.println("all方法" + method.getName());
}

(7)getConstructors:获取所有public修饰的构造器,不包括父类,因为子类不会继承父类的构造器

//getConstructors 获取所有public的构造器 不能拿到父类的,因为子类不会继承父类的构造器
Constructor[] constructors = cls.getConstructors();
for (Constructor constructor : constructors) {
    System.out.println("public构造器:" + constructor.getName());
}

(8)getDeclaredConstructors:获取本类中所有构造器,不包括父类

//getDeclaredConstructor 获取所有构造器
Constructor[] constructors1 = cls.getDeclaredConstructors();
for (Constructor constructor : constructors1) {
    System.out.println("所有构造器" + constructor.getName());
}

(9)getPackage:以Package形式返回包信息

//getPackage 获取包名
System.out.println(cls.getPackage().getName());

(10)getSuperClass:获取父类的Class对象

//getSuperClass 获取父类的Class对象
System.out.println(cls.getSuperclass().getName());

(11)getInterfaces:以Class[]形式返回接口信息

//getInterfaces 以Class[]形式返回接口信息
Class[] classInterfaces = cls.getInterfaces();
for (Class classInterface : classInterfaces) {
    System.out.println(classInterface);
}

(12)getAnnotations:以Annotation[]形式返回注解信息

//getAnnotations 以Annotation[]形式返回注解信息
Annotation[] Annotations =  cls.getAnnotations();

2.Field类

(1)getModfiers:以int形式返回修饰符(默认修饰符default是0, public是1, private是2,protected是4, static是8, final是16,若是多个修饰符则相加,例如public static返回 1 + 8 = 9)。

(2)getType:以Class形式返回类型

(3)getName:返回类型名

public static void api2() throws ClassNotFoundException{
        Class cls = Class.forName("Text_Reflection.Son");
        Field[] declaredFields = cls.getDeclaredFields();
        for (Field field : declaredFields) {
            System.out.println("属性:" + field.getName() + "的修饰符类型为" + field.getModifiers() + " 该属性的数据类型为" + field.getType());
        }
    }

3.Method类

(1)getModifiers:以int形式返回修饰符

(2)getReturnType:以Class形式获取方法返回类型

(3)getName:返回方法名

(4)getParameterTypes:以Class[]形式返回参数类型数组

public static void api2() throws ClassNotFoundException{
        Method[] declaredMethods = cls.getDeclaredMethods();
        for (Method method : declaredMethods) {
            System.out.println("方法" + method.getName() +"的返回类型为" + method.getReturnType());
            Class[] parameterTypes = method.getParameterTypes();
            for (Class parameter : parameterTypes) {
                System.out.println("方法" + method.getName() + "参数类型为" + parameter + " " + parameter.hashCode());
            }
        }
    }

4.Constructor类

(1)getModifiers:以int形式返回修饰符

(2)getName:返回构造器名(全类名)

(3)getParaMeterTypes:以Class[]返回参数类型数组

public static void api2() throws ClassNotFoundException{
        Constructor[] constructors = cls.getDeclaredConstructors();
        for (Constructor constructor : constructors) {
            Class[] constructorType = constructor.getParameterTypes();
            for (Class aClass : constructorType) {
                System.out.println("构造器" + constructor.getName() + "参数类型为" + aClass);
            }
        }
    }

五,通过反射创建类的实例

假设如下一个user类,有三种构造器:

class User{
    public User(){}
    public User(String name){}
    private User(String name, int num){}
}

 对于三种构造器,创建实例的方法为:

1.public无参构造:newInstance()

//1.public无参构造
Class userClass = Class.forName("Text_Reflection.User");
Object user1 = userClass.newInstance();

2.public有参构造:通过构造器Constructor,需要传入参数的Class对象

//2.public有参构造
Class userClass = Class.forName("Text_Reflection.User");
Constructor userConstructor = userClass.getConstructor(String.class);
userConstructor.newInstance("吴彦祖");

3.private有参构造:通过构造器Constructor及反射暴破,直接使用private的构造器会报错,需要使用setAccessible将安全检查取消,使得可以访问private构造器,这便是反射暴破

//3.private有参构造
Class userClass = Class.forName("Text_Reflection.User");
Constructor userConstructor1 = userClass.getDeclaredConstructor(String.class, int.class);
userConstructor1.setAccessible(true); //暴破(暴力破解),取消安全检查,使得可以访问private构造器,破环了封装性
Object user2 = userConstructor1.newInstance("吴彦祖" , 114514);
System.out.println(user2);

六,通过反射调用属性及方法

假设如下一个user类:

class User{
    private int num;
    private static String name;
    private int fun(int num){
        return 2333;
    }
}

1.通过反射调用属性

        对于成员属性,我们可以用set方法设置属性值,get方法获取属性值,这两种方法都由属性Class对象调用,调用时需传入对应类的对象,调用私有属性需要用到反射暴破:

Class userClass = Class.forName("Text_Reflection.User");
Object user1 = userClass.newInstance();

Field num = userClass.getDeclaredField("num");
num.setAccessible(true);  //调用私有属性,反射暴破
num.set(user1, 114514); //更改num的值
System.out.println(num.get(user1)); //返回num的值

        对于静态成员属性,因为静态属性属于类而不属于对象,所以调用set,get方法时可以传入任意对象:

Class userClass = Class.forName("Text_Reflection.User");
Object user1 = userClass.newInstance();

Field name = userClass.getDeclaredField("name");
name.setAccessible(true);
name.set(null, "jjb"); //静态属性与对象无关,任何对象都可访问
System.out.println(name.get(null));

2.通过反射调用方法

        对于成员方法,使用invoke方法调用,同样是由方法的Class对象调用,调用时需要传入对应类的对象:

Class userClass = Class.forName("Text_Reflection.User");
Object user = userClass.newInstance();

Method fun = userClass.getDeclaredMethod("fun", int.class);
fun.setAccessible(true);
Object returnValue = fun.invoke(user,1000);
System.out.println(returnValue);

七,反射的优化

        反射基本是解释执行,所以对执行速度有很大影响,例如下面分别使用传统方式和反射方式调用同一方法1e8次:

public class testReflectionTime {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
        normalFun();
        reflectionFun();
        reflectionOptimizeFun();
    }

    public static void normalFun(){
        Cat cat = new Cat();
        long start = System.currentTimeMillis();
        for(int i = 1; i <= 1000000000; i++){
            cat.cry();
        }
        long end = System.currentTimeMillis();
        System.out.println("传统方法耗时:" + (end - start) + "秒;");
    }

    public static void reflectionFun() throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Class cls = Class.forName("Text_Reflection.Cat");
        Object object = cls.newInstance();
        Method method = cls.getMethod("cry");
        long start = System.currentTimeMillis();
        for(int i = 1; i <= 1000000000; i++){
            method.invoke(object);
        }
        long end = System.currentTimeMillis();
        System.out.println("反射方法耗时:" + (end - start) + "秒;");
    }
}

 可以看到反射方法耗时远远多于传统方法:

         我们可以通过关闭访问检查的方法来对反射进行优化,其实就是前面的反射暴破set-Accessible,setAccessible作用是启动和禁用访问安全检查的开关,参数为true表示反射的对象在使用时取消访问检查,提高反射效率,参数值为false则表示反射的对象执行访问检查。

public static void reflectionFun() throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Class cls = Class.forName("Text_Reflection.Cat");
        Object object = cls.newInstance();
        Method method = cls.getMethod("cry");
        method.setAccessible(true); //在反射调用方法时,取消访问检查
        long start = System.currentTimeMillis();
        for(int i = 1; i <= 1000000000; i++){
            method.invoke(object);
        }
        long end = System.currentTimeMillis();
        System.out.println("反射优化方法耗时:" + (end - start) + "秒;");
    }

 优化效果如下,可以看到有效果,但不多:

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值