Android开发者必知的java知识(二)Annotation

本文介绍了Java注解的基本概念,包括内置注解、自定义注解的定义与使用、注解处理器及注解嵌套等内容,并通过实例展示了注解在实际开发中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载请注明出处: 西木的博客

注解是众多引入Java SE5中的重要语言变化之一。它们可以提供用来完整地描述程序所需的信息,而这些信息是无法用Java来表达的。因此,注解使得我们可以将由编译器来测试和验证的格式,存储有关程序的额外信息。在Android开发中,大量的基础框架都用到了注解机制,我们也可以编写自己的注解,来加快我们程序的开发效率。这一讲,我们就先来回顾一下注解的基本原理,为后续的章节打下基础:

1.内置注解

JavaSE中内置了三种注解,定义在java.lang中:

  • @Override 表示当前的方法定义将覆盖超类中的方法
  • @Deprecated 如果程序元使用了注解为它的元素,编译器将发出警告
  • @SuppressWarnings 关闭不当的编译器警告信息

Java还另外提供了四种注解,专门负责新注解的创建。稍后我们将学习它们。

2. 定义注解

我们先来看一个最简单的定义注解的例子

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

注解的定义看起来很像接口的定义,注解定义需要注意以下几点:

  1. 定义注解时,会需要一些元注解,入@Target和@Retention, @Target用来定义你的注解将应用于什么地方,@Retention用来定义注解在哪一级别可用(Source, Class 或 Runtime)
  2. 注解的名称前使用@interface关键字,这个关键字声明隐含了一个信息:它是继承了java.lang.annotation.Annotation接口,并非声明了一个interface
  3. 注解可以定义一些元素,看起来像接口中的方法,注解的元素方法必须无参数,无异常抛出。并且可以后面可以用default 加上一个默认值
  4. 如果注解中的元素没有提供默认值,则注解在使用时必须为其提供值
  5. 如果注解中定义了名为value的元素,并且在应用该注解的时候,该元素是唯一需要负值的元素,那么此时无需使用名-值对的形式,只需在括号内给出value元素的值即可
  6. 注解元素可用的类型有限,仅为:[基本类型, String,Class,enum,Annotation,以及以上类型的数组],如果使用其他任何自定义类型就会报错。
  7. 默认值限制: 元素的默认值不能是不确定的值,对于非基本类型,不能以null作为其值。

3.注解的使用

我们先来看一个稍微复杂点的例子:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase{
    public int id();
    public String description() default "no description";
}

我们定义了一个简单的注解,用它来跟踪一个项目中的用例。如果一个方法或一组方法实现了某个用例的需求,那么程序员可以为此方法加上该注解。

我们来看该注解如何使用:

class PassWordUtils
{

    @UseCase(id = 47, description = "password must contain at least one character")
    public boolean validatePassword(String password)
    {
        return password.matches("\\w+\\d\\w*");
    }

    @UseCase(id = 48)
    public String encryptPassword(String password){
        return new StringBuilder(password).reverse().toString();
    }
}

这是一个验证password的实用类,它的两个函数都用UseCase标记了,我们可以看到,第二个case,我们没有提供 description元素值,因为它声明时,包括了默认值。

4.注解处理器

如果没有注解处理器,那么注解实际上不能发挥任何作用,大多数时候,程序员主要是定义自己的注解,并编写注解处理器来处理它们。以上述UseCase为例,我们可以编写一个处理器,找到PasswordUtils类中,所有声明的用例,输出它们。

class UseCaseTracker{
    public static void trackUseCases(Class<?> cl)
    {
        for(Method m: cl.getDeclaredMethods()){
            UseCase uc = m.getAnnotation(UseCase.class);
            if(uc != null){
                System.out.println("Found use case: " + uc.id() + " " 
            + uc.description()+ ", method: "+ m.getName());
            }
        }
    }
}

public class Launcher{
    public static void main(String[] args) {
        UseCaseTracker.trackUseCases(PassWordUtils.class);
    }
}

我们首先通过 cl.getDeclaredMethods 拿到类中声明的所有方法,然后依次遍历这些方法,拿到类型为UseCase的annotation,如果拿到,就通过注解的元素方法输出注解上标注的值。

5.注解嵌套

我们依然以一个例子开始演示注解嵌套的使用:

//Constraints 注解代表对SQL列元素限制修饰
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
    boolean primaryKey() default false;
    boolean allowNull() default true;
    boolean unique() default false;
}
//代表类型为String的SQL表列
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {
    //length of varchar
    int value() default 0;
    String name() default "";
    Constraints constraints() default @Constraints;
}

大家可以看到我们定义的SQLString注解中,嵌套了类型为Constraints注解的元素,而且它的默认值为@Constraints,这实际上就是一个所有元素都为默认值的@Constraints注解,如果我们想让默认值得unique位true,我们可以写成:

Constraints constraints() default @Constraints(unique = true);

我们来看看如何使用嵌套注解呢:

@DBTable(name = "MEMBER")
public class Member {
    @SQLString(30)
    String firstName;

    @SQLString(40)
    String lastName;

    @SQLString(value = 40, constraints = @Constraints(primaryKey = true))
    String token;
}

我们可以看到对于嵌套注解的使用,还是比较麻烦的,我们构造出一个类型为Constraints注解的元素,赋给SQLString中得constraints元素。

我们希望通过声明一个Member类,来创建一个数据库表,所有加注解得field位表中的列,我们应该再创建一个注解处理器,这个处理器可以分析Member类,从而创建数据库表。

public class TableCreator {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        createTable(Member.class);
    }

    public static void createTable(Class<?> clazz)
    {
        //1.找到数据库表注解和name元素
        DBTable dbTable = clazz.getAnnotation(DBTable.class);
        if(dbTable == null){
            System.out.println("No dbtable annotations in class " + clazz.getName());
            return;
        }

        String tableName = dbTable.name();
        if(tableName.length() < 1){
            tableName = clazz.getName().toUpperCase();
        }

        ArrayList<String> columnsDefs = new ArrayList<>();
        //2.遍历所有field
        for(Field field :  clazz.getDeclaredFields())
        {
            String columnName = null;
            Annotation[] anns = field.getDeclaredAnnotations();
            if(anns.length < 1){
                continue;
            }

            //找到SQLString类型的注解
            if(anns[0] instanceof SQLString){
                SQLString sString = (SQLString)anns[0];

                if(sString.name().length() < 1){
                    columnName = field.getName().toUpperCase();
                }else{
                    columnName = sString.name();
                }
                //添加列定义
                columnsDefs.add(columnName + " VARCHAR("+sString.value()+")" 
                        +getConstraints(sString.constraints()));

            }
        }

        StringBuilder createCommand = new StringBuilder("CREATE TABLE "+tableName +"(");
        for(String columnDef : columnsDefs){
            createCommand.append("\n   "+columnDef + " ");
        }
        String tableCreate = createCommand.substring(0, createCommand.length() - 1) + ")";
        System.out.println("Table creation sql for "+ clazz.getName() + " is : \n"+ tableCreate);
    }

    private static String getConstraints(Constraints con)
    {
        String constraints = "";
        if(!con.allowNull()){
            constraints += "NOT NULL";
        }
        if(con.primaryKey()){
            constraints += " PRIMARY KEY";
        }
        if(con.unique()){
            constraints += " UNIQUE";
        }

        return constraints;
    }

}

main函数执行最后的输出为:

Table creation sql for com.junli.annotation.Member is : 
CREATE TABLE MEMBER(
   FIRSTNAME VARCHAR(30) 
   LASTNAME VARCHAR(40) 
   TOKEN VARCHAR(40) PRIMARY KEY)

createTable方法会对传入的Class,检查该类是否带有@DBTable注解, 如果有,就将发现的表名保存下来。然后读取这个类的所有域,对于SQLString注解的域使用对应的处理块构造出相应地column名,对于嵌套的Constraints注解则传递给getConstraints方法,构造出一个含有SQL约束的String对象。

好了,到这里对Java中注解的基本讲解就到这里,下一节中,我们将结合实际的例子分析注解的巧妙用处。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值