注解的概述
从JDK1.5开始,Java增加了对元数据的支持,也就是注解,Annotation。注解与注释是两个不同的概念,可以把注解理解为是一种特殊的标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。通过注解开发人员可以在不改变原有代码和逻辑的情况下在源代码中嵌入补充信息。现在主流的Java框架都使用了大量的注解,例如SpringBoot、SpringCloud、Hibernate等等。
基本注解
在JDK1.8后Java总共提供了5个基本注解,分别是:@Override、@Deprecated、@SuppressWarnings、@SafeVarargs、@FunctionalInterface。
@Override
@Override注解是用来限定被标记的方法必须是从父类或接口重写的方法,如果使用此注解的方法不是重写父类或接口的方法,则编译将无法通过,此注解只能在方法上使用。
package com.icypt.override;
/**
*运行结果:
*我是重写的接口方法
*我是重写的父类方法
*/
public class Test extends TestClass implements TestInterface{
//重写接口方法
@Override
public void interTestMethod() {
System.out.println("我是重写的接口方法");
}
//重写父类方法
public void classTestMethod() {
System.out.println("我是重写的父类方法");
}
public static void main(String[] args) {
Test test = new Test();
test.interTestMethod();
test.classTestMethod();
}
}
//定义接口
interface TestInterface {
//接口方法
public void interTestMethod();
}
//定义类
class TestClass {
public void classTestMethod() {
System.out.println("我是父类方法");
}
}
@Override注解的作用就是让开发人员在编译期间能够及时的发现方法是否有重写错误的问题。
@Deprecated
@Deprecated注解用于表示类、方法、属性等是否过时,一旦被此注解标记的类或方法等在其他地方被使用时,会出现编译警告,但是并不影响程序的编译。
package com.icypt.deprecated;
public class Test{
public static void main(String[] args) {
TestClass testClass = new TestClass();
//使用过时方法
testClass.classTestMethod();
}
}
//定义类
class TestClass {
@Deprecated
public void classTestMethod() {
System.out.println("我是一个过时方法");
}
}
以上栗子使用javac命令进行编译时,会报如下警告:
注: Test.java使用或覆盖了已过时的 API。 注: 有关详细信息, 请使用 -Xlint:deprecation 重新编译。
那么如何解决这个问题,最简单的办法就是使用@SuppressWarnings注解来抑制警告。
@SuppressWarnings
@SuppressWarnings表示被注解修饰的程序元素以及该元素中的所有子元素取消显示指定的编译器警告。
/**
*运行结果:
*我是一个过时方法
*/
public class Test{
//压制使用过时方法带来的警告
@SuppressWarnings(value="deprecation")
public static void main(String[] args) {
TestClass testClass = new TestClass();
//使用过时方法
testClass.classTestMethod();
}
}
//定义类
class TestClass {
@Deprecated
public void classTestMethod() {
System.out.println("我是一个过时方法");
}
}
通过使用@SuppressWarnings注解抑制了调用过时方法所引发的警告后,再此编译时,发现程序已经不报之前的警告了。
@SafeVarargs
@SafeVarargs是在JDK1.7之后提出的,主要用于抑制方法中使用形参类型为泛型的可变参数所引发的警告信息,前提是此方法必须由static或者final关键字修饰,否则注解将不生效。
package com.icypt.safevarargs;
import java.util.*;
/**
*运行结果
*123
*abc
*/
public class Test {
public static void main(String[] args) {
test(new String[] {"123", "abc"});
}
@SafeVarargs
public static <T> void test(T ... args) {
for(int i=0; i < args.length; i++) {
System.out.println(args[i]);
}
}
}
以上的例子中,test方法的形参列表是由泛型定义的可变参数,编译器会认为这种编码方式有可能会造成堆污染,所以引发警告信息。但是就上栗而言并不存在堆污染的情况,所以使用@SafeVarargs进行警告抑制,当然也可以使用@SuppressWarnings,个人理解是使用@SafeVarargs注解,可以把有引发堆污染警告的程序单独标记,与其他普通的警告抑制方式做一区分。
@FunctionalInterface
@FunctionalInterface注解是在JDK1.8之后提出的,只能用于修饰接口,被该注解修饰的接口被称之为函数式接口,关于函数式接口的定义在之前的文章中已经讨论过了这里就不赘述了。
//编译期保留,默认值 @Retention(value = RetentionPolicy.CLASS); //运行期保留 @Retention(value = RetentionPolicy.RUNTIME); //源码中保留 @Retention(value = RetentionPolicy.SOURCE);
//表示被标记的注解只能修饰注解
@Target(value= ElementType.ANNOTATION_TYPE);
//表示被标记的注解只能修饰类或接口
@Target(value = ElementType.TYPE);
//表示被标记的注解只能修饰构造器
@Target(value = ElementType.CONSTRUCTOR);
//表示被标记的注解只能修饰包
@Target(value = ElementType.PACKAGE);
//表示被标记的注解只能修饰成员变量
@Target(value = ElementType.FIELD);
//表示被标记的注解只能修饰方法
@Target(value = ElementType.METHOD);
//表示被标记的注解只能修饰常规参数
@Target(value = ElementType.PARAMETER);
//表示被标记的注解只能局部变量
@Target(value = ElementType.LOCAL_VARIABLE);
//JDK1.8新增,表示被标记的注解只能修饰泛型中的类型参数
@Target(value = ElementType.TYPE_PARAMETER);
//JDK1.8新增,表示被标记的注解可以修饰任何的类型
@Target(value = ElementType.TYPE_USE);
自定义注解
自定义注解使用@interface关键字定义,而且自定义注解也拥有自己的成员变量,成员变量的定义方式和接口的抽象方法类似,唯一不同的地方在于它可以使用default关键字指定默认值。
package com.icypt.annotation;
import java.lang.annotation.*;
//通过元注解指定该注解的保存时间在运行期有效
@Retention(RetentionPolicy.RUNTIME)
//通过元注解指定该注解的目标对象是方法
@Target(value = ElementType.METHOD)
//通过元注解指定该注解可以使用javadoc生成文档
@Documented
//通过元注解指定该注解可以被继承
@Inherited
public @interface Log {
//定义注解成员变量,并指定默认值
public String type() default "common";
public String desc() default "公共日志";
}
上栗中创建了一个@Log的注解,并定义了两个成员变量,发现注解的定义在形式上和接口的定义有些相似,但是细品之下确实大相径庭。根据注解的定义可知,注解被定义之后需要在程序运行的不同时机去获取注解的信息,这样定义的注解才有意义,否则仅仅是把注解作为一种标记置于所需要的地方而不做处理,是完全没有任何意义的。那么如何才能获取注解的信息呢?
获取注解信息
如果在程序运行期间需要获取注解的信息就必须借助反射来实现,关于反射的使用我会在后续的文章中详细的介绍,下面来演示一下注解信息的获取。
package com.icypt.annotation;
import java.lang.reflect.Method;
/**
* 运行结果
* type:insert
* desc:新增
* 模拟增加逻辑
*/
public class Test {
public static void main(String[] args) throws Exception{
//获取insert方法的注解信息,并执此方法,通过反射完成
//加载测试类Test
Class clzz = Class.forName("com.icypt.annotation.Test");
//获取insert方法
Method method = Class.forName("com.icypt.annotation.Test").getMethod("insert");
//获取insert方法的Log注解
Log log = method.getAnnotation(Log.class);
//打印log注解的成员变量
System.out.println("type:" + log.type());
System.out.println("desc:" + log.desc());
//后的Test类的实例
Object object = clzz.newInstance();
//通过反射执行insert方法
method.invoke(object);
}
@Log(type="insert", desc="新增")
public void insert() {
System.out.println("模拟增加逻辑");
}
}
通过以上栗子可以看出自定义注解和反射联合使用,可以使得程序在运行期间更加方便的获得一些额外的元数据,像这样的操作在后续Java框架的学习中会比较常用。
Java培训班:http://www.baizhiedu.com/java2019
注释:本文来自公众号冰点IT