这篇文章主要是想要介绍注解,在现在的开发中,使用注解是十分常见的操作了,不仅在 JDK
中提供了注解,现在各个框架也提供了注解方式进行开发,在这篇文章中我们会介绍注解的原理,以及如何自定义注解,掌握了注解的原理之后,我们也能更好地使用各个注解,并可以使用自定义注解来创建一些工具,以简化我们平时的开发任务,因此注解同样也是非常重要的基础知识,值得积累。
1.介绍
注解是一种代码级别的说明,是从 JDK1.5
版本开始引入的一个特性,与类、接口、枚举是在一个层次,它可以用于创建文档,跟踪代码中的依赖性,甚至是执行基本编译时检查,注解是以 @注解名
这样的形式在代码中存在的。
注解可以声明在包、类、字段、方法、局部变量和方法参数等的前面,用于对这些元素进行说明和注释,同时我们可以在编写注解时指定注解的作用范围,是在源代码级,还是 class
文件级别,还是运行时也有效,分别对应于 SOURCE
、CLASS
以及 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
元注解的取值一般会是 TYPE
和 METHOD
,表示注解可以用在类和方法上面。
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
中注解的所有内容,重要的就是掌握注解的本质,以及如何自定义注解和使用自定义的注解,最终的目的当然还是使用我们自定义的注解完成想要的功能。写完了注解相关的内容,感觉自己的工具箱又多了一件有用的工具,慢慢地积累总是会给人这样的踏实感。