👳我亲爱的各位大佬们好😘😘😘
♨️本篇文章记录的为 java反射初入门 相关内容,适合在学Java的小白,帮助新手快速上手,也适合复习中,面试中的大佬🙉🙉🙉。
♨️如果文章有什么需要改进的地方还请大佬不吝赐教❤️🧡💛
👨🔧 个人主页 : 阿千弟
🔥 上期内容👉👉👉 : AOP的另类用法 (权限校验&&自定义注解)
反射被应用于许多方面, spring的注解, jdbc的连接都是基于反射来实现的, 可能在工作中我们很少能用到反射, 但是在面试的过程中面试官经常会问道, 我们可以不用反射, 但作为一个程序猿, 还是应该了解了解的😎😎😎
今天来一起入门了解魔法反射吧👉👉👉
@[toc]
什么是java反射
在 java 的面向对象编程过程中,通常我们需要先知道一个 Class 类,然后 new 类名() 方式来获取该类的对象。也就是说我们需要在写代码的时候(编译期或者编译期之前)就知道我们要实例化哪一个类,运行哪一个方法,这种通常被称为静态的类加载。
但是在有些场景下,我们事先是不知道我们的代码的具体行为的。比如,我们定义一个服务任务工作流,每一个服务任务都是对应的一个类的一个方法。
- 服务任务 B 执行哪一个类的哪一个方法,是由服务任务 A 的执行结果决定的
- 服务任务 C 执行哪一个类的哪一个方法,是由服务任务 A 和 B 的执行结果决定的
- 并且用户不希望服务任务的功能在代码中写死,希望通过配置的方式执行不同的程序
面对这个情况,我们就不能用代码 new 类名() 来实现了,因为你不知道用户具体要怎么做配置,这一秒他希望服务任务 A 执行 Xxxx 类的 x 方法,下一秒他可能希望执行 Yyyy 类的 y 方法。当然你也可以说提需求嘛,用户改一次需求,我改一次代码。这种方式也能需求,但对于用户和程序员个人而言都是痛苦,那么有没有一种方法在运行期动态的改变程序的调用行为的方法呢?这就是要为大家介绍的 “java 反射机制”。
那么 java 的反射机制能够做那些事呢?大概是这样几种:
在程序运行期动态的根据 package名.类名实例化类对象
在程序运行期动态获取类对象的信息,包括对象的成本变量和方法
在程序运行期动态使用对象的成员变量属性
在程序运行期动态调用对象的方法(私有方法也可以调用)
反射基础
反射就是把java类中的各种成分映射成一个个的Java对象
例如:一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把个个组成部分映射成一个个对象。
这里我们首先需要理解 Class类,以及类的加载机制; 然后基于此我们如何通过反射获取Class类以及类中的成员变量、方法、构造方法等。
Class类
Class类,Class类也是一个实实在在的类,存在于JDK的java.lang包中。Class类的实例 表示 java 应用运行时的类(class and enum)或接口(interface and annotation)(每个java类运行时都在JVM里表现为一个class对象,可通过类名.class、类型.getClass()、Class.forName("类名")等方法获取class对象)。数组同样也被映射为为class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。基本类型boolean,byte,char,short,int,long,float,double 和 关键字 void 同样表现为 class 对象。
public final class Class<T> implements java.io.Serializable,
GenericDeclaration,
Type,
AnnotatedElement {
private static final int ANNOTATION= 0x00002000;
private static final int ENUM = 0x00004000;
private static final int SYNTHETIC = 0x00001000;
private static native void registerNatives();
static {
registerNatives();
}
/*
* Private constructor. Only the Java Virtual Machine creates Class objects.
* This constructor is not used and prevents the default constructor being
* generated.
*/
private Class(ClassLoader loader) {
// Initialize final field for classLoader. The initialization value of non-null
// prevents future JIT optimizations from assuming this final field is null.
classLoader = loader;
}
到这我们也就可以得出以下几点信息:
- Class类也是类的一种,与class关键字是不一样的。
- 手动编写的类被编译后会产生一个Class对象,其表示的是创建的类的类型信息,而且这个Class对象保存在同名.class的文件中(字节码文件)
- 每个通过关键字class标识的类,在内存中有且只有一个与之对应的Class对象来描述其类型信息,无论创建多少个实例对象,其依据的都是用一个Class对象。
- Class类只存私有构造函数,因此对应Class对象只能有JVM创建和加载
- Class类的对象作用是运行时提供或获得某个对象的类型信息,这点对于反射技术很重要(关于反射稍后分析)。
类加载与反射关系
java 的类加载机制还是挺复杂的,我们这里为了不混淆重点,只为大家介绍和 “反射” 有关系的一部分内容。
java 执行编译的时候将 java 文件编译成字节码 class 文件,类加载器在类加载阶段将 class 文件加载到内存,并实例化一个 java.lang.Class 的对象。比如:对于 Student 类在加载阶段
- 在内存 (方法区或叫代码区) 中实例化一个 Class 对象,注意是 Class 对象不是 Student 对象
- 一个 Class 类(字节码文件)对应一个 Class 对象
- 该 Class 对象保存了 Student 类的基础信息,比如这个 Student 类有几个字段(Filed)?有几个构造方法(Constructor)?有几个方法(Method)?有哪些注解(Annotation)?等信息。
有了上面的关于 Student 类的基本信息对象(java.lang.Class 对象), 在运行期就可以根据这些信息来实例化
Student 类的对象。
- 在运行期你可以直接 new 一个 Student 对象
- 也可以使用反射的方法构造一个 Student 对象
但是无论你 new 多少个 Student 对象,不论你反射构建多少个 Student 对象,保存 Student 类信息的 java.lang.Class 对象都只有一个。下面的代码可以证明。
Class cls = Class.forName("com.java.reflection.Student");
Class cls2 = new Student().getClass();
System.out.println(cls == cls2); //比较Class对象的地址,输出结果是true
反射的使用
基于此我们如何通过反射获取Class类对象以及类中的成员变量、方法、构造方法等
Class类对象的获取
在类加载的时候,jvm会创建一个class对象
class对象是可以说是反射中最常用的,获取class对象的方式的主要有三种
- 根据类名:类名.class
- 根据对象:对象.getClass()
- 根据全限定类名:Class.forName(全限定类名)
- 再来看看 Class类的方法
方法名 | 说明 |
---|---|
forName() | 获取Class对象的一个引用,但引用的类还没有加载(该类的第一个对象没有生成)就加载了这个类。
Object-getClass() |获取Class对象的一个引用,返回表示该对象的实际类型的Class引用。
getName() |取全限定的类名(包括包名),即类的完整名字。
getSimpleName() |获取类名(不包括包名)
getCanonicalName() |获取全限定的类名(包括包名)
isInterface() |判断Class对象是否是表示一个接口
getInterfaces()| 返回Class对象数组,表示Class对象所引用的类所实现的所有接口。
getSuperclass() |返回Class对象,表示Class对象所引用的类所继承的直接基类。应用该方法可在运行时发现一个对象完整的继承结构。
newInstance() |返回一个Oject对象,是实现“虚拟构造器”的一种途径。使用该方法创建的类,必须带有无参的构造器。
getFields() |获得某个类的所有的公共(public)的字段,包括继承自父类的所有公共字段。 类似的还有getMethods和getConstructors。
getDeclaredFields |获得某个类的自己声明的字段,即包括public、private和proteced,默认但是不包括父类声明的任何字段。类似的还有getDeclaredMethods和getDeclaredConstructors。
getName、getCanonicalName与getSimpleName的区别:
getSimpleName
:只获取类名getName
:类的全限定名,jvm中Class的表示,可以用于动态加载Class对象,例如Class.forName。getCanonicalName
:返回更容易理解的表示,主要用于输出(toString)或log打印,大多数情况下和getName一样,但是在内部类、数组等类型的表示形式就不同了。
package com.jrm;
public class Test {
private class inner{
}
public static void main(String[] args) throws ClassNotFoundException {
//普通类
System.out.println(Test.class.getSimpleName()); //Test
System.out.println(Test.class.getName()); //com.jrm.Test
System.out.println(Test.class.getCanonicalName()); //com.jrm.Test
//内部类
System.out.println(inner.class.getSimpleName()); //inner
System.out.println(inner.class.getName()); //com.jrm.Test$inner
System.out.println(inner.class.getCanonicalName()); //com.jrm.Test.inner
//数组
System.out.println(args.getClass().getSimpleName()); //String[]
System.out.println(args.getClass().getName()); //[Ljava.lang.String;
System.out.println(args.getClass().getCanonicalName()); //java.lang.String[]
//我们不能用getCanonicalName去加载类对象,必须用getName
//Class.forName(inner.class.getCanonicalName()); 报错
Class.forName(inner.class.getName());
}
}
Constructor类及其用法
Constructor类存在于反射包(java.lang.reflect)中,反映的是Class 对象所表示的类的构造方法。
获取Constructor
对象是通过Class类中的方法获取的,Class类与Constructor
相关的主要方法如下:
| 方法返回值 | 方法名称 | 方法说明 |
|--|--|--|
static Class<?> |forName(String className) |返回与带有给定字符串名的类或接口相关联的 Class 对象。
Constructor |getConstructor(Class<?>... parameterTypes) |返回指定参数类型、具有public访问权限的构造函数对象
Constructor<?>[] |getConstructors() |返回所有具有public访问权限的构造函数的Constructor对象数组
Constructor |getDeclaredConstructor(Class<?>... parameterTypes) |返回指定参数类型、所有声明的(包括private)构造函数对象
Constructor<?>[] |getDeclaredConstructor() |返回所有声明的(包括private)构造函数对象
T |newInstance() |调用无参构造器创建此 Class 对象所表示的类的一个新实例。
例子:
User
public class User {
private int age;
private String name;
private boolean sex;
public User() {
super();
}
public User(String name){
super();
this.name = name;
}
public User(String name,boolean sex) {
super();
this.name = name;
this.sex =sex;
}
/**
* 私有构造
* @param age
* @param name
*/
private User(int age, String name,boolean sex) {
super();
this.age = age;
this.name = name;
this.sex =sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"age=" + age +
", name='" + name + '\'' +
", sex=" + sex +
'}';
}
}
main类:
public class TestMain {
public static void main(String[] args) throws IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException {
Class<User> userClass = User.class;
//第一种方法,实例化默认构造方法,User必须无参构造函数,否则将抛异常
User user = (User) userClass.newInstance();
user.setAge(20);
user.setName("Jack");
System.out.println(user);
System.out.println("--------------------------------------------");
//获取带String参数的public构造函数
Constructor cs1 =userClass.getConstructor(String.class);
//创建User
User user1= (User) cs1.newInstance("chen");
user1.setAge(22);
System.out.println("user1:"+user1.toString());
System.out.println("--------------------------------------------");
//取得指定带int和String参数构造函数,该方法是私有构造private
Constructor cs2=userClass.getDeclaredConstructor(String.class,boolean.class);
//由于是private必须设置可访问
cs2.setAccessible(true);
//创建user对象
User user2= (User) cs2.newInstance("chen",true);
System.out.println("user2:"+user2.toString());
System.out.println("--------------------------------------------");
//获取所有构造包含private
Constructor<?> cons[] = userClass.getDeclaredConstructors();
// 查看每个构造方法需要的参数
for (int i = 0; i < cons.length; i++) {
//获取构造函数参数类型
Class<?> clazzs[] = cons[i].getParameterTypes();
System.out.println("构造函数["+i+"]:"+cons[i].toString() );
System.out.print("参数类型["+i+"]:(");
for (int j = 0; j < clazzs.length; j++) {
if (j == clazzs.length - 1)
System.out.print(clazzs[j].getName());
else
System.out.print(clazzs[j].getName() + ",");
}
System.out.println(")");
}
}
}
输出结果
D:\Program_JDK\jrmJDK8\bin\java.exe "-javaagent:G:\idea\IntelliJ IDEA 2021.2.3\lib\idea_rt.jar=60610:G:\idea\IntelliJ IDEA 2021.2.3\bin" -Dfile.encoding=UTF-8 -classpath D:\Program_JDK\jrmJDK8\jre\lib\charsets.jar;D:\Program_JDK\jrmJDK8\jre\lib\deploy.jar;D:\Program_JDK\jrmJDK8\jre\lib\ext\access-bridge-64.jar;D:\Program_JDK\jrmJDK8\jre\lib\ext\cldrdata.jar;D:\Program_JDK\jrmJDK8\jre\lib\ext\dnsns.jar;D:\Program_JDK\jrmJDK8\jre\lib\ext\jaccess.jar;D:\Program_JDK\jrmJDK8\jre\lib\ext\jfxrt.jar;D:\Program_JDK\jrmJDK8\jre\lib\ext\localedata.jar;D:\Program_JDK\jrmJDK8\jre\lib\ext\nashorn.jar;D:\Program_JDK\jrmJDK8\jre\lib\ext\sunec.jar;D:\Program_JDK\jrmJDK8\jre\lib\ext\sunjce_provider.jar;D:\Program_JDK\jrmJDK8\jre\lib\ext\sunmscapi.jar;D:\Program_JDK\jrmJDK8\jre\lib\ext\sunpkcs11.jar;D:\Program_JDK\jrmJDK8\jre\lib\ext\zipfs.jar;D:\Program_JDK\jrmJDK8\jre\lib\javaws.jar;D:\Program_JDK\jrmJDK8\jre\lib\jce.jar;D:\Program_JDK\jrmJDK8\jre\lib\jfr.jar;D:\Program_JDK\jrmJDK8\jre\lib\jfxswt.jar;D:\Program_JDK\jrmJDK8\jre\lib\jsse.jar;D:\Program_JDK\jrmJDK8\jre\lib\management-agent.jar;D:\Program_JDK\jrmJDK8\jre\lib\plugin.jar;D:\Program_JDK\jrmJDK8\jre\lib\resources.jar;D:\Program_JDK\jrmJDK8\jre\lib\rt.jar;G:\idea\workspace_spring\reflex\out\production\reflex com.jrm.reflex.TestMain
User{
age=20, name='Jack', sex=false}
--------------------------------------------
user1:User{
age=22, name='chen', sex=false}
--------------------------------------------
user2:User{
age=0, name='chen', sex=true}
--------------------------------------------
构造函数[0]:private com.jrm.reflex.User(int,java.lang.String,boolean)
参数类型[0]:(int,java.lang.String,boolean)
构造函数[1]:public com.jrm.reflex.User(java.lang.String,boolean)
参数类型[1]:(java.lang.String,boolean)
构造函数[2]:public com.jrm.reflex.User(java.lang.String)
参数类型[2]:(java.lang.String)
构造函数[3]:public com.jrm.reflex.User()
参数类型[3]:()
Process finished with exit code 0
Field类及其用法
Field 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段。
同样的道理,我们可以通过Class类的提供的方法来获取代表字段信息的Field对象,Class类与Field对象相关方法如下:
方法返回值 | 方法名称 | 方法说明 |
---|---|---|
Field |getDeclaredField(String name) |获取指定name名称的(包含private修饰的)字段,不包括继承的字段
Field[] |getDeclaredField() |获取Class对象所表示的类或接口的所有(包含private修饰的)字段,不包括继承的字段
Field |getField(String name) |获取指定name名称、具有public修饰的字段,包含继承字段
Field[] |getField() |获取修饰符为public的字段,包含继承字段
其中的set(Object obj, Object value)
方法是Field类本身的方法,用于设置字段的值,而get(Object obj)
则是获取字段的值,当然关于Field类还有其他常用的方法如下:
方法返回值 | 方法名称 | 方法说明 |
---|---|---|
void |set(Object obj, Object value) |将指定对象变量上此 Field 对象表示的字段设置为指定的新值。
Object |get(Object obj) |返回指定对象上此 Field 表示的字段的值
Class<?> |getType() |返回一个 Class 对象,它标识了此Field 对象所表示字段的声明类型。
boolean |isEnumConstant() |如果此字段表示枚举类型的元素则返回 true;否则返回 false
String |toGenericString() |返回一个描述此 Field(包括其一般类型)的字符串
String |getName() |返回此 Field 对象表示的字段的名称
Class<?> |getDeclaringClass() |返回表示类或接口的 Class 对象,该类或接口声明由此 Field 对象表示的字段
void |setAccessible(boolean flag) |将此对象的 accessible 标志设置为指示的布尔值,即设置其可访问性
上述方法可能是较为常用的,事实上在设置值的方法上,Field类还提供了专门针对基本数据类型的方法,如setInt()
/getInt()
、setBoolean()
/getBoolean
、setChar()
/getChar()
等等方法,这里就不全部列出了,需要时查API文档即可。需要特别注意的是被final关键字修饰的Field字段是安全的,在运行时可以接收任何修改,但最终其实际值是不会发生改变的
Method类及其用法
Method 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息,所反映的方法可能是类方法或实例方法(包括抽象方法)。
下面是Class类获取Method对象相关的方法:
方法返回值 | 方法名称 | 方法说明 |
---|---|---|
Method |getDeclaredMethod(String name, Class<?>... parameterTypes) |返回一个指定参数的Method对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。
Method[] |getDeclaredMethod() |返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。
Method |getMethod(String name, Class<?>... parameterTypes) |返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。
Method[] |getMethods() |返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法。
getReturnType方法
/getGenericReturnType方法
都是获取Method对象表示的方法的返回类型,只不过前者返回的Class类型后者返回的Type(前面已分析过),Type就是一个接口而已,在Java8中新增一个默认的方法实现,返回的就参数类型信息
public interface Type {
//1.8新增
default String getTypeName() {
return toString();
}
}
而getParameterTypes
/getGenericParameterTypes
也是同样的道理,都是获取Method对象所表示的方法的参数类型,其他方法与前面的Field和Constructor是类似的.
反射的最基本应用
我们定义一个类叫做 Student
com.java.reflection.Student
public class Student {
public String nickName;
private Integer age;
public void dinner(){
System.out.println("吃晚餐!");
}
private void sleep(int minutes){
System.out.println("睡" + minutes + "分钟");
}
}
如果不用反射的方式,我相信只要学过 java 的朋友肯定会调用 dinner 方法
Student student = new Student();
student.dinner();
如果是反射的方式我们该怎么调用呢?
//获取Student类信息
Class cls = Class.forName("com.zimug.java.reflection.Student");
//对象实例化
Object obj = cls.getDeclaredConstructor().newInstance();
//根据方法名获取并执行方法
Method dinnerMethod = cls.getDeclaredMethod("dinner");
dinnerMethod.invoke(obj); //打印:吃晚餐!
通过上面的代码我们看到,com.java.reflection.Student 类名和 dinner 方法名是字符串。既然是字符串我们就可以通过配置文件,或数据库、或什么其他的灵活配置方法来执行这段程序了。这就是反射最基础的使用方式。
如果这篇【文章】有帮助到你💖,希望可以给我点个赞👍,创作不易,如果有对Java后端或者对
spring
感兴趣的朋友,请多多关注💖💖💖
👨🔧 个人主页 : 阿千弟