【EFJava】关于泛型的一些总结

Num1:请不要在新代码中使用原生类型

泛型类和接口统称为泛型。每种泛型定义一组参数化的类型,构成格式是:类或接口名称,接着用<>把对应于泛型形式类型的参数的实际参数列表括起来。比如:List<String>是一个参数化的类型,表示元素类型为String的列表。最后一点,每个泛型都定义一个原生类型,raw type,即不带任何实际类型参数的泛型名称。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class Raw {
// Uses raw type (List) - fails at runtime! - Page 112
public static void main(String[] args) {
List<String> strings = new ArrayList<String>();
unsafeAdd(strings, new Integer(42));
String s = strings.get(0); // Compiler-generated cast
}

private static void unsafeAdd(List list, Object o) {
list.add(o);
}

// Use of raw type for unknown element type - don't do this! - Page 113
static int rawNumElementsInCommon(Set s1, Set s2) {
int result = 0;
for (Object o1 : s1)
if (s2.contains(o1))
result++;
return result;
}

// Unbounded wildcard type - typesafe and flexible - Page 113
static int numElementsInCommon(Set<?> s1, Set<?> s2) {
int result = 0;
for (Object o1 : s1)
if (s2.contains(o1))
result++;
return result;
}
}

从Java1.5发行版本开始,Java就提供了一种安全的替代方法,称作无限制的通配符类型,如果使用泛型,但不确定或者不关心实际的类型参数,就可以使用一个问号代替。

那么无限制通配类型Set<?>和原生类型Set之间有什么区别呢?通配符类型是安全的,原生类型则不安全。

Num2:消除非受检警告

当使用泛型编程时,会遇到许多编译器警告,那么该如何消除?

可以用@SuppressWarnings(“unchecked”)这个注解来禁止警告。需要注意的是,每当使用@SuppressWarnings(“unchecked”)注解时,都要添加一条注释,说明为什么这么做是安全的,这样可以帮助其他人理解代码,更重要的是,可以尽量减少其他人修改代码后导致计算不安全的概率。

Num3:列表优先于数组

数组与泛型相比,有两个重要的不同点。

首先,数组是协变的,泛型则是不可变的。

第二大区别:数组是具体化的,因此数组会在运行时才知道并检查它们的元素类型约束。相比之下,泛型则是通过擦除来实现的,因此泛型只在编译时强化它们的类型信息,并在运行时丢弃它们的元素类型信息。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class Reduction {
static <E> E reduce(List<E> list, Function<E> f, E initVal) {
List<E> snapshot;
synchronized (list) {
snapshot = new ArrayList<E>(list);
}
E result = initVal;
for (E e : snapshot)
result = f.apply(result, e);
return result;
}

// A few sample functions
private static final Function<Integer> SUM = new Function<Integer>() {
public Integer apply(Integer i1, Integer i2) {
return i1 + i2;
}
};

private static final Function<Integer> PRODUCT = new Function<Integer>() {
public Integer apply(Integer i1, Integer i2) {
return i1 * i2;
}
};

private static final Function<Integer> MAX = new Function<Integer>() {
public Integer apply(Integer i1, Integer i2) {
return Math.max(i1, i2);
}
};

private static final Function<Integer> MIN = new Function<Integer>() {
public Integer apply(Integer i1, Integer i2) {
return Math.min(i1, i2);
}
};

public static void main(String[] args) {
List<Integer> intList = Arrays.asList(2, 7, 1, 8, 2, 8, 1, 8, 2, 8);

// Reduce intList using each of the above reducers
System.out.println(reduce(intList, SUM, 0));
System.out.println(reduce(intList, PRODUCT, 1));
System.out.println(reduce(intList, MAX, Integer.MIN_VALUE));
System.out.println(reduce(intList, MIN, Integer.MAX_VALUE));
}
}

Num4:优先考虑泛型类和方法

示例类代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class Stack<E> {
private E[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;

// The elements array will contain only E instances from push(E).
// This is sufficient to ensure type safety, but the runtime
// type of the array won't be E[]; it will always be Object[]!
@SuppressWarnings("unchecked")
public Stack() {
elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}

public void push(E e) {
ensureCapacity();
elements[size++] = e;
}

public E pop() {
if (size == 0)
throw new EmptyStackException();
E result = elements[--size];
elements[size] = null; // Eliminate obsolete reference
return result;
}

public boolean isEmpty() {
return size == 0;
}

private void ensureCapacity() {
if (elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}

// Little program to exercise our generic Stack
public static void main(String[] args) {
Stack<String> stack = new Stack<String>();
for (String arg : args)
stack.push(arg);
while (!stack.isEmpty())
System.out.println(stack.pop().toUpperCase());
}
}

如果类可以从泛型中收益一般,方法也一样,静态工具方法尤其适合于泛型化。

示例方法代码:单例工厂模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public interface UnaryFunction<T> {
T apply(T arg);
}

public class GenericSingletonFactory {
// Generic singleton factory pattern
private static UnaryFunction<Object> IDENTITY_FUNCTION = new UnaryFunction<Object>() {
public Object apply(Object arg) {
return arg;
}
};

// IDENTITY_FUNCTION is stateless and its type parameter is
// unbounded so it's safe to share one instance across all types.
@SuppressWarnings("unchecked")
public static <T> UnaryFunction<T> identityFunction() {
return (UnaryFunction<T>) IDENTITY_FUNCTION;
}

// Sample program to exercise generic singleton
public static void main(String[] args) {
String[] strings = { "jute", "hemp", "nylon" };
UnaryFunction<String> sameString = identityFunction();
for (String s : strings)
System.out.println(sameString.apply(s));

Number[] numbers = { 1, 2.0, 3L };
UnaryFunction<Number> sameNumber = identityFunction();
for (Number n : numbers)
System.out.println(sameNumber.apply(n));
}
}

示例方法代码:静态方法模式

1
2
3
4
5
6
7
8
9
10
11
public class GenericStaticFactory {
// Generic static factory method
public static <K, V> HashMap<K, V> newHashMap() {
return new HashMap<K, V>();
}

public static void main(String[] args) {
// Parameterized type instance creation with static factory
Map<String, List<String>> anagrams = newHashMap();
}
}

泛型方法一个显著特征:无需明确指定类型参数的值,不像调用泛型构造器的时候必须指定一个类型。

简而言之,使用泛型比使用需要在客户端代码中进行转换的类型来的更加安全,也更加容易。

,