21天学会Java之(Java SE第十四篇):注解、反射机制

注解和反射机制使用特别简单,但是它们在框架中被大量的使用,而如何灵活运用,想要深入理解框架,牢牢的掌握注解和反射机制的知识就显得极其的重要了。

注解

注解不同于注释,注释仅只用于写在源代码中,来使自己或者别人更容易的翻阅源代码。

注解是那些插入到源代码中使用其他工具可以对其进行处理的标签。这些工具可以在源代码层次上进行操作,或者可以处理编译器在其中放置了注解的类文件。

注解不会改变程序的编译方式。Java编译器对于包含注解和不包含注解的代码会生成相同的虚拟机指令。

注解主要用途有以下两点:

  • 附属文件的自动生成,例如部署描述符或者bean信息类。
  • 测试、日志、事务语义等代码的自动生成。

使用注解的前提下,首先我们应该知道注解本身不会做任何事情,它们只是存在于源文件中。编译器将它们置于类文件中,并且虚拟机会将它们载入。

注解的使用

注解接口

注解是由注解接口来定义的,语法格式如下:

修饰符 @interface 注解名{
   
    元素类型声明1
    元素类型声明2
    ...
}

每个元素类型声明语法格式如下(元素类型见下文):

元素类型 对象名();   //不带默认值就是类型的默认值
或者
元素类型 对象名() default value;   //带默认值

举个栗子,下面的注解具有三个元素,id、name、age:

public @interface Test{
   
    int id();
    String name() default "小王";
    int age() default 21;
}

所有的注解接口都隐性地扩展自java.lang.annotation.Annotation接口。这个接口是一个常规接口,不是一个注解接口。下表是这个接口的一些常用方法:

方法 用途
Class<? extends Annotation> annotationType() 返回Class对象,它用于描述该注解对象的注解接口。注意:调用注解对象上的getClass方法可以返回真正的类,而不是接口
boolean equals(Object other) 如果other是一个实现了与该注解对象相同的注解接口的对象,并且如果该对象和other的所有元素彼此相等。那么返回True
int hashCode() 返回一个与equals方法兼容、由注解接口名以及元素值衍生而来的散列码
String toString() 返回一个包含注解接口名以及元素值的字符串表示,例如,@Test(id=0,name=“小王”,age=21)

所有的注解接口都直接扩展自java.lang.annotation.Annotation,我们不需要为注解接口提供实现类。

注解元素的类型为下列之一:

  • 基础数据类型(byte、short、int、long、char、double、float或boolean)
  • String
  • Class(具有一个可选的类型参数,例如Class<? extends MyClass> )
  • enum类型
  • 注解类型
  • 有前面所述类型组成的数组(由数组组成的多维数组不是合法的元素类型)

注解

注解的格式

每个注解都具有这种格式@注解名(元素名1=值1,元素名2=值2,....)

例如上文的注解他应该这种格式@Test(id=0,name="小王",age=21)

而元素的顺序无关紧要,@Test(name="小王",age=21,id=0)这个注解和前面那个注解一样。

如果某个元素的值并未指定,那么就使用声明的默认值,或者元素类型的默认值。例如@Test(id=0),那么元素name的值就是字符串小王,元素age的值就是21。

注意事项:默认值并不是和注解存储在一起的;它们是动态计算而来的。例如,如果你将元素age的默认值改为21,然后重新编译Test接口,那么注释@Test(id=0)将使用这个新的默认值,甚至在那些在默认值修改之前就已经编译过的类文件中也是如此。

注解格式简化

有两个特殊的快捷方式可以用来简化注解。

  1. 如果没有指定元素,要么是因为注解中没有任何元素,要么是因为所有元素都使用默认值,那么你就不需要使用圆括号了。例如@Test和这个注解是一样的@Test(id=0,name="小王",age=21),这样的注解又称为标记注解。
  2. 另外一种快捷方式是单值注解。如果一个元素具有特殊的名字value,并且没有指定其他元素,那么你就可以忽略掉这个元素名以及等号。例如:
定义一个注解接口如下形式:
public @interface TestOneValue(){
   
    String value();
}
那么,我们可以将这个注解书写成如下形式:
@TestOneValue("test")
而不是
@TestOneValue(value="test")
注解的使用

一个项可以有多个注解,例如:

@Test(id=0,name="小王",age=21)
@TestOneValue("test")
public void test01(){
   }

如果注解声明为可重复的,那么我们就可以重复使用同一个注解:

@Test(id=0,name="小王",age=21)
@Test(id=1,name="小二",age=2)
public void test02(){
   }

注意事项:一个注解元素永远不能设置为null,并且不允许其默认值为null。这样在实际应用中会相当不方便。你必须使用其他的默认值,泥例如“”或者Void.class。

如果元素值是一个数组,那么要将它的值用括号括起来,例如:@Test(...,score={100,101,102})

如果该元素只有一个值,那么可以忽略这些括号,例如:@Test(...,score=100),这个就和@Test(...,score={100})一样。

既然个注解可以是另一个注解,那么就可以创建出任意复杂的注解,但是一般我们不这么用理解即可,例如:@Test(ref=@TestOneValue("test")

注意事项:在注解中引入循环依赖是一种错误。例如,因为Test具有一个注解类型为Reference的元素,所以Reference就不能再拥有一个类型为Test的元素。

注解各类声明

注解可以出现在许多地方,这些地方可以分为两类:声明和类型用法声明注解可以出现在下列声明处:

  • 类(包括enum)
  • 接口(包括注解接口)
  • 方法
  • 构造器
  • 实例域(包含enum常量)
  • 局部变量
  • 参数变量
  • 类型参数

对于类和接口,需要将注解放置在class和interface关键词的前面:

@Test
public class Student {
   ...}

对于变量,需要将它们放置在类型的前面:

@SuppressWarnings("unchecked")
int age;

泛型或者方法中的类型参数可以想下面这样被注解:

public class Cache<@Immutable V>{
   ...}

包是在文件package-info.java中注解的,该文件只包含以注解先导的包语句:

/**
	Package-level Javadoc
*/
@GPL(version="3")
package cn.ac.whz.annotation;
import org.gnu.GPL;

注意事项:对局部变量的注解只能在源码级别上进行处理。类文件并不描述局部变量。因此,所有的局部变量注解在编译完一个类的时候就会被遗弃掉。同样的,对包的注解不能在源码级别之外存在。

注解类型用法

声明注解提供了正在被声明的项的相关信息。例如下面的声明中:

public User getUser(@NonNull String userId)

就断言userId参数不为空。

@NonNull注解是Checker Framework的一部分。通过使用这个框架,可以在程序中包含断言,例如某个参数不为空,或者某个String包含一个正则表达式。然后,静态分析工具将检查在给定的源代码段中这些断言是否有效。

现在,假设我们有一个类型为List<String>的参数,并且想要表示其中所有的字符串都不为null。这就是类型用法注解大显身手之处,可以将该注解放置到类型参数之前:List<@NonNull String>

类型用法注解可以出现在下面的位置:

  • 和泛型参数一起使用:List<@NonNull String>,Comparator.<@NonNull String> reverseOrder()
  • 数组中的任何位置:@NonNull String[] [] words(word[i] [j]不为null),String @NonNull [] [] words(words不为null),String[] @NonNull [] words(word[i]不为null)
  • 与超类和实现接口一起使用:class Warning extends @Localized Message
  • 与构造器调用一起使用:new @Localized String(…)。
  • 与强制类型和instanceof检查一起使用:(@Localized String) text,if (text instanceof @Localized String)。(这些注解只提供外部工具使用,它们对强制转型和instanceof检查不会产生任何影响)。
  • 与异常规约一起使用:public String read() throws @Localized IOException
  • 与通配符和类型边界一起使用:List<@Localized ? extends Message>,List<? extends @Localized Message>
  • 与方法和构造器引用一起使用:@Localized Message::getText
  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值