ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Java 反射

2022-02-04 10:33:35  阅读:190  来源: 互联网

标签:反射 Java 对象 clazz 获取 时类 Class 运行


参考:

《尚硅谷Java入门视频教程》https://www.bilibili.com/video/BV1Kb411W75N

《【狂神说Java】Java零基础学习视频通俗易懂》https://www.bilibili.com/video/BV12J41137hu

推荐看尚硅谷的视频,更加详细

动态/静态语言

动态语言是在运行时能动态改变结构的语言,例如PHP,所以可以用一句话木马执行很多命令。

静态语言是在运行时不能动态改变结构的语言,如Java、C等。

反射机制能让静态语言Java获得一些动态特性,变为准动态语言。

反射是什么

Reflection(反射)是Java被视为动态语言的关键,反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。

加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,形象地称之为:反射。

反射,要注意这个“反”字,例如下图:

image-20220202125519124

优缺点

优点:可以实现动态创建对象和编译,体现出很大的灵活性

缺点:对性能有影响。使用反射基本上是一种解释操作,我们告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于直接执行相同的操作。

下面是一个测试性能的例子:

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

// 分析性能问题
public class Test10 {
    // 普通方式调用
    public static void test01() {
        User user = new User();

        long startTime = System.currentTimeMillis();

        for (int i = 0; i < 1000000000; i++) {
            user.getName();
        }

        long endTime = System.currentTimeMillis();

        System.out.println("普通方式执行10亿次:" + (endTime - startTime) + "ms");
    }

    // 反射方式调用
    public static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user = new User();
        Class c1 = user.getClass();

        Method getName = c1.getDeclaredMethod("getName", null);

        long startTime = System.currentTimeMillis();

        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user, null);
        }

        long endTime = System.currentTimeMillis();

        System.out.println("反射方式执行10亿次:" + (endTime - startTime) + "ms");
    }

    // 反射方式调用,关闭检测
    public static void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user = new User();
        Class c1 = user.getClass();

        Method getName = c1.getDeclaredMethod("getName", null);
        getName.setAccessible(true);

        long startTime = System.currentTimeMillis();

        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user, null);
        }

        long endTime = System.currentTimeMillis();

        System.out.println("关闭检测执行10亿次:" + (endTime - startTime) + "ms");
    }

    public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
        test01();
        test02();
        test03();
    }
}

执行结果:

image-20220202131004973

Class对象

要学会反射,首先要学习一下Class对象,要学习Class对象,首先要简单复习一下类的加载过程。

一个Class对象就是java.lang.Class类的一个实例。

.java文件(源代码)经过javac.exe编译后生成一个或多个.class文件(字节码文件),使用java.exe对某个字节码文件进行解释运行,相当于将某个字节码文件加载到内存中,这就是类的加载。这个加载到内存中的类,就是java.lang.Class的一个实例,也称为运行时类。

上面说过,这个java.lang.Class对象包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。通过这个对象,可以获得其对应类的属性、方法、构造器、实现的接口等。那我们怎么获取这个对象呢?有三种方式可以获取一个类的Class对象:

  1. 调用运行时类的class属性:Class clazz1 = User.class;

  2. 通过运行时类的对象,调用getClass():

    User u1 = new User();
    Class clazz2 = u1.getClass();
    
  3. 调用Class的静态方法:forName(String classPath) Class clazz3 = Class.forName("com.a.reflection.User");。forName方法既可以获取我们自己写的类的Class对象,也可以获取系统类的Class对象。

上面三种方式如果获取的是同一个类的Class对象,则它们获取的是同一个对象,也就是 clazz1 == clazz2 == clazz3,因为一个类同一时间只会在内存中生成一个Class对象。加载到内存中的运行时类,会缓存一定的时间,在此时间之内,我们可以通过不同的方式来获取此运行时类。

除了类,还可以有Class对象的类型有:interface(接口)、数组(对于数组来说,只要数组的元素类型与维度一样,就是同一个Class)、enum(枚举)、annotation(注解)、基本数据类型、void(void也可以看作一种类型)。

下面是Class对象的一些注意点:

  • Class对象只能由系统建立
  • 一个加载的类在JVM中只会有一个Class实例
  • 一个Class对象对应的是一个加载到JVM中的.class文件
  • 每个类的实例都会记得自己是由哪个Class实例所生成
  • 通过Class对象可以完整地得到一个类中的所有被加载的结构
  • Class类是Reflection的根源,针对任何想动态加载、运行的类,唯有先获得相应的Class对象。

java.lang.Class

类的加载

下面简单讲一下类的加载过程,类的加载分为3个过程(加载、链接、初始化):

  1. 加载:使用java.exe将字节码文件内容加载到内存中,并生成一个代表这个类的java.lang.Class对象。
  2. 链接:为类变量(static)分配内存并设置默认初始值。
  3. 初始化:使用类构造器<clinit>()方法对类进行初始化,顺序是:静态变量显式赋值/静态代码块 => 匿名代码块 => 构造方法。初始化一个类时,如果发现其父类还没有被初始化,则会先初始化它的父类。

反射的实际应用

创建运行时类的对象

newInstance(): 创建对应的运行时类的对象

Class<Person> clazz = Person.class;
Person person = clazz.newInstance();

只有通过构造器才能构造对象,newInstance()内部也是调用了对应类的无参构造器。

newInstance()能正常地创建运行时类的对象的条件:

  1. 运行时类必须提供空参的构造器。
  2. 运行时类的空参构造器可被访问,通常设置为public

获取运行时类的完整结构

包括获取运行时类的所有方法、构造器、父类、接口、所在包、注解等,都是用形如getXxx()的方法来实现的。

Class clazz = Person.class;

// getFields(): 获取当前类及其父类的所有public属性
Field[] fields = clazz.getFields();
// getDeclaredFields(): 获取当前运行时类声明的所有属性,包括所有权限(不包含父类中声明的属性)。
Field[] declaredFields = clazz.getDeclaredFields();

// getMethods(): 获取当前运行时类及其父类声明的所有public方法。
Method[] methods = clazz.getMethods();
// getDeclaredMethods(): 获取当前运行时类声明的所有方法,包括所有权限(不包含父类中声明的方法)。
Method[] declaredMethods = clazz.getDeclaredMethods();

// getConstructors(): 获取当前运行时类中声明为public的构造器
Method[] methods = clazz.getConstructors();
// getDeclaredConstructors(): 获取当前运行时类中声明的所有构造器
Method[] declaredMethods = clazz.getDeclaredConstructors();

// getSuperclass(): 获取运行时类的父类
Class superclass = clazz.getSuperclass();

Class[] interfaces = clazz.getInterfaces();
// 要获取父类的接口,可以先获取父类,再获取接口
Class[] interfaces1 = clazz.getSuperclass().getInterfaces();

// 获取当前运行时类所在的包
Package pack = clazz.getPackage();

// 获取运行时类声明的注解
Annotation[] annotations = clazz.getAnnotations();

调用运行时类的指定结构

操作运行时类中的指定属性

Class clazz = Person.class;

// 创建运行时类的对象
Person p = (Person) clazz.newInstance();

// 获取指定的public属性
Field id = clazz.getField("id");
// getDeclaredField(String fieldName): 获取运行时类中指定的属性
Field name = clazz.getDeclaredField("name");
// 使当前属性可访问
name.setAccessible(true);
/*
设置当前属性的值
set(): 参数1:指明设置哪个对象的属性  参数2:将此属性值设置为多少
*/
id.set(p, 1001);
// 设置静态属性的值
id.set(null, 1001);
/*
获取当前属性的值
get(): 参数1:获取哪个对象的当前属性值
*/
int pId = (int) id.get(p);
// 获取静态属性的值
int pId2 = (int) id.get(null);

操作运行时类中的指定方法

Class clazz = Person.class;

// 创建运行时类的对象
Person p = (Person) clazz.newInstance();

/*
1. 获取指定的某个方法
getDeclaredMethod(): 参数1:指明获取的方法的名称  参数2:指明获取的方法的形参列表
*/
Method show = clazz.getDeclaredMethod("show", String.class);
// 2. 保证当前方法是可访问的
show.setAccessible(true);
/*
3. invoke(): 参数1:方法的调用者  参数2:给方法形参赋值的实参
invoke()的返回值即为对应类中调用的方法的返回值。
*/
Object returnValue = show.invoke(p, "CHN");

// 调用静态方法
// private static void showDesc()
Method showDesc = clazz.getDeclaredMethod("showDesc");
showDesc.setAccessible(true);
// 如果调用的运行时类中的方法没有返回值,则此invoke()返回null
Object returnVal = showDesc.invoke(null);

调用运行时类中的指定构造器

Class clazz = Person.class;

// private Person(String name)
/*
1. 获取指定的构造器
getDeclaredConstructor(): 参数:指明构造器的参数列表
*/
Constructor constructor = clazz.getDeclaredConstructor(String.class);

// 2. 保证此构造器是可访问的
constructor.setAccessible(true);

// 3. 调用此构造器创建运行时类的对象
Person per = (Person) constructor.newInstance("Tom");

标签:反射,Java,对象,clazz,获取,时类,Class,运行
来源: https://www.cnblogs.com/rnss/p/15862546.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有