注解

这篇文章主要是想要介绍注解,在现在的开发中,使用注解是十分常见的操作了,不仅在 JDK 中提供了注解,现在各个框架也提供了注解方式进行开发,在这篇文章中我们会介绍注解的原理,以及如何自定义注解,掌握了注解的原理之后,我们也能更好地使用各个注解,并可以使用自定义注解来创建一些工具,以简化我们平时的开发任务,因此注解同样也是非常重要的基础知识,值得积累。

1.介绍

注解是一种代码级别的说明,是从 JDK1.5 版本开始引入的一个特性,与类、接口、枚举是在一个层次,它可以用于创建文档,跟踪代码中的依赖性,甚至是执行基本编译时检查,注解是以 @注解名 这样的形式在代码中存在的。

注解可以声明在包、类、字段、方法、局部变量和方法参数等的前面,用于对这些元素进行说明和注释,同时我们可以在编写注解时指定注解的作用范围,是在源代码级,还是 class 文件级别,还是运行时也有效,分别对应于 SOURCECLASS 以及 RUNTIME 这三个阶段。

现在的开发中,我们常常会使用注解来替代 xml 配置文件。

2.基本内置注解

我们可以先看看 JDK 提供的几个内置注解,来熟悉一下注解的使用和作用,分别是以下三个:

@Override
@Deprecated
@SuppressWarnings

首先看第 1@Override 注解,这个注解的作用是对覆盖超类中方法的方法进行标记,如果被标记的方法实际上并没有覆盖超类中的方法,那编译器就会提示编译错误。

使用可以如下:

public class AnnotationTest {

    @Override
    public String toString() {
        return super.toString();
    }
}

需要注意的一点是,对于接口中的方法覆盖,在 JDK1.5 时,使用 @Override 注解是会提示编译报错的,也就是在该版本的 JDK 中,尚不支持使用 @Override 注解来标识接口中方法的重写,而在 JDK1.6 时就可以支持了。

interface A {
    public void show();
}

class B implements A {

    @Override
    public void show() {

    }
}

也就是对于上面的示例来说,在 JDK1.5 版本时是不支持的,而在 JDK1.6 版本及以后的版本是支持的。

2@Deprecated 注解,则是用来标记已经过时或者不建议再使用的方法,用该注解标注的方法可能是在较早的 JDK 版本中出现,随着 JDK 版本的升级,后续的版本中有更加高效的处理方法,这时候就不再建议使用之前版本中的方法了。使用 @Deprecated 注解标注的方法,定义和使用时都可以看到方法名上面有一条横线,就是不再建议使用的意思了,而通常我们也可以找到更适合的方法。

public class AnnotationTest {

    @Deprecated
    public static void show() {

    }

    public static void main(String[] args) {
        show();
    }
}

这便是我们可以使用 @Deprecated 注解的方式了。

3@SuppressWarnings 注解,主要是用来消除程序中的一些警告信息,比如我们在使用集合没有加泛型时,这时候编译器就会有警告信息,或者声明了一个 int 型变量却没有使用,这时候也会有警告信息,如果想要消除这些警告信息,我们便可以使用 @SuppressWarnings 注解。

public class AnnotationTest {

    public static void main(String[] args) {

        @SuppressWarnings("rawtypes")
        List list = new ArrayList();
        System.out.println(list);

        @SuppressWarnings("unused")
        int a = 10;
    }
}

上面的代码便是使用示例了,从上面也可以看出,@SuppressWarnings 注解里面的参数有很多种,分别对应表示消除不同的警告信息,一般会有如下这些:

deprecation,使用了过时的类或方法时的警告
unchecked,执行了未检查的转换时的警告
fallthrough,当switch程序块直接通往下一种情况而没有 break 时的警告
path,在类路径、源文件路径等中有不存在的路径时的警告
serial,当在可序列化的类上缺少serialVersionUID 定义时的警告
finally ,任何finally子句不能正常完成时的警告
all,关于以上所有情况的警告

当然上面这些告警类型是不需要记忆的,当我们编程时,碰到这些情况,编译器会自动提示我们应该加哪种告警信息类型,同时还有一种简便方法,那就是在类的声明上加上 @SuppressWarnings("all"),这样的话类中所有的警告信息就都会被清除了。

3.自定义注解

下面我们是想能够自定义一个注解,通过这个能学会注解的声明,以及注解的本质是怎样的,还有注解的成员可以有哪些,为了我们自己能自定义一些注解,我们可以先看看 JDK 提供的一些注解是如何进行声明的,比如 @Override 注解,我们可以看下源码:

public @interface Override {
}

这样我们就可以看出,注解是通过 @interface 来进行声明的,因此我们可以像下面这样自定义一个注解:

public @interface MyAnnotation {

}

这样的话我们就自定义了一个 MyAnnotation 的注解了,后续我们便可以像这样定义自己想要的注解。

为了了解到注解的本质,我们需要找到上面我们刚定义的注解 MyAnnotation 所编译生成的 class 文件,然后反编译该 class 文件,查看其对应的代码。

public interface com.laucloud.annotation.MyAnnotation extends java.lang.annotation.Annotation {
}

我们可以看到我们声明的 MyAnnotation 注解其实本质上还是一个接口,然后它实现了一个 Annotation 接口,查看该 Annotation 接口的 api 描述,可以发现这个接口是所有注解类型扩展的公共接口,但是不允许手动实现该接口来声明注解类型,也就是说,我们在声明注解时,只能通过 @interface 来声明,而不能直接实现 java.lang.annotation.Annotation 接口来声明。

关于注解中的成员,由于注解本质上是一个接口,因此也是可以声明成员变量的,但是我们在注解中一般不会使用成员变量,同时注解中也可以有方法,注解中的方法还有普通接口和类中不一样的作用,因此注解中的方法一般是称为注解的属性。

public @interface MyAnnotation {

    public String name = "123";

    public String value();
}

上面便是一个自定义注解的定义,这里只是简单了解,后面我们还会详细介绍使用细节的。

4.注解属性类型

在我们上面自定义的注解中,声明了一个注解属性,那就是 value

public String value();

该注解属性的类型是 String 类型的,那在 Java 注解中,属性类型都可以是什么类型呢?一共就是以下这些了。

1.基本类型 byte short int long float double char boolean
2.String
3.枚举类型
4.注解类型
5.Class类型
6.以上类型的一维数组类型

结合例子来看,为了说明枚举类型和注解类型也能作为注解属性的类型,我们需要先创建一个枚举类和一个注解类。

public enum MyEnum {

    A, B, C;
}

public @interface MyAnnotation2 {

}

public @interface MyAnnotation {

    int a();

    String value();

    MyEnum b();

    MyAnnotation2 c();

    Class d();

    int[] e();

    String[] f();
}

在上面的代码中,我们就可以看到各种可以作为注解属性类型的类型了。

5.注解属性使用

下面我们来看使用注解时,对于注解中有属性的情况,应该如何运用,下面是一些方法和总结,根据这些总结,可以让我们更容易地使用各种注解。

1.如果一个注解有属性,那么在使用注解时,要对属性进行赋值操作.
例如:

@MyAnnotation(st = "aaa")

2.如果一个注解的属性有多个,都需要赋值,使用”,”分开属性.

@MyAnnotation(st = "aaa",i=10)

3.也可以给属性赋默认值

double d() default 1.23;

如果属性有默认值,在使用注解时,就可以不用为属性赋值。

4.如果属性是数组类型

1.可以直接使用属性名={值1,值2,。。。}方式,例如

        @MyAnnotation(st = "aaa",i=10,sts={"a","b"})

2.如果数组的值只有一个也可以写成下面方式

        @MyAnnotation(st = "aaa",i=10,sts="a")

注意sts属性它是数组类型,也就是说,只有一个值时,可以省略"{}"

5.对于属性名称 value的操作.

1.如果属性名称叫value,那么在使用时,可以省略属性名称

        @MyAnnotation("hello")

2.如果有多个属性,都需要赋值,其中一个叫value,这时,必须写属性名称

        @MyAnnotation(value="hello",i=10)

3.如果属性名称叫value,它的类型是数组类型.

    1.只有这个value属性
    可以直接赋值,不用写属性名称,但是,如果只有一个值

        @MyAnnotation({"abc"})或  @MyAnnotation("abc")

    但是如果有多个值

        @MyAnnotation({"abc","def"})

    2.如果有多个属性,属性名称叫value
    所有属性都需要赋值,那么必须写属性名称.

6.元注解

下面我们再来看元注解,元注解是用来修饰注解的注解,主要是用来描述注解在什么范围以及在什么阶段使用。

元注解一共有 4 个,分别为:

@Retention
@Target
@Documented
@Inherited

先看第 1@Retention 元注解,该元注解是用来描述注解在哪个阶段有效的,分别有 3 个阶段,分别为 SOURCE 编译阶段,CLASS 解析执行阶段以及 RUNTIME 运行阶段,当我们想标注某个注解在什么阶段有效时,就可以像下面这样使用。

@Retention(RetentionPolicy.RUNTIME)

上面的元注解标识的注解为在运行时有效,如果想标注其它两个阶段也是类似的用法。

2@Target 元注解,是用来标注注解可以用在哪些地方的,比如类上面,方法上面这些,也就是修饰目标对象类型,具体的使用可以如下:

@Target(ElementType.TYPE)

被这样标注的注解表示修饰的目标对象为类、接口或者枚举,该元注解可以修饰的目标对象类型,都在 ElementType 类中有定义,分别如下:

ANNOTATION_TYPE        注解类型声明
CONSTRUCTOR            构造方法声明
FIELD                字段声明
LOCAL_VARIABLE        局部变量声明
METHOD                方法声明
PACKAGE                包声明
PARAMETER            参数声明
TYPE                类、接口(包含注解)、枚举声明

3@Documented 元注解,主要是将注解的信息生成到 javadoc 当中。

4@Inherited 元注解,主要是用来标识注解具有继承性,被具有该元注解的注解标识的目标对象,其子类也会自动继承该注解。

总的来说,@Retention@Target 这两个元注解是我们自定义注解时必须使用的两个元注解,而且 @Retention 元注解通常会取值 RUNTIME,表示该注解运行阶段仍有效,我们通常可以结合反射技术一起使用来实现想要的功能,@Target 元注解的取值一般会是 TYPEMETHOD,表示注解可以用在类和方法上面。

7.案例

现在我们使用注解完成一个案例,使用注解将数据库连接信息放到工具类的方法上面,然后再通过反射技术获取注解中配置的数据库连接信息,进而获取得到数据库连接对象。

首先定义一个注解,用于标识数据库连接信息的。

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

    String driverClass();

    String url();

    String user();

    String password();
}

然后在数据库工具类中通过反射获取数据库连接配置信息。

public class JdbcUtils {

    @JdbcProperty(driverClass = "com.mysql.jdbc.Driver", url = "jdbc:mysql:///test", user = "root", password = "123456")
    public static Connection getConnection() throws Exception {
        Class<JdbcUtils> clazz = JdbcUtils.class;
        Method method = clazz.getDeclaredMethod("getConnection");

        // 获取方法上的注解对象
        JdbcProperty jp = method.getAnnotation(JdbcProperty.class);

        String driverClass = jp.driverClass();
        // 1.加载驱动
        Class.forName(driverClass);

        String url = jp.url();
        String user = jp.user();
        String password = jp.password();
        // 2.获取连接
        Connection connection = DriverManager.getConnection(url, user, password);
        return connection;
    }
}

这样就能实现我们想要的目的了。

8.总结

以上就是我们想要介绍 Java 中注解的所有内容,重要的就是掌握注解的本质,以及如何自定义注解和使用自定义的注解,最终的目的当然还是使用我们自定义的注解完成想要的功能。写完了注解相关的内容,感觉自己的工具箱又多了一件有用的工具,慢慢地积累总是会给人这样的踏实感。

坚持原创技术分享,您的支持将鼓励我继续创作!