文章目录
一、反射
对于反射的理解
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
获取Class实例的三种方式
调用Class静态方法:forName(String classPath)
Class<?> clazz = Class.forName("java.lang.Object");
System.out.println(clazz);
通过运行时类的对象调用getClass()
String name = "qi";
Class<? extends String> clazz = name.getClass();
System.out.println(clazz);
调用运行时类的属性 .class
lass clazz = String.class;
System.out.println(clazz);
反射之Constructor
定义一个Student类
package reflect;
public class Student {
private String name;
private int age;
public String gender;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
private void say() {
System.out.println("hello");
}
}
获取构造器
获取Student类的Class实例,获取构造器,并输出构造器中的参数
@Test
public void test01() throws ClassNotFoundException {
// 获取Class实例
Class<?> clazz = Class.forName("reflect.Student");
// 获取构造器(多个)
Constructor<?>[] constructors = clazz.getConstructors();
for (Constructor<?> constructor : constructors) {
// 输出构造器的名称
System.out.println(constructor.getName());
// 获取构造器中的参数
Class<?>[] parameterTypes = constructor.getParameterTypes();
for (Class<?> parameterType : parameterTypes) {
System.out.print(parameterType + "--");
}
System.out.println("\n===================");
}
}
Student类中定义了两个构造器,getParameterTypes()都获取到了;也可以通过getConstructor(String.class, int.class);获取单个构造器
Class<?> clazz = Class.forName("reflect.Student");
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
System.out.println(constructor);
创建运行时类对象
通过构造器获取类对象 constructor.newInstance()
Class<?> clazz = Class.forName("reflect.Student");
// 获取构造器
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
// 获取运行时类对象
Object stu = constructor.newInstance("xiaoming", 21);
Student s = (Student) stu;
System.out.println(s.getName());
通过Class实例对象获取对象
Class<?> clazz = Class.forName("reflect.Student");
// 默认是通过无参构造器获取
Student student = (Student)clazz.newInstance();
System.out.println(student.getName());
反射之Field
- getDeclaredFields():获取当前运行时类中声明的所有属性(不包含父类中声明的属性)
// 获取Class实例
Class<?> clazz = Class.forName("reflect.Student");
// 获取当前运行类中声明的所有属性(不包含父类中的属性)
Field[] declaredFields = clazz.getDeclaredFields();
// 输出属性名和属性类型
for (Field declaredField : declaredFields) {
System.out.println(declaredField.getName() + ":" + declaredField.getType());
}
- getFields():获取当前运行时类及其父类中声明为public访问权限的属性
Field[] fields = clazz.getFields();
for (Field field : fields) {
System.out.println(field.getName() + ":" + field.getType());
}
只有gender属性声明为public,其他属性都是private
- getDeclaredField(String name),可以传入属性名获取单个属性
Field name = clazz.getDeclaredField("name");
System.out.println(name);
反射之Method
获取运行时类的方法
- getDeclaredMethods():获取当前运行时类中声明的所方法。(不包含父类中声明的方法)
- getMethods():获取当前运行时类及其所父类中声明为public权限的方法
- getDeclaredMethod(String name, Class type):根据输入的方法名和方法的参数类型获取单个方法
Class<?> clazz = Class.forName("reflect.Student");
// 获取运行时类的所有方法
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName());
}
另外两种方法就不一一演示了
唤醒方法:invoke()
Class<?> clazz = Class.forName("reflect.Student");
// 创建运行时类Student的对象
Object o = clazz.newInstance();
// 根据方法名和方法的参数类型获取 setName方法
Method setName = clazz.getDeclaredMethod("setName", String.class);
// 唤醒方法:传入对象以及方法的参数,等价于一个对象调用方法
setName.invoke(o, "qi");
// 获取 getName 方法,该方法没有参数
Method getName = clazz.getDeclaredMethod("getName");
// 唤醒 getName方法,该方法没有参数,所以没有指定参数类型
System.out.println(getName.invoke(o));
反射之类型擦除
有以下场景,声明一个类型为String的List,如果想要插入Integer类型的数字怎么办?
List<String> list = new ArrayList<>();
list.add("abc");
很显然通过add方法只能插入String类型的字符串,因为我们指明了泛型
那么通过反射可以做到,思路如下:
首先获取 list 对象的运行时类的Class实例,再通过Class实例获取声明参数类型的add方法,然后在invoke唤醒该方法即可。
查看add方法的源码:
代码如下:
List<String> list = new ArrayList<>();
list.add("abc"); // 添加成功
// list.add(123); 肯定添加失败
// 获取Class实例
Class<?> clazz = list.getClass();
// 获取泛型为Object类型的add方法
Method addObject = clazz.getDeclaredMethod("add", Object.class);
// 对 list 对象唤醒该方法 ==> list.add(123)
addObject.invoke(list, 123); // 插入成功
// 输出list对象
System.out.println(list);
私有变量/私有方法能否被外界访问?
如果不用反射的话,私有方法和私有变量是不能被外界访问到的
Student有如下私有属性和私有方法,现在通过反射从外部访问他们
private String name;
private int age;
private void say() {
System.out.println("hello");
}
Class<?> clazz = Class.forName("reflect.Student");
// 获取stu对象
Student stu = (Student)clazz.newInstance();
// 获取私有属性 name
Field name = clazz.getDeclaredField("name");
// 设置私有属性能被访问到!!!
name.setAccessible(true);
// 从外部访问name属性,并设置为 "qi"
name.set(stu, "qi");
// 查看一下名字是否被设置为 "qi"
System.out.println(stu.getName());
Method say = clazz.getDeclaredMethod("say");
// 设置私有方法能被访问
say.setAccessible(true);
// 唤醒方法
say.invoke(stu);
二、注解
对于注解的理解
Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。
定义注解
首先定义两个注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface QiBean {
String value() default "nihao";
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface QiField {
String value();
}
- @Target注解:ElementType可以取TYPE:可以用在类、接口上;FIELD:可以用在类的属性上;METHOD:可以用在类的方法上等等
- @Retention:表示注解的生命周期SOURCE/CLASS/RUNTIME(默认行为\RUNTIME,只有声明为RUNTIME生命周期的注解,才能通过反射获取
- @Inherited:可以被继承(后面会例子)
获取注解
@QiBean(table = "qi_animal")
public class Animal {
private Integer id;
private String name;
@QiField("gender")
private String sex;
public Animal() {
}
public Animal(Integer id, String name, String sex) {
this.id = id;
this.name = name;
this.sex = sex;
}
}
public class Pig extends Animal {}
通过getAnnotation方法可以获取注解
Class<?> clazz = Class.forName("annotation.Animal");
// 是否存在注解
System.out.println(clazz.isAnnotationPresent(QiBean.class));
// 获取到注解
QiBean qiBean = clazz.getAnnotation(QiBean.class);
if (null != qiBean) {
System.out.println(qiBean.table() + ":"
+ qiBean.annotationType()
);
}
// 获取到属性
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
// 获取到属性对应的注解
QiField qiField = field.getAnnotation(QiField.class);
if (null != qiField) {
// 输出属性名称和对应的注解中的名称
System.out.println(field.getName() + "=>" + qiField.value());
}
}
从输入结果可以看到,获取到了Animal类注解中的值:qi_animal,也获取到了sex属性上的注解值:gender
现在我们看一些Pig类有没有注解(Pig继承Aanimal,但是自己没有声明注解)
Class<?> clazz = Class.forName("annotation.Pig");
System.out.println(clazz.isAnnotationPresent(QiBean.class));
可以发现Pig类上是有注解的,这是因为定义注解的时候声明了@Inherited
总结
本文记录了的反射的一些基本用法:获取运行时类对象、通过反射获取方法、属性,以及通过反射进行类型擦除、访问私有属性和方法;同时也简单的介绍如何获取类、属性中的注解。