编程技术是改变世界的力量。
本站
当前位置:网站首页 > 后端语言 > 正文

一篇文章全面了解Java反射机制 java反射机制的原理和用途

gowuye 2024-04-04 11:57 9 浏览 0 评论

Java的反射机制在实践中可谓无处不在,如果你已经工作几年,还对Java的反射机制一知半解,那么这篇文章绝对值得你读一读。

什么是反射

反射 (Reflection) 是Java的特征之一,它允许运行中的Java程序获取自身的信息,并且可以操作类或对象的内部属性。

通俗的来讲就是:通过反射机制,可以在运行时获得程序或程序集中每一个类型的成员和成员的信息。

注意这里的重点是:运行时,而不是编译时。我们常规情况下写的对象类型都是在编译期就确定下来的。而Java反射机制可以动态地创建对象并调用其属性,这样创建对象的方式便异常灵活了。

虽然通过反射可以动态的创建对象,增加了灵活性,但也不是什么地方都可用,还要考虑性能、编码量、安全、面向对象性等。

我们知道Java是面向对象的,如果通过反射机制去操作对象里面的属性或方法,一定程度上破坏了面向对象的特性。同时,通过反射机制还可以修改私有变量,也存在一定的安全性问题。

但这并不影响反射在实践中的应用,几乎各大框架多多少少都在使用Java反射机制。特别是主流的Spring框架。

功能及用途

Java反射主要提供以下功能:

  • 在运行时判断任意一个对象所属的类;
  • 在运行时构造任意一个类的对象;
  • 在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法);
  • 在运行时调用任意一个对象的方法

反射最重要的用途之一就是开发各类通用框架。以Spring为例,当基于XML进行配置Bean时,我们通常写如下代码:

<bean class="com.choupangxia.UserServiceImpl">
</bean>

Spring在启动的时候便会利用反射机制去加载对应的UserServiceImpl类,然后进行实例化。如果不存在该类则会抛出异常,通常异常中还会出现invoke方法调用的堆栈信息。

当Spring基于注解去实例化对象时,同样利用的是反射机制。下面通过一个简单demo示例,演示一下如何通过反射获得注解信息:

static void initUser(User user) throws IllegalAccessException {

    // 获取User类中所有的属性(getFields无法获得private属性)
    Field[] fields = User.class.getDeclaredFields();

    // 遍历所有属性
    for (Field field : fields) {
        // 如果属性上有此注解,则进行赋值操作
        if (field.isAnnotationPresent(InitSex.class)) {
            InitSex init = field.getAnnotation(InitSex.class);
            field.setAccessible(true);
            // 设置属性的性别值
            field.set(user, init.sex().toString());
            System.out.println("完成属性值的修改,修改值为:" + init.sex().toString());
        }
    }
}

上述代码是之前写《一篇文章,全面了解Java自定义注解》中的示例,相关文章可关注公众号“程序新视界”,回复“注解”获得全文。

更多关于Java反射的例子我们就不多说了。上面的示例现在看不懂也没关系,下面我们就来详细介绍一下Java反射机制的具体使用。

简单示例

我们通过一个简单的示例对比,来了解一下Java反射机制。首先来看正常情况下创建对象并使用对象的示例:

User user = new User();
user.setUsername("公众号:程序新视界");
user.setAge(3);

那么,当基于反射机制来达到统一效果该怎么做呢?看下面的具体实现:

@Test
public void testCreateReflect() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException,
        InvocationTargetException, InstantiationException {
    // 获取User所对应的Class对象
    Class clz = Class.forName("com.choupangxia.reflect.User");
    // 获取User类无参数的构造器
    Constructor constructor = clz.getConstructor();
    // 通过构造器创建User对象
    User user = (User) constructor.newInstance();
    user.setUsername("公众号:程序新视界");
    user.setAge(3);
    System.out.println("username=" + user.getUsername());
    System.out.println("age=" + user.getAge());
}

在上述过程中通过Class.forName获得User所对应的Class对象,获得构造器Constructor,通过构造器创建出来一个User对象,然后调用对应的方法。

当然,后面的步骤中也可以完全不出现User类,直接通过Class对象获得对应的Method进行调用。示例如下:

Method setUsernameMethod = clz.getMethod("setUsername", String.class);
Method setAgeMethod = clz.getMethod("setAge", int.class);

setUsernameMethod.invoke(obj,"公众号:程序新视界");
setAgeMethod.invoke(obj,3);

关于get方法也是如此操作,就不再赘述。

经过上面的实例我们已经能够正常创建对象,并使用对象了。下面就看看反射常用的API,通过这些API我们可以实现更多的更复杂的功能。

反射常用API

获取Class对象的三种方法

第一种方法:当你知道类的全路径名时,可使用Class.forName静态方法来获得Class对象。上面的示例就是通过这种方法获得:

Class clz = Class.forName("com.choupangxia.reflect.User");

第二种方法:通过“.class”获得。前提条件是在编译前就能够拿到对应的类。

Class clz = User.class;

第三种:使用类对象的getClass()方法。

User user = new User();
Class clz = user.getClass();

创建对象的两种方法

可以通过Class对象的newInstance()方法和通过Constructor 对象的newInstance()方法创建类的实例对象。

第一种:通过Class对象的newInstance()方法。

Class clz = User.class;
User user = (User) clz.newInstance();

第二种:通过Constructor对象的newInstance()方法。

Class clz = Class.forName("com.choupangxia.reflect.User");
Constructor constructor = clz.getConstructor();
User user = (User) constructor.newInstance();

其中第二种方法创建类对象可以选择特定构造方法,而通过 Class对象则只能使用默认的无参数构造方法。

Class clz = User.class;
Constructor constructor = clz.getConstructor(String.class);
User user = (User) constructor.newInstance("公众号:程序新视界");

获取类属性、方法、构造器

通过Class对象的getFields()方法获取非私有属性。

Field[] fields = clz.getFields();
for(Field field : fields){
    System.out.println(field.getName());
}

上述实例中的User对象属性都是private,无法直接通过上述方法获取,可将其中一个属性改为public,即可获取。

通过Class对象的getDeclaredFields()方法获取所有属性。

Field[] fields = clz.getDeclaredFields();
for(Field field : fields){
    System.out.println(field.getName());
}

执行打印结果:

username
age

当然针对属性的其他值也是可以获取的,针对私有属性的修改需要先调用field.setAccessible(true)方法,然后再进行赋值。关于具体应用,回头看我们最开始关于注解的实例中的使用。

获取方法的示例如下:

Method[] methods = clz.getMethods();
for(Method method : methods){
    System.out.println(method.getName());
}

打印结果:

setUsername
setAge
getUsername
getAge
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll

可以看到,不仅获取到了当前类的方法,还获取到了该类父类Object类中定义的方法。

关于获取构造器的方法上面已经讲到了,就不再赘述。而上述的这些方法在Class中都有相应的重载的方法,可根据具体情况进行灵活使用。

利用反射创建数组

最后,我们再看一个通过反射创建数组的实例。

@Test
public void createArray() throws ClassNotFoundException {
    Class<?> cls = Class.forName("java.lang.String");
    Object array = Array.newInstance(cls,5);
    // 向数组添加内容
    Array.set(array,0,"Hello");
    Array.set(array,1,"公众号");
    Array.set(array,2,"程序新视界");
    Array.set(array,3,"二师兄");
    Array.set(array,4,"Java");
    // 获取数组中指定位置的内容
    System.out.println(Array.get(array,2));
}

小结

想必经过上述的学习,对Java反射机制有了更进一步的了解,在最开始我们已经说了反射机制也是有不足的。因此,如果可能尽量使用正统的写法,但如果你在开发通用框架,则可考虑使用。

后面,有机会我们再讲解反射机制的源码,别忘记关注公众号:程序新视界。

本文首发来自微信公众号:程序新视界。一个软实力、硬技术同步学习的平台。

相关推荐

嵌入式C语言中常量的应用实例(嵌入式c语言中常量的应用实例是什么)

常量,我们都知道,就是数值保持不变的量。在C语言中,常量一旦初始化了,它的值将在整个程序运行周期内,不允许发生任何变化。常量与变量是相对的,我们实际项目中经常会用到它。定义常量的两种方式C语言中主要有...

C语言编程基础知识汇总学习,适合初学者!更新常量知识

(二)整型常量整型常量有3种形式:十进制整型常量、八进制整型常量和十六进制整型常量。(注意:c语言中没有直接表示二进制的整型常量,在c语言源程序中不会出现二进制。)书写方式如下:十进制整型常量:123...

【C语言】第二章第六节:字符串常量

第二章第六节:字符串常量。下表C语言中的常用转义字符。·字符形式功能:ASCIl码(十进制形式)。→\t水平制表(横向跳格:跳到下一个tab位置)。→\b退格8。→\r回车(不换行,光标移到本行行首)...

「GCTT 出品」Go 系列教程——5. 常量

这是我们Golang系列教程的第五篇。定义在Go语言中,术语”常量”用于表示固定的值。比如5、-89、IloveGo、67.89等等。看看下面的代码:varaint=50v...

每日C语言-常量指针、指针常量、指向常量的指针常量

一、常量指针1)什么是常量指针?通过该指针不可以修改其所指向存储单元中的值指针本身即地址可以被修改2)定义:类型说明符const*指针变量;类型说明符表示指针所指向存储单元中的值得数据类型指针...

C语言-符号常量、常变量、变量之我见

更新内容:新增音频。音频和文章一起更配oHello,大家好,又和大家见面了~~相信很多朋友们听了C语言的“符号常量”、“常变量”、“变量”后还是对这三者一脸懵逼吧。不管老师怎么歇斯底里地讲解,同学们迷...

零基础带你学习C语言:四:探索常量与变量

前言常量与变量学习;一:分析:short、float、long类型#include<stdio.h>intmain(){shortage=18;floatweight=12...

C语言中是如何定义常量的?那定义字符串呢?

常量有整型常量、浮点型常量、字符型常量及字符串常量。‘常量定义是指定义符号常量,用一个标识符来代表一个常量,通过宏定义预处理指令来实现。常量的定义:#definecount60这就定义了一个常量...

C语言符号常量的优点,会是那几点?

符号常量是一个常量,是不变量,所以,在编译的时候,就把符号常量出现的地方,替换为符号常量对应的常量。符号常量一般用户定义一个全局使用的数据,而且要改变该数据的时候,只需要改变符号常量的值,代码中引用符...

嵌入式开发- C语言数据类型-常量(c语言嵌入式是干嘛的)

基本数据类型的常量-掌握**整型常量:**常量是指在程序运行期间其数值不发生变化的数据。整型常量通常简称为整数整数可以是十进制数、八进制数、十六进制数八进制06334十六进制0xd1...

c语言解剖课:只读变量、常量、字面量傻傻分不清?

写在前面本篇主题的缘起,是因为一个计算机专业的大学生在和我讨论c语言问题时,说const常量如何如何,我说变量被const修饰了,还是变量,不是“常量”。他给了我一个截图:他说大模型都是这样回答的,变...

C/C++编程笔记:C数组、字符串常量和指针!三分钟弄懂它

想弄懂C语言中数组和指针的关系吗?这篇文章就占据你三分钟时间,看完你肯定会有收获!数组数组声明为数据类型名称[constant-size],并将一个数据类型的一个或多个实例分组到一个可寻址的位...

C语言入门到精通【第008讲】——C语言常量

C语言常量常量是固定值,在程序执行期间不会改变。这些固定的值,又叫做字面量。常量可以是任何的基本数据类型,比如整数常量、浮点常量、字符常量,或字符串字面值,也有枚举常量。常量就像是常规的变量,只不过常...

这是C语言无法修改得东西,C语言基础教程之常量解析

常量是指程序在执行期间不会改变的固定值。这些固定值也称为文字。常量可以是任何基本数据类型,如整数常量,浮点常量,字符常量或字符串文字,还有枚举常量。常量被视为常规变量,除了它们的值在定义后无法修改。整...

C语言中的单精度、双精度、常量等都有什么意思?

刚接触C语言时,对于常量,变量,浮点,单精度,双精度等问题的理解,大都很模糊不清,其实在程序运行过程中,其值不能改变的量称为常量。如12、0、-3为整型常量,4.6、-1.23为实型常量,'a'、'...

取消回复欢迎 发表评论: