Java注解,以及在框架中的使用

在Android开发中经常会使用到各种框架,如Retrofit、ButterKnife等,而这些框架中往往通过定义一些注解提供给我们使用,并且通过遵循框架暴露的规则就可以很方便的使用框架从而简化我们的开发过程。因此,今天我们就了解下注解的定义以及如何使用。

注解,为在代码中添加信息提供了一种形式化的方法,使我们可以在稍后的某个时刻非常方便的使用这些数据。

Java中提供了三种类型的注解:

  • @Override 表示当前的方法定义将覆盖父类中的方法。如果不小心拼错,编译器会发出错误提示。
  • @Deprecated  表示已过时,不建议使用。如果使用了注解为它的元素,编译器会发出警告信息。
  • @SuppressWarnings  使用它可以关闭不当的编译器警告信息。

Java提供了四种元注解,元注解用来负责注解其他的注解:

  • @Target  表示这个注解的作用域。可选的ElementType参数包括:CONSTRUCTOR(构造方法声明),FIELD(字段声明),LOCAL_VARIABLE(局部变量声明),METHOD(方法声明),PACKAGE(包声明),PARAMETER(参数声明),TYPE(类、接口,或enum声明),ANNOTATION_TYPE(注解类型的声明)
  • @Retention  表示在什么级别保存该注解信息。可选的RetentionPolicy参数包括:SOURCE(只在源码显示,编译时丢弃),CLASS(编译时记录到class中,运行时忽略),RUNTIME(运行时存在,可以通过反射读取)
  • @Inherited  允许子类继承父类中的注解
  • @Documented  将此注解包含在Javadoc中

我们看一下如何自己定义一个注解:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
    String value() default "";
}
//注解的使用
public class Testable {
    @Test 
    public void test() {
        System.out.println("test");
    }
}

可以看到,注解的定义跟接口的定义很像,但是它使用的是 @interface 关键字,并且需要用到元注解,上述注解定义中的 @Target 表示该注解应用于方法,@Retention 表示可以应用于运行时。在内部定义了一个类似方法的元素value,元素在注解中以无参的方式声明,可以通过default关键字指定其默认值。

在注解定义的内部可以不定义任何元素,这种称为标记注解。标记注解在程序中可以起到对某个内容标记的作用。

在注解中,还可以定义多个元素,当分析处理注解时,程序可以利用这些元素的值。下面是一个多元素的注解定义和使用:

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface UseCase {
    public int id();
    public String desc() default "no description";
}
//注解的使用
public class StringUtils {
    @UseCase(id = 3, desc = "hello")
    public void check(String s) {
        System.out.println("check " + s);
    }

    @UseCase(id = 5)
    public void print(String s) {
        System.out.println(s);
    }
}

提醒:如果注解中只有一个元素定义,最好把元素名称设为value,因为在使用该注解时,value作为key可省略,在使用的时候比较方便。

 

注解的解析

我们通过一个完整的例子来看下注解的解析。

定义一个注解(其中 @Target 中指定了该注解可以作用于方法、字段和类上):

package com.test.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Description {
	String value() default "no desc" ;
}

使用并解析注解信息:

package com.test.annotation;

public interface People {
	public String name();
	public int age();
	public void work();
}

package com.test.annotation;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

@Description("I am Class")
public class Child implements People {
    @Description("name filed")
    private String name;

    @Override
    @Description("name method")
    public String name() {
        return name;
    }

    @Override
    @Description
    public int age() {
        return 0;
    }

    @Override
    public void work() {
    }

    public static void main(String[] args) {
        try {
            Class<?> cls = Class.forName("com.test.annotation.Child");
            //Class<?> cls = Child.class;

            //判断类是否存在这个注解
            boolean isExist = cls.isAnnotationPresent(Description.class);
            if (isExist) {
                //获取类的注解
                Description des = cls.getAnnotation(Description.class);
                System.out.println(des.value());
            }

            //获取类的所有属性
            Field[] fields = cls.getDeclaredFields();
            for (Field f : fields) {
                //判断该属性是否存在这个注解
                boolean exist = f.isAnnotationPresent(Description.class);
                if (exist) {
                    //获取该属性的注解
                    Description des = f.getAnnotation(Description.class);
                    System.out.println(des.value());
                }
            }

            //获取类的所有公开方法
            Method[] methods = cls.getMethods();
            for (Method m : methods) {
                //判断该方法是否存在这个注解
                boolean exist = m.isAnnotationPresent(Description.class);
                if (exist) {
                    //获取该方法的注解
                    Description des = m.getAnnotation(Description.class);
                    System.out.println(des.value());
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

输出打印结果:

I am Class
name filed
name method
no desc

在该例中为了获取到注解信息,还用到了Java的反射机制获取类、方法和属性。并且为了更好的展示注解的使用,我们还通过类的继承关系看到了JDK的注解@Override的使用。

另外,解析注解信息还可以通过下面的方式(其实注解的解析相关的方法有很多,详细可以查看文档或者在开发环境中进行查看):

            Method[] methods = cls.getMethods();
            for (Method m : methods) {
                //获取该方法的所有注解
                Annotation[] annotations = m.getAnnotations();
                for (Annotation a : annotations) {
                    if (a instanceof Description) {
                        System.out.println(((Description) a).value());
                    }
                }
            }

 

简单框架的实现

下面通过实现一个简单的框架来看下注解在框架中的使用,该功能实现通过在Android Activity中对View进行注解,从而避免view通过findViewById的初始化过程。

首先定义一个注解:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BindView {
    int value();
}

框架的实现,获取注解,并进一步处理(其中用到了反射相关的操作,对反射不了解的话最好先去了解一下):

import android.app.Activity;
import android.view.View;

import java.lang.reflect.Field;

public class BindUtils {

    public static void bindView(Activity activity) {
        Class<? extends Activity> cls = activity.getClass();
        Field[] fields = cls.getDeclaredFields();

        for (Field field : fields) {
            boolean exist = field.isAnnotationPresent(BindView.class);
            if (exist) {
                BindView bindView = field.getAnnotation(BindView.class);
                if (bindView != null) {
                    int viewId = bindView.value();
                    View view = activity.findViewById(viewId);
                    field.setAccessible(true);
                    try {
                        field.set(activity, view);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

具体使用:

public class MainActivity extends AppCompatActivity {
    @BindView(R.id.textView)
    private TextView textView;
    @BindView(R.id.bottom_layout)
    private LinearLayout bottomLayout;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        BindUtils.bindView(this); //需要放在setContentView后面

        textView.setText("hello");
        bottomLayout.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                
            }
        });
    }
}

可以看到通过这个简单的框架,我们不需要再通过findViewById对view进行初始化了,而是框架帮我们做了这部分工作。

 

参考:《Java编程思想》

           https://www.jianshu.com/p/b560b30726d4

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值