Generics泛型
编译错误很容易检查出来,但是运行时错误可不是那么容易检查出来,当发生运行时错误时,程序早就不知道运行到哪里去了
generics enable types (classes and interfaces) to be parameters when defining classes, interfaces and methods:泛型使类型(类和接口)在定义类、接口和方法时成为参数
泛型给程序增加了稳定性
Why Use Generics
- 编译时更强的类型检查:Java编译器对泛型代码应用强类型检查,并在代码违反类型安全性时发出错误。修复编译时错误比修复运行时错误更容易,因为运行时错误可能很难找到
- Elimination of casts
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);
List<String> list = new ArrayList<String>();
list.add("hello");
String s = list.get(0); // no cast
- 使程序员能够实现通用算法:通过使用泛型,程序员可以实现在不同类型的集合上工作的泛型算法,可以自定义,并且类型安全且易于阅读
Generic Types
A generic type is a generic class or interface that is parameterized over types:将class或者interface参数化,而不关注类型
A Simple Box Class
public class Box {
private Object object;
public void set(Object object) { this.object = object; }
public Object get() { return object; }
}
传入的不能是primitive type
代码一部分可能希望传入Integer,然后得到Integer,但是另一部分代码给传了个String,这样会得到runtime error
A Generic Version of the Box Class
class name<T1, T2, ..., Tn> { /* ... */ }
泛型模板
/**
* Generic version of the Box class.
* @param <T> the type of the value being boxed
*/
public class Box<T> {
// T stands for "Type"
private T t;
public void set(T t) { this.t = t; }
public T get() { return t; }
}
Invoking and Instantiating a Generic Type
Box<Integer> integerBox = new Box<Integer>();
Box<Integer> integerBox = new Box<>();
JDK7之后,可以省略
Type Parameter and Type Argument
T in Foo<T> is a type parameter
the String in Foo<String> f is a type argument
Multiple Type Parameters
public interface Pair<K, V> {
public K getKey();
public V getValue();
}
public class OrderedPair<K, V> implements Pair<K, V> {
private K key;
private V value;
public OrderedPair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
}
Pair<String, Integer> p1 = new OrderedPair<String, Integer>("Even", 8);
Pair<String, String> p2 = new OrderedPair<String, String>("hello", "world");
OrderedPair<String, Integer> p1 = new OrderedPair<>("Even", 8);
OrderedPair<String, String> p2 = new OrderedPair<>("hello", "world");
OrderedPair<String, Box<Integer>> p = new OrderedPair<>("primes", new Box<Integer>(...));
当然了也可以这么使用
Raw Types
public class Box<T> {
public void set(T t) { /* ... */ }
// ...
}
Box<Integer> intBox = new Box<>();
Box rawBox = new Box();
Box<String> stringBox = new Box<>();
Box rawBox = stringBox; // OK
Box rawBox = new Box(); // rawBox is a raw type of Box<T>
Box<Integer> intBox = rawBox; // warning: unchecked conversion
Box<String> stringBox = new Box<>();
Box rawBox = stringBox;
rawBox.set(8); // warning: unchecked invocation to set(T)
不建议使用raw type
Generic Methods
public class Util {
public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
return p1.getKey().equals(p2.getKey()) &&
p1.getValue().equals(p2.getValue());
}
}
public class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public void setKey(K key) { this.key = key; }
public void setValue(V value) { this.value = value; }
public K getKey() { return key; }
public V getValue() { return value; }
}
Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Util.<Integer, String>compare(p1, p2);
Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Util.compare(p1, p2);
Bounded Type Parameters
目的是想对类型参数做出一定的限制,比如想让它是Number的实例或者是子类
public class Box<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
public <U extends Number> void inspect(U u){
System.out.println("T: " + t.getClass().getName());
System.out.println("U: " + u.getClass().getName());
}
public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
integerBox.set(new Integer(10));
integerBox.inspect("some text"); // error: this is still String!
}
}
Box.java:21: <U>inspect(U) in Box<java.lang.Integer> cannot
be applied to (java.lang.String)
integerBox.inspect("10");
^
1 error
会报错,需要number类型给的确实string
Multiple Bounds
Class A { /* ... */ }
interface B { /* ... */ }
interface C { /* ... */ }
class D <T extends A & B & C> { /* ... */ }
class D <T extends B & A & C> { /* ... */ } // compile-time error
若A是类,则其必须在前
Generic Methods and Bounded Type Parameters
public static <T> int countGreaterThan(T[] anArray, T elem) {
int count = 0;
for (T e : anArray)
if (e > elem) // compiler error
++count;
return count;
}
会报错,因为>只能用于基本类型的操作
public interface Comparable<T> {
public int compareTo(T o);
}
public static <T extends Comparable<T>> int countGreaterThan(T[] anArray, T elem) {
int count = 0;
for (T e : anArray)
if (e.compareTo(elem) > 0)
++count;
return count;
}
利用Comparable
Generics, Inheritance, and Subtypes
Object someObject = new Object();
Integer someInteger = new Integer(10);
someObject = someInteger; // OK
public void someMethod(Number n) { /* ... */ }
someMethod(new Integer(10)); // OK
someMethod(new Double(10.1)); // OK
Box<Number> box = new Box<Number>();
box.add(new Integer(10)); // OK
box.add(new Double(10.1)); // OK
Integer是Number的子类,但是Box和Box没关系
Generic Classes and Subtyping
Subtyping 是由extends,implement关键字决定的
ArrayList<E> implements List<E>, and List<E> extends Collection<E>. So ArrayList<String> is a subtype of List<String>, which is a subtype of Collection<String>
interface PayloadList<E,P> extends List<E> {
void setPayload(int index, P val);
...
}
PayloadList<String,String>
PayloadList<String,Integer>
PayloadList<String,Exception>
Type Inference
public class BoxDemo {
public static <U> void addBox(U u,
java.util.List<Box<U>> boxes) {
Box<U> box = new Box<>();
box.set(u);
boxes.add(box);
}
public static <U> void outputBoxes(java.util.List<Box<U>> boxes) {
int counter = 0;
for (Box<U> box: boxes) {
U boxContents = box.get();
System.out.println("Box #" + counter + " contains [" +
boxContents.toString() + "]");
counter++;
}
}
public static void main(String[] args) {
java.util.ArrayList<Box<Integer>> listOfIntegerBoxes =
new java.util.ArrayList<>();
BoxDemo.<Integer>addBox(Integer.valueOf(10), listOfIntegerBoxes);
BoxDemo.addBox(Integer.valueOf(20), listOfIntegerBoxes);
BoxDemo.addBox(Integer.valueOf(30), listOfIntegerBoxes);
BoxDemo.outputBoxes(listOfIntegerBoxes);
}
}
The following is the output from this example:
Box #0 contains [10]
Box #1 contains [20]
Box #2 contains [30]
BoxDemo.<Integer>addBox(Integer.valueOf(10), listOfIntegerBoxes);
BoxDemo.addBox(Integer.valueOf(20), listOfIntegerBoxes);
Type Inference and Instantiation of Generic Classes
Map<String, List<String>> myMap = new HashMap<String, List<String>>();
Map<String, List<String>> myMap = new HashMap<>();
Map<String, List<String>> myMap = new HashMap(); // unchecked conversion warning
<>dimand符号不能省去
Type Inference and Generic Constructors of Generic and Non-Generic Classes
class MyClass<X> {
<T> MyClass(T t) {
// ...
}
}
MyClass<Integer> myObject = new MyClass<>("");
Target Types
JDK8之后会自动推断需要的类型
static <T> List<T> emptyList();
List<String> listOne = Collections.emptyList();
Collections.emptyList();返回的是List<T>
the compiler infers that the type argument T must be the value String
void processStringList(List<String> stringList) {
// process stringList
}
JAVA SE8 之后
processStringList(Collections.emptyList());
也是可以了
之前得
processStringList(Collections.<String>emptyList());
Wildcards通配符
In generic code, the question mark (?), called the wildcard
Upper Bounded Wildcards
List<? extends Number>
extends既可以指代子类,也可以指代实现的接口
List<Number>比List<? extends Number> 范围小些后者也可以指代子类
public static double sumOfList(List<? extends Number> list) {
double s = 0.0;
for (Number n : list)
s += n.doubleValue();
return s;
}
List<Integer> li = Arrays.asList(1, 2, 3);
System.out.println("sum = " + sumOfList(li));
List<Double> ld = Arrays.asList(1.2, 2.3, 3.5);
System.out.println("sum = " + sumOfList(ld));
Unbounded Wildcards
使用场景:
- 如果您正在编写一个可以使用对象类中提供的功能实现的方法。
- 当代码在泛型类中使用不依赖于类型参数的方法时For example, List.size or List.clear. In fact, Class<?> is so often used because most of the methods in Class do not depend on T.
public static void printList(List<Object> list) {
for (Object elem : list)
System.out.println(elem + " ");
System.out.println();
}
这个不能打印出来数值相加,只能打印出来对象实例
public static void printList(List<?> list) {
for (Object elem: list)
System.out.print(elem + " ");
System.out.println();
}
List<Integer> li = Arrays.asList(1, 2, 3);
List<String> ls = Arrays.asList("one", "two", "three");
printList(li);
printList(ls);
Lower Bounded Wildcards
<? super A>.
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 10; i++) {
list.add(i);
}
}
A 或者A的父类
Wildcards and Subtyping
class A { /* ... */ }
class B extends A { /* ... */ }
B b = new B();
A a = b;
b是A的子类实例,所以上述是成立的
List<B> lb = new ArrayList<>();
List<A> la = lb; // compile-time error
List<Integer> is not a subtype of List<Numbe
The common parent of List and List is List<?>
?通配符 可以理解为一种规则
Wildcard Capture and Helper Methods
import java.util.List;
public class WildcardError {
void foo(List<?> i) {
i.set(0, i.get(0));
}
}
WildcardError.java:6: error: method set in interface List<E> cannot be applied to given types;
i.set(0, i.get(0));
^
required: int,CAP#1
found: int,Object
reason: actual argument Object cannot be converted to CAP#1 by method invocation conversion
where E is a type-variable:
E extends Object declared in interface List
where CAP#1 is a fresh type-variable:
CAP#1 extends Object from capture of ?
1 error
public class WildcardFixed {
void foo(List<?> i) {
fooHelper(i);
}
// Helper method created so that the wildcard can be captured
// through type inference.
private <T> void fooHelper(List<T> l) {
l.set(0, l.get(0));
}
}
Type Erasure类型擦除
- 如果类型参数是无界的,则用其边界或对象替换泛型类型中的所有类型参数。因此,生成的字节码只包含普通类、接口和方法。
- 如有必要,插入类型强制转换以保持类型安全。
- 生成桥接方法以在扩展泛型类型中保留多态性。
public class Node<T> {
private T data;
private Node<T> next;
public Node(T data, Node<T> next) {
this.data = data;
this.next = next;
}
public T getData() { return data; }
// ...
}
public class Node {
private Object data;
private Node next;
public Node(Object data, Node next) {
this.data = data;
this.next = next;
}
public Object getData() { return data; }
// ...
}
public class Node<T extends Comparable<T>> {
private T data;
private Node<T> next;
public Node(T data, Node<T> next) {
this.data = data;
this.next = next;
}
public T getData() { return data; }
// ...
}
public class Node {
private Comparable data;
private Node next;
public Node(Comparable data, Node next) {
this.data = data;
this.next = next;
}
public Comparable getData() { return data; }
// ...
}
// Counts the number of occurrences of elem in anArray.
//
public static <T> int count(T[] anArray, T elem) {
int cnt = 0;
for (T e : anArray)
if (e.equals(elem))
++cnt;
return cnt;
}
public static int count(Object[] anArray, Object elem) {
int cnt = 0;
for (Object e : anArray)
if (e.equals(elem))
++cnt;
return cnt;
}
class Shape { /* ... */ }
class Circle extends Shape { /* ... */ }
class Rectangle extends Shape { /* ... */ }
public static <T extends Shape> void draw(T shape) { /* ... */ }
public static void draw(Shape shape) { /* ... */ }
Effects of Type Erasure and Bridge Methods
先来看两个类:
public class Node<T> {
public T data;
public Node(T data) { this.data = data; }
public void setData(T data) {
System.out.println("Node.setData");
this.data = data;
}
}
public class MyNode extends Node<Integer> {
public MyNode(Integer data) { super(data); }
public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}
}
public class Node {
public Object data;
public Node(Object data) { this.data = data; }
public void setData(Object data) {
System.out.println("Node.setData");
this.data = data;
}
}
public class MyNode extends Node {
public MyNode(Integer data) { super(data); }
public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}
}
经过Type Erasure
public class Node {
public Object data;
public Node(Object data) { this.data = data; }
public void setData(Object data) {
System.out.println("Node.setData");
this.data = data;
}
}
public class MyNode extends Node {
public MyNode(Integer data) { super(data); }
public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}
}
After type erasure, the method signatures do not match; the Node.setData(T) method becomes Node.setData(Object). As a result, the MyNode.setData(Integer) method does not override the Node.setData(Object) method.
为了解决构造函数类型不匹配的问题:
class MyNode extends Node {
// Bridge method generated by the compiler
//
public void setData(Object data) {
setData((Integer) data);
}
public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}
// ...
}
Bridge Method 取代了原来的构造函数
Non-Reifiable Types不可具体化的类型
Non-reifiable types are types where information has been removed at compile-time by type erasure— invocations of generic types that are not defined as unbounded wildcards. A non-reifiable type does not have all of its information available at runtime
public class ArrayBuilder {
public static <T> void addToList (List<T> listArg, T... elements) {
for (T x : elements) {
listArg.add(x);
}
}
public static void faultyMethod(List<String>... l) {
Object[] objectArray = l; // Valid
objectArray[0] = Arrays.asList(42);
String s = l[0].get(0); // ClassCastException thrown here
}
}
会有 heap pollution
@SafeVarargs
@SuppressWarnings({"unchecked", "varargs"})
可以用这两个注释保证不会出现 ClassCastException你自己得保证
Restrictions on Generics
Cannot Instantiate Generic Types with Primitive Types
class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
// ...
Pair<int, char> p = new Pair<>(8, 'a'); // compile-time error
Pair<Integer, Character> p = new Pair<>(8, 'a');//autoboxes
}
Cannot Create Instances of Type Parameters
public static <E> void append(List<E> list) {
E elem = new E(); // compile-time error
list.add(elem);
}
public static <E> void append(List<E> list, Class<E> cls) throws Exception {
E elem = cls.newInstance(); // OK
list.add(elem);
}
List<String> ls = new ArrayList<>();
append(ls, String.class);
Cannot Declare Static Fields Whose Types are Type Parameters
public class MobileDevice<T> {
private static T os;
// ...
}
MobileDevice<Smartphone> phone = new MobileDevice<>();
MobileDevice<Pager> pager = new MobileDevice<>();
MobileDevice<TabletPC> pc = new MobileDevice<>();
Because the static field os is shared by phone, pager, and pc, what is the actual type of os? It cannot be Smartphone, Pager, and TabletPC at the same time.
Cannot Use Casts or instanceof with Parameterized Types
public static <E> void rtti(List<E> list) {
if (list instanceof ArrayList<Integer>) { // compile-time error
// ...
}
}
这种是不行的,因为type erasure 会将E弄成Object so it cannot tell the difference between an ArrayList<Integer> and an ArrayList<String>
public static void rtti(List<?> list) {
if (list instanceof ArrayList<?>) { // OK; instanceof requires a reifiable type
// ...
}
}
这种是可以的 ? 通配符的应用
List<Integer> li = new ArrayList<>();
List<Number> ln = (List<Number>) li; // compile-time error
类型不同不可转换
List<String> l1 = ...;
ArrayList<String> l2 = (ArrayList<String>)l1; // OK
相同可转换
Cannot Create Arrays of Parameterized Types
List<Integer>[] arrayOfLists = new List<Integer>[2]; // compile-time error
Object[] strings = new String[2];
strings[0] = "hi"; // OK
strings[1] = 100; // An ArrayStoreException is thrown.
If you try the same thing with a generic list, there would be a problem:
Object[] stringLists = new List<String>[2]; // compiler error, but pretend it's allowed
stringLists[0] = new ArrayList<String>(); // OK
stringLists[1] = new ArrayList<Integer>(); // An ArrayStoreException should be thrown,
// but the runtime can't detect it.
Cannot Create, Catch, or Throw Objects of Parameterized Types
// Extends Throwable indirectly
class MathException<T> extends Exception { /* ... */ } // compile-time error
// Extends Throwable directly
class QueueFullException<T> extends Throwable { /* ... */ // compile-time error
public static <T extends Exception, J> void execute(List<J> jobs) {
try {
for (J job : jobs)
// ...
} catch (T e) { // compile-time error
// ...
}
}
这个是可以的
class Parser<T extends Exception> {
public void parse(File file) throws T { // OK
// ...
}
}
Cannot Overload a Method Where the Formal Parameter Types of Each Overload Erase to the Same Raw Type
public class Example {
public void print(Set<String> strSet) { }
public void print(Set<Integer> intSet) { }
}
A class cannot have two overloaded methods that will have the same signature after type erasure.The overloads would all share the same classfile representation and will generate a compile-time error.