Java注解 + 反射的使用

在写代码的过程中,经常会碰见注解,类似于重写的注解@Override,在架构的过程中,也常常需要自定义注解,因此掌握Java注解的使用,是非常有必要的。

1、Java注解

Java注解就是用来解释代码,对程序作出解释,其他的程序也可以获取该注解。注解使用的场景也很多,像package、method、class、field等,通常是通过反射来获取注解信息。

2、Java的注解类型

(1)内置注解

常见的内置注解有@Override,关于重写的注解;

@SupressWraning,用来抑制编译时的异常;

	@SuppressWarnings("all")
    public void text01(){
        
    }

没有加SupressWraning之前,方法名是灰色的,加了之后,就是编译之后的颜色。

@Deprecated,过时的,程序员不鼓励使用的元素,但是还是可以使用的。

    @Deprecated
    public static void test(){
      
    }
   	//这里没有体现,test是被画横线的,就是被移除的
    public static void main(String[] args){
        test();
    }

(2)元注解

Java中定义了4种元注解,用来对注解进行说明,分别为@Target@Rentation@Document@Inherited

@Target:用来描述注解的使用范围,该注解可以使用在什么地方。
@Rentation:用来描述注解的生命周期(SOURCE < CLASS < RUNTIME)
@Documented:该注解将被包含在javadoc中
@Inherited:表示子类可以继承父类的注解

常用的注解就是前两种。

(3)自定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
   
}

在自定义注解的时候,需要添加TargetRetention注解,Target代表该注解只能使用在方法上,不能使用在其他地方,该注解的声明周期是在运行时才会有效。

3、自定义注解的使用

(1)注解参数

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {

    //参数类型 + 参数名 ();     如果没有默认值,需要赋值
    String name() default "";

}

注意,在注解中写入的都是参数,不是方法!这些参数的返回值只能是基本数据类型、Class、String、枚举

	@MyAnnotation(name = "jiajia")
    public static void main(String[] args){
        test();
    }

在注解中,也需要将这个参数写到注解上,这是在没有使用default赋值的时候,如果使用default赋初始值,那么就不需要再注解中赋值。

4、反射机制

自定义的注解,只能通过反射来获取注解信息,这样自定义的注解才有意义。

public class Student {

    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public Student(int age){
        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;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

下面在使用反射时,就是利用上述实体类来完成。

(1)根据对象获取该类名。

        Student student = new Student("lei",20);

        Class<? extends Student> aClass = student.getClass();
        String name = aClass.getName();
        System.out.println("class=="+aClass+",name=="+name);

通过getClass + getName方法可以获取该对象的类。

那么那些有class对象:类、Class、接口、注解、枚举、数组、void、基本数据类型…

(2)通过类对象获取构造方法(全部)

        //获取构造方法
        Constructor<?>[] constructors = aClass.getDeclaredConstructors();
        for (int i = 0; i < constructors.length; i++) {
            //获取该构造方法的修饰符(private public protected)
            int modifiers = constructors[i].getModifiers();
            System.out.println(Modifier.toString(modifiers));
            //构造方法成员类型
            Class<?>[] parameterTypes = constructors[i].getParameterTypes();
            for (int j = 0; j < parameterTypes.length; j++) {
                System.out.println(parameterTypes[j].getName());
            }
public
java.lang.String
int
public
int

通过使用某个类的getDeclaredConstructors方法就可以获取到所有的构造方法,当然想要获取某个构造函数,比如说只有int形参的构造方法。

        //获取只有int类型参数的构造方法
        Class[] intType = {int.class};
        Constructor<? extends Student> constructor = aClass.getDeclaredConstructor(intType);

(3)通过构造方法创建实例

        //得到构造方法,创建实例
        Student stu = constructor.newInstance(12);
        int age = stu.getAge();
        System.out.println(stu.toString()+",age==="+age);
Student{name='null', age=12},age===12

在得到某个构造方法后,调用newInstance方法,就可以创建一个实例。

注意一点:当类使用newInstance()动态创建一个对象的时候,它默认调用的是类的无参构造函数,因此在类中必须要实现一个无参的构造方法;当然也不是一定要存在无参构造,可以在调用newInstance时,传入参数。

		Class<?> c1 = Class.forName("com.example.eventbus.User");
        System.out.println(c1);

        User user = (User) c1.newInstance();
		#User类中必须要有一个空参的构造方法,否则这样的方式运行就会报错

(4)使用类中的私有方法

 //获取类中的私有方法
        Class[] classType = {String.class,int.class};
        Constructor<? extends Student> declaredConstructor = aClass.getDeclaredConstructor(classType);
        Student student1 = declaredConstructor.newInstance("lay", 28);
        //setName方法中的形参是String类型的
        Class[] newtype = {String.class};
        Method method = aClass.getDeclaredMethod("setName", newtype);
        //private类型需要设置为可使用
        method.setAccessible(true);
        String[] strList = {"jia"};
        //修改后的新实例
        Object student2 =  method.invoke(student1, strList);
        System.out.println(student1.toString());

通过getDeclaredMethod得到某个具体的方法,传入方法名和该方法的形参类型,调用invoke执行该方法,然后传入要修改的对象,以及要修改的属性。

invoke:激活该方法,该方法可以传入可变的参数列表,第一个参数往往是该类对象,后面的参数就是需要修改的参数,根据Paramtype来设置。

(5)获取类中的静态私有方法

  //调用类中的私有静态方法
        Method work = aClass.getDeclaredMethod("work");
        work.setAccessible(true);
        //静态方法可直接执行
        work.invoke(null);

(6)获取类中某个私有字段

        Student stu3 = new Student("lay",23);
        Class<? extends Student> stu3Class = stu3.getClass();
        //获取该字段属性
        Field name= stu3Class.getDeclaredField("name");
        name.setAccessible(true);
        //得到该类中的字段值
        //Object filedObject = name.get(stu3);
        name.set(stu3,"jia");

通过getDeclaredField方法,获取“name”这个字段属性,name字段拥有get和set方法,通过set方法可以设置该字段的值,传入的第一个参数为该类对象,第二个参数为修改的属性值。

(7)setAccessible的性能优化

当调用的属性或者方法是私有的时候,需要设置setAccessible为true,这样就可以访问该属性或者方法;当访问的属性和方法是共有属性的时候,设置setAccessible为true,则可以提高性能。

 public static void testNormal(){
        long startTime = System.currentTimeMillis();

        User user = new User("lay",20,001);

        for (int i = 0; i < 1000000000; i++) {
            user.getUsername();
        }

        long endTime = System.currentTimeMillis();

        System.out.println("正常的处理:"+(endTime - startTime));
    }

    public static void testReject() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        long startTime = System.currentTimeMillis();

        User user = new User("lay",20,001);

        Class<? extends User> c1 = user.getClass();
        Method getUsername = c1.getDeclaredMethod("getUsername", null);

        for (int i = 0; i < 1000000000; i++) {
            getUsername.invoke(user,null);
        }

        long endTime = System.currentTimeMillis();

        System.out.println("正常的处理:"+(endTime - startTime));
    }

    public static void testRejectNo() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        long startTime = System.currentTimeMillis();

        User user = new User("lay",20,001);

        Class<? extends User> c1 = user.getClass();
        Method getUsername = c1.getDeclaredMethod("getUsername", null);
        //关闭检测
        getUsername.setAccessible(true);

        for (int i = 0; i < 1000000000; i++) {
            getUsername.invoke(user,null);
        }

        long endTime = System.currentTimeMillis();

        System.out.println("正常的处理:"+(endTime - startTime));
    }

运行结果:

正常的处理:2477
反射处理:3163
关闭检测处理:2315

(8)反射获取泛型

在JVM进行编译时,会将Java的泛型信息擦除,所有的对象都视为Object,因此Java中多出了几个方法,用反射来获取泛型信息。

public class Test11{
        public void test01(Map<String,User> map, List<User> list){
            System.out.println("test01");
        }

        public Map<String,User> getMap(){
            System.out.println("getMap");
            return null;
        }
    }

想要获取test01方法的参数泛型信息,可以使用该方法的getGenericParameterTypes来获取。

		Method test01 = Test11.class.getMethod("test01", Map.class, List.class);
        Type[] parameterTypes = test01.getGenericParameterTypes();
        for (Type types :
                parameterTypes) {
            System.out.println(types);
        }

这样获取的是List和Map的信息:

java.util.Map<java.lang.String, com.example.eventbus.User>
java.util.List<com.example.eventbus.User>

如果想要获取Map中的String和User的信息,需要解析types 类型:

Method test01 = Test11.class.getMethod("test01", Map.class, List.class);
        Type[] parameterTypes = test01.getGenericParameterTypes();
        for (Type types :
                parameterTypes) {
            System.out.println(types);
            //如果types 是参数化类型,就解析这个types 
            if(types instanceof ParameterizedType){
                Type[] actualTypeArguments = ((ParameterizedType) types).getActualTypeArguments();
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.println(actualTypeArgument);
                }
            }
        }

最终得到了全部的泛型信息:

java.util.Map<java.lang.String, com.example.eventbus.User>
class java.lang.String
class com.example.eventbus.User
java.util.List<com.example.eventbus.User>
class com.example.eventbus.User

如果想要获取getMap返回的泛型信息,就得使用getGenericReturnType获取返回的泛型信息。

Method getMap = Test11.class.getMethod("getMap", null);
        Type type = getMap.getGenericReturnType();
        if(type instanceof ParameterizedType){
            Type[] actualTypeArguments = ((ParameterizedType) type).getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments) {
                System.out.println(actualTypeArgument);
            }
        }

获取的泛型信息。

class java.lang.String
class com.example.eventbus.User

(9)反射获取注解信息

之间在JetPack组件中,使用过Room组件,其中就涉及到了很多注解,这里就写一些用到过的注解。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Entity {
    String value();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ColumInfo {
    boolean PrimaryKey();
    String column_name();
    int length();
}
@Entity("db_user")
public class User {

    @ColumInfo(PrimaryKey = true,column_name = "db_username",length = 10)
    public String username;
    @ColumInfo(PrimaryKey = false,column_name = "db_age",length = 10)
    private int age;
    @ColumInfo(PrimaryKey = false,column_name = "db_number",length = 10)
    private int number;

如果通过反射得到这些注解信息,然后做相应的操作处理。

 Class<?> c1 = Class.forName("com.example.eventbus.User");
        Annotation[] annotations = c1.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }

通过getAnnotations获取全部的注解,发现得到的是User类的全部信息。

@com.example.eventbus.annotation.Entity(value=db_user)

如果想要获取value的值,需要通过getAnnotation获取特定的注解。

 		Entity entity = c1.getAnnotation(Entity.class);
        String value = entity.value();
        System.out.println(value);

对于属性的注解,获取的方式和类一致。

		Field username = c1.getDeclaredField("username");
        Annotation[] annotations1 = username.getAnnotations();
        for (Annotation annotation : annotations1) {
            System.out.println(annotation);
        }

得到的结果:

@com.example.eventbus.annotation.ColumInfo(PrimaryKey=true, column_name=db_username, length=10)

如果想要获取其中的值,就需要单独解析。

		ColumInfo annotation = username.getAnnotation(ColumInfo.class);
        String name = annotation.column_name();
        int length = annotation.length();
        boolean isKey = annotation.PrimaryKey();
        System.out.println(name);
        System.out.println(length);
        System.out.println(isKey);

最终的结果:

db_username
10
true
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Awesome_lay

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值