文章目录
14、注解小结
14.1 什么是注解?
概念
从 Java 5 版本之后可以在源代码中嵌入一些补充信息,这种补充信息称为注解(Annotation),是 Java 平台中非常重要的一部分。注解都是 @ 符号开头的
,例如我们在学习方法重写时使用过的 @Override 注解。同 Class 和 Interface 一样,注解也属于一种类型。
理解:
Annotation 可以翻译为“注解”或“注释”,一般翻译为“注解”
因为“注释”一词已经用于说明“//”、“/**...*/”和“/*...*/”等注释符号了
这里的“注释”是英文 Comment 翻译。
注解并不能改变程序的运行结果,也不会影响程序运行的性能。有些注解可以在编译时给用户提示或警告,有的注解可以在运行时读写字节码文件信息。
注解就是一种描述数据的数据。所以可以说注解就是源代码的元数据。
分类
根据注解是否包含成员变量,可以分为如下两类。
标记注解
:没有定义成员变量的注解类型
被称为标记注解。这种注解仅利用自身的存在与否来提供信息,如 @Override就是标记注解。元数据注解
:包含成员变量的注解
,因为它们可以接受更多的元数据,所以也被称为元数据注解。
注解的实例代码
@Override
public String toString() {//@Override注解说明重写继承类或者接口方法。
return "string";
}
上面的代码重写了 Object 类的 toString() 方法并使用了 @Override 注解。如果不使用 @Override 注解标记代码,程序也能够正常执行。那么这么写有什么好处吗?
事实上,使用 @Override 注解就相当于告诉编译器这个方法是一个重写方法,如果父类中不存在该方法,编译器便会报错,提示该方法没有重写父类中的方法。这样可以防止不小心拼写错误造成麻烦。
例如,在没有使用 @Override 注解的情况下,将 toString() 写成了 toStrring(),这时程序依然能编译运行,但运行结果会和所期望的结果大不相同。
14.2 注解的作用有哪些?
注解常见的作用有以下几种:
- 生成帮助文档。这是最常见的,也是 Java 最早提供的注解。常用的有 @see、@param 和 @return 等;
- 跟踪代码依赖性,实现替代配置文件功能。比较常见的是 Spring 2.5 开始的基于注解配置。作用就是减少配置。现在的框架基本都使用了这种配置来减少配置文件的数量;
- 在编译时进行格式检查。如把
@Override 注解
放在方法前,如果这个方法并不是重写了父类方法,则编译时就能检查出。
无论是哪一种注解,本质上都一种数据类型,是一种接口类型
。
14.3 我们常见的注解,如何使用?
14.3.1 常见注解
到 Java 8 为止 Java SE 提供了 11 个内置注解。其中有 5 个是基本注解,它们来自于 java.lang 包。有 6 个是元注解,它们来自于 java.lang.annotation 包,自定义注解会用到元注解。
元注解
元注解就是负责注解其他的注解。如@Target用来限制注解使用的范围
Java 5 定义了 4 个注解,分别是 @Documented、@Target、@Retention 和 @Inherited。
Java 8 又增加了 @Repeatable 和 @Native 两个注解。这些注解可以在 java.lang.annotation 包中找到。
名字 | 作用 |
---|---|
Documented | @Documented 是一个标记注解,没有成员变量。用 @Documented 注解修饰的注解类会被 JavaDoc 工具提取成文档。默认情况下,JavaDoc 是不包括注解的,但如果声明注解时指定了 @Documented,就会被 JavaDoc 之类的工具处理,所以注解类型信息就会被包括在生成的帮助文档中。 |
Inherited | @Inherited 是一个标记注解,用来指定该注解可以被继承。使用 @Inherited 注解的 Class 类,表示这个注解可以被用于该 Class 类的子类。就是说如果某个类使用了被 @Inherited 修饰的注解,则其子类将自动具有该注解。 |
Native | 使用 @Native 注解修饰成员变量,则表示这个变量可以被本地代码引用,常常被代码生成工具使用。 |
Repeatable | @Repeatable 注解是 Java 8 新增加的,它允许在相同的程序元素中重复注解,在需要对同一种注解多次使用时,往往需要借助 @Repeatable 注解。 |
Retention | @Retention 用于描述注解的生命周期,也就是该注解被保留的时间长短。 |
Target | @Target 注解用来指定一个注解的使用范围,即被 @Target 修饰的注解可以用在什么地方。@Target 注解有一个成员变量(value)用来设置适用目标 |
基本注解包括:
- 1)@Override
用来指定方法重写的,只能修饰方法并且只能用于方法重写,不能修饰其它的元素。它可以强制一个子类必须重写父类方法或者实现接口的方法。 - 2)@Deprecated
可以用来注解类、接口、成员方法和成员变量等,用于表示某个元素(类、方法等)已过时。当其他程序使用已过时的元素时,编译器将会给出警告。Java 9 为 @Deprecated 注解增加了以下两个属性:forRemoval
该 boolean 类型的属性指定该 API 在将来是否会被删除。since
该 String 类型的属性指定该 API 从哪个版本被标记为过时。 - 3)@SuppressWarnings
指示被该注解修饰的程序元素(以及该程序元素中的所有子元素)取消显示指定的编译器警告,且会一直作用于该程序元素的所有子元素。例如,使用 @SuppressWarnings 修饰某个类取消显示某个编译器警告,同时又修饰该类里的某个方法取消显示另一个编译器警告,那么该方法将会同时取消显示这两个编译器警告。 - 4)@SafeVarargs
可用 @SafeVarargs 注解抑制编译器警告,@SafeVarargs注解不适用于非 static 或非 final 声明的方法,对于未声明为 static 或 final 的方法,如果要抑制 unchecked 警告,可以使用 @SuppressWarnings 注解。 - 5)@FunctionalInterface
用来指定某个接口必须是函数式接口,所以 @FunInterface 只能修饰接口,不能修饰其它程序元素。
14.3.2 如何使用(基本注解使用)
1)@Override
用来指定方法重写的,只能修饰方法并且只能用于方法重写,不能修饰其它的元素。它可以强制一个子类必须重写父类方法或者实现接口的方法。
package Annotation_Learning;
public class Override_test {
@Override
public String toString(){
System.out.println("这是重写Object类的toString()方法,使用Override注解告知编译器。\n" +
"如果没有重写,则 Error:(4, 5) java: 方法不会覆盖或实现超类型的方法");
return "返回值为String类型";
}
public static void main(String[]args){
Override_test test = new Override_test();
test.toString();
}
}
2)@Deprecated
可以用来注解类、接口、成员方法和成员变量等,用于表示某个元素(类、方法等)已过时。当其他程序使用已过时的元素时,编译器将会给出警告。Java 9 为 @Deprecated 注解增加了以下两个属性:forRemoval
该 boolean 类型的属性指定该 API 在将来是否会被删除。since
该 String 类型的属性指定该 API 从哪个版本被标记为过时。
package Annotation_Learning;
/**
* 当其他程序使用已过时的元素时,编译器将会给出警告。同时Ide会给该行代码画上横线表示过时
*/
public class Deprecated_Test {
@Deprecated(since="9",forRemoval = true)
public String toString1(){
System.out.println("这是@Deprecated,表示过时的注解,可以用来注解类、接口、成员方法等");
return "@Deprecated";
}
public static void main(String[]args){
Deprecated_Test test = new Deprecated_Test();
test.toString1();
}
}
发现toString方法上画了线,表示过时。
3)@SuppressWarnings
指示被该注解修饰的程序元素(以及该程序元素中的所有子元素)取消显示指定的编译器警告,且会一直作用于该程序元素的所有子元素。例如,使用 @SuppressWarnings 修饰某个类取消显示某个编译器警告,同时又修饰该类里的某个方法取消显示另一个编译器警告,那么该方法将会同时取消显示这两个编译器警告。
注解的使用有以下三种:
- 抑制单类型的警告:@SuppressWarnings(“unchecked”)
- 抑制多类型的警告:@SuppressWarnings(“unchecked”,“rawtypes”)
- 抑制所有类型的警告:@SuppressWarnings(“unchecked”)
抑制警告的关键字如下表所示。
关键字 | 用途 |
---|---|
all | 抑制所有警告 |
boxing | 抑制装箱、拆箱操作时候的警告 |
cast | 抑制映射相关的警告 |
dep-ann | 抑制启用注释的警告 |
deprecation | 抑制过期方法警告 |
fallthrough | 抑制在 switch 中缺失 breaks 的警告 |
finally | 抑制 finally 模块没有返回的警告 |
hiding | 抑制相对于隐藏变量的局部变量的警告 |
incomplete-switch | 忽略不完整的 switch 语句 |
nls | 忽略非 nls 格式的字符 |
null | 忽略对 null 的操作 |
rawtypes | 使用 generics 时忽略没有指定相应的类型 |
restriction | 抑制禁止使用劝阻或禁止引用的警告 |
serial | 忽略在 serializable 类中没有声明 serialVersionUID 变量 |
static-access | 抑制不正确的静态访问方式警告 |
synthetic-access | 抑制子类没有按最优方法访问内部类的警告 |
unchecked | 抑制没有进行类型检查操作的警告 |
unqualified-field-access | 抑制没有权限访问的域的警告 |
unused | 抑制没被使用过的代码的警告 |
package Annotation_Learning;
public class SuppressWarnings_Test {
@Deprecated(since="9",forRemoval = true)
public String toString1(){
System.out.println("这是过时的方法");
return "被@SuppressWarnings注解抑制了。";
}
@SuppressWarnings("deprecation")
public static void main(String[]args){
SuppressWarnings_Test test = new SuppressWarnings_Test();
System.out.println(test.toString1());
}
}
4)@FunctionalInterface
在学习 Lambda 表达式时,我们提到如果接口中只有一个抽象方法(可以包含多个默认方法或多个 static 方法),那么该接口就是函数式接口
- @FunctionalInterface 就是用来指定某个接口必须是函数式接口,所以 @FunInterface 只能修饰接口,不能修饰其它程序元素。
- 函数式接口就是为 Java 8 的 Lambda 表达式准备的,Java 8 允许使用 Lambda 表达式创建函数式接口的实例,
- 因此 Java 8 专门增加了 @FunctionalInterface。
- @FunctionalInterface 注解主要是帮助程序员避免一些低级错误,例如,在下面的 FunInterface 接口中再增加一个抽象方法 abc(),编译程序时将出现错误提示。
package Annotation_Learning;
@FunctionalInterface
interface FunctionInterface_Lambda{
void display();//默认为抽象方法,函数式接口只能有一个抽象方法,可以有静态方法和普通方法
default void learn(){
System.out.println("这是普通方法");
}
static void learn_static(){
System.out.println("这是静态方法");
}
}
public class FunctionalInterface_Test implements FunctionInterface_Lambda {
@Override
public void display(){//使用注解表示为重写接口的抽象方法。
System.out.println("继承函数式接口,必须在相应的类里边实现该方法");
}
public static void main(String[]args){
FunctionInterface_Lambda test = new FunctionalInterface_Test();
test.display();
test.learn();
FunctionInterface_Lambda.learn_static();
}
}
只有一个抽象方法,编译通过
加多一个抽象方法,就会报错
5)@SageVarargas
可用 @SafeVarargs 注解抑制编译器警告,@SafeVarargs注解不适用于非 static 或非 final 声明的方法
,对于未声明为 static 或 final 的方法,如果要抑制 unchecked 警告,可以使用 @SuppressWarnings 注解。
package Annotation_Learning;
public class SafeVarargs_Demo {
//定义一个可变参数类型的静态成员函数,并使用泛型
@SafeVarargs
public static<T> void display(T... array){//得到一个泛型数组
for(T t:array)
System.out.println(t.getClass().getName()+":"+t);//打印其类型信息及值的大小
System.out.println("代码在可变参数 display 前添加了 @SafeVarargs 注解,当然也可以使用 @SuppressWarnings(\\\"unchecked\\\") " +
"注解,但是两者相比较来说 @SafeVarargs 注解更适合。\n" );
}
public static void main(String[]args){
display(12,133,14);
display("12",3,6);
}
}
不使用@SafeVarargs注解前会提示你
14.4 如何去创建自定义注解?(跟创建接口类似)
声明自定义注解使用 @interface 关键字(interface 关键字前加 @ 符号)实现。定义注解与定义接口非常像,如下代码可定义一个简单形式的注解类型。
// 定义一个简单的注解类型
public @interface Test {
}
上述代码声明了一个 Test 注解。默认情况下,注解可以在程序的任何地方使用,通常用于修饰类、接口、方法和变量等。
定义注解和定义类相似,注解前面的访问修饰符和类一样有两种,分别是公有访问权限(public)和默认访问权限(默认不写)。一个源程序文件中可以声明多个注解,但只能有一个是公有访问权限的注解。且源程序文件命名和公有访问权限的注解名一致。
不包含任何成员变量的注解称为标记注解
,例如上面声明的 Test 注解以及基本注解中的 @Override 注解都属于标记注解。
包含成员变量的注解称为元数据注解
,因为它们可以接受更多的元数据,所以也被称为元数据注解。根据需要,注解中可以定义成员变量,成员变量以无形参的方法形式来声明,其方法名和返回值定义了该成员变量的名字和类型。代码如下所示:
public @interface MyTag {
// 定义带两个成员变量的注解
// 注解中的成员变量以方法的形式来定义
String name();
int age();
String value() default "abcd";//默认注解里边没有分号!
}
以上代码中声明了一个 MyTag 注解,定义了两个成员变量,分别是 name 和 age。成员变量也可以有访问权限修饰符,但是只能有公有权限和默认权限。
如果在注解里定义了成员变量,那么使用该注解时就应该为它的成员变量指定值,如下代码所示。
public class Test {
// 使用带成员变量的注解时,需要为成员变量赋值
@MyTag(name="xx", age=6)//默认注解可以不用赋值
public void info() {
...
}
...
}
14.5 多重注解写?
14.5.1 java8以前定义多重注解的写法(容器、数组实现)
1)使用了注解“容器”,说白点就是注解数组。
package Annotation_Learning;
@interface Continue_Tags{//定义Continue_Tags注解
Continue_Tag[] value();//定义为Continue_Tag注解的数组
}
@interface Continue_Tag{
String con();//注解的常量
}
public class Continue_Annotation {
@Continue_Tags({@Continue_Tag(con="hhh"),@Continue_Tag(con="jjj")})//使用第一个注解
public void sayHell0(){
System.out.println("展示多重注解的java7的写法,其实就相当于定义了两个注解,第一个注解其成员为一注解数组,第二个为该注解数组类型的注解");
}
@Continue_Tag(con="jj")//使用第二个注解
public static void main(String[]args){
new Continue_Annotation().sayHell0();
}
}
14.5.2 java8以后使用@Repeatable元注解
2)
package Annotation_Learning;
import java.lang.annotation.Repeatable;
//定义一个注解
@Repeatable(Tags.class)
@interface Tag{
String value();//都是叫做value
}
//定义一个注解可以多重使用
@interface Tags{
Tag[] value();//都是叫做value,只不过类型为注解数组
}
public class Repeatable_Test {
@Tag(value="name2")
@Tag(value="name1")//连续使用注解
public void sayHell0(){
System.out.println("两个注解的成员变量的method要一样!,这里都是value,然后类似于java7的写法,只不过在第二个注解上面加上@Repeatable(第一个注解的class静态量)");
}
@Continue_Tag(con="jj")
public static void main(String[]args){
new Repeatable_Test().sayHell0();
}
}
运行结果:
如果成员变量的“名不同”,就会报错