在学习Java的过程中, 注解关注的不多, 这次就来看一下.
注解
注解的核心就是给被注解的内容添加一些元数据. 可以把元数据和代码结合在一起. 加上注解的类或者方法, 实际就是代表了一种元数据.
java.lang中有三个注解:
@Override
, 用来表示当前的方法定义覆盖超类的方法@Depreciated
, 如果程序员使用了被注解的元素, 编译器会发出警告, 提示这个方法或者类已经被标记为不再使用.@SuppressWarnings
, 强制关闭编译器警告
每当创建描述符性质的类或者接口的时候, 如果其中包含了重复性的工作, 就可以考虑使用注解了.
注意注解不同于文档, 注解的信息是直接保存在源代码级别.
创建注解, 使用注解 和 获取注解的元数据
注解的使用方式本质上像一个修饰符, 通常将注解单独放在一行:
@Test void testExecute() { execute(); }
或者写成:
@Test void testExecute() { execute(); }
定义注解的方式, 类似于声明一个类或者接口, 需要用到特殊的关键字 @interface
:
public @interface Test { }
这样一个注解并没有任何实际用途, 因为没有携带任何元数据, 要让注解有实际用途, 还需要用到一些元注解, 也就是Java提供给注解用的注解.
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Test { }
@Target表示这个注解可以用于哪里, 可以选方法, 类, 可以设置多个Target, @Retention表示注解在哪一个级别可用, 是源代码, 类文件还是运行时.
前边说了注解是为了携带元数据, 注解内部的元素就是用来包含元数据的. 注解的元数据实际上看上去像方法.
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Test { public int id(); public String description() default "no description"; }
这里定义了两个元数据名称叫做id 和 description, 注意虽然看上去是方法, 但是只是类似于方法定义, 而且会被类型检查. default关键字表示注解如果没有给出对应的值, 就使用该默认值.
使用注解的时候, 在注解之后加上括号, 然后写上注解的名称与对应的值, 看上去就像是一个构造器, 但是其中指定了值.
@Test(id = 1, description = "first")
void testExecute() {
execute();
}
现在已经知道了创建注解和向其中添加元数据的方法, 以及使用注解的方法, 最后一步是如何获取注解. 因为如果无法获取注解的内容, 那就和注释没有什么区别了.
读取注解通过的是反射的机制:
public class UserCaseTracker { public static void trackUserCases(Class cl) { //对于类的每个方法, 获取其中Test类型的注解 for (Method m : cl.getDeclaredMethods()) { Test uc = m.getAnnotation(Test.class); if (uc != null) { System.out.println(uc.id()); System.out.println(uc.description()); } } } public static void main(String[] args) { trackUserCases(UserCase.class); } } class UserCase { @Test(id = 1, description = "first method") public void exe() { System.out.println("exed"); } @Test(id = 2) public void exe2() { } }
通过反射获取一个类的方法, 然后针对方法,传入要获取的注解的类型, 然后打印其中的方法, 就可以得到注解的内容.
因此注解绝对不是注释, 而是可以通过获取注解来进行相对应的工作.
深入看注解
首先是注解里能使用哪些元素, 实际上只能使用如下:
- 所有基本类型
- String
- Class
- enum
- Annotation, 也就是注解类型
- 以上类型的数组
也就是说注解内部的方法的返回值只能是上边这些类型. 此外注解类型也可以作为返回值, 意味着注解可以嵌套, 由于注解不能继承, 所以注解嵌套可以用来稍微复用一下注解. 然后要注意的就是元素不能没有值, 要么在加注解的时候赋值, 要么在注解的时候使用元素提供的值. 没有值指的是基本类型不能不赋值, 非基本类型不能等于null.
所以一般可以用特殊值或者空字符串来代表不存在的值. 注解不支持继承.
注解的主要使用场合, 一般是用于一些特殊的要设置一些名称, 但其他很多方面相同的对象, 通过给类的域加上注解, 可以很方便的在处理对象的时候使用元数据.
比如最常用的就是数据表对应的Bean, 就像Spring的@Bean:
@Target(ElementType.TYPE) //表示作用于类 @Retention(RetentionPolicy.RUNTIME) public @interface DBTable { public String name() default ""; } @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Constrains { boolean primaryKey() default false; boolean allowNull() default true; boolean unique() default false; } @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface SQLString { int value() default 0; String name() default ""; Constrains constrains() default @Constrains; } @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface SQLInteger { String name() default ""; Constrains constrains() default @Constrains; } public @interface Uniqueness { Constrains constrains() default @Constrains(unique = true); }
这是一批注解, 分别是注解在类上, 表示数据表名是多少; 注释在域上, 表示对应的字段设置; 注释在字段上, 设置SQL查询内容; 设置SQL查询的值, 以及一个实际上就是unique=true的Constrains注解.
利用上述注解就可以定义一个Bean:
@DBTable(name = "Member") public class Member { @SQLString(30) String firstname; @SQLString(50) String lastName; @SQLInteger Integer age; @SQLString(value = 30, constrains = @Constrains(primaryKey = true)) String handle; static int memberCount; public String getFirstname() { return firstname; } public String getLastName() { return lastName; } public Integer getAge() { return age; } public String getHandle() { return handle; } public static int getMemberCount() { return memberCount; } @Override public String toString() { return "Member{" + "firstname='" + firstname + '\'' + ", lastName='" + lastName + '\'' + ", age=" + age + ", handle='" + handle + '\'' + '}'; } }
在使用这个Bean的时候, 就可以通过使用注解的元数据, 来对这个类的对象进行操作.
如果注解中使用了名称为value的元素, 可以在赋值的时候直接使用括号内部值, 而不用写出value=xxx这种语法.
现在先知道注解的基本内容, 之后的就等学了设计模式再来看如何高级的根据注解来操作对象吧.