java注解类型命名_第三十九条:注解优先于命名模式

根据经验,一般使用命令模式表明有些程序元素需要通过某种工具或者框架进行特殊处理。例如,在Java4发行版本之前,JUnit测试框架原本要求用户一定要用test作为测试方法名称的开头。这种方法可行,但是有几个很严重的缺点。首先,文字拼写错误会导致失败,且没有任何提示。例如,假设不小心将一个测试方法命名为tsetSafeyOverride而不是testSafeyOverride。JUnit3不会提示,但也不会执行测试,造成错误的安全感。

命名模式的第二个缺点是,无法确保它们只用于相应的程序元素上。例如,假设将某个类称作TestSafeyMechanisms,是希望JUnit3会自动地测试它所有地方法,而不管它们叫什么名称。JUnit3还是不会提示,但也同样不会执行测试。

命名模式的第三个缺点是,它们没有提供将参数值与程序元素关联起来的好方法。例如,假设想要支持一种测试类别,它只在抛出特殊异常时才会成功。异常类型本质上时测试的一个参数。你可以利用某种具体的命名模式,将异常类型名称编码到测试方法中,但是这样的代码很不雅观,也很脆弱(见第62条)。编译器不知道要去检验准备命名异常的字符串是否真正命名成功。如果命名的类不存在,或者不是一个异常,你也要到试着运行测试时才会发现。

注解很好的解决了所有这些问题,JUnit从Java4开始使用。在本条目中,我们要编写自己的试验测试框架,展示一下注解的使用方法。假设想要定义一个注解类型来指定简单的测试,它们自动运行,并在抛出异常时失败。以下就是这样的一个注解类型,命名为Test:

// Marker annotation type declaration

import java.lang.annotation.*;

/**

* Indicates that the annotated method is a test method. * Use only on parameterless static methods.

*/

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)

public @interface Test {

}

Test注解类型的声明就是它自身通过Retention和Target注解进行了注解。注解类型声明的这种注解被称作元注解。@Retention(RetentionPolicy.RUNTIME)元注解表明Test注解在运行时也应该存在,否则测试工具就无法知道Test注解。@Target(ElementType.METHOD)元注解表明,Test注解只在方法声明中才是合法的:它不能运用到类声明、域声明或者其他程序元素上。

注意Test注解声明上方的注释:“User only on parameterless static method”(只用于无参的静态方法)。如果编译器能够强制这一限制最好,但是它做不到,除非编写一个注解处理器,让它来完成。关于这个主题的更多信息,请参阅javax.annotation.processing的文档。在没有这类注解处理器的情况下,如果将Test注解放在实例方法的声明中,或者放在带有一个或者多个参数的方法中,测试程序还是可以编译,让测试工具运行时来处理这个问题。

下面就是现实应用中的Test注解,称作标记注解,因为它没有参数,只是标注被注解的元素。如果程序员拼错了Test,或者Test注解应用到程序元素而非方法声明,程序就无法编译:

// Program containing marker annotations

public class Sample {

@Test public static void m1() { } // Test should pass

public static void m2() { }

@Test public static void m3() { // Test should fail

throw new RuntimeException("Boom");

}

public static void m4() { }

@Test public void m5() { } // INVALID USE: nonstatic method

public static void m6() { }

@Test public static void m7() { // Test should fail

throw new RuntimeException("Crash");

}

public static void m8() { }

}

Sample类有7个静态方法,其中4个被注解为测试。这4个中有2个抛出了异常:m3和m7,另外两个则没有:m1和m5。但是其中一个没有抛出异常的被注解方法:m5,是一个实例方法,因此不属于注解的有效使用。总之,Sample包含4项测试:一项会通过,两项会失败,另一项无效。没有用Test注解进行标注的另外4个方法会被测试工具忽略。

Test注解对Sample类的语义没有直接的影响。它们只负责提供信息供相关的程序使用。更一般的讲,注解永远不会改变被注解代码的含义,但是使它可以通过工具进行特殊的处理,例如像这种简单的测试运行类:

// Program to process marker annotations

import java.lang.reflect.*;

public class RunTests {

public static void main(String[] args) throws Exception {

int tests = 0;

int passed = 0;

Class> testClass = Class.forName(args[0]);

for (Method m : testClass.getDeclaredMethods()) {

if (m.isAnnotationPresent(Test.class)) {

tests++;

try {

m.invoke(null);

passed++;

} catch (InvocationTargetException wrappedExc) {

Throwable exc = wrappedExc.getCause();

System.out.println(m + " failed: " + exc);

} catch (Exception exc) {

System.out.println("Invalid @Test: " + m);

}

}

}

System.out.printf("Passed: %d, Failed: %d%n", passed, tests - passed);

}

}

测试运行工具在命令行上使用完全匹配的类名,并通过调用Method.invoke反射的运行的运行类中所有标注了Test注解的方法。isAnnotationPresent方法告知该工具运行哪些方法。如果测试方法抛出异常,反射机制就会将它封装在InvocationTargetException中。该工具捕捉到这个异常,并打印失败报告,包含测试方法抛出的原始异常,这些信息是通过getCause方法从InvocationTargetException中提取出来的。

如果尝试通过反射测试方法时抛出InvocationTargetException之外的任何异常。表明编译时没有捕捉到Test注解的无效用法。这种用法包括实例方法的注解,或者带有一个或多个参数的方法的注解,或者不可访问的方法的注解。测试运行类中的第二catch块捕捉到这些Test用法错误,并打印出相关的错误消息。下面就是RunTests在Sample上运行时打印的输出:

public static void Sample.m3() failed: RuntimeException: Boom Invalid @Test: public void Sample.m5()

public static void Sample.m7() failed: RuntimeException: Crash Passed: 1, Failed: 3

现在我们要针对只在抛出特殊异常时才成功的测试添加支持。为此需要一个新的注解类型:

// Annotation type with a parameter

import java.lang.annotation.*;

/**

* Indicates that the annotated method is a test method that * must throw the designated exception to succeed.

*/

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)

public @interface ExceptionTest {

Class extends Throwable> value();

}

这个注解的参数类型是Class extends Throwable>。这个通配符类型有点绕口。它在英语中的意思是:某个扩展Throwable的类的Class对象,它允许注解的用户指定任何异常(或错误)类型。这种用法是有限制的类型令牌(详见第33条)的第一个示例。下面就是实际应用中的这个注解。注意类名称被用作了注解参数的值:

// Program containing annotations with a parameter

public class Sample2 {

@ExceptionTest(ArithmeticException.class)

public static void m1() { // Test should pass

int i = 0;

i = i / i;

}

@ExceptionTest(ArithmeticException.class)

public static void m2() { // Should fail (wrong exception)

int[] a = new int[0];

int i = a[1];

}

@ExceptionTest(ArithmeticException.class)

public static void m3() { } // Should fail (no exception)

}

现在我们要修改一下测试运行工具来处理新的注解。这其中包括将以下代码添加到main方法中:

if (m.isAnnotationPresent(ExceptionTest.class)) {

tests++;

try {

m.invoke(null);

System.out.printf("Test %s failed: no exception%n", m);

} catch (InvocationTargetException wrappedEx) {

Throwable exc = wrappedEx.getCause();

Class extends Throwable> excType = m.getAnnotation(ExceptionTest.class).value();

if (excType.isInstance(exc)) {

passed++;

} else {

System.out.printf("Test %s failed: expected %s, got %s%n", m, excType.getName(), exc);

}

} catch (Exception exc) {

System.out.println("Invalid @Test: " + m);

}

}

这段代码类似于用来处理Test注解的代码,但有一处不同:这段代码提取了注解参数的值,并用它检验该测试抛出的异常是否为正确的类型。没有显式的转换,因此没有出现ClassCastException的危险。编译过的测试程序确保它的注解参数表示的是有效的异常类型,需要提醒一点:有可能注解参数在编译时是有效的,但是表示特定异常类型的类文件在运行时却不存在。在这种希望很少出现的情况下,测试运行类会抛出TypeNotPresenException异常。

将上面的异常测试示例再深入一点,想象测试可以在抛出任何一种指定异常时能够通过。注解机制有一种工具,使得支持这种用法变得十分容易。假设我们将ExceptionTest注解的参数类型改成Class对象的一个数组:

// Annotation type with an array parameter

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)

public @interface ExceptionTest {

Class extends Exception>[] value();

}

注解数组参数的语法十分灵活。它是进行过优化的单元素数组。使用了ExceptionTest新版的数组参数之后,之前的所有ExceptionTest注解仍然有效,并产生单元素的数组。为了指定多元素的数组,要用花括号将元素包围起来,并用逗号将它们隔开:

// Code containing an annotation with an array parameter

@ExceptionTest({

IndexOutOfBoundsException.class,

NullPointerException.class })

public static void doublyBad() {

List list = new ArrayList<>();

// The spec permits this method to throw either

// IndexOutOfBoundsException or NullPointerException

list.addAll(5, null);

}

修改测试运行工具来处理新的ExceptionTest相当简单。下面的代码代替了原来的代码:

if (m.isAnnotationPresent(ExceptionTest.class)) {

tests++;

try {

m.invoke(null);

System.out.printf("Test %s failed: no exception%n", m);

} catch (Throwable wrappedExc) {

Throwable exc = wrappedExc.getCause();

int oldPassed = passed;

Class extends Exception>[] excTypes = m.getAnnotation(ExceptionTest.class).value();

for (Class extends Exception> excType : excTypes) {

if (excType.isInstance(exc)) {

passed++;

break;

}

}

if (passed == oldPassed)

System.out.printf("Test %s failed: %s %n", m, exc);

}

}

从Java8开始,还有另一种方法可以进行多值注解。它不是用一个数组参数声明一个注解类型,而是用@Repeatable元注解对注解的声明进行注解,表示该注解可以被重复的应用个单个元素。这个元注解只有一个参数,就是包含注解类型的类对象,它唯一的参数是一个注解类型数组。下面的注解声明就是把ExceptionTest注解改成使用这个方法之后的版本。注意包含的注解类型必须利用适当的保留策略和目标进行注解,否则声明将无法编译:

// Repeatable annotation type

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)

@Repeatable(ExceptionTestContainer.class)

public @interface ExceptionTest {

Class extends Exception> value();

}

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)

public @interface ExceptionTestContainer {

ExceptionTest[] value();

}

下面是doublyBad测试方法用重复注解代替数组值注解之后的代码:

// Code containing a repeated annotation

@ExceptionTest(IndexOutOfBoundsException.class)

@ExceptionTest(NullPointerException.class)

public static void doublyBad() { ... }

处理可重复的注解要非常小心。重复的注解会产生一个包含注解类型的合成注解。getAnnotationsByType方法掩盖了这个事实,可以用于访问可重复注解类型的重复和非重复。但isAnnotationPresent使它变成了显式的,即重复的注解不是注解类型(而是所包含的注解类型)的一部分。如果一个元素具有某种类型的重复注解,并且用isAnnotationPresent方法检验该元素是否具有该类型的注解,会发现它没有。用这种方法检验是否存在注解类型,会导致程序默默的忽略掉重复的注解。同样的,用这种方法检验是否存在包含的注解类型,会导致程序默默的忽略掉非重复的注解。为了利用isAnnotationPresent检测重复和非重复的注解,必须检查注解类型及其包含的注解类型。下面是Runtests程序改成使用ExceptionTest注解时有关部分的代码:

// Processing repeatable annotations

if (m.isAnnotationPresent(ExceptionTest.class) || m.isAnnotationPresent(ExceptionTestContainer.class)) {

tests++;

try {

m.invoke(null);

System.out.printf("Test %s failed: no exception%n", m);

} catch (Throwable wrappedExc) {

Throwable exc = wrappedExc.getCause();

int oldPassed = passed;

ExceptionTest[] excTests = m.getAnnotationsByType(ExceptionTest.class);

for (ExceptionTest excTest : excTests) {

if (excTest.value().isInstance(exc)) {

passed++;

break;

}

}

if (passed == oldPassed) System.out.printf("Test %s failed: %s %n", m, exc);

}

}

假如可重复的注解,提升了源代码的可读性,逻辑上是将同一个注解类型的多个实例应用到了一个指定的程序元素。如果你觉得它们增强了源代码的可读性就是用它们,但是记住在声明和处理可重复注解的代码中会出现更多的样板代码,并且处理可重复的代码容易出错。

本条目中的测试框架只是一个试验。但它清楚的示范了注解相对于命名模式的优越性。这只是揭开了注解功能的冰山一角。如果是在编写一个需要程序员给源文件添加信息的工具,就要定义一组适当的注解类型。既然有了注解,就完全没有理由再使用命名模式了。

也就是说,除了“工具铁匠”(toolsmiths,即平台框架程序员)之外,大多数程序员都不必定义注解类型。但是所有的程序员都应该使用Java平台所提供的预定义的注解类型(详见第40条和第27条)。还要考虑使用IDE或者静态分析工具所提供的任何注解。这种注解可以提升由这些工具所提供的诊断信息的质量。但是要注意这些注解还没有标准化,因此如果变换工具或者形成标准,就有很多工作要做了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/369632.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

查看Servlet 3.0的新增功能

随着JEE6规范上市&#xff0c;在企业应用程序领域中如何开发应用程序方面发生了一些重大变化。 在本文中&#xff0c;我将介绍有关Web应用程序开发的一些更改。 首先&#xff0c;请告别web.xml部署描述符&#xff08;至少是其中的一部分&#xff09;。 好吧&#xff0c;它并不是…

block,inline,inline-block的区别

最近正在复习&#xff0c;紧张地准备几天后的笔试&#xff0c;然后刚好看到这个地方。 block&#xff1a;块级元素&#xff0c;会换行&#xff0c;如div,p,h1~h6,table这些&#xff0c;可以设置宽高&#xff1b; inline:行内元素&#xff0c;不换行&#xff0c;挤在一行显示&am…

假期(模块相关)

# ---------------------------------------------------------------------------------------- import time timestamp time.time() #时间戳 struct_time time.localtime() #结构化时间 format_time time.strftime("%Y-%m-%d %X") #格式化时间# print…

anyproxy抓取移动http、https请求

windows下安装AnyProxy抓取移动App Http请求AnyProxy是阿里巴巴基于 Node.js 开发的一款开源代理服务器。做为中间代理服务器&#xff0c;它可以收集所有经过它的http请求流量&#xff08;包括https明文内容&#xff09;&#xff1b;它提供了友好的web界面&#xff0c;便于直观…

振作起来– Spring Framework 4.0即将来临!

几天前&#xff0c;SpringSource 宣布流行的Spring框架的4.0版本正在开发中。 下一个迭代将是Spring Framework 4.0&#xff01; 如SpringSource所言&#xff0c;即将发布的版本的重点是“ 2013年及以后出现的企业主题”&#xff1a; 支持Java SE 8 Spring应用程序 使用Groo…

java内存管理课程设计_Java内存管理分析

Java内存主要分为stack, heap, data segment, and code segment.stack(栈)&#xff1a;存放非静态基本数据类型变量的名称和值&#xff0c;以及非静态对象的引用若是非静态基本数据类型变量&#xff0c;则变量的名称和值一起被存入stack(栈)中&#xff0c;变量的名称指向变量的…

Windows 10 IoT Core 17101 for Insider 版本更新

除夕夜&#xff0c;微软发布了Windows 10 IoT Core 17101 for Insider 版本更新&#xff0c;本次更新只修正了一些Bug&#xff0c;没有发布新的特性。已知的问题: F5 driver deployment from Visual Studio does not work on IoT Core.F5 application deployment of headed f…

Spring Batch中的块处理

大数据集的处理是软件世界中最重要的问题之一。 Spring Batch是一个轻量级且强大的批处理框架&#xff0c;用于处理数据集。 Spring Batch Framework提供了“面向TaskletStep”和“面向块”的处理风格。 在本文中&#xff0c;将解释面向块的处理模型。 此外&#xff0c;绝对建…

type=file文件上传H5新特性

1、语法 <input name"myFile" type"file"> 2、属性&#xff08;以下三个仅 HTML5支持&#xff0c;因此存在兼容性问题&#xff09;&#xff08;1&#xff09;multiple &#xff1a;表示用户是否可以选择多个值。multiple只能用于typefile和typeemail…

关于git的一些文章

为什么Github没有记录你的Contributions转载于:https://www.cnblogs.com/xiaobie123/p/7391266.html

epoll学习

一、epoll_create #include <sys/epoll.h>int epoll_create(int size); int epoll_create1(int flags); 返回&#xff1a;成功非负文件描述符&#xff0c;-1出错size:内核监听数目一共多大 创建一个epoll接口&#xff0c;size参数和select不同&#xff0c;不是fd1&#x…

为什么我不能关闭垃圾收集器?

首先让我们快速回顾一下我作为Java开发人员的职业生涯的早期。 我想消除正在进行的测试中的垃圾回收&#xff08;GC&#xff09;暂停。 瞧&#xff0c;当我发现无法完成时&#xff0c;我很生气。 那时&#xff0c;我将问题抛在了“设计错误”上&#xff0c;并继续前进。 对Jame…

background使用

background-position 有两个参数&#xff0c;定义背景图片起始位置可选值有&#xff1a; center top left right bottom px % background-size 可以用 px % 设定其宽高 值 cover 完全覆盖背景区域 contain 适应背景区域 background-origin 背景图片可以放置于 content-bo…

java牛客排序算法题_《剑指offer》面试题28:字符串的排列(牛客网版本) java...

输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。输入描述: 输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。这里尤其需要注意2点&#xff1a;1.所有组…

致第8年的自己

刚开始工作时&#xff0c;可能会用很多技术&#xff0c;知道很多技术就好像是很了不起的了。 但工作久了之后&#xff0c;如果还是只停留在知识的表面&#xff0c;技术只知个大概&#xff0c;那就显得太肤浅了。 别被人问倒&#xff0c;什么东西都能说出个一二&#xff0c;那才…

配置Ubuntu虚拟环境

配置Ubuntu虚拟环境 1.ubuntu默认root用户没有激活&#xff0c;激活root用户&#xff0c;就要为root用户创建密码$sudo passwd root2、修改主机名$vi /etc/hostname3、安装ssh服务$sudo apt-get install openssh-server//安装sshd服务$ sudo apt-get install openssh-server//开…

布局 — 样式重置

/* 清除默认内边距和外边距*/ body, dl, dd, h1, h2, h3, h4, h5, h6, p, form {margin: 0; } ol, ul {margin:0;padding:0; }/* 设置默认文字样式*/ html {font-size: calc((16 * 100vw) / 320);-webkit-text-size-adjust: none; } body, button, input, select, textarea {fo…

从Java执行可执行的命令行

在本文中&#xff0c;我们将介绍Java开发人员的常见需求。 从Java内部执行和管理外部流程。 由于这项任务很常见&#xff0c;因此我们着手寻找一个Java库来帮助我们完成它。 该库的要求是&#xff1a; 异步执行该过程。 能够中止流程执行。 等待流程完成的能力。 处理中的输…

java代码实现解压文件_Java压缩/解压文件的实现代码

用java压缩/解压文件&#xff1a;import java.io.*;import java.awt.*;import java.awt.event.*;import java.util.*;import java.util.zip.*;import javax.swing.*;//从压缩包中提取文件public class ZipExtractDemo extends JFrame{JFileChooser fileChooser; //文件选择器JT…