枚举和注解

This commit is contained in:
尉勇强2 2020-10-15 14:10:53 +08:00
parent 85d02fd51a
commit 2edf799df1
19 changed files with 452 additions and 1 deletions

BIN
Effective-Java.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View File

@ -1,5 +1,7 @@
# 说明
![](Effective-Java.jpg)
### 目录
#### ch02.[创建和销毁对象](ch02创建和销毁对象)

View File

@ -26,4 +26,5 @@
该条目中描述的模式在 `Java` 类库中有所使用。例如,`java.nio.file.LinkOption` 枚举类型实现了 `CopyOption``OpenOption` 接口。
**总之,虽然不能编写可扩展的枚举类型,但是你可以编写一个接口来配合实现接口的基本的枚举类型,来对它进行模拟。** 这允许客户端编写自己的枚举(或其它类型)来实现接口。如果 `API` 是根据接口编写的,那么在任何使用基本枚举类型实例的地方,都可以使用这些枚举类型实例。
**总之,虽然不能编写可扩展的枚举类型,但是你可以编写一个接口来配合实现接口的基本的枚举类型,来对它进行模拟。** 这允许客户端编写自己的枚举(或其它类型)来实现接口。如果 `API` 是根据接口编写的,那么在任何使用基本枚举类型实例的地方,都可以使用这些枚举类型实例。

View File

@ -0,0 +1,101 @@
## 注解优于命名模式
过去,通常使用 **命名模式**naming patterns来指示某些程序元素需要通过工具或框架进行特殊处理。
但命名模式有几个很大的缺点:
1. 拼写错误导致失败,但不会提示。
2. 无法确保它们仅用于适当的程序元素。
3. 没有提供将参数值与程序元素相关联的好的方法。
**注解** 很好地解决了所有这些问题。
我们将编写我们自己的测试框架来显示注解的工作方式。
假设你想定义一个注解类型来指定自动运行的简单测试,并且如果它们抛出一个异常就会失败。
- [Item39Example01.java](EnumsAnnotations/src/main/java/com/jueee/item39/Item39Example01.java)**声明注解类型**
注解类型的声明本身使用 `Retention``Target` 注解进行标记。
注解类型声明上的这种注解称为元注解。
`@Retention``RetentionPolicy.RUNTIME`)元注解指示 `Test` 注解应该在运行时保留。
没有它,测试工具就不会看到 `Test` 注解。
`@Target.getElementType.METHOD`元注解表明 `Test` 注解只对方法声明合法:它不能应用于类声明,属性声明或其他程序元素。
- [Item39Example02.java](EnumsAnnotations/src/main/java/com/jueee/item39/Item39Example02.java)[Item39Example01.java](EnumsAnnotations/src/main/java/com/jueee/item39/Item39Example01.java) 注解在实践中的应用。
未使用 `Item39Example01` 注解标注的四种方法将被测试工具忽略。
`Item39Example01` 注解对 `Item39Example02` 类的语义没有直接影响。
他们只提供信息供相关程序使用。
更一般地说,注解不会改变注解代码的语义。
- [Item39Example03.java](EnumsAnnotations/src/main/java/com/jueee/item39/Item39Example03.java):可以通过诸如这个简单的测试运行器等工具对其进行特殊处理。
测试运行器工具接受完全限定的类名,并通过调用 `Method.invoke` 来反射地运行所有类标记有 `Item39Example01` 注解的方法。
`isAnnotationPresent` 方法告诉工具要运行哪些方法。
如果测试方法引发异常,则反射机制将其封装在 `InvocationTargetException` 中。
该工具捕获此异常并打印包含由 `test` 方法抛出的原始异常的故障报告,该方法是使用 `getCause` 方法从 `InvocationTargetException` 中提取的。
打印结果:
```
public static void com.jueee.item39.Item39Example02.m3() failed: java.lang.RuntimeException: Boom
public static void com.jueee.item39.Item39Example02.m7() failed: java.lang.RuntimeException: Crash
Invalid @Item39Example01: public void com.jueee.item39.Item39Example02.m5()
Passed: 1, Failed: 3
```
#### 添加对仅在抛出特定异常时才成功的测试的支持
- [Item39Example04.java](EnumsAnnotations/src/main/java/com/jueee/item39/Item39Example04.java):添加一个新的注解类型。
此注解的参数类型是 `Class<? extends Throwable>`
它表示“扩展 `Throwable` 的某个类的 `Class` 对象”,它允许注解的用户指定任何异常(或错误)类型。
- [Item39Example05.java](EnumsAnnotations/src/main/java/com/jueee/item39/Item39Example05.java):注解在实际中的例子。
- [Item39Example06.java](EnumsAnnotations/src/main/java/com/jueee/item39/Item39Example06.java):修改测试运行器工具来处理异常测试的新版本。
#### 执行多值注解
从 Java 8 开始,还有另一种方法来执行多值注解。
- [Item39Example07.java](EnumsAnnotations/src/main/java/com/jueee/item39/Item39Example07.java):可以使用 `@Repeatable` 元注解来标示注解的声明,而不用使用数组参数声明注解类型,以指示注解可以重复应用于单个元素。
- [Item39Example08.java](EnumsAnnotations/src/main/java/com/jueee/item39/Item39Example08.java):该元注解采用单个参数,该参数是包含注解类型的类对象,其唯一参数是注解类型的数组。
- [Item39Example09.java](EnumsAnnotations/src/main/java/com/jueee/item39/Item39Example09.java):测试使用重复注解代替数组值注解。
- [Item39Example10.java](EnumsAnnotations/src/main/java/com/jueee/item39/Item39Example10.java):处理可重复注解。重复注解生成包含注解类型的合成注解。
`getAnnotationsByType` 方法掩盖了这一细节,可以用于访问可重复注解类型的重复注解和非重复注解。
但是 `isAnnotationPresent` 明确指出,重复注解不是注解类型,而是包含注解的类型。
如果一个元素具有某种类型的重复注解,并且您使用 `isAnnotationPresent` 方法检查该元素是否具有该类型的注解,您将发现它没有。
因此,使用此方法检查注解类型是否存在,将导致程序无声地忽略重复的注解。
类似地,使用此方法检查包含的注解类型将导致程序无声地忽略非重复注解。
要使用 `isAnnotationPresent` 检测重复和非重复注解,您需要检查注解类型及其包含的注解类型。
添加可重复的注解是为了提高源代码的可读性,源代码逻辑上将相同注解类型的多个实例应用于给定的程序元素。
如果您觉得它们增强了源代码的可读性,那么就使用它们,但是请记住,在声明和处理可重复注解时有更多的样板文件,而且处理可重复注解很容易出错。

View File

@ -0,0 +1,17 @@
## 始终使用 Override 注解
Java 类库包含几个注解类型。对于典型的程序员来说,最重要的是 `@Override`
此注解只能在方法声明上使用,它表明带此注解的方法声明重写了父类的声明。
如果始终使用这个注解,它将避免产生大量的恶意 bug。
#### 示例:表示一个 双字母组,或有序的字母对
主程序反复向一个集合添加26个bigram每个bigram由两个相同的小写字母组成。然后它打印集合的大小。
- [Item40Example01.java](EnumsAnnotations/src/main/java/com/jueee/item40/Item40Example01.java)没有重写equals而是重载了它(item 52)。
为了覆盖 Object 的 equals 方法我们必须定义一个参数是Object类型的equals方法但是 Bigram 的 equals 方法的参数不是 Object 类型的,所以 Bigram 继承了 Object 的 equals 方法。这个 equals 方法测试对象标识,就像 == 操作符一样。每一个双字母的十份副本都与其他九份不同,因此它们被认为是不相等的。
- [Item40Example02.java](EnumsAnnotations/src/main/java/com/jueee/item40/Item40Example02.java):使用 Override 注解重写equals。

View File

@ -3,11 +3,13 @@ package com.jueee.item38;
//Emulated extension enum
public enum ExtendedOperation implements Operation {
EXP("^") {
@Override
public double apply(double x, double y) {
return Math.pow(x, y);
}
},
REMAINDER("%") {
@Override
public double apply(double x, double y) {
return x % y;
}

View File

@ -0,0 +1,12 @@
package com.jueee.item39;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Item39Example01 {
}

View File

@ -0,0 +1,28 @@
package com.jueee.item39;
public class Item39Example02 {
@Item39Example01
public static void m1() { } // Item39Example01 should pass
public static void m2() { }
@Item39Example01
public static void m3() { // Item39Example01 should fail
throw new RuntimeException("Boom");
}
public static void m4() { }
@Item39Example01
public void m5() { } // INVALID USE: nonstatic method
public static void m6() { }
@Item39Example01
public static void m7() { // Item39Example01 should fail
throw new RuntimeException("Crash");
}
public static void m8() { }
}

View File

@ -0,0 +1,39 @@
package com.jueee.item39;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Item39Example03 {
public static void main(String[] args) throws Exception {
String testClassName = "com.jueee.item39.Item39Example02";
int tests = 0;
int passed = 0;
Class<?> testClass = Class.forName(testClassName);
for (Method m : testClass.getDeclaredMethods()) {
if (m.isAnnotationPresent(Item39Example01.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 @Item39Example01: " + m);
}
}
}
System.out.printf("Passed: %d, Failed: %d%n", passed, tests - passed);
}
/*
* 打印结果
* public static void com.jueee.item39.Item39Example02.m3() failed: java.lang.RuntimeException: Boom
* public static void com.jueee.item39.Item39Example02.m7() failed: java.lang.RuntimeException: Crash
* Invalid @Item39Example01: public void com.jueee.item39.Item39Example02.m5()
* Passed: 1, Failed: 3
*/
}

View File

@ -0,0 +1,13 @@
package com.jueee.item39;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Item39Example04 {
Class<? extends Throwable> value();
}

View File

@ -0,0 +1,20 @@
package com.jueee.item39;
public class Item39Example05 {
@Item39Example04(ArithmeticException.class)
public static void m1() { // Test should pass
int i = 0;
i = i / i;
}
@Item39Example04(ArithmeticException.class)
public static void m2() { // Should fail (wrong exception)
int[] a = new int[0];
int i = a[1];
}
@Item39Example04(ArithmeticException.class)
public static void m3() {
} // Should fail (no exception)
}

View File

@ -0,0 +1,43 @@
package com.jueee.item39;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Item39Example06 {
public static void main(String[] args) throws Exception {
String testClassName = "com.jueee.item39.Item39Example05";
int tests = 0;
int passed = 0;
Class<?> testClass = Class.forName(testClassName);
for (Method m : testClass.getDeclaredMethods()) {
if (m.isAnnotationPresent(Item39Example04.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(Item39Example04.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);
}
}
}
System.out.printf("Passed: %d, Failed: %d%n", passed, tests - passed);
}
/*
* 打印结果
* Test public static void com.jueee.item39.Item39Example05.m3() failed: no exception
* Test public static void com.jueee.item39.Item39Example05.m2() failed: expected java.lang.ArithmeticException, got java.lang.ArrayIndexOutOfBoundsException: 1
* Passed: 1, Failed: 2
*/
}

View File

@ -0,0 +1,16 @@
package com.jueee.item39;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//Repeatable annotation type
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Repeatable(Item39Example08.class)
public @interface Item39Example07 {
Class<? extends Throwable> value();
}

View File

@ -0,0 +1,13 @@
package com.jueee.item39;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Item39Example08 {
Item39Example07[] value();
}

View File

@ -0,0 +1,12 @@
package com.jueee.item39;
public class Item39Example09 {
// Code containing a repeated annotation
@Item39Example07(IndexOutOfBoundsException.class)
@Item39Example07(NullPointerException.class)
public static void doublyBad() {
}
}

View File

@ -0,0 +1,44 @@
package com.jueee.item39;
import java.lang.reflect.Method;
public class Item39Example10 {
public static void main(String[] args) throws Exception {
String testClassName = "com.jueee.item39.Item39Example09";
int tests = 0;
int passed = 0;
Class<?> testClass = Class.forName(testClassName);
for (Method m : testClass.getDeclaredMethods()) {
if (m.isAnnotationPresent(Item39Example07.class) || m.isAnnotationPresent(Item39Example08.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;
Item39Example07[] excTests = m.getAnnotationsByType(Item39Example07.class);
for (Item39Example07 excTest : excTests) {
if (excTest.value().isInstance(exc)) {
passed++;
break;
}
}
if (passed == oldPassed) {
System.out.printf("Test %s failed: %s %n", m, exc);
}
}
}
}
System.out.printf("Passed: %d, Failed: %d%n", passed, tests - passed);
}
/*
* 打印结果
* Test public static void com.jueee.item39.Item39Example09.doublyBad() failed: no exception
* Passed: 0, Failed: 1
*/
}

View File

@ -0,0 +1,37 @@
package com.jueee.item40;
import java.util.HashSet;
import java.util.Set;
/**
* Can you spot the bug?
* @author hzweiyongqiang
*/
public class Item40Example01 {
private final char first;
private final char second;
public Item40Example01(char first, char second) {
this.first = first;
this.second = second;
}
public boolean equals(Item40Example01 b) {
return b.first == first && b.second == second;
}
public int hashCode() {
return 31 * first + second;
}
public static void main(String[] args) {
Set<Item40Example01> s = new HashSet<>();
for (int i = 0; i < 10; i++) {
for (char ch = 'a'; ch <= 'z'; ch++) {
s.add(new Item40Example01(ch, ch));
}
}
System.out.println(s.size()); // 260
}
}

View File

@ -0,0 +1,42 @@
package com.jueee.item40;
import java.util.HashSet;
import java.util.Set;
/**
* @author hzweiyongqiang
*/
public class Item40Example02 {
private final char first;
private final char second;
public Item40Example02(char first, char second) {
this.first = first;
this.second = second;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Item40Example02)) {
return false;
}
Item40Example02 b = (Item40Example02) o;
return b.first == first && b.second == second;
}
@Override
public int hashCode() {
return 31 * first + second;
}
public static void main(String[] args) {
Set<Item40Example02> s = new HashSet<>();
for (int i = 0; i < 10; i++) {
for (char ch = 'a'; ch <= 'z'; ch++) {
s.add(new Item40Example02(ch, ch));
}
}
System.out.println(s.size()); // 26
}
}

View File

@ -0,0 +1,9 @@
package com.jueee.item41;
/**
* 使用标记接口定义类型
* @author hzweiyongqiang
*/
public class Item41Example01 {
}