• Java注解学习笔记


    我们平常写Java代码,对其中的注解并不是很陌生,比如说写继承关系的时候经常用到@Override来修饰方法。但是@Override是用来做什么的,为什么写继承方法的时候要加上它,不加行不行。如果对Java的注解没有了解过,很难回答这些问题。并且,现在越来越多的第三方库开始使用注解,不了解注解的话很难理解他们的逻辑。趁着五一假期,赶紧补习一下什么是注解。

    概况

    注解是Java5之后引入的新特性,它与class,interface,enum处于同一层次。可以理解为在代码中插入一段元数据。它们是在实际的源代码级别保存信息,而不是某种注释性质的文字,这样能够使源代码整洁,便于维护。它可以在三个时期起作用,分别是编译时,构建时和运行时。他们可以在编译时使用预编译工具进行处理,也可以在构建时影响到Ant,Maven等打包工具,还可以在运行期使用反射机制进行处理。

    基本用法

    不带参数:

    @Override
    public void onCreate(Bundle savedInstanceState){
        //...
    }
    

    带参数:

    @CustomizeAnnotation( name = "wakaka", age = 22)
    //...
    

    只有一个参数(可以不指定字段名):

    @CustomizeAnnotation("wakaka")
    

    java自带的标准注解:

    • @Deprecated 标记这个元素被弃用,如果在其它地方对它引用/使用,编译器会发出警告信息。
    • @Override 表示当前的方法覆盖父类中定义的方法。如果不小心拼写错误,或者方法签名对应不上父类的方法,编译器会报出错误提示。
    • @SuppressWarnings 关闭警告信息。

    定义注解

    直接上例子:

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

    使用方法:

    public class MainAnnotation {
        @TestAnnotation
        public void testMethod() {
        }
    }
    

    定义注解跟定义接口差不多,只不过关键字要换成 @interface。定义注解时需要用到元注解,比如 @Target, @Retention@Target用来定义注解的使用位置,包括类,方法等;@Retention定义注解在哪一个级别可用,源代码、类、运行时。

    注解中一般都包含某些元素来表示某些值。分析注解的时候,主程序或者构建工具可以获取到这些信息。没有元素的注解称为标记注解,比如说@Override @Deprecated

    定义注解元素的方式类似于定义接口中的方法,区别在于可以为注解中的元素添加默认值。例子:

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

    用法:

    public class PasswordUtils {
        @UseCase(id = 47, description = "Password must contain at least one numeric")
        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();
        }
    
        @UseCase(id = 49, description = "New password can't equal previously used ones")
        public boolean checkForNewPassword(List<String> prevPasswords, String password) {
            return !prevPasswords.contains(password);
        }
    }
    

    从上面的例子可以看到,元素的定义类似于方法的定义。方法名就是元素名。使用default关键字可以为一个元素增加一个默认值。

    使用的时候除了带有默认值的元素,需要把所有的元素的值填满。

    元注解

    Java目前内置了四种元注解

    1. @Target
      表示该注解可以应用的地方。参数使用ElementType:
      • CONSTRUCTOR 构造器的声明;
      • FIELD 域声明;
      • LOCAL_VARIABLE局部变量的声明;
      • METHOD方法声明;
      • PACKAGE包的声明;
      • PARAMETER参数声明;
      • TYPE 类、接口、注解、枚举声明;
    2. @Retention
      表示需要在什么级别保存该注解信息。参数使用RetentionPolicy
      • SOURCE注解将被编译器丢弃;
      • CLASS注解在class文件中使用,但是会被VM丢弃;
      • RUNTIMEVM将在运行期也保留注解,因此可以通过反射机制读取注解的信息。
    3. @Documented
      将此注解包含在Javadoc中。
    4. @Inherited
      允许子类继承父类的注解

    大多数时候,我们都需要定义自己注解,并编写自己的处理器来处理他们。

    注解元素

    注解元素可用的类型如下:

    • 所有基本类型(int, boolean, char, long, byte...)
    • String
    • Class
    • enum
    • Annotation
    • 以上类型的数组

    使用这些类型以外的类型会报错。不允许使用Integer,Character等包装类型。

    默认值的限制

    • 所有元素要么有指定的值,要么有默认值;
    • 非基本类型的值,无论是指定值还是默认值都不能用null

    注解不支持继承

    不能使用extend来继承某个@interface类型。

    编写注解处理器

    如果没有读取注解的逻辑,那注解跟注释是差不多的。我们可以利用Java的反射机制构造注解处理器,或者利用工具apt解析带有注解的Java源代码。

    例子:

    public class UseCaseTracker {
        public static void trackUseCases(List<Integer> usecases, Class<?> cl) {
            for (Method m : cl.getDeclaredMethods()) {
                UseCase uc = m.getDeclaredAnnotation(UseCase.class);
                if (uc != null) {
                    System.out.printf("Found Use Case: %d %s\n", uc.id(), uc.description());
                    usecases.remove(new Integer(uc.id()));
                }
            }
            for (int i : usecases) {
                System.out.printf("Warning: Missiong use case-%d", i);
            }
        }
    
        public static void main(String[] args) {
            List<Integer> useCases = new ArrayList<>();
            Collections.addAll(useCases, 47, 48, 49, 50);
            trackUseCases(useCases, PasswordUtils.class);
        }
    }
    

    UseCase注解已经在之前的例子中定义。

    这个例子功能就是简单比较一下是否缺少一些没有编写的测试用例。其中useCases列表包含了所有应当包含的测试用例,PasswodUtils是所有UseCase测试源代码所在的类。

    检测过程中使用了反射方法getDeclaredMethods()getDeclaredAnnotation()。先获取PasswordUtils类中的所有方法,并遍历这个列表中的所有方法。如果一个方法被UseCase注解修饰,获取这个UseCase对象,并取出它的所有元素值。打印UseCase的信息,并在useCases中删除这Usecase编号。最后打印所有没有编写的用例编号。

    生成信息

    有些框架除了需要写java代码之外还需要一些额外的配置文件才能协同工作,这种情况最能体现出注解的价值。

    比如说像EJB,Hibernate这样的框架,一般都需要一份xml描述文件。他们提供了Java源文件中类和包的原始信息。如果没有注解,我们在写完java代码之后需要额外再写一份关于Java类的配置问文件。

    如果我们想添加一个实体类,建立一份基本的的对象/关系的映射,达到自动生成数据库表的目的。我们可以使用注解,它可以清晰的保存在Java源文件中,方便我们了解实体与的关系。

    例子:

    数据库中的所有属性都通过注解来传递,所以我们需要定义一些数据库中的‘类型’。这里我们简单的做一个例子,并没有定义全部的属性和类型。

    //对应数据库中的表, 只有一个属性,表名;
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface DBTable {
        public String name() default "";
    }
    
    //表中每个字段的约束,只写了3个
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Constraints {
        boolean primaryKey() default false;
    
        boolean allowNull() default true;
    
        boolean unique() default false;
    }
    
    //对应数据库中的 INT 类型
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface SQLInteger {
        String name() default "";
    
        Constraints contraints() default @Constraints();
    }
    
    //对应数据库中的 VARCHAR 类型
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface SQLString {
        int value() default 0;
    
        String name() default "";
    
        Constraints constraints() default @Constraints();
    }
    
    /**
     * Created by yuxiaofei on 2016/5/7.
     * 实体类,被注解修饰的成员变量会被加入到数据库中
     */
    @DBTable(name = "Member")
    public class Member {
    
        @SQLString(value = 30)
        String firstName;
        @SQLString(value = 50)
        String lastName;
        @SQLInteger
        Integer age;
        @SQLString(value = 30, constraints = @Constraints(primaryKey = true))
        String handle;
        static int memberCount;
    }
    
    /**
     * Created by yuxiaofei on 2016/5/7.
     * 根据目标实体类,声称创建表的SQL
     */
    public class TableCreator {
        public static void main(String[] args) {
            Class<?> targetClass = Member.class;
            DBTable dbTable = targetClass.getAnnotation(DBTable.class);
            if (dbTable == null) {
                System.out.printf("No DBTable in class %s. \n", targetClass.getSimpleName());
                System.exit(-1);
            }
            String tableName = dbTable.name();
            if (tableName.length() < 1) {
                //默认名使用类名的全字母全大写
                tableName = targetClass.getName().toUpperCase();
            }
    
            List<String> columnDefs = new ArrayList<String>();
            getColumnDefs(targetClass, columnDefs);
    
            String SQL = createSQL(tableName, columnDefs);
            System.out.println(SQL);
        }
    
        //根据表名和字段声明,生成创建表的SQL
        private static String createSQL(String tableName, List<String> columnDefs) {
            StringBuilder sb = new StringBuilder();
            sb.append(String.format("CREATE TABLE %s (\n", tableName));
            for (int i = 0; i < columnDefs.size(); i++) {
                String column = columnDefs.get(i);
                if (i != columnDefs.size() - 1) {
                    sb.append(String.format("   %s,\n", column));
                } else {
                    sb.append(String.format("   %s\n);", column));
                }
            }
            return sb.toString();
        }
    
        /**
         * 根据实体类生成创建表的字段
         *
         * @param targetClass 目标实体类
         * @param columnDefs  字段声明列表
         */
        private static void getColumnDefs(Class<?> targetClass, List<String> columnDefs) {
            for (Field field : targetClass.getDeclaredFields()) {
                String columnName = null;
                Annotation[] anns = field.getDeclaredAnnotations();
                if (anns.length < 1) {
                    continue;//没有被注解修饰,非数据库表内字段
                }
                if (anns[0] instanceof SQLInteger) {
                    SQLInteger sqlInteger = (SQLInteger) anns[0];
                    if (sqlInteger.name().length() < 1) {//默认名,用变量名全大写形式代替
                        columnName = field.getName().toUpperCase();
                    } else {
                        columnName = sqlInteger.name();
                    }
                    Constraints constraints = sqlInteger.contraints();
                    columnDefs.add(String.format("%s INT %s", columnName, getConstraints(constraints)));
                } else if (anns[0] instanceof SQLString) {
                    SQLString sqlString = (SQLString) anns[0];
                    if (sqlString.name().length() < 1) {//默认名用变量名全字母大写
                        columnName = field.getName().toUpperCase();
                    } else {
                        columnName = sqlString.name();
                    }
                    Constraints constraints = sqlString.constraints();
                    columnDefs.add(
                            String.format(
                                    "%s VARCHAR(%d) %s",
                                    columnName, sqlString.value(), getConstraints(constraints)
                            )
                    );
                }
            }
        }
    
        /**
         * 根据注解中的配置声称字段的约束
         *
         * @param constraints 注解约束配置
         * @return 字段约束
         */
        private static String getConstraints(Constraints constraints) {
            StringBuilder sb = new StringBuilder();
            if (!constraints.allowNull()) {
                sb.append(" NOT NULL");
            }
            if (constraints.primaryKey()) {
                sb.append(" PRIMARY KEY");
            }
            if (constraints.unique()) {
                sb.append(" UNIQUE");
            }
            return sb.toString();
        }
    }
    

    输出结果就是一条sql语句:

    CREATE TABLE Member (
       FIRSTNAME VARCHAR(30) ,
       LASTNAME VARCHAR(50) ,
       AGE INT ,
       HANDLE VARCHAR(30)  PRIMARY KEY
    );
    

    例子比较简单,运行一下就可以看到结果。虽然创建一个实体的代码变多了,但是以后每次添加一个实体,一张表都很方便。

    对于注解的学习就到这里了,有什么疑问可以在回复中一起交流。

    参考文献: 《Java编程思想》

    于晓飞
  • 相关阅读:
    屏蔽指定IP访问网站
    如何辨别一个网站是否是基于织梦搭建
    PC端访问移动站跳转PC站
    点击/swt/直接跳转商务通(1)
    页面加入视频
    dedecms教程:搜索页显示条数更改
    青蛙走迷宫问题(体力值)
    计算n的阶乘(n!)末尾0的个数
    Leetcode27--->Remove Element(移除数组中给定元素)
    Leetcode26--->Remove Duplicates from Sorted Array(从排序数组中移除相同的元素)
  • 原文地址:https://www.cnblogs.com/yuxiaofei93/p/5722678.html
Copyright © 2020-2023  润新知