Java注解基础复习
1.标准注解
1.1 @Override
定义:@Override应该仅用于方法(不用于类、包声明或其他构造),指明被注解的方法将覆盖超类中的方法。
这个注解的出现主要是在开发之中有可能会由于手误等原因导致方法不能被正确地覆写,有了这个注解就可以帮助检查是否覆写正确
1.2 @Deprecated
定义:@Deprecated来对不应再使用的方法进行注释,这通常用在对新版本代码的修改中。例如旧的版本中提供的方法,在新版本中有了更好的方法,将提示用户可以放弃旧的方法的使用
1.3 @SuppressWarning
定义:@SuppressWarnings,作用是用来消除代码的警告
2.元注解
定义:用于定义注解本身的注解叫元注解
在Java中有4种元注解,分别是Target、Retention、Inherited、Document。
2.1 @Target
定义:@Target表示注解的目标
下面结合@override注解这个例子来看:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
@Target(ElementType.METHOD)这句话表明@override的目标是方法即用来注解在方法上的。ElementType是一个枚举,主要可选值有:
- TYPE:表示类、接口(包括注解),或者枚举声明;
- FIELD:字段,包括枚举常量;
- METHOD:方法;
- PARAMETER:方法中的参数;
- CONSTRUCTOR:构造方法
- LOCAL_VARIABLE:本地变量;
- MODULE:模块(Java 9引入的)
目标可以有多个,用{}表示,比如@SuppressWarnings的@Target就有多个。Java 7的定义为:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
如果没有声明@Target,默认为适用于所有类型。
2.2 @Retention
定义:表示注解保留在什么时候,取值只有一个,类型为RetentionPolicy,它是一个枚举值,有三个取值:
- SOURCE:只在源代码中保留,编译器将代码编译为字节码文件后就会丢掉。
- CLASS:保留到字节码文件中,但Java虚拟机将class文件加载到内存时不一定会在内存中保留。
- RUNTIME:一直保留到运行时。
如果没有声明@Retention,则默认为CLASS
2.3 @Inherited
public class InheritDemo {
@Inherited
@Retention(RetentionPolicy.RUNTIME)
static @interface Test {
}
@Test
static class Base {
}
static class Child extends Base {
}
public static void main(String[] args) {
System.out.println(Child.class.isAnnotationPresent(Test.class));
}
}
Test是一个注解,类Base有该注解,Child继承了Base但没有声明该注解。main方法检查Child类是否有Test注解,输出为true,这是因为Test有注解@Inherited,如果去掉,输出会变成false。
2.4 @Document
定义:它表示注解信息包含到生成的文档中
2.5 注解参数
可以为注解定义一些参数,定义的方式是在注解内定义一些方法。
比如@SuppressWarnings内定义的方法value,返回值类型表示参数的类型
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
因此使用@SuppressWarnings时必须给value提供值,比如:
@SuppressWarnings(value={"deprecation", "unused"})
当只有一个参数,且名称为value时,提供参数值时可以省略"value=",即上面的代码可以简写为:
@SuppressWarnings({"deprecation", "unused"})
注解内参数的类型不是什么都可以的,合法的类型有基本类型、String、Class、枚举、注解,以及这些类型的数组。
参数定义时可以使用default指定一个默认值,比如:
@Target({ METHOD, CONSTRUCTOR, FIELD })
@Retention(RUNTIME)
@Documented
public @interface Inject {
boolean optional() default false;
}
它有一个参数optional,默认值为false。如果类型为String,默认值可以为"",但不能为null。如果定义了参数且没有提供默认值,在使用注解时必须提供具体的值,不能为null。
3.自定义注解
内置的注释实际上都是JDK 5.0中的一种注释类型,存在于包java.lang.annotation中。因此,我们也可以定义自己的注释类,即自定义注释。
3.1自定义注解步骤:
1.定义注解类型@interface。
- 添加注解参数(类似方法)。
3.设置默认值。
4.为注解类设置相关属性,包括以下几种。
● 设置目标范围@Target。
● 设置保持性@Retention。
● 添加公文档@Documentedz。
● 设置集成@Inherited。
//设置注解属性
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
@Inherited
@Document
public @interface TestInterface { //定义为注解类型
int number(); //添加注解参数,没有默认值
String value() default "" ; //添加注解参数,默认值为空字符串
}
使用注解:
@TestInterface(number=1, value="test")
public void test() {
}
4.查看注解信息
@Retention为RetentionPolicy.RUNTIME的注解,利用反射机制在运行时进行查看和利用这些信息。反射相关类中与注解有关的方法,这里汇总说明下,Class、Field、Method、Constructor中都有如下方法:
//获取所有的注解
public Annotation[] getAnnotations()
//获取所有本元素上直接声明的注解,忽略inherited来的
public Annotation[] getDeclaredAnnotations()
//获取指定类型的注解,没有返回null
public <A extends Annotation> A getAnnotation(Class<A> annotationClass)
//判断是否有指定类型的注解
public boolean isAnnotationPresent(
Class<? extends Annotation> annotationClass)
Annotation是一个接口,它表示注解,具体定义为:
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
//返回真正的注解类型
Class<? extends Annotation> annotationType();
}
对于Method和Contructor,它们都有方法参数,而参数也可以有注解,所以它们都有如下方法:
public Annotation[][] getParameterAnnotations()
返回值是一个二维数组,每个参数对应一个一维数组。我们看个简单的例子:
public class MethodAnnotations {
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
static @interface QueryParam {
String value();
}
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
static @interface DefaultValue {
String value() default "";
}
public void hello(@QueryParam("action") String action,
@QueryParam("sort") @DefaultValue("asc") String sort){
//…
}
public static void main(String[] args) throws Exception {
Class<? > cls = MethodAnnotations.class;
Method method = cls.getMethod("hello",
new Class[]{String.class, String.class});
Annotation[][] annts = method.getParameterAnnotations();
for(int i=0; i<annts.length; i++){
System.out.println("annotations for paramter " + (i+1));
Annotation[] anntArr = annts[i];
for(Annotation annt : anntArr){
if(annt instanceof QueryParam){
QueryParam qp = (QueryParam)annt;
System.out.println(qp.annotationType()
.getSimpleName()+":"+ qp.value());
}else if(annt instanceof DefaultValue){
DefaultValue dv = (DefaultValue)annt;
System.out.println(dv.annotationType()
.getSimpleName()+":"+ dv.value());
}
}
}
}
}
这里定义了两个注解@QueryParam和@DefaultValue,都用于修饰方法参数,方法hello使用了这两个注解,在main方法中,我们演示了如何获取方法参数的注解信息,输出为:
annotations for paramter 1
QueryParam:action
annotations for paramter 2
QueryParam:sort
DefaultValue:asc
5.注解应用
5.1 定制序列化
实现一个简单的类SimpleFormatter,它有一个方法:
public static String format(Object obj)
定义两个注解:@Label和@Format。@Label用于定制输出字段的名称,@Format用于定义日期类型的输出格式,它们的定义如下:
@Retention(RUNTIME)
@Target(FIELD)
public @interface Label {
String value() default "";
}
@Retention(RUNTIME)
@Target(FIELD)
public @interface Format {
String pattern() default "yyyy-MM-dd HH:mm:ss";
String timezone() default "GMT+8";
}
可以用这两个注解来修饰要序列化的类字段,比如:
static class Student {
@Label("姓名")
String name;
@Label("出生日期")
@Format(pattern="yyyy/MM/dd")
Date born;
@Label("分数")
double score;
//其他代码
我们可以这样来使用SimpleFormatter:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Student zhangsan = new Student("张三", sdf.parse("1990-12-12"), 80.9d);
System.out.println(SimpleFormatter.format(zhangsan));
输出为:
姓名:张三
出生日期:1990/12/12
分数:80.9
SimpleFormatter.format()是怎么利用这些注解的呢?
public static String format(Object obj) {
try {
Class<? > cls = obj.getClass();
StringBuilder sb = new StringBuilder();
for(Field f : cls.getDeclaredFields()) {
if(! f.isAccessible()) {
f.setAccessible(true);
}
Label label = f.getAnnotation(Label.class);
String name = label ! = null ? label.value() : f.getName();
Object value = f.get(obj);
if(value ! = null && f.getType() == Date.class) {
value = formatDate(f, value);
}
sb.append(name + ":" + value + "\n");
}
return sb.toString();
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
对于日期类型的字段,调用了formatDate,其代码为:
private static Object formatDate(Field f, Object value) {
Format format = f.getAnnotation(Format.class);
if(format ! = null) {
SimpleDateFormat sdf = new SimpleDateFormat(format.pattern());
sdf.setTimeZone(TimeZone.getTimeZone(format.timezone()));
return sdf.format(value);
}
return value;
}
5.2 DI容器(依赖注入容器)
- @SimpleInject
引入一个注解@SimpleInject,修饰类中字段,表达依赖关系,定义为:
@Retention(RUNTIME)
@Target(FIELD)
public @interface SimpleInject {
}
定义两个简单的服务ServiceA和ServiceB, ServiceA依赖于ServiceB,它们的代码如下:
public class ServiceA {
@SimpleInject
ServiceB b;
public void callB(){
b.action();
}
}
public class ServiceB {
public void action(){
System.out.println("I'm B");
}
}
ServiceA使用@SimpleInject表达对ServiceB的依赖。
DI容器的类为SimpleContainer,提供一个方法:
public static <T> T getInstance(Class<T> cls)
应用程序使用该方法获取对象实例,而不是自己new,使用方法如下所示:
ServiceA a = SimpleContainer.getInstance(ServiceA.class);
a.callB();
SimpleContainer.getInstance会创建需要的对象,并配置依赖关系,其代码为:
public static <T> T getInstance(Class<T> cls) {
try {
T obj = cls.newInstance();
Field[] fields = cls.getDeclaredFields();
for(Field f : fields) {
if(f.isAnnotationPresent(SimpleInject.class)) {
if(! f.isAccessible()) {
f.setAccessible(true);
}
Class<? > fieldCls = f.getType();
f.set(obj, getInstance(fieldCls));
}
}
return obj;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
- @SimpleSingleton
在上面的代码中,每次获取一个类型的对象,都会新创建一个对象,实际开发中,这可能不是期望的结果,期望的模式可能是单例,即每个类型只创建一个对象,该对象被所有访问的代码共享,怎么满足这种需求呢?我们增加一个注解@SimpleSingleton,用于修饰类,表示类型是单例,定义如下:
@Retention(RUNTIME)
@Target(TYPE)
public @interface SimpleSingleton {
}
我们可以这样修饰ServiceB:
@SimpleSingleton
public class ServiceB {
public void action(){
System.out.println("I'm B");
}
}
SimpleContainer也需要做修改,首先增加一个静态变量,缓存创建过的单例对象:
private static Map<Class<? >, Object> instances
= new ConcurrentHashMap<>();
getInstance也需要做修改,如下所示:
public static <T> T getInstance(Class<T> cls) {
try {
boolean singleton = cls.isAnnotationPresent(SimpleSingleton.class);
if(! singleton) {
return createInstance(cls);
}
Object obj = instances.get(cls);
if(obj ! = null) {
return (T) obj;
}
synchronized (cls) {
obj = instances.get(cls);
if(obj == null) {
obj = createInstance(cls);
instances.put(cls, obj);
}
}
return (T) obj;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
createInstance与第一版的getInstance类似,代码为:
private static <T> T createInstance(Class<T> cls)
throws Exception {
T obj = cls.newInstance();
Field[] fields = cls.getDeclaredFields();
for(Field f : fields) {
if(f.isAnnotationPresent(SimpleInject.class)) {
if(! f.isAccessible()) {
f.setAccessible(true);
}
Class<? > fieldCls = f.getType();
f.set(obj, getInstance(fieldCls));
}
}
return obj;
}
6.总结
注解提升了Java语言的表达能力,有效地实现了应用功能和底层功能的分离,框架/库的程序员可以专注于底层实现,借助反射实现通用功能,提供注解给应用程序员使用,应用程序员可以专注于应用功能,通过简单的声明式注解与框架/库进行协作。