注解使用
简介
在Java中,注解就是给程序添加一些信息,用字符@开头,这些信息用于修饰它后面紧挨着的其他代码元素,比如类、接口、字段、方法、方法中的参数、构造方法等。注解可以被编译器、程序运行时和其他工具使用,用于增强或修改程序行为等。
内置注解
Java有一些自己的原生注解。
-
@Override:重写,子类继承父类,重写父类方法时,会出现这个注解。
@Override
public void test01() {
super.test01();
}
-
@Deprecated:它表示对应的代码已经过时了,程序员不应该使用它,不过,它是一种警告,而不是强制性的。比如Date的构造方法就已经过时了,但我们依然能够使用。
public class AnnotationTest01 {
public static void main(String[] args) {
Date date=new Date(2000,4,12);
int year=date.getYear();
}
}
-
@SuppressWarnings:表示压制Java的编译警告,它有一个必填参数,表示压制哪种类型的警告,它可以修饰大部分代码元素,在更大范围的修饰也会对内部元素起效,比如,在类上的注解会影响到方法,在方法上的注解会影响到代码行。
public class AnnotationTest01 {
// 压制注解
@SuppressWarnings({"deprecation","unused"})
public static void main(String[] args) {
Date date=new Date(2017,4,12);
int year=date.getYear();
}
}
具体参数:
-
@FunctionalInterface:函数式接口,比如java的四大函数式接口中的消费接口。
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
创建注解的方式
定义注解与定义接口有点类似,都用了interface,不过注解的interface前多了@这个符号。另外,它还有两个元注解@Target和@Retention,这两个注解专门用于定义注解本身。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
元注解
所谓元注解就是用来修饰其他注解的注解
@Target
@Target: 表示这个注解可以修饰哪些地方,它有一个枚举,里面有一些可选值可以选择。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
ElementType 可选值如下:
-
TYPE:表示类、接口(包括注解)、或者枚举声明 -
FIELD:字段,包括枚举常量 -
METHOD:方法 -
PARAMETER:方法中的参数 -
CONSTRUCTOR:构造方法 -
LOCAL_VARLABLE:本地变量 -
MODULE:模块(Java9 引入的)
它的可选值可以有多个,用{}表示,枚举常量之间用逗号隔开,比如SuppressWarnings压制Java警告的注解;
如果没有声明@Target,默认为适用于所有类型。
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
@Retention
@Retention:表示这个注解的生命周期,就是这个注解保留到什么时候。它的可选参数取值只能选择其中一个。
类型为RetenionPolicy,是一个枚举,有三个取值:
-
SOURCE:只在源代码中保留,编译器将代码编译为字节码文件后就会被丢失 -
CLASS:保留到字节码文件中,但Java虚拟机将class文件加载到内存时不一定会在内存保留 -
RUNTIME:一直保留到运行时,编译后也会生成 注解名.class的文件,因此我们之后我们可以使用反射机制来读取这些注解。
如果没有声明@Retention,则默认为CLASS。
@Inherited
可以让注解被继承,但这不是真正的继承,只是说子类通过使用@Inherited注解,可以让子类使用一个反射方法getAnnotations获取父类被@Inherited修饰的注解。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited // 继承
public @interface AnimalDemo {
String value() default "1";
}
@AnimalDemo("2")
public class Animal {
}
class Dog extends Animal{
public static void main(String[] args) {
// 判断是否有指定类型的注解
boolean annotationPresent = Dog.class.isAnnotationPresent(AnimalDemo.class);
// 这里返回的true,因为父类上的注解有@Inherited修饰
System.out.println(annotationPresent);
// 可以通过子类的class对象,获取到父类身上被@Inherited修饰的注解
// 获取所有的注解,返回来一个数组
Annotation[] annotations = Dog.class.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation.annotationType().getSimpleName());
}
}
}
@Documented
@AnimalDemo
public class Main{
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@interface AnimalDemo{
String value() default "";
}
此注解,可以让被修饰的注解生成到java的doc文档上面。
注意事项
1. 注解内的参数的类支持八大基本类型(基本类型对应的包装类是不可以的),Class,String,枚举,注解以及这些类型的数组。 如果使用了其他数据类型,编译器会抛出错误。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited // 继承
public @interface AnimalDemo {
String value() default "1";
int number() default 0;
boolean flag() default false;
byte byteNum() default 1;
Class<?> test() default int.class;
AnimalDe an() default @AnimalDe(shortNum = 5);
double[] doubleNum() default {1.0,2.0,3.0};
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface AnimalDe{
short shortNum() default 2;
}
2. 与接口和类不同,注解不能继承,因此不能使用关键字extends来继承另外一个注解。
但是注解在编译后,注解会自动继承Annotation接口。我们来反编译看一下。即使Java接口可以多继承,但是在定义注解时依然无法使用extends继承其他注解。
3. 如果在注解中定义了参数,但是没有提供默认值,在使用注解的时候必须提供一个具体的值,不能为null,否则编译不过去。此时的name并没有提供默认值。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnimalDemo1 {
String name();
//String name() default "";
}
但是我们在参数后面加上default “”,表示一个默认值,这样就不会出现错误了。
4. 当注解中的有参数名称为value时,可以直接省略key=value这种写法,直接写值即可。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnimalDemo1 {
String value() default "";
}
@AnimalDemo1("1")
public class Dog{
}
注解与反射
在我们反编译后,看到定义到的注解都会默认继承Annotation接口,这个接口是所有注解的父接口。为了能够在运行时准确获取到注解的相关信息,java在反射包中有一个AnnotationElement接口,主要表示正在虚拟机中运行的程序中已使用注解的元素,通过该接口提供的方法可以利用反射技术读取注解的信息,比如反射包下的Constructor类、Field类、Method类、Package类、Class类等等都实现了AnnotationElement接口。
AnnotationElement接口中通用的5个方法。
<T extends Annotation> T getAnnotation(Class<T> annotationClass); // 获取指定类型的注解,没有返回null
Annotation[] getAnnotations(); // 获取所有的注解
default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) // 判断是否有指定类型的注解
Annotation[] getDeclaredAnnotations() // 获取所有本元素上直接声明的注解,不包括@Inherited修饰的注解
default <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass) // 获取指定类型的注解,不包括@Inherited修饰的注解,如果有则返回,没有返回null
案例(自定义注解的应用)
自定义一个单例注解
这个案例有1个注解,只要某个类带上这个注解,那么就是单例的。 我们先来看注解。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Singleton {
}
创建一个检查此类是否带有单例注解的类
public class ServiceSingleton {
private static ConcurrentHashMap<Class<?>,Object> map=new ConcurrentHashMap<>();
public static<T> T getInstance(Class<T> cls) throws IllegalAccessException, InstantiationException {
// 1. 传入一个对象
// 2. 判断该类身上是否有单例注解
// 3. 如果没有则创建,如果存在则直接返回
boolean singletonFlag = cls.isAnnotationPresent(Singleton.class);
if(!singletonFlag){
return cls.newInstance();
}
if(map.contains(cls)){
return (T) map.get(cls);
}else{
synchronized (cls){
Object o = map.get(cls);
if(o!=null){
return (T) o;
}else{
o= cls.newInstance();
map.put(cls,o);
return (T) o;
}
}
}
}
}
使用ServiceA类的main方法进行测试,ServiceA类带有Singleton注解
@Singleton
public class ServiceA {
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
ServiceA instance1 = ServiceSingleton.getInstance(ServiceA.class);
ServiceA instance2 = ServiceSingleton.getInstance(ServiceA.class);
System.out.println(instance1==instance2);
}
}
结果返回true
总结
注解提升了Java语言的表达能力,有效的实现了应用功能和底层功能的分离,应用程序员可以专注于应用功能。并且注解简化了代码并提高编码的效率。
注解分析就到这里了。。。
原文始发于微信公众号(阿黄学编程):如何使用注解
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/35618.html