注解是为程序元素设置元数据的。本质上是一个接口。
注解起的是标记作用,可以用来标记包、类、构造器、方法、成员变量、参数、局部变量。
但是,注解不会影响程序代码的执行。
如果想让注释在程序中起作用,需要配套的注释工具ATP(Annotation Processing Tool)
学Java的人,对@Override一定不陌生,当我们实现一个接口后,编译器自动添加的方法,上边都有这个注解。如:
public class OverrideTest implements father{
@Override
public void m1() {
// TODO 自动生成的方法存根
}
}
interface father {
public void m1();
}
告诉编译器,下边的方法是重写\实现的方法,让编译器检查有没有拼写错误。好处是:避免低级错误,有的编译器里边 o、O、0 还有 1、I、l 基本没区别,当我们想重写父类方法,却因为这种“小”问题出错,而且这种“小”问题,排错非常非常难!!(教训过于惨痛,不举例说明了。。。
)
public class DeprecatedTest {
public static void main(String[] args) {
// 使用过时方法会被警告
new Apple().info();
}
}
class Apple{
// 定义 info 方法,并设置过时
@Deprecated
public void info() {
System.out.println("Apple 的info方法");
}
}
图示:
代码
import java.util.ArrayList;
import java.util.List;
public class SuppressWarningsTest {
// 抑制没有进行类型检查操作的警告
@SuppressWarnings(value="unchecked")
public static void main(String[] args) {
// 使用 generics(泛型) 时忽略没有指定相应的类型
@SuppressWarnings("rawtypes")
List myList = new ArrayList();
myList.add("");
}
}
value取值 | 含义 |
---|---|
RetentionPolicy.SOURCE | 注解将被编译器丢弃 |
RetentionPolicy.CLASS | 注解在class文件中可用,但会被VM丢弃 |
RetentionPolicy.RUNTIME | VM将在运行期也保留注释,因此可以通过反射机制读取注解的信息 |
value取值 | 含义 |
---|---|
ElemenetType.CONSTRUCTOR | 构造器声明 |
ElemenetType.FIELD | 域声明(包括 enum 实例) |
ElemenetType.LOCAL_VARIABLE | 局部变量声明 |
ElemenetType.METHOD | 方法声明 |
ElemenetType.PACKAGE | 包声明 |
ElemenetType.PARAMETER | 参数声明 |
ElemenetType.TYPE | 类,接口(包括注解类型)或enum声明 |
允许子类继承父类中的注解。
我们模仿上边的注解,写个注解试试。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface MyOverridn {
}
好像有点看不太懂,反编译一下,看看具体是什么。
反编译后生成的代码:
public class annotation.test.OverrideTest implements annotation.test.father {
public annotation.test.OverrideTest();
public void m1();
}
这下就可以看出来了。本质上就是一个类,去实现了一个接口
我们上边已经说过,注解就是一个标签,那么我们这样做个 标签,它又不会影响代码执行,那注解有什么用?
想让注解发挥更大的作用,需要用到反射!
反射可以拿到注解标记的元素,方法如下。
返回值 | 方法及详解 |
---|---|
getAnnotation(Class annotationClass) | |
T | 如果存在该元素的指定类型的注释,则返回这些注释,否则返回 null。 |
Annotation[] | getAnnotations() |
返回此元素上存在的所有注释。 | |
Annotation[] | getDeclaredAnnotations() |
返回直接存在于此元素上的所有注释。 | |
boolean | isAnnotationPresent(Class extends Annotation> annotationClass) |
如果指定类型的注释存在于此元素上,则返回 true,否则返回 false。 |
学过框架或者反射的人,应该都听“过框架就是注解+反射”、“反射是框架的灵魂”这些话,下边咱们就做2个小“框架”,来练练手。
一个类的方法有很多,平常咱们想用哪个就调那个,现在,咱们要用自定义注解去标识一下,然后就让这些被标示的方法跑起来。并且记录一下异常出现的次数。
Testable——自定义注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 指定 Testable 注释可以保留多久
@Retention(RetentionPolicy.RUNTIME)
// 指定 Testable 注释能修饰的目标(只能是方法)
@Target(ElementType.METHOD)
public @interface Testable {
}
MyTest——有需要运行方法的类
public class MyTest {
@Testable
public static void m1() {
}
public static void m2() {
}
@Testable
public static void m3() {
throw new RuntimeException("Boom");
}
public static void m4() {
}
@Testable
public static void m5() {
}
public static void m6() {
}
@Testable
public static void m7() {
throw new RuntimeException("Crash");
}
public static void m8() {
}
}
TestProcessor——使用反射加载被标识方法,并运行的核心列
import java.lang.reflect.Method;
public class TestProcessor {
public static void process(Class<MyTest> class1) throws SecurityException, ClassNotFoundException {
int passed = 0;
int failed = 0;
for (Method m : class1.getMethods()) {
if (m.isAnnotationPresent(Testable.class)) {
try {
m.invoke(null);
passed++;
} catch (Exception e) {
System.out.println("方法"+m+"运行失败,异常:"+e.getCause()+"\n");
failed++;
}
}
}
System.out.println("共运行了:"+(passed+failed)+"个方法,其中\n"+
"失败了:"+failed+"个,\n"+
"成功了:"+passed+"个,\n"
);
}
}
RunTests——调用核心类
public class RunTests {
public static void main(String[] args) throws Exception {
TestProcessor.process(MyTest.class);
}
}
运行结果
方法public static void annotation.useAnnotation.MyTest.m7()运行失败,异常:java.lang.RuntimeException: Crash
方法public static void annotation.useAnnotation.MyTest.m3()运行失败,异常:java.lang.RuntimeException: Boom
共运行了:4个方法,其中
失败了:2个,
成功了:2个,
学习JavaSE,做小游戏,相信按钮监听让很多人心烦,每个按钮都需要一个个添加监听,一个个写事件处理,不如只用一个注解,给添加得了
ActionListenerFor——自定义注解
import java.awt.event.ActionListener;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ActionListenerFor {
// 定义一个成员变量,用于保存监听器实现类
Class<? extends ActionListener> listener();
}
ActionListenerInstaller——核心处理类
import java.awt.event.ActionListener;
import java.lang.reflect.Field;
import javax.swing.AbstractButton;
public class ActionListenerInstaller {
// 处理注释的方法,其中 obj 是包含注释的对象
public static void processAnnotations(Object obj) {
try {
// 获取 obj 对象的类
Class cl = obj.getClass();
// 获取指定 obj 对象的所有成员变量,并遍历每个成员变量
for (Field f : cl.getDeclaredFields()) {
// 将成员变量设置为可自由访问
f.setAccessible(true);
// 获取成员变量上 ActionListenerFor 类型的Annotation
ActionListenerFor a = f.getAnnotation(ActionListenerFor.class);
// 获取成员变量 f 的值
Object fObj = f.get(obj);
// 如果 f 是 AbstractButton 的实例,且 a 不为 null
if(a != null && fObj != null && fObj instanceof AbstractButton) {
// 获取 a 注解里的 listener 源数据(它是一个监听类)
Class<? extends ActionListener > listennerClazz = a.listener();
// 使用反射创建 listener 类的对象
ActionListener al = listennerClazz.newInstance();
AbstractButton ab = (AbstractButton)fObj;
// 为 ab 按钮添加事件监听器
ab.addActionListener(al);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
AnnotationTest——需要添加按钮的类(以及按钮点击事件的处理类)
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
public class AnnotationTest {
private JFrame mainWin = new JFrame("使用注解绑定事件监听器");
// 使用注解为 ok 按钮绑定事件监听
@ActionListenerFor(listener=OkListener.class)
private JButton ok = new JButton("确定");
// 使用注解为cancel 按钮绑定事件监听
@ActionListenerFor(listener=CancelListener.class)
private JButton cancel = new JButton("取消");
public void init() {
// 初始化界面
JPanel jp = new JPanel();
jp.add(ok);
jp.add(cancel);
mainWin.add(jp);
ActionListenerInstaller.processAnnotations(this);
mainWin.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainWin.pack();
mainWin.setVisible(true);
}
public static void main(String[] args) {
new AnnotationTest().init();
}
}
// 定义 ok 按钮的事件监听器实现类
class OkListener implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(null, "单击了确认按钮");
}
}
// 定义 cancel 按钮的事件监听器实现类
class CancelListener implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(null, "单击了取消按钮");
}
}
学到这里,有些问题,在你心里边应该有个大致答案了。
比如:
Spring的注解@Table——怎么把对象映射到数据库的数据表??
SpringMVC的注解@Autowired——怎么实现自动注入??
让你自己实现类似功能,你是否能实现?