消除非检查警告

This commit is contained in:
尉勇强2 2019-11-21 19:59:07 +08:00
parent e3a1234bb2
commit 35b55d1c36
10 changed files with 339 additions and 1 deletions

View File

@ -0,0 +1,84 @@
## 不要使用原始类型
#### 泛型类或泛型接口
一个类或接口它的声明有一个或多个类型参数type parameters ),被称之为**泛型类**或**泛型接口**。
例如,`List` 接口具有单个类型参数 E表示其元素类型。 接口的全名是 `List<E>`读作「E」的列表但是人们经常称它为 `List`
泛型类和接口统称为泛型类型generic types
#### 参数化类型
每个泛型定义了一组**参数化类型**parameterized types它们由类或接口名称组成后跟一个与泛型类型的形式类型参数相对应的实际类型参数的尖括号「<>」列表。
例如,`List<String>`(读作「字符串列表」)是一个参数化类型,表示其元素类型为 String 的列表。 `String` 是与形式类型参数 `E` 相对应的实际类型参数)。
#### 原始类型
每个泛型定义了一个原始类型raw type它是没有任何类型参数的泛型类型的名称。
例如,对应于 `List<E>` 的原始类型是 `List`
原始类型的行为就像所有的泛型类型信息都从类型声明中被清除一样。 它们的存在主要是为了与没有泛型之前的代码相兼容。
#### 不要使用原始类型
使用原始类型(没有类型参数的泛型)是合法的,但是你不应该这样做。
**如果你使用原始类型,则会丧失泛型的所有安全性和表达上的优势。** 鉴于你不应该使用它们,为什么语言设计者首先允许原始类型呢? 答案是为了兼容性。
泛型被添加时Java 即将进入第二个十年,并且有大量的代码没有使用泛型。 所有这些代码都是合法的,并且与使用泛型的新代码进行交互操作被认为是至关重要的。 将参数化类型的实例传递给为原始类型设计的方法必须是合法的,反之亦然。 这个需求,被称为迁移兼容性,驱使决策支持原始类型,并使用擦除来实现泛型(详见第 28 条)。
#### `List` vs `List<Object>`
虽然不应使用 `List` 之类的原始类型,但可以使用参数化类型来允许插入任意对象(如 `List<Object>`)。
原始类型 List 和参数化类型 `List<Object>` 之间有什么区别? 松散地说,前者已经选择了泛型类型系统,而后者明确地告诉编译器,它能够保存任何类型的对象。
虽然可以将 `List<String>` 传递给 `List` 类型的参数,但不能将其传递给 `List<Object>` 类型的参数。
泛型有子类型的规则,`List<String>` 是原始类型 `List` 的子类型,但不是参数化类型 `List<Object>` 的子类型(条目 28
因此,如果使用诸如 `List` 之类的原始类型,则会丢失类型安全性,但是如果使用参数化类型(例如 `List<Object>`)则不会。
总之,使用原始类型可能导致运行时异常,所以不要使用它们。 它们仅用于与泛型引入之前的传统代码的兼容性和互操作性。 作为一个快速回顾,`Set<Object>` 是一个参数化类型,表示一个可以包含任何类型对象的集合,`Set<?>` 是一个通配符类型,表示一个只能包含某些未知类型对象的集合,`Set` 是一个原始类型,它不在泛型类型系统之列。 前两个类型是安全的,最后一个不是。
#### 示例
- [Item26Example01.java](Generics/src/main/java/com/jueee/item26/Item26Example01.java):【×】使用诸如 List 之类的原始类型,则会丢失类型安全性,会收到警告。
- [Item26Example02.java](Generics/src/main/java/com/jueee/item26/Item26Example02.java):【×】用参数化类型 `List<Object>` 替换原始类型 `List`,会发出错误消息。
- [Item26Example03.java](Generics/src/main/java/com/jueee/item26/Item26Example03.java):【√】使用正确的参数化类型 `List<String>`
- [Item26Example04.java](Generics/src/main/java/com/jueee/item26/Item26Example04.java):【×】使用**原始类型**来处理元素类型未知且无关紧要的集合。
- [Item26Example05.java](Generics/src/main/java/com/jueee/item26/Item26Example05.java):【√】使用**无限制通配符类型**来处理元素类型未知且无关紧要的集合。
- [Item26Example06.java](Generics/src/main/java/com/jueee/item26/Item26Example06.java):使用泛型类型的 instanceof 运算符的首选方法。
#### 总结
使用原始类型可能导致运行时异常,所以不要使用它们。
它们仅用于与泛型引入之前的传统代码的兼容性和互操作性。
- `Set<Object>` 是一个参数化类型,表示一个可以包含任何类型对象的集合。
- `Set<?>` 是一个通配符类型,表示一个只能包含某些未知类型对象的集合。
- `Set` 是一个原始类型,它不在泛型类型系统之列。
前两个类型是安全的,最后一个不是。
#### 术语
为了快速参考,下表中总结了本条目(以及本章稍后介绍的一些)中介绍的术语:
| 术语 | 中文含义 | 举例 | 所在条目 |
| ----------------------- | ---------------- | ---------------------------------- | -------- |
| Parameterized type | 参数化类型 | `List<String>` | 条目 26 |
| Actual type parameter | 实际类型参数 | `String` | 条目 26 |
| Generic type | 泛型类型 | `List<E>` | 条目 26 |
| Formal type parameter | 形式类型参数 | `E` | 条目 26 |
| Unbounded wildcard type | 无限制通配符类型 | `List<?>` | 条目 26 |
| Raw type | 原始类型 | `List` | 条目 26 |
| Bounded type parameter | 限制类型参数 | `<E extends Number>` | 条目 29 |
| Recursive type bound | 递归类型限制 | `<T extends Comparable<T>>` | 条目 30 |
| Bounded wildcard type | 限制通配符类型 | `List<? extends Number>` | 条目 31 |
| Generic method | 泛型方法 | `static <E> List<E> asList(E[] a)` | 条目 30 |
| Type token | 类型令牌 | `String.class` | 条目 33 |

View File

@ -0,0 +1,43 @@
## 消除非检查警告
使用泛型编程时,会看到许多编译器警告:
- 未经检查的强制转换警告
- 未经检查的方法调用警告
- 未经检查的参数化可变长度类型警告
- 未经检查的转换警告
你使用泛型获得的经验越多,获得的警告越少,但不要期望新编写的代码能够干净地编译。
**示例代码**[Item27Example01.java](Generics/src/main/java/com/jueee/item27/Item27Example01.java)
```java
// 警告HashSet是原始类型。 泛型类型HashSet <E>的引用应参数化。
new HashSet();
// 进行指示修正,让警告消失。
// 请注意,实际上并不需要指定类型参数,只是为了表明它与 Java 7 中引入的钻石运算符(「<>」)一同出现。
new HashSet<>();
```
但一些警告更难以消除。 本章充满了这种警告的例子。 当你收到需要进一步思考的警告时,坚持不懈! **尽可能地消除每一个未经检查的警告。** 如果你消除所有的警告,你可以放心,你的代码是类型安全的,这是一件非常好的事情。 这意味着在运行时你将不会得到一个 `ClassCastException` 异常,并且增加了你的程序将按照你的意图行事的信心。
**如果你不能消除警告,但你可以证明引发警告的代码是类型安全的,那么(并且只能这样)用 @SuppressWarnings("unchecked") 注解来抑制警告。** 如果你在没有首先证明代码是类型安全的情况下压制警告,那么你给自己一个错误的安全感。 代码可能会在不发出任何警告的情况下进行编译,但是它仍然可以在运行时抛出 `ClassCastException` 异常。 但是,如果你忽略了你认为是安全的未经检查的警告(而不是抑制它们),那么当一个新的警告出现时,你将不会注意到这是一个真正的问题。 新出现的警告就会淹没在所有的错误警告当中。
`SuppressWarnings` 注解可用于任何声明,从单个局部变量声明到整个类。 始终在尽可能最小的范围内使用 `SuppressWarnings` 注解。 通常这是一个变量声明或一个非常短的方法或构造方法。 切勿在整个类上使用 `SuppressWarnings` 注解。 这样做可能会掩盖重要的警告。
如果你发现自己在长度超过一行的方法或构造方法上使用 `SuppressWarnings` 注解,则可以将其移到局部变量声明上。 你可能需要声明一个新的局部变量,但这是值得的。
```java
// 使用 SuppressWarnings 注解声明单个局部变量
@SuppressWarnings({"rawtypes", "unchecked"})
Set<String> set = new HashSet();
```
**示例代码**[Item27Example02.java](Generics/src/main/java/com/jueee/item27/Item27Example02.java):最小化未经检查的警告被抑制的范围。
未经检查的警告是重要的。 不要忽视他们。
每个未经检查的警告代表在运行时出现 `ClassCastException` 异常的可能性。 尽你所能消除这些警告。
如果无法消除未经检查的警告,并且可以证明引发该警告的代码是安全类型的,则可以在尽可能小的范围内使用 `@SuppressWarnings(“unchecked”)` 注解来禁止警告。 记录你决定在注释中抑制此警告的理由。

View File

@ -1,5 +1,20 @@
package com.jueee.example26;
package com.jueee.item26;
import java.util.ArrayList;
import java.util.List;
public class Item26Example01 {
// Fails at runtime - unsafeAdd method uses a raw type (List)!
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
unsafeAdd(strings, Integer.valueOf(42));
String s = strings.get(0); // java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
System.out.println(s);
}
private static void unsafeAdd(List list, Object o) {
list.add(o);
}
}

View File

@ -0,0 +1,23 @@
package com.jueee.item26;
import java.util.ArrayList;
import java.util.List;
public class Item26Example02 {
// Fails at runtime - unsafeAdd method uses a raw type (List)!
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
// The method unsafeAdd(List<Object>, Object) in the type Item26Example02 is not applicable for the arguments (List<String>, Integer)
// unsafeAdd(strings, Integer.valueOf(42));
String s = strings.get(0);
System.out.println(s);
}
private static void unsafeAdd(List<Object> list, Object o) {
list.add(o);
}
}

View File

@ -0,0 +1,22 @@
package com.jueee.item26;
import java.util.ArrayList;
import java.util.List;
public class Item26Example03 {
// Fails at runtime - unsafeAdd method uses a raw type (List)!
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
unsafeAdd(strings, "42");
String s = strings.get(0);
System.out.println(s);
}
private static void unsafeAdd(List<String> list, String o) {
list.add(o);
}
}

View File

@ -0,0 +1,21 @@
package com.jueee.item26;
import java.util.Set;
public class Item26Example04 {
public static void main(String[] args) {
}
// 需要两个集合并返回它们共同拥有的元素的数量
// Use of raw type for unknown element type - don't do this!
private static int numElementsInCommon(Set s1, Set s2) {
int result = 0;
for (Object o1 : s1)
if (s2.contains(o1))
result++;
return result;
}
}

View File

@ -0,0 +1,22 @@
package com.jueee.item26;
import java.util.Set;
public class Item26Example05 {
public static void main(String[] args) {
}
// 需要两个集合并返回它们共同拥有的元素的数量
// Uses unbounded wildcard type - typesafe and flexible
private static int numElementsInCommon(Set<?> s1, Set<?> s2) {
int result = 0;
for (Object o1 : s1)
if (s2.contains(o1))
result++;
return result;
}
}

View File

@ -0,0 +1,37 @@
package com.jueee.item26;
import java.util.HashSet;
import java.util.Set;
public class Item26Example06 {
public static void main(String[] args) {
Set<String> set = new HashSet<String>();
set.add("test");
instanceofObject(set);
instanceofObjectWarn(set);
}
// 使用泛型类型的 instanceof 运算符的首选方法
// 一旦确定 o 对象是一个 Set则必须将其转换为通配符 Set<?>而不是原始类型 Set
// 这是一个强制转换所以不会导致编译器警告
private static void instanceofObject(Object o) {
if (o instanceof Set) { // Raw type
Set<?> s = (Set<?>) o; // Wildcard type
System.out.println(s);
} else {
System.out.println("worry type.");
}
}
private static void instanceofObjectWarn(Object o) {
if (o instanceof Set) {
Set s = (Set) o; // 编译器警告Set is a raw type. References to generic type Set<E> should be parameterized
System.out.println(s);
} else {
System.out.println("worry type.");
}
}
}

View File

@ -0,0 +1,22 @@
package com.jueee.item27;
import java.util.HashSet;
import java.util.Set;
public class Item27Example01 {
public static void main(String[] args) {
// 警告HashSet是原始类型 泛型类型HashSet <E>的引用应参数化
new HashSet();
// 进行指示修正让警告消失
// 请注意实际上并不需要指定类型参数只是为了表明它与 Java 7 中引入的钻石运算符<>一同出现
new HashSet<>();
// 使用 SuppressWarnings 注解声明单个局部变量
@SuppressWarnings({"rawtypes", "unchecked"})
Set<String> set3 = new HashSet();
System.out.println(set3);
}
}

View File

@ -0,0 +1,49 @@
package com.jueee.item27;
import java.util.AbstractList;
import java.util.Arrays;
public class Item27Example02<T> extends AbstractList<T> {
transient Object[] elements;
private int size;
// 把注释放在整个方法上
@SuppressWarnings({"unchecked", "hiding"})
public <T> T[] toArray(T[] a) {
if (a.length < size)
return (T[]) Arrays.copyOf(elements, size, a.getClass());
System.arraycopy(elements, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
// 声明一个局部变量来保存返回值并标注它的声明
public <T> T[] toArray2(T[] a) {
if (a.length < size) {
@SuppressWarnings("unchecked")
T[] result = (T[]) Arrays.copyOf(elements, size, a.getClass());
return result;
}
System.arraycopy(elements, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
@Override
public T get(int index) {
return null;
}
@Override
public int size() {
return 0;
}
public static void main(String[] args) {
}
}