十、Java注解

目录

1.什么是注解

2.注解的作用

3.注解的使用

4.解析注解——模拟工厂模式


1.什么是注解

从 JDK 1.5 开始在源代码中嵌入一些补充信息,这种补充信息称为注解(Annotation),是 Java 中非常重要的一部分。注解都是 @ 符号开头的,例如我们在学习方法重写时使用过的 @Override 注解。

    @Override
    public String toString() {
        return this.hashCode()+"";
    }

上面的代码重写了 Object 类的 toString( ) 方法并使用了 @Override 注解。如果不使用 @Override 注解标记代码,程序也能够正常执行。那么这么写有什么好处吗?事实上,使用 @Override 注解就相当于告诉编译器这个方法是一个重写方法,如果父类中不存在该方法,编译器便会报错,提示该方法没有重写父类中的方法。这样可以防止不小心拼写错误造成麻烦。 

同 Class 和 Interface 一样,注解也属于一种类型 。注解并不能改变程序的运行结果,也不会影响程序运行的性能。有些注解可以在编译时给用户提示或警告,有些则可以在运行时读写字节码文件信息。

注解和注释都是用来描述程序的,但不同点就在于注解是编译器能够理解的,是给编译器看的,因此具有一定的格式限制;而注释是给阅读代码的人看的,因此可以随意编写,对代码没有任何影响。

注解可以用元数据这个词来描述,即一种描述数据的数据。所以可以说注解就是源代码的元数据。

2.注解的作用

(1)生成帮助文档。这是最常见的,也是 Java 最早提供的注解。

(2)在编译时进行格式检查。例如上面所提到的 @Override 注解;

(3)跟踪代码依赖性,实现替代配置文件功能。比较常见的是 Spring 2.5 开始的基于注解配置,作用就是减少配置量。现在的框架基本都使用了这种配置来减少配置文件的数量。

3.注解的使用

到 Java 8 为止 Java SE 提供了 11 个内置注解。其中有 5 个是基本注解,它们来自于 java.lang 包;有 6 个是元注解,负责注解其他的注解,它们来自于 java.lang.annotation 包,自定义注解时会用到元注解。

(1)基本注解的使用

① @Override 注解:用来指定方法重写的,只能修饰方法并且只能用于方法重写,不能修饰其它的元素。它可以强制一个子类必须重写父类方法或者实现接口的方法。

② @Deprecated 注解:用来注解类、接口、成员方法和成员变量等,用于表示某个元素(类、方法等)已过时。当使用已过时的元素时,编译器将会给出警告(过时元素中间有一横线)。

③ @SuppressWarnings 注解:指示被该注解修饰的程序元素(以及该程序元素中的所有子元素)取消显示指定的编译器警告,且会一直作用于该程序元素的所有子元素。通常情况下,如果你确认程序中的警告没有问题,可以不用理会。

④ @SafeVarargs 注解:与 @SuppressWarnings ("unchecked") 作用相同,取消未检查不安全代码的警告

⑤ @FunctionalInterface:用来指定某个接口必须是函数式接口,所以 @FunInterface 只能修饰接口,不能修饰其它程序元素。

(2)自定义注解

注解的本质就是一个接口,该接口默认继承了 Annotation 接口,但是注解的定义格式又不同于普通的接口:

① 在定义注解时要在 interface 前加上@,表示这是一个注解;

② 注解的属性为接口的无参抽象方法,并且返回值只能为基本类型、String、枚举类以及以上类型的数组

③ 在使用注解时必须给里面的属性赋值(格式:属性名 = 属性值)。若不想赋值,则必须在定义注解时属性后注明该属性的默认值;

④ 若属性只有一个且属性名为 value ,则使用时无需写属性名,直接传入值即可。

下面我们来声明一个自定义的注解并使用它:

/**
 * 定义一个注解
 */
public @interface MyAnnotation {

    //给注解添加属性
    String name();//String 类型
    int age();//基本数据类型
    Enum pet() default Enum.Dog;//枚举类型,默认值为Dog
    
}

/**
 * 使用自定义注解
 */
//pet有默认值,可以不写
//数组赋值要使用大括号包裹
@MyAnnotation(name = "张三",age = 25,sonName = {"张大大","张小小","张巧巧"})
public class Start {

    //类中的内容
    
}

(3)元注解

JDK 1.5 定义了 4 个注解,分别是 @Documented、@Target、@Retention 和 @Inherited。JDK 1.8 又增加了@Repeatable 和 @Native 两个注解,在此只介绍前四种常用注解的使用。

① @Documented 注解:用 @Documented 注解修饰的注解类会被 JavaDoc 工具提取成文档

② @Target 注解:用来指定一个注解的使用范围,即被 @Target 修饰的注解可以用在什么地方。@Target 注解有一个成员变量(value)用来设置适用目标,value 是 java.lang.annotation.ElementType 枚举类型的数组,下表为 ElementType 常用的枚举常量。

名称说明
CONSTRUCTOR用于构造方法
FIELD用于成员变量(包括枚举常量)
LOCAL_VARIABLE用于局部变量
METHOD用于方法
PACKAGE用于包
PARAMETER用于类型参数(JDK 1.8新增)
TYPE用于类、接口(包括注解类型)或 enum 声明

③ @Retention 注解:用于描述注解的生命周期,也就是该注解被保留的时间长短。@Retention 注解中的成员变量(value)用来设置保留策略,value 是 java.lang.annotation.RetentionPolicy 枚举类型,RetentionPolicy 有 3 个枚举常量,如下所示。

名称说明
SOURCE在源文件之后失效
CLASS在 class 文件之后失效
RUNTIME在运行完成后失效

④ @Inherited 注解:是一个标记注解,用来指定该注解可以被继承。如果某个类使用了被 @Inherited 修饰的注解,则其子类将自动具有该注解。

下面我们对在第 (2) 小节自定义的注解加上这些元注解限制:

/**
 * 定义一个注解
 */
@Documented//声明javadoc文档中会显示注解信息
@Target(ElementType.TYPE)//声明该注解作用于类
@Retention(RetentionPolicy.RUNTIME)//声明该注解持续作用至运行结束
@Inherited//声明该注解可以被被声明者的子类继承
public @interface MyAnnotation {

    //给注解添加属性
    String name();//String 类型
    int age();//基本数据类型
    Enum pet() default Enum.Dog;//枚举类型,默认值为Dog
    String[] sonName();//数组类型
}


/**
 * 使用自定义注解
 */
//该注解此时可被Demo1的子类继承
@MyAnnotation(name = "张三",age = 25,sonName = {"张大大","张小小","张巧巧"})
public class Demo1 {

    //编译时报错,只能用于修饰类,不能修饰方法
    //@MyAnnotation(name = "张三",age = 25,sonName = {"张大大","张小小","张巧巧"})
    public void method(){
        System.out.println("方法执行");
    };

}

并且在 Demo1 生成的帮助文档中可以看到该注解的相关信息: 

     

4.解析注解——模拟工厂模式

 至此我们已经学会了如何定义一个注解以怎么去使用一个注解,但对于自定义的注解,如果只是单纯的在类,方法或者属性上标注这些注解信息,那有什么意义呢?看起来就像是多此一举,不如注释来的直接。

俗话说得好:“存在即合理”,注解也是如此。上面曾提到过,注解是给编译器看的,因此注解中的信息就一定能够被编译器所读取。如果能够通过一种方式获取注解的信息,是不是就可以用注解来完成很多的事情?目前所流行的 Spring 系列框架正是这样做的,其使用注解代替了原来的配置文件,简化了配置文件的复杂步骤,虽然这样做加深了项目的耦合度,但同时也具有其独特的优势。

那么如何才能获取注解中的信息呢?反射机制中所用到的 Class 类就为我们提供了获取注解的方法 —— getAnnotation ( ) 。不止是 Class ,与其相关的 Field、Constructor 以及 Method 都为我们提供了此方法来获取注解。

下面我们先实现一个小案例 —— 模拟工厂模式:

① 声明一个名为 Bean 的注解,其中有一个 String 属性:className ;声明此注解只能作用在类上,并且有效期直到运行结束。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {
    String className();
}

② 声明一个只含有默认私有化构造方法的 Student 类,限制随意实例化,只能通过 Factory 实例化。

public class Student {
    
    private String name;//姓名
    private Integer age;//年龄

    private Student(){}

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

③ 声明一个被 Bean 注解修饰的 Factory 类,用于实例化没有属性特征的指定对象。

//配置需要实例化的类名称
@Bean(className = "com.java.day04.Student")
public class Factory {

    public static Object getInstance() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

        //获取Factory的Class对象
        Class<Factory> factoryClass = Factory.class;

        //获取Factory的Bean注解
        Bean bean =  factoryClass.getAnnotation(Bean.class);

        //获取注解的className属性
        String className = bean.className();

        //根据属性值生成Class对象
        Class<?> instanceClass = Class.forName(className);

        //通过Class对象获取Constructor对象
        Constructor<?> constructor = instanceClass.getDeclaredConstructor();
        constructor.setAccessible(true);//暴力反射私有构造方法

        //返回Object类型的对象
        return constructor.newInstance();
    }
}

④ 此时我们是无法直接实例化一个学生对象的,因此只能使用 Factory 工厂模式进行实例化。

    public static void main(String[] args) throws ClassNotFoundException, 
    NoSuchMethodException, InvocationTargetException, InstantiationException, 
    IllegalAccessException {
        //通过工厂模式实例化一个学生
        Student student = (Student)Factory.getInstance();
        //为学生注入属性
        student.setName("张三");
        student.setAge(20);
        //打印学生信息
        System.out.println(student);
    }


    运行结果:

    Student{name='张三', age=20}

至此我们就完成了一个工厂模式的模拟,但是在模拟过程中有一个问题:我们知道注解是一个接口,那么下面这行代码又是如何实现的呢?

    //获取Factory的Bean注解
    Bean bean =  factoryClass.getAnnotation(Bean.class);

其实是 JVM 自动为我们在内存中创建了一个 Bean 的实现类对象,并把它指向 bean 。该过程与下面代码过程完全一致:

Bean bean = new Bean(){
    @Override
    public String className(){
        return "com.java.day04.Student";
    };
}

其次还需要注意的是:还有一个获得类所有注解而并非指定注解的 getAnnotations ( ) 方法,返回的是该类所有注解的 Annotaion 数组。

Object[] objects = factoryClass.getAnnotations();

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值