zoukankan      html  css  js  c++  java
  • Java中的注解和反射

    个人博客

    http://www.milovetingting.cn

    Java中的注解和反射

    注解

    Java注解(Annotation)又称Java标注,是JDK5.0引入的一种注释机制。

    注解定义

    通过@interface来声明一个注解

    public @interface Anno {
        
    }
    

    元注解

    对注解进行注解的类就是元注解(meta-annotation),在自定义时,一般需要指定两个元注解

    @Target

    限制可以应用注解的Java元素类型,包括以下几种:

    public enum ElementType {
        /** Class, interface (including annotation type), or enum declaration */
        TYPE,
    
        /** Field declaration (includes enum constants) */
        FIELD,
    
        /** Method declaration */
        METHOD,
    
        /** Formal parameter declaration */
        PARAMETER,
    
        /** Constructor declaration */
        CONSTRUCTOR,
    
        /** Local variable declaration */
        LOCAL_VARIABLE,
    
        /** Annotation type declaration */
        ANNOTATION_TYPE,
    
        /** Package declaration */
        PACKAGE,
    
        /**
         * Type parameter declaration
         *
         * @since 1.8
         */
        TYPE_PARAMETER,
    
        /**
         * Use of a type
         *
         * @since 1.8
         */
        TYPE_USE
    }
    
    • TYPE:作用于类、接口或者枚举

    • FIELD:作用于字段

    • METHOD:作用于方法

    • PARAMETER:作用于方法参数

    • CONSTRUCTOR:作用于构造方法

    • LOCAL_VARIABLE:作用于局部变量

    • ANNOTATION_TYPE:作用于注解

    @Retention

    指定注解的保留阶段,有以下几种

    public enum RetentionPolicy {
        /**
         * Annotations are to be discarded by the compiler.
         */
        SOURCE,
    
        /**
         * Annotations are to be recorded in the class file by the compiler
         * but need not be retained by the VM at run time.  This is the default
         * behavior.
         */
        CLASS,
    
        /**
         * Annotations are to be recorded in the class file by the compiler and
         * retained by the VM at run time, so they may be read reflectively.
         *
         * @see java.lang.reflect.AnnotatedElement
         */
        RUNTIME
    }
    
    • SOURCE:注解只保留在源码中,编译时会被忽略。

    • CLASS:注解在编译时会保留,但JVM会忽略。

    • RUNTIME: 注解会被JVM保留,因此运行环境可以使用。

    注解类型元素

    @Retention(RetentionPolicy.SOURCE)
    @Target(ElementType.TYPE)
    public @interface Anno {
    
        //无默认值,在应用注解时必须设置
        String value();
    
        //有默认值
        int age() default 18;
    }
    

    注解应用场景

    根据Retention的类型,注解的应用场景有以下三种:

    SOURCE

    作用于源码级别的注解,可提供给IDE语法检查、APT等场景使用。

    IDE语法检查

    Android中提供了@IntDef注解

    @Retention(SOURCE)
    @Target({ANNOTATION_TYPE})
    public @interface IntDef {
        /** Defines the allowed constants for this element */
        int[] value() default {};
    
        /** Defines whether the constants can be used as a flag, or just as an enum (the default) */
        boolean flag() default false;
    }
    

    这个注解的意义在于能够取代枚举,实现如方法入参限制。

    如我们要限制参数只能在MONDAYTUESDAY中的一个,可以先定义常量

    public class WeekDay {
    
        public static final int MONDAY = 1;
    
        public static final int TUESDAY = 2;
    
    }
    

    然后定义注解

    @IntDef(value = {WeekDay.MONDAY, WeekDay.TUESDAY})
    @Target(ElementType.PARAMETER)
    @Retention(RetentionPolicy.SOURCE)
    public @interface Week {
    
    }
    

    使用注解

    public void test(@Week int week) {
    
    }
    
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        test(WeekDay.MONDAY);
    }
    
    APT注解处理器

    APT全称为:"Anotation Processor Tools",意为注解处理器。编写好的Java源文
    件,需要经过 javac 的编译,翻译为虚拟机能够加载解析的字节码Class文件。注解处理器是 javac 自带的一个工
    具,用来在编译时期扫描处理注解信息。

    注解处理器是对注解应用最为广泛的场景。在Glide、EventBus、ButterKnife、ARouter等常用框架中都有注解处理器的身影。

    CLASS

    定义为CLASS的注解,会保留在class文件中,但是会被JVM忽略。应用场景为:字节码增强,通过修改字节码文件来达到修改代码执行逻辑的目的。常用的框架有:AspectJ、热修复Roubust。

    RUNTIME

    注解保留至运行期,我们可以通过反射获取注解中的所有信息。

    反射

    反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。

    Class

    Class是一个类,封装了当前对象所对应的类的信息。

    获取Class对象

    获取Class对象的三种方法

    • 通过类名获取:类名.class

    • 通过对象获取:对象名.getClass()

    • 通过全类名获取:Class.forName(全类名) classLoader.loadClass(全类名)

    创建实例

    • 使用Class对象的newInstance()方法来创建Class对象对应类的实例。
    Class<?> c = String.class;
    Object str = c.newInstance();
    
    • 先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。这
      种方法可以用指定的构造器构造类的实例
    //获取String所对应的Class对象
    Class<?> c = String.class;
    //获取String类带一个String参数的构造器
    Constructor constructor = c.getConstructor(String.class);
    //根据构造器创建实例
    Object obj = constructor.newInstance("hello");
    System.out.println(obj);
    

    获取构造器信息

    得到构造器的方法

    Constructor getConstructor(Class[] params) -- 获得使用特殊的参数类型的public构造函数(包括父类)
    Constructor[] getConstructors() -- 获得类的所有公共构造函数
    Constructor getDeclaredConstructor(Class[] params) -- 获得使用特定参数类型的构造函数(包括私有)
    Constructor[] getDeclaredConstructors() -- 获得类的所有构造函数(与接入级别无关)
    

    获取类构造器的用法与上述获取方法的用法类似。主要是通过Class类的getConstructor方法得到Constructor类的
    一个实例,而Constructor类有一个newInstance方法可以创建一个对象实例:

    public T newInstance(Object ... initargs)
    

    获取类的成员变量信息

    Field getField(String name) -- 获得命名的公共字段
    Field[] getFields() -- 获得类的所有公共字段
    Field getDeclaredField(String name) -- 获得类声明的命名的字段
    Field[] getDeclaredFields() -- 获得类声明的所有字段
    

    调用方法

    获得方法信息的方法

    Method getMethod(String name, Class[] params) -- 使用特定的参数类型,获得命名的公共方法
    Method[] getMethods() -- 获得类的所有公共方法
    Method getDeclaredMethod(String name, Class[] params) -- 使用特写的参数类型,获得类声明的命名的方法
    Method[] getDeclaredMethods() -- 获得类声明的所有方法
    

    当我们从类中获取了一个方法后,我们就可以用 invoke() 方法来调用这个方法。 invoke 方法的原型为:

    public Object invoke(Object obj, Object... args)
    

    利用反射创建数组

    数组在Java里是比较特殊的一种类型,它可以赋值给一个Object Reference 其中的Array类为
    java.lang.reflect.Array类。我们通过Array.newInstance()创建数组对象,它的原型是:

    public static Object newInstance(Class<?> componentType, int length);
    

    反射获取泛型真实类型

    Type genType = object.getClass().getGenericSuperclass();
    Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
    return (Class<?>) params[0];
    

    基于注解和反射的简单应用

    通常我们获取Intent传过来的extra,是通过这样的形式:

    getIntent().getStringExtra("name");
    getIntent().getIntExtra("age",18);
    

    现在,我们通过注解和反射实现自动获取Extra,类似这样:

    @InjectExtra
    private String name;
    
    @InjectExtra("age")
    private int age;
    
    @InjectExtra("gender")
    private boolean gender;
    
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        InjectHelper.inject(this);
        Log.i(TAG, "name:" + name + ",age:" + age + ",gender:" + gender);
    }
    

    实现步骤

    1. 定义注解
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface InjectExtra {
        String value() default "";
    }
    
    1. 定义InjectHelper
    public class InjectHelper {
    
        /**
         * 注入Extra
         *
         * @param activity
         */
        public static void inject(Activity activity) {
            try {
                Class<? extends Activity> clz = activity.getClass();
                Field[] declaredFields = clz.getDeclaredFields();
                for (Field field : declaredFields) {
                    boolean annotationPresent = field.isAnnotationPresent(InjectExtra.class);
                    if (annotationPresent) {
                        InjectExtra annotation = field.getAnnotation(InjectExtra.class);
                        String name = annotation.value();
                        if (TextUtils.isEmpty(name)) {
                            //如果注解没有指定value,就用字段名
                            name = field.getName();
                        }
                        Object object = activity.getIntent().getExtras().get(name);
                        field.setAccessible(true);
                        field.set(activity, object);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
    }
    
    1. 使用
    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Intent intent = new Intent(this, TargetActivity.class);
            intent.putExtra("name", "zs");
            intent.putExtra("age", 18);
            intent.putExtra("gender", true);
            startActivity(intent);
        }
    }
    
    public class TargetActivity extends AppCompatActivity {
    
        private static final String TAG = InjectHelper.class.getSimpleName();
    
        @InjectExtra
        private String name;
    
        @InjectExtra("age")
        private int age;
    
        @InjectExtra("gender")
        private boolean gender;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            InjectHelper.inject(this);
            Log.i(TAG, "name:" + name + ",age:" + age + ",gender:" + gender);
        }
    }
    
    1. 输出结果
    2020-04-27 10:38:22.495 19687-19687/com.wangyz.annotation I/InjectHelper: name:zs,age:18,gender:true
    

    这里为了演示注解与反射,指定Retentaion为RUNTIME,实际上可以指定为SOURCE级别,通过APT来生成辅助类,来减少手动获取Extra的工作量。

  • 相关阅读:
    神经网络 分类: 机器学习 2014-08-12 11:41 87人阅读 评论(0) 收藏
    怎样使用OpenCV进行人脸识别 分类: 计算机视觉(转载) 2014-08-11 16:54 255人阅读 评论(0) 收藏
    machine learning的网站总结 2014-08-10 21:58 91人阅读 评论(0) 收藏
    C程序设计语言习题解答
    Debian下安装vim
    我不能输
    除了爱你还能爱谁
    Linux下配置OpenCV1.0环境
    1:A+B Problem
    3:大整数加法(待复习版)
  • 原文地址:https://www.cnblogs.com/milovetingting/p/12785305.html
Copyright © 2011-2022 走看看