「这是我参与2022首次更文挑战的第8天,活动详情查看:2022首次更文挑战」
前言
哈喽大家好,我是卡诺,一名致力于成为全栈的全粘工程师!
在「new对象不行吗?为什么要用反射?」一文中介绍了类的装载过程以及反射的一些前置知识,相信大家对反射应该有了新的认识,马克思主义说“实践是检验真理的唯一标准”,本章内容将围绕反射相关的类进行案例演示,相信通过这两章的温习,项目中遇到的反射问题一定是手到擒来,那我们就开始吧!
本文已经加入 【再学一次Java】 专栏,该专栏旨在重温Java知识,夯实基础,包含:Lambda、反射、注解、多线程等进阶知识。如果有需要的小伙伴可以关注一下,专栏持续更新ing!
反射中你必须知道的类
反射我们常用的类主要是:Class
、Constructor
、Field
、Method
,其他反射相关的类均在java.lang.reflect
包下!
我们借助idea的dirgrams功能,展示反射中Class
、Constructor
、Field
、Method
的关系图,如下:
上面的关系图暂时保留个印象即可,接下来我们通过案例进行加深巩固。请跟着我的脚步继续向下走!
案例准备
- 基础实体类 BaseEntity
/**
* 基础实体类
*
* @author : uu
* @version : v1.0
* @Date 2022/1/26
*/
public class BaseEntity {
private String id;
private Long createBy;
// 无参构造
public BaseEntity() {
}
// 有参构造
public BaseEntity(String id) {
this.id = id;
}
// 私有构造
private BaseEntity(String id, Long createBy) {
this.id = id;
this.createBy = createBy;
}private String id;
public BaseEntity() {
}
public BaseEntity(String id) {
this.id = id;
}
//...getter/setter省略
}
- 学生类 Student(继承 BaseEntity)
/**
* 学生
* @author : uu
* @version : v1.0
* @Date 2022/1/24
*/
public class Student extends BaseEntity {
private String name;
private int age;
private String[] hobbies;
// 无参构造
public Student() {
}
// 多参构造
public Student(String id, String name) {
super(id);
this.name = name;
}
// 私有构造
private Student(String id, String name, int age, String[] hobbies) {
super(id);
this.name = name;
this.age = age;
this.hobbies = hobbies;
}
//...getter/setter省略
}
Class自述
官方的Class Api文档中有这么一段描述:Instances of the class Class represent classes and interfaces in a running Java application. An enum is a kind of class and an annotation is a kind of interface. Every array also belongs to a class that is reflected as a Class object that is shared by all arrays with the same element type and number of dimensions. The primitive Java types (boolean, byte, char, short, int, long, float, and double), and the keyword void are also represented as Class objects.
Class has no public constructor. Instead Class objects are constructed automatically by the Java Virtual Machine as classes are loaded and by calls to the defineClass method in the class loader.
谷歌翻译一下:Class类的实例表示正在运行的Java程序中的类和接口,其中枚举是一种类,注解是一种接口。每个数组也属于一个类,该类被映射为一个Class对象,所有具有相同元素类型和维度的数组都共享该Class对象。基本Java类型(boolean、byte、char、short、int、long、float和double)以及关键字void也表示为Class对象。
Class没有公共的构造方法,类对象是在类加载时由Java虚拟机通过调用类加载器中的defineClass方法来构造自动构造的。
通过上面的介绍,以及我们前一章关于类加载的流程,我们可以得到一个信息: “类被加载后会生成一个当前类的Class对象,该Class对象包含了这个类的所有信息,而反射就是通过读取这个Class对象,反向获取类的相关信息,并对其进行相关操作” 。
⚠️:在Java中,万物皆对象。类本身也是对象,任何一个类都是Class类的实例对象。上述Student类,它是Class类的实例,Class类是Student类的类型!
如何获取Class对象?
通过类获取
public void testCreateClass() {
// 通过类获取
Class<Person> personClass = Person.class;
System.out.println(personClass.getName()); // com.uucoding.advance.entity.Person
}
通过对象获取
public void testCreateClass() {
// 通过对象获取
Person person = new Person();
Class<? extends Person> personClass2 = person.getClass();
System.out.println(personClass2.getName()); // com.uucoding.advance.entity.Person
}
通过类加载器获取
public void testCreateClass() throws ClassNotFoundException {
// 通过类加载器获取
ClassLoader classLoader = Person.class.getClassLoader();
Class<?> personClass4 = classLoader.loadClass("com.uucoding.advance.entity.Person");
System.out.println(personClass4.getName()); // com.uucoding.advance.entity.Person
}
通过类路径获取
这种方式用的比较多,一般将类路径放在配置文件中去读取,比如:Spring数据驱动的配置
public void testCreateClass() throws ClassNotFoundException {
// 通过类路径获取
Class<?> personClass3 = Class.forName("com.uucoding.advance.entity.Person");
System.out.println(personClass3.getName()); // com.uucoding.advance.entity.Person
}
Class对象能做什么?
获取类的基本信息
public void testGetClassInfo(){
// 通过类获取
Class<Student> studentClass = Student.class;
System.out.println("对象简单类名(不含包名) " + studentClass.getSimpleName()); // 对象简单类名 Student
System.out.println("对象完整类名(包含包名)" + studentClass.getName()); // 对象完整类名(包含包)com.uucoding.advance.entity.Student
System.out.println("对象类所在包 " + studentClass.getPackage()); // 对象类所在包 package com.uucoding.advance.entity
System.out.println("类的修饰符 " + Modifier.toString(studentClass.getModifiers())); // 类的修饰符 public
System.out.println("当前类的父类 " + studentClass.getSuperclass()); // 当前类的父类 class com.uucoding.advance.entity.BaseEntity
System.out.println("当前类的接口 " + studentClass.getInterfaces().length); // 当前类的接口 0
System.out.println("Object类的父类 " + Object.class.getSuperclass()); // Object类的父类 null
}
获取构造方法(Constructor)
class.getConstructors()
获取public修饰的所有构造、class.getDeclaredConstructors()
获取所有构造,演示代码如下:
public void testConstructor(){
Class<Student> studentClass = Student.class;
// getConstructors 获取public修饰的构造方法
Constructor<?>[] constructors = studentClass.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println(constructor);
// public com.uucoding.advance.entity.Student(java.lang.String,java.lang.String)
// public com.uucoding.advance.entity.Student()
}
//
// getDeclaredConstructors 获取所有构造方法构造
constructors = studentClass.getDeclaredConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println(constructor);
// private com.uucoding.advance.entity.Student(java.lang.String,java.lang.String,int,java.lang.String[])
// public com.uucoding.advance.entity.Student(java.lang.String,java.lang.String)
// public com.uucoding.advance.entity.Student()
}
}
上面两个方法获取的是个构造方法的数组,如果想获取某个指定的构造呢?
我们可以使用class.getConstructor(Class<?>... parameterTypes)
、class.getDeclaredConstructor(Class<?>... parameterTypes)
其中parameterTypes为可变参数,表示构造参数的类型。演示代码如下:
public void testParamsConstructor() throws NoSuchMethodException {
Class<Student> studentClass = Student.class;
// getConstructor 获取public修饰的指定参数的构造方法
Constructor<Student> constructor = studentClass.getConstructor();
// 无参数的public构造 public com.uucoding.advance.entity.Student()
System.out.println("无参数的构造 " + constructor);
constructor = studentClass.getConstructor(String.class, String.class);
// 两个参数的public构造 public com.uucoding.advance.entity.Student(java.lang.String,java.lang.String)
System.out.println("两个参数的构造 " + constructor);
// 四个参数的private构造 private com.uucoding.advance.entity.Student(java.lang.String,java.lang.String,int,java.lang.String[])
constructor = studentClass.getDeclaredConstructor(String.class, String.class, int.class, String[].class);
System.out.println("三个参数的构造 " + constructor);
}
该方法使用时需注意参数类型、个数请与构造方法的保持一致,否在抛出了NoSuchMethodException
异常,表示没有方法匹配!
Student类继承BaseEntity,我想获取父类构造怎么办?
方法很简单,先通过studentClass.getSuperclass()
获取父类的Class -> baseEntityClass
,然后再使用baseEntityClass
获取其构造方法,代码如下:
public void testGetParentConstructor() {
Class<Student> studentClass = Student.class;
// 获取父类的class
Class<? super Student> superclass = studentClass.getSuperclass();
Constructor<?>[] declaredConstructors = superclass.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor);
//public com.uucoding.advance.entity.BaseEntity()
//private com.uucoding.advance.entity.BaseEntity(java.lang.String,java.lang.Long)
//public com.uucoding.advance.entity.BaseEntity(java.lang.String)
}
}
获取属性(Field)
class.getFields()
获取public修饰的所有属性(包括当前类和父类的)、class.getDeclaredFields()
仅获取当前类的所有属性,但不包括父类属性,演示代码如下:
public void testField(){
Class<Student> studentClass = Student.class;
// 获取所有public修饰的属性(包含父类的public属性)
Field[] fields = studentClass.getFields();
for (Field field : fields) {
System.out.println(field);
// public java.lang.String[] com.uucoding.advance.entity.Student.hobbies
// public java.lang.Long com.uucoding.advance.entity.BaseEntity.createBy
}
// 获取当前类的所有属性(不包含父类的任何属性)
fields = studentClass.getDeclaredFields();
for (Field declaredField : fields) {
System.out.println(declaredField);
// private java.lang.String com.uucoding.advance.entity.Student.name
//private int com.uucoding.advance.entity.Student.age
//public java.lang.String[] com.uucoding.advance.entity.Student.hobbies
}
}
与获取Constructor
一样,Field
的获取也为我们提供了有参方法 class.getField(String name)
、class.getDeclaredField(String name)
,其中name表示属性名,演示代码如下:
public void testParamsField() throws NoSuchFieldException{
Class<Student> studentClass = Student.class;
// 获取指定名称的属性
// 获取public修饰的属性
Field createBy = studentClass.getField("createBy");
// public java.lang.Long com.uucoding.advance.entity.BaseEntity.createBy
System.out.println(createBy);
// 获取自身的属性
Field hobbies = studentClass.getDeclaredField("hobbies");
// public java.lang.String[] com.uucoding.advance.entity.Student.hobbies
System.out.println(hobbies);
Field name = studentClass.getDeclaredField("name");
// private java.lang.String com.uucoding.advance.entity.Student.name
System.out.println(name);
}
该方法使用时需注意类中是否存在该属性,如果使用不存在的属性值将抛出NoSuchFieldException
异常!
开发中如果有继承关系其实更希望是获得子类 + 父类的所有属性,上述Student的所有属性(包含父类)获取方式如下:
public void testGetAllField() {
Class<Student> studentClass = Student.class;
// 获取student当前类的属性
Field[] fields = studentClass.getDeclaredFields();
// 获取BaseEntity的属性
Class<? super Student> superclass = studentClass.getSuperclass();
Field[] parentFields = superclass.getDeclaredFields();
List<Field> fieldList = new ArrayList<>();
fieldList.addAll(Arrays.asList(fields));
fieldList.addAll(Arrays.asList(parentFields));
for (Field field : fieldList) {
System.out.println(field);
// private java.lang.String com.uucoding.advance.entity.Student.name
//private int com.uucoding.advance.entity.Student.age
//public java.lang.String[] com.uucoding.advance.entity.Student.hobbies
//private java.lang.String com.uucoding.advance.entity.BaseEntity.id
//public java.lang.Long com.uucoding.advance.entity.BaseEntity.createBy
}
}
上述继承链不确定的情况下,采用递归是一个不错的选择,并根据getSuperclass
获取最上层的父类结果是null,判断跳出递归。
获取方法(Method)
class.getMethods()
获取public修饰的所有方法(包括当前类和父类的)、class.getDeclaredMethods()
仅获取当前类的所有方法,但不包括父类方法,演示代码如下:
public void testMethod(){
Class<Student> studentClass = Student.class;
// 获取所有public修饰的方法(包含父类的public方法)
Method[] methods = studentClass.getMethods();
for (Method method : methods) {
System.out.println(method);
}
// 获取当前类的所有方法(不包含父类的任何方法)
methods = studentClass.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method);
}
}
与获取Constructor
、Field
一样,Method
的获取也为我们提供了有参方法class.getMethod(String name, Class<?>... parameterTypes)
、
getDeclaredMethod(String name, Class<?>... parameterTypes)
其中name表示方法名,parameterTypes表示方法参数,演示代码如下
public void testParamsMethod() throws NoSuchMethodException {
Class<Student> studentClass = Student.class;
// 获取所有public修饰的方法(包含父类的public方法)
Method method = studentClass.getMethod("setId", String.class);
System.out.println(method);
// 获取当前类的所有方法(不包含父类的任何方法)
method = studentClass.getDeclaredMethod("setName", String.class);
System.out.println(method);
}
获取父类所有方法,方式同Constructor
、Field
,不过好像一般也用不上...大家了解一下就行了
简单的案例
上文,我们通过对Class
进行了基础的API运用,可以获取类的基本信息
、Constructor
、Field
、Method
,但均是getter类型操作,接下来我们通过一个简单的小案例将获取的对象使用起来。演示代码如下:
public void testReflectExample() throws Exception{
Class<Student> studentClass = Student.class;
// 通过反射创建对象
Student student = studentClass.newInstance();
// 获取构造public Student(String id, String name)
Constructor<Student> constructor = studentClass.getConstructor(String.class, String.class);
student = constructor.newInstance("1", "卡诺");
// student id = 1 name = 卡诺
System.out.println("student id = " + student.getId() + " name = " + student.getName());
// 更改student的name值,修改为:卡诺2
Field name = studentClass.getDeclaredField("name");
// 私有属性需要使用setAccessible设置为可见
name.setAccessible(true);
name.set(student, "卡诺2");
// student id = 1 name = 卡诺2
System.out.println("student id = " + student.getId() + " name = " + student.getName());
// 调用方法设置值
Method setName = studentClass.getDeclaredMethod("setName", String.class);
// 执行方法 invoke,调用setName设置名字为卡诺3
setName.invoke(student, "卡诺3");
// student id = 1 name = 卡诺3
System.out.println("student id = " + student.getId() + " name = " + student.getName());
}
代码中新出现了一些方法:
class.newInstance()
:使用类的默认构造,如果类没有默认构造方法则抛出异常InstantiationException
;setAccessible(boolean)
:Constructor
、Field
、Method
均有该方法,当修饰符为非public时,设置为true,才能允许反射调用,如果是public修饰,则不需要设置为true;
field.set(obj, val)
: 给对象属性设置值,参数1表示属性所属对象,参数2表示属性新值;method.invoke(obj, ...args)
:执行对象方法,参数1表示属性所属对象,参数2可变参数,表示方法的入参值。
源码
总结
- 本章主要通过案例代码介绍反射中常用的
Class
、Constructor
、Field
、Method
,以及相关的API操作,基本上支持常规的业务开发使用,但反射远远不止于此,比如:反射和注解的结合、属性方法的其他操作等,我们以后再作讨论! Constructor
、Field
、Method
的获取存在getXxx
和getDeclaredXxx
两类方法,其中getXxx
表示仅获取public
修饰的,getDeclaredXxx
表示获取所有修饰符修饰的。
最后
- 感谢铁子们耐心看到最后,如果大家感觉本文有所帮助,麻烦给个赞👍或关注➕;
- 由于本人技术有限,文章和代码可能存在错误,希望大家评论指出,万分感激🙏;
- 同时也欢迎大家V我(uu2coding)一起讨论学习前端、Java知识,一起卷一起进步。