前言
记录一些,反射与注解使用的一些基本方法。
一、注解
1.自定义注解
- 注解定义方式:用@interface来声明该类为注解类。
- 元注解:TODO
- 注解的属性定义方式
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义注解
*
* @Author: John Smith
* @Date: 2022/4/23
*/
@Target(ElementType.METHOD) // 注解针对什么生效
@Retention(RetentionPolicy.RUNTIME) // 注解啥时候生效
public @interface InitMethod {
// 这是注解的参数 后面是他的默认值。在没有默认值的情况下,如果不给值的话,会报错。
String name() default "";
// int 类型如果默认值为-1,代表不存在
int id() default -1;
}
2.测试类
/**
* 测试用类
*
* @Author: John Smith
* @Date: 2022/4/23
*/
public class InitDemo {
@InitMethod
public void init(){
System.out.println("init........");
}
@InitMethod
public void demo() {
System.out.println("demo.........");
}
}
3.测试结果
执行该main方法,会自动执行InitDemo类中所有加了@InitMethod注解的方法。
执行方式在下面的注解章中讲到。
import java.lang.reflect.Method;
/**
*
*
* @Author: John Smith
* @Date: 2022/4/23
*/
public class TestMain {
public static void main(String[] args) throws Exception {
// 通过反射创建对象
Class<?> clazz = Class.forName("org.example.spring.annotation.InitDemo");
// 拿到该对象所有的方法
Method[] methods = clazz.getMethods();
// 遍历所有方法
for (Method method : methods) {
// 判断哪个方法上加了这个InitMethod注解
boolean b = method.isAnnotationPresent(InitMethod.class);
// 如果加了这个注解的,就反射执行该方法
if (b) {
method.invoke(clazz.getConstructor(null).newInstance(null),null);
}
}
}
}
- 执行结果
二、反射
0.测试用类
使用了lombok,省去多余代码。
import lombok.Data;
/**
* 父类
*
* @Author: John Smith
* @Date: 2022/4/26
*/
@Data
public class Person {
public Person(){}
public Person(String name){
this.name = name;
}
String name;
}
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 子类
*
* @Author: John Smith
* @Date: 2022/4/26
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
class User extends Person{
private int id;
private int age;
}
1.基本用法
1.1 获取Class的几种方式
1.1.1 获取类的对象
// 方式一
// 这种方式不管获取几次都是同一个对象,可以通过打印类的hashCode来判断
// 一个类在内存中只有一个class对象,一个类被加载后整个类的结构全部存在这个class当中
Class<?> c1 = Class.forName("org.example.spring.reflection.User");
// 方式二
User user = new User();
Class c2 = user.getClass();
// 方式三
Class<User> c3 = User.class;
// 方式四(只有内置类型包装类有)
Class<Integer> type = Integer.TYPE;
1.1.2 获取父类对象
Class<?> superclass = c1.getSuperclass();
System.out.println(superclass);
1.2 获取类的信息
1.2.1 获取类的名称
// 获取类全限定名
System.out.println(c1.getName());
// 获取简单类名
System.out.println(c1.getSimpleName());
1.2.2 获取类的属性
// 类的属性(这个没办法获取到)这种方式只能找到public
Field[] fields = c1.getFields();
for (Field field : fields) {
System.out.println(field);
}
// 获得类的属性(可以找到所有的)
Field[] declaredFields = c1.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println(declaredField);
}
// 获取类的指定属性(同样,不加declared的话只能获取到public修饰的属性)
System.out.println(c1.getDeclaredField("age"));
下面是用了getDeclaredFields才获取出来的:
1.2.3 获取类的方法
// 获取方法(获取所有的方法 包括父类的一些方法 全是public的)
Method[] methods = c1.getMethods();
for (Method method : methods) {
System.out.println("没加declare:" + method);
}
// 获取方法(只获得本类的 private的也可以)
Method[] declaredMethods = c1.getDeclaredMethods();
for (Method method : declaredMethods) {
System.out.println("加declare:" + method);
}
获取类的指定方法:
/*
获得指定方法 第一个属性为方法名称 第二个属性为方法的入参
getAge方法由于没有入参所以null可写可不写
在方法重载的时候,第二个属性就很重要了,尽量都写上
*/
Method setAge = c1.getMethod("setAge", int.class);
Method getAge = c1.getMethod("getAge", null);
System.out.println(setAge);
System.out.println(getAge);
1.2.4 获取类的构造器
// 获取所有public的构造器 但不会获得父类的
Constructor<?>[] constructors = c1.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println("不加declared:" + constructor);
}
// 获取本类的全部构造器
Constructor<?>[] declaredConstructors = c1.getDeclaredConstructors();
for (Constructor<?> constructor : declaredConstructors) {
System.out.println("加了declare" + constructor);
}
// 获得指定构造器 入参填构造器的入参
Constructor<?> constructor = c1.getConstructor(int.class, int.class);
System.out.println("指定构造器:" + constructor);
1.3 通过获取类的构造器来实例化对象
1.3.1 通过无参构造器创建对象
Class<?> c1 = Class.forName("org.example.spring.reflection.User");
// 这种方式创建对象是调用了无参构造器 如果没有的话可能创建不了
User user = (User)c1.newInstance();
System.out.println(user);
1.3.2 通过有参构造器创建对象
// 获取到有参构造器
Constructor<?> declaredConstructor = c1.getDeclaredConstructor(int.class, int.class);
// 通过该构造器,传入参数并实例化对象(强转一下)
User u2 = (User) declaredConstructor.newInstance(1, 1);
System.out.println(u2);
1.4 通过反射调用方法
复用1.3.1中创建的Class对象和User对象
// 获取到普通方法,后面传入方法名和该方法的入参
Method setAge = c1.getDeclaredMethod("setAge", int.class);
// invoke是执行该方法,传入的是:1.调用该方法的对象 2.该方法的入参
setAge.invoke(user, 20);
System.out.println(user);
1.5 通过反射操作属性
复用1.3.1中创建的Class对象和User对象
会报错:
// 按照名称得到属性
Field id = c1.getDeclaredField("id");
// 下面直接执行的话无法成功,因为类里的id属性为private
// 设置属性值,传入的是:1.持有该属性的对象 2.给属性赋的值
id.set(user,2);
System.out.println(user.getId());
正确用法:
// 按照名称得到属性
Field id = c1.getDeclaredField("id");
// 关掉id属性的权限校验,让反射可以访问private属性
// method、constructor、field 都有setAccessible方法,都可以设置访问权限
id.setAccessible(true);
id.set(user, 2);
System.out.println(user.getId());
1.6 通过反射获取泛型
前提:在一个类中包含如下两个方法,分别是入参和出参含有泛型。
public void m1(Map<String, User> map, List<User> list){
System.out.println("m1");
}
public Map<String,User> m2() {
System.out.println("m2");
return null;
}
public static void main(String[] args) throws Exception {
// 获取指定方法
Method m1 = TestMain3.class.getMethod("m1", Map.class, List.class);
// 获取该方法的泛型参数信息
Type[] genericParameterTypes = m1.getGenericParameterTypes();
// 循环该参数泛型数组
for (Type genericParameterType : genericParameterTypes) {
// 输出
System.out.println("m1泛型类型" + genericParameterType);
// 如果泛型属于是“参数化类型”
if (genericParameterType instanceof ParameterizedType) {
// 强转成参数化类型并获取他的真实类型
Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
// 循环该真实类型
for (Type actualTypeArgument : actualTypeArguments) {
// 输出泛型参数的真实类型
System.out.println("m1泛型真实类型" + actualTypeArgument);
}
}
}
// 得到m2方法
Method m2 = TestMain3.class.getMethod("m2", null);
// 得到m2方法的返回值泛型
Type genericReturnType = m2.getGenericReturnType();
// 如果泛型属于是“参数化类型”
if (genericReturnType instanceof ParameterizedType) {
// // 强转成参数化类型并获取他的真实类型
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
// 循环并输出
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println("m2: "+actualTypeArgument);
}
}
}
1.7 通过反射获取注解
定义两个注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TableZ {
String value();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldZ {
String colName();
String type();
int length();
}
定义实体类及属性,并打上自定义注解:
@AllArgsConstructor
@NoArgsConstructor
@Data
@TableZ("db_student")
class Student {
@FieldZ(colName = "db_id", type = "int", length = 10)
private int id;
@FieldZ(colName = "db_colName", type = "int", length = 10)
private int age;
@FieldZ(colName = "db_name", type = "String", length = 3)
private String name;
}
测试:
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class<?> c1 = Class.forName("org.example.spring.reflection.Student");
// 获取类的注解
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println("student类的注解为:" + annotation);
}
// 获得类的注解的值
TableZ tableZ = c1.getAnnotation(TableZ.class);
System.out.println("student类的注解的value值为:" + tableZ.value());
// 获得类指定的注解
Field field = c1.getDeclaredField("name");
FieldZ annotation = field.getAnnotation(FieldZ.class);
System.out.println("student类的name属性的注解的colName值为:" + annotation.colName());
System.out.println("student类的name属性的注解的length值为:" + annotation.length());
System.out.println("student类的name属性的注解的type值为:" + annotation.type());
}
三、类的加载顺序(题外)
有如下类:
class Temp {
static int a = 100;
static {
a = 300;
}
}
那么a的结果到底为多少?
a的结果为300。
实践证明,静态代码块和属性赋值,哪个写在前面便先执行哪个,所以后面的静态代码块中的代码后执行会覆盖前面的,故a = 300
总结
个人简单记录下。