注解的基本概念
• 注解(Annotation)又叫标注,是从Java5开始增加的一种引用数据类型。
• 注解本质上就是代码中的特殊标记,通过这些标记可以在编译、类加载、以及运行时执行指定的处理。
注解的语法格式
• 访问修饰符@interface 注解名称{ 注解成员; }
• 自定义注解自动继承java.lang.annotation.Annotation接口。
• 通过@注解名称的方式可以修饰包、类、成员方法、成员变量、构造方法、参数、局部变量的声明等。
注解的使用方式
• 注解体中只有成员变量没有成员方法,而注解的成员变量以“无形参的方
法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。
• 如果注解只有一个参数成员,建议使用参数名为value,而类型只能是八种基本数据类型、String类型、Class类型、enum类型及Annotation类型。
元注解的概念
• 元注解是可以注解到注解上的注解,或者说元注解是一种基本注解,但
是它能够应用到其它的注解上面。
• 元注解主要有@Retention、@Documented、@Target、@Inherited、@Repeatable。
元注解@Retention
• @Retention 应用到一个注解上用于说明该注解的的生命周期,取值如下:
• RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时
它将被丢弃忽视。
• RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加
载到JVM 中,默认方式。
• RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载
进入到JVM 中,所以在程序运行时可以获取到它们。
元注解@Documented
• 使用javadoc工具可以从程序源代码中抽取类、方法、成员等注释形成一个和源代码配套的API帮助文档,而该工具抽取时默认不包括注解内容。
• @Documented用于指定被该注解将被javadoc工具提取成文档。
• 定义为@Documented的注解必须设置Retention值为RUNTIME。
元注解@Target
• @Target用于指定被修饰的注解能用于哪些元素的修饰,取值如下:
元注解@Inherited
• @Inherited并不是说注解本身可以继承,而是说如果一个超类被该注解标记过的注解进行注解时,如果子类没有被任何注解应用时,则子类就继承超类的注解。
元注解@Repeatable
• @Repeatable表示自然可重复的含义,从Java8开始增加的新特性。
• 从Java8开始对元注解@Target的参数类型ElementType枚举值增加了两个:
• 其中ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中,如:泛型。
• 其中ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。
常见的预制注解
• 预制注解就是Java语言自身提供的注解,具体如下:
常见的预制注解
• 常用的预制注解如下:
============================================================================
注解是一种元数据(metadata),所谓元数据,就是对数据进行描述的数据;例如在重包裹上写的"易碎,小心轻放",就是一种元数据,这样运送包裹的人员根据元数据信息,会谨慎处理该包裹;
预定义的注解:
Java在java.lang包中定义了五种注解类型,如下所示
(1)Override
(2)Deprecated (
读音:
- ['deprəkeɪt]
释义:
- v.声明不赞成;抨击;反对
)
(3)SuppressWarnings
(4)SafeVararges (Java 7 中新增)
(5)FunctionalInterface(Java 8 中新增)
在java中,注解是被当作一个修饰符类使用的,在注解的名称前面加上@符号,放置在注解项(类,方法,字段等)之前,中间没有分号。
(一)@Override 注解表示当前方法重写了父类的某个方法,或者实现了接口中的某个方法,
如果父类或接口中对应的方法不存在,这会方式编译错误,这个注解主要是在覆盖方法时使用,
以防止在重写方法时出错;
package org.jy.sso.cas.data.server.system;
/**
* 基础类
*/
public class Base {
public void desc(int val) {
}
public void desc(String val) {
}
}
package org.jy.sso.cas.data.server.system;
/**
* 演示Override注解
*/
public class Derived extends Base {
@Override
public void desc(int val) {
super.desc(val);
}
/* @Override
public void desc(double val) {// 编译报错,为正确覆盖方法,该方法在父类中没有定义
}*/
@Override
public void desc(String val) {
super.desc(val);
}
}
(二)@Deprecated 注解表示被注解的程序元素已经弃用,不应该在使用。一个程序元素可能因为以下几种原因而被标记为已弃用:
(1)它的使用可能会导致错误
(2)它可能在未来的版本中不兼容
(3)它可能在未来的版本中被删除
(4)它被一个更新的,更适合的方案所取代
(5)它已经过时
package org.jy.sso.cas.data.server.system;
// 用在接口上
@Deprecated
public interface AnnotationB {
}
package org.jy.sso.cas.data.server.system;
class A {
// 注解用在方法上
@Deprecated
public void fn() {
}
}
class C implements AnnotationB{
}
public class DeprecatedAnnotation{
public static void main(String[] args){
A a = new A();
a.fn();
}
}
(三)@SuppressWarnings
SuppressWarnings 注解用于关闭指定的一类编译器警告。例如,你使用了被标记为废弃的API,但又不想看到警告信息,就可以使用@SuppressWarnings("deprecated")来告知编译器不要产生这类警告。如下面的代码所示
package org.jy.sso.cas.data.server.system;
// 用在接口上
@Deprecated
public interface AnnotationB {
}
package org.jy.sso.cas.data.server.system;
class A {
// 注解用在方法上
@Deprecated
public void fn() {
}
}
@SuppessWarnings("deprecated") // 该注解表示编译时,不会参数过时警告
class C implements AnnotationB{
}
public class DeprecatedAnnotation{
public static void main(String[] args){
A a = new A();
a.fn();
}
}
(四)
@SafeVarargs注解是在JAVA1.7中新增加的,在声明具体模糊类型(例如泛型) * 的可变参数(变长参数)的构造方法或者方法时,Java编译器会报(unchecked警告)警告. * 如果程序员断定声明的构造方法或者方法的代码不会对可变参数执行潜在的不安全操作,则可使用 * SafeVarargs注解进行标记,这样编译器就不会在报unchecked警告了; * 注意该注解只能使用与构造方法,静态方法和final方法,Java中具有可变参数的泛型方法使用 * SafeVarargs注解,如Arrays类中的方法asList静态方法,如下所示: * @SafeVarargs * public static <T> List<T> asList(T ... a);
package org.jy.sso.cas.data.server.system;
/**
*
*
* @SafeVarargs注解是在JAVA1.7中新增加的,在声明具体模糊类型(例如泛型)
* 的可变参数(变长参数)的构造方法或者方法时,Java编译器会报(unchecked警告)警告.
* 如果程序员断定声明的构造方法或者方法的代码不会对可变参数执行潜在的不安全操作,则可使用
* SafeVarargs注解进行标记,这样编译器就不会在报unchecked警告了;
* 注意该注解只能使用与构造方法,静态方法和final方法,Java中具有可变参数的泛型方法使用
* SafeVarargs注解,如Arrays类中的方法asList静态方法,如下所示:
* @SafeVarargs
* public static <T> List<T> asList(T ... a);
*/
public class SafeVarargsAnnotation <T>{
private T[] array;
// 构造方法可以使用@SafeVarargs注解
@SafeVarargs
public SafeVarargsAnnotation(T ... varArgs){
this.array = varArgs;
}
// forEach方法不能使用@SafeVararg
// 如果要关闭unchecked警告,可以使用@SuppressWarnings注解
@SuppressWarnings("unchecked")
public void forEach(T ... varArgs){
for (T arg : varArgs){
System.out.println(arg);
}
}
// final 方法可以使用@SafeVarargs注解
@SafeVarargs
public final void finalForEach(T ... varArgs){
for (T arg : varArgs){
System.out.println(arg);
}
}
// 静态方法可以使用@SafeVarargs
@SafeVarargs
public static <T> void staticForEach(T... varArgs){
for (T arg : varArgs){
System.out.println(arg);
}
}
}
package org.jy.sso.cas.data.server.system;
/**
* 自定义注解: 注解是一个种类型,定义注解与定义接口类似,通过使用@interface
* 关键字进行定义;
* 我们在自定义注解类型会自动继承自java.lang.annotation.Annotation接口
* 在该注解中可以有元素,每个元素声明都具有下面两种形式
* type elementName();
* 或者
* type elementName() default value;
*/
public @interface BugReport {
}
-------------------------------------------------------------------------------------------------------------------------------------
package org.jy.sso.cas.data.server.system;
/**
* * 在该注解中可以有元素,每个元素声明都具有下面两种形式
* * type elementName();
* * 或者
* * type elementName() default value;
*/
public @interface EmBugReport {
// default关键字用于指定元素的默认值
public int severity() default 0;
public String msg();
/**
*(1)当定义好注解后,就可以使用这个注解来进行标记。注解的使用格式为:
* @AnnotationName(elementName1 = value1,elementName2 = value2,...)
*
* (2)例如:
* @BugReport(msg ="普通bug")
* void test1(){}
*
* @BugReport(severity=1,msg ="较为严重的Bug")
* void test2()
*
* (3)如果注解有元素,且没有默认值,那么在使用注解时,必须要给元素赋值。
* 有两种特殊的快捷方式可以用来简化注解的使用
* 1.如果注解中没有元素,或者所有元素都使用了默认值,那么在使用注解时就不需要使用
* 圆括号和指定元素了,没有使用任何元素的注解,我们称之为标记注解(market annotation)
* 例如Override注解
* 2.另外一种快捷方式是单值注解,如果一个元素具有特殊的名字value,并且没有指定其他元素,那么在使用注解时可以
* 省略元素名和等号,直接给出元素的值; 例如:
*
* public @interface BugReport{
* String value();
* }
* 3.在使用时,可以直接写为: @BugReport("发生了bug")
* 要注意的是,注解元素的类型时有限制的,只能使用如下的类型
* 基本数据类型
* String 类型
* Class 类型
* enum (枚举)类型
* Annotation类型
* 以上类型的数组
*
*/
}
(五)自定义注解处理器
我们定义注解后,使用自定义的注解标注后,需要定义注解处理器,完成我们特定的目的,如下是我们使用自定义注解的流程:
(步骤一)自定义注解
package org.jy.sso.cas.data.server.system;
import java.lang.annotation.*;
/**
* 自定义注解
*/
@Target(ElementType.FIELD) // 注解用在域上
@Retention(RetentionPolicy.RUNTIME) // 注解的生命周期
@Documented
public @interface FruitProvider {
// 供应商编号
public int id() default -1; // 成员
// 供应商名称
public String name() default ""; // 成员
// 供应商编号
public String address() default ""; // 成员
}
--------------------------------------------------------------------------------------------------------------------------------------
(步骤二)使用注解标注具体的类的域
package org.jy.sso.cas.data.server.system.annotationHandler;
import org.jy.sso.cas.data.server.system.FruitProvider;
/**
* 定义一个类在类的域上使用自定义的注解
*/
public class AppleAnnotationApplication {
// 在类的域上使用自定义的注解
@FruitProvider(id = 1, name = "贵州红富士集团", address = "贵州省贵阳市南明区")
private String appleProvider;
public String getAppleProvider() {
return appleProvider;
}
public void setAppleProvider(String appleProvider) {
this.appleProvider = appleProvider;
}
}
---------------------------------------------------------------------------------------------------------------------------------------
(步骤三)定义注解处理器
package org.jy.sso.cas.data.server.system.annotationHandler;
import org.jy.sso.cas.data.server.system.FruitProvider;
import java.lang.reflect.Field;
/**
* 定义注解处理器
*/
public class FruitInfoHandler {
// 通过反射来获取注解的相关信息
public static void getFruitInfo(Class<?> clazz) {
// 获取传入类的所有属性
Field[] fields = clazz.getDeclaredFields();
String strFruitProvider = "供应商信息: ";
for (Field field : fields) {
if (field.isAnnotationPresent(FruitProvider.class)) { // 判断给定的类(FruitProvider)的域上是否有注解
FruitProvider fruitProvider = field.getAnnotation(FruitProvider.class); // 获得FruitProvider 实例
// 获取注解信息
strFruitProvider = "供应商编号: " + fruitProvider.id() + " 供应商名称: " + fruitProvider.name() + " 供应商地址: " + fruitProvider.address();
System.out.println(strFruitProvider);
}
}
}
}
----------------------------------------------------------------------------------------------------------------------------------------
(步骤四) 使用注解处理器
package org.jy.sso.cas.data.server.system.annotationHandler;
/**
* 运行自定义处理器
*/
public class FruitInfoHandlerRun {
public static void main(String[] args){
FruitInfoHandler.getFruitInfo(AppleAnnotationApplication.class);
}
}
(六)@Repeatable 重复注解
/** * 自从Java5以来,这个特性在各个框架和项目中都得到了广泛的应用。 * 不过,注解存在一个很大的限制是: 在同一个地方不能多次使用同一个注解。 * Java8打破了这个限制,引入了重复注解的限制,允许在同一个地方多次使用同一个注释。 * Repeatable元注解就是用来说明可重复的注解. * 声明可重复的注解,需要两个步骤: * (1)声明可重复的注解类型: @Listener注解 * (2)声明包含的注解类型:@Listeners注解 */
package org.jy.sso.cas.data.server.system.bugreport;
import java.lang.annotation.*;
/**
* 自从Java5以来,这个特性在各个框架和项目中都得到了广泛的应用。
* 不过,注解存在一个很大的限制是: 在同一个地方不能多次使用同一个注解。
* Java8打破了这个限制,引入了重复注解的限制,允许在同一个地方多次使用同一个注释。
* Repeatable元注解就是用来说明可重复的注解.
* 声明可重复的注解,需要两个步骤:
* (1)声明可重复的注解类型: @Listener注解
* (2)声明包含的注解类型:@Listeners注解
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Listeners.class)
public @interface Listener {
String value();
}
----------------------------------------------------------------------------------------------------------------------------------------
package org.jy.sso.cas.data.server.system.bugreport;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Listeners {
Listener[] value();
}
(七)重复注解的使用实例
package org.jy.sso.cas.data.server.system.bugreport;
import org.jy.sso.cas.data.server.system.EmBugReport;
/**
* 在Listener中使用@Repeatable注解,否则该处会报错
*/
@Listener("ActionListener")
@Listener("WindowListener")
public class Derived extends Base{
@EmBugReport(severity = 1,msg = "name字段Bug")
private String name;
@EmBugReport(severity = 2,msg="action方法的Bug")
private void action(){}
}