Java基础-注解/反射(一)

Android知识总结

前言

  • 注解的底层也是使用反射实现的,我们可以自定义一个注解来体会下。注解和接口有点类似,不过申明注解类需要加上@interface,注解类里面,只支持基本类型、String及枚举类型,里面所有属性被定义成方法,并允许提供默认值。
  • 成员参数只能使用八种基本类型(byte、short、char、int、long、float、double、boolean)和String、Enum、Class、annotations等数据类型,及其数组。
  • 使用带有属性的Annotation时,必须为其所有定义的属性指定值(使用default的可以不用指定)
  • 定义Annotation时可以使用default关键字为属性设置默认值,使用时不为该属性指定值时会使用默认值
  • 如果Annotation中具有名为value的属性,在使用时如果只使用value属性的话,可以不写属性名直接指

一、四大元注解

1)、@Target ——注解用于什么地方
    ANNOTATION_TYPE,//给注解注解(这貌似把自己不当类来看)
    ElementType.FIELD  //注解作用于变量
    ElementType.METHOD //注解作用于方法
    ElementType.PARAMETER //注解作用于参数
    ElementType.CONSTRUCTOR //注解作用于构造方法
    ElementType.LOCAL_VARIABLE //注解作用于局部变量
    ElementType.PACKAGE //注解作用于包
2)、@Retention — 注解运行状态

Java中

public enum RetentionPolicy {
    /**
     *源码状态运行,
     */
    SOURCE,

    /**
     *编译类文件时运行
     */
    CLASS,

    /**
     * 运行时运行
     */
    RUNTIME
}

kotlin中

public enum class AnnotationRetention {
    /** Annotation isn't stored in binary output */
    SOURCE,
    /** Annotation is stored in binary output, but invisible for reflection */
    BINARY,
    /** Annotation is stored in binary output and visible for reflection (default retention) */
    RUNTIME
}
  • RetentionPolicy.SOURCE:标记的注解仅保留在源码中,并被编译器忽略,注解信息会被丢弃,不会保留在编译好的class文件里。
  • RetentionPolicy.CLASS:标记的注解在编译时由编译器保留在class字节码文件,在类加载的时被丢弃,运行时无法获取到,在java虚拟机(JVM)中忽略(Android 会在打包成dex的时候抛弃)。
  • RetentionPolicy.RUNTIME:标记的注解由JVM保留,可以在运行时环境可以使用它,可以通过反射机制读取注解信息(源码、class文件和执行的时候都有注解的信息)。

总结: SOURCE < CLASS < RUNTIME, 既CLASS包含了SOURCE,RUNTIME包含SOURCU、CLASS

3)、@Documented — 生成说明文档,添加类的解释

表示注解会被包含在javaapi文档中
当一个注解被@Documented元注解所修饰时,那么无论在哪里使用这个注解,都会被Javadoc工具文档化。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)public @interface Documented {
}

这个元注解呗@Documented修饰,表示它本身会被文档化。@Retention注解的值RetentionPolicy.RUNTIME表示@Documented这个注解能保留在运行时;@Target元注解的值ElementType.ANNOTATION_TYPE表示@Documented这个注解只能够来修饰注解类型

4)、@Inherited — 允许子类继承父类中的注解

允许子类继承父类的注解。
用于描述某个被标注的类型可被继承的,如果一个使用了@Inherited修饰的annotation类型类型被用于一个class,则这个annotation将被用于该class类的子类。
表明被修饰的注解类型是自动继承的。如果你想让一个类和它的子类都包含某个注解,就可以使用@Inherited来修饰这个注解。也就是说,假设@Parent类是Child类的父类,那么我们若用被@Inherited元注解所修饰的某个注解对Parent类进行了修饰,则相当于Child类也被该注解所修饰了。这个元注解的定义如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
@Inherited
public @interface MyAnnotation {
}

@MyAnnotation
public class MySuperClass {
}

public class MySubClass extends MySuperClass {
}

上述代码的大致意思是使用@Inherited修饰注解MyAnnotation使用MyAnnotation注解MySuperClass实现类MySubclass继承自MySuperClass

当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。

二、注解的运用场景

  • 运行时注解(RUNTIME)在程序运行期间,运用反射动态获取对象、属性、方法等,一般的IOC框架就是这样,可能会牺牲一点效率。 运用反射技术
  • 字节码注解(CLASS)在程序编译时,编译出CLASS后,通过修改CLASS数据已实现修改代码的逻辑目的。对于是否需要修改的区分或者要修改成不同逻辑的逻辑判断可以使用注解。 运用字节码增强(AspectJ技术实现)
    大名鼎鼎的ButterKnife、ARouter运用的就是编译时注解。在我们编译时,就根据注解,自动生成了一些辅助类。
  • 源码(SOURCE)在编译期间能获取注解与注解声明的类包括类中所有成员信息,一般用于生成额外的辅助类,提供语法检查(IDE实现检测语法)。如:@IntDef,@DrawableRes。 运用APT技术
  • 你得先依赖APT,然后自己写一个类继承AbstractProcessor,重写process方法,在里面实现如何把配置或注解的信息变成所需要的类。APT技术可以在SOURCE、CLASS、RUNTIME中使用。

三、自定义实现注解

1)、运行时注解(RUNTIME)
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface TestAnnonation {
       String name() default "";
       int Id()  default 0;
    }

注解关键字时@interface,然后上面标注为元注解,表示只能修饰方法并且加载到虚拟机中,里面时这个注解所具有的属性,name, id,我们在给方法加注解的时候设置相应的值。

    @TestAnnonation(name = "android" , Id = 1)
    private void testAnno(){

    }

上面我们在一个方法上面添加注解,然后我们通过下面的方法将这个注解打印出来

    private void outputAnnoDetail(Class clazz){
        Method [] methods = clazz.getDeclaredMethods();
        for(Method method  : methods) {
            TestAnnonation testAnnonation  = method.getAnnotation(TestAnnonation.class);
            if (testAnnonation != null) {
                Log.d("anonation", "name------>" + testAnnonation.name() + "------>Id------>" + testAnnonation.Id());
            }
        }
    }
2)、编译时注解(CLASS)
compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'

四、标准注解/内建注解

1、@Override注解

@Override注解用来修饰对父类进行重写的方法。如果一个并非重写父类的方法使用这个注解,编译器将提示错误。

public class MySuperClass {

    public void doTheThing() {
        System.out.println("Do the thing");
    }
}
public class MySubClass extends MySuperClass{

    @Override
    public void doTheThing() {
        System.out.println("Do it differently");
    }
}
2、@ Deprecated

@Deprecate 标记类、方法、属性,如果上述三种元素不再使用,使用@Deprecated注解,建议用户不再使用。

@Deprecated
public class MyComponent {
}
3、@SuppressWarnings

@SuppressWarnings 用来抑制编译器生成警告信息。可以修饰的元素为类,方法,方法参数,属性,局部变量。

@SuppressWarnings
public void methodWithWarning() {
}

五、Android注解框架 ButterKnife

  • ButterKnife注解框架是大家常用的注解框架,它主要作用是绑定View并且绑定View常用的监听事件,下面是其中一个注解
        @Retention(CLASS)
        @Target(FIELD)
        public @interface BindView {
           /** View ID to which the field will be bound. */
           @IdRes int value();
       }
  • 通过上面的代码可以看出,ButterKnife的注解保留方式为CLASS模式,也就是会保留到class中但是不会背加载到虚拟机中,这个时候我们就要看下它的AbstractProcessor,一般标注为Class的都会重写AbstractProcessor类,这样在虚拟机进行编译的时候就会做相应的处理。

主要看一下几个方法:

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new LinkedHashSet<>();
        for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
          types.add(annotation.getCanonicalName());
        }
        return types;
     }

    private Set<Class<? extends Annotation>> getSupportedAnnotations() {
        Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();

        annotations.add(BindArray.class);
        annotations.add(BindBitmap.class);
        annotations.add(BindBool.class);
        annotations.add(BindColor.class);
        annotations.add(BindDimen.class);
        annotations.add(BindDrawable.class);
        annotations.add(BindFloat.class);
        annotations.add(BindInt.class);
        annotations.add(BindString.class);
        annotations.add(BindView.class);
        annotations.add(BindViews.class);
        annotations.addAll(LISTENERS);

        return annotations;
    }

上面的方法主要表明会处理哪些注解

    @Override
    public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
        Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);

        for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
          TypeElement typeElement = entry.getKey();
          BindingSet binding = entry.getValue();

          JavaFile javaFile = binding.brewJava(sdk);
          try {
            javaFile.writeTo(filer);
          } catch (IOException e) {
           error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
          }
        }

        return true;
     }
  • 然后就是在编译的时候的具体处理过程,这个过程主要时先找到并解析注解,然后生成java文件,这样在虚拟机真正执行的时候就不用去查找和解析,也就不会耗时了。
  • EventBus是使用运行时注解,主要的作用是在运行的时候会去查找所有被注解的方法,然后再去解析注解。运行时注解会影响程序的性能,毕竟在运行的时候有一个查找的过程,所以运行时注解的作用一般是标记一个作用区。

五、注解基础知识思维导图

六、实战

1)用RUNTIME注解模仿ButterKnife

1、定义注解

  • 作用字段:减少 view 的 findViewById 使用
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectVariate {
    @IdRes int value();
}
  • 作用字段:直接给参数复制使用
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectValue {
    String value() default "";
}

2、工具类

public class InjectUtils {
    public static void init(Activity activity) {
        InjectUtils.initVariateControl(activity);
        InjectUtils.initValueControl(activity);
    }
    /**
     * 减少 view的 findViewById 使用
     *
     * @param activity
     */
    public static void initVariateControl(Activity activity) {
        Class<? extends Activity> aClass = activity.getClass();
        //获取此类所有成员
        Field[] declaredFields = aClass.getDeclaredFields();
        for (Field field : declaredFields) {
            //判断此类是否是 ControlAnnotation 注解
            if (field.isAnnotationPresent(InjectVariate.class)) {
                InjectVariate annotation = field.getAnnotation(InjectVariate.class);
                //获取注解中设置的ID
                int value = annotation.value();
                //通过ID获取view
                View view = activity.findViewById(value);
                //设置可以访问私有类型
                field.setAccessible(true);
                try {
                    //第一个参数是,着那个对象上去设置属性/调用方法。
                    //static 方法/属性时,第一个参数可为空。因为 A.i, new A().i;
                    //可以看到static 是可以直接调用,非static是必须new一个对象才能调用。
                    field.set(activity, view);  //设置值
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }
  /**
     * 用于Activity 的获取值
     * 用Intent设置数据,都是保存在Bundle中;获取也是从 Bundle中根据key获取。
     * @param activity
     */
    public static void initValueControl(Activity activity) {
        Class<? extends Activity> aClass = activity.getClass();
        Field[] declaredFields = aClass.getDeclaredFields();
        for (Field filed : declaredFields) {
            if (filed.isAnnotationPresent(InjectValue.class)) {
                InjectValue annotation = filed.getAnnotation(InjectValue.class);
                String value = annotation.value();
                filed.setAccessible(true);
                Bundle extras = activity.getIntent().getExtras();
                if (extras == null) {
                    return;
                }
                String key = value.isEmpty() ? filed.getName() : value;
                if (extras.containsKey(key)) {
                    Object obj = extras.get(key);
                    //获取数组单个元素类型
                    Class<?> componentType = filed.getType().getComponentType();
                    //当前属性数组是否是parcelable(子类)数组
                    if (filed.getType().isArray() && Parcelable.class.isAssignableFrom(componentType)) {
                        Object[] objs = (Object[]) obj;
                        //创建对应数组的object拷贝
                        Object[] objects = Arrays.copyOf(objs, objs.length, (Class<? extends Object[]>) filed.getType());
                        obj = objects;
                    }
                    filed.setAccessible(true);
                    try {
                        filed.set(activity, obj);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

3、运用

public class MainActivity extends AppCompatActivity {
    @InjectVariate(value = R.id.name_tv)
    private TextView nameTv;
    @InjectValue(value = "name")
    private String name;
    @InjectValue
    private int age;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        InjectUtils.init(this);
        nameTv.setText(name + "=========" + age);
    }
}

利用APT技术实现的项目,源码

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,542评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,822评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,912评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,449评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,500评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,370评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,193评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,074评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,505评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,722评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,841评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,569评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,168评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,783评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,918评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,962评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,781评论 2 354

推荐阅读更多精彩内容