泛型(generic):聲明中具有一個或多個類型參數
原始類型(raw type):不帶任何實際類型參數的泛型名稱
格式:
類或接口的名稱
<
對應於泛型形式類型參數的實際參數
>
如
List<String>
就是對應於List<E>
的實際參數為String
的參數化類型如與
List<E>
對應的原始類型是List
優點:
//JDK5之前的寫法,使用的是原始類型
private static final List stringList = new ArrayList();
//有了泛型之後的寫法,使用泛型
private static final List<String> stringList2 = new ArrayList<String>();
//JDK7 能將後面<>裡的類型省略,被稱為Diamond
private static final List<String> stringList3 = new ArrayList<>();
public static void main(String[] args) {
String str = "test";
Integer integer = 1;
stringList.add(str);
stringList.add(integer);//可通過編譯,但之後報ClassCastException錯誤
stringList2.add(str);
// stringList2.add(integer);//無法通過編譯
for(Iterator iterator = stringList.iterator();iterator.hasNext();){
String string = (String) iterator.next();
System.out.println(string);
}
for(Iterator iterator = stringList2.iterator();iterator.hasNext();){
String string = iterator.next();
System.out.println(string);
}
List
和List<Object>
之間的區別?
List
逃避了泛型檢查,List<Object>
則是告知編譯器,它能夠持有任意類型的對象
無限制的通配符類型:
使用泛型,但不確定或者不關心實際的類型參數,可以用一個問號代替。如List<?>
學習鏈接:
1.https://docs.oracle.com/javase/tutorial/java/generics/erasure.html
2.http://stackoverflow.com/questions/313584/what-is-the-concept-of-erasure-in-generics-in-java
下面通過一個小demo說明類型擦除
//類型擦除
List<String> stringList = new ArrayList<>();
List<Integer> integerList = new ArrayList<>();
System.out.println(stringList.getClass().toString());
System.out.println(integerList.getClass().toString());
System.out.println(stringList.getClass()==integerList.getClass());
integerList.add(100);
Method method = integerList.getClass().getMethod("add",Object.class);
method.invoke(integerList,"abc");
System.out.println(integerList);
運行結果:
一般不在代碼中使用原始類型,除了兩種例外情況(都是因為泛型信息在運行時會被擦除):
如: List.class,String[].class,int.class都合法 List<String>.class,List<String>.class都不合法
if(o instanceof Set){ //原始類型(Raw Type)
Set<?> set = (Set<?>)o;//通配符類型(WildCard Type)
}
下面的表格是泛型相關的術語:
下面這張圖很好的介紹了無限制通配符和其他泛型符號之間的關系:
始終在盡可能小的范圍內使用SuppressWarnings注解
Java源碼中的ArrayList類中有個toArray方法,其中就有強轉的警告:
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
最好是將范圍進一步縮小。將注解由整個方法到局部的變量聲明上去。
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
if (a.length < size){
@SuppressWarnings("unchecked")
T[] result = (T[]) Arrays.copyOf(elementData, size, a.getClass());
return result;
}
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
Object[] objectArray = new String[1];
List<Object> objectList = new ArrayList<String>();//無法通過編譯 imcompatible types
// String類是Object類的子類
//String[]是Object[]的子類
//而List<String>並不是List<String>的子類型
Object[] objectArray = new String[1];
List<String> objectList = new ArrayList<String>();
objectArray[0] = 3;//可通過編譯,運行時報錯
// objectList.add(1);//編譯時報錯
數組和泛型不能很好地混合使用。可用列表代替數組。
總結:數組是協變且可具體化的,泛型是不可變的且可被擦除的。-->數組提供了運行時類型安全而編譯時類型不安全。而泛型反之。
泛型相比於Object的優點:
public class SomeClazz<T> {
public Object dosthWithObj(Object obj){
return obj;
}
public T dosthWithT(T t){
return t;
}
public static void main(String[] args) {
SomeClazz<Foo> someClazz = new SomeClazz<Foo>();
Foo foo = new Foo();
Foo foo1 = (Foo) someClazz.dosthWithObj(foo);
Foo foo2 = someClazz.dosthWithT(foo);
}
}
public class Stack<E> {
private E [] elements;
private static final int MAX_SIZE = 16;
private int size = 0;
@SuppressWarnings("unchecked")
public Stack(){
elements = (E[]) new Object[MAX_SIZE];
}
public void push(E e){
ensureSize();
elements[size++]=e;
}
public E pop(){
if(size==0)
throw new EmptyStackException();
E e = elements[--size];
elements[size]=null;
return e;
}
private void ensureSize() {
if(size==elements.length){
elements= Arrays.copyOf(elements,2*size+1);
}
}
public static void main(String[] args) {
Stack<Integer> stack = new Stack<>();
for(int i =0;i<50;i++){
stack.push(i);
}
for(int i = 0;i<10;i++){
System.out.println(i+": "+stack.pop());
}
}
}
class EmptyStackException extends RuntimeException{
}
前面曾鼓勵優先使用列表而不是數組。並不意味著所有的泛型中都要使用列表。況且Java並不是生來就支持列表的。
每個類型都是它自身的子類型。
如有 SomeClazz<E extends Number>
SomeClazz<Number>是合法的
方法可以考慮泛型化,特別是靜態工具方法。
泛型方法語法:
方法修飾語 泛型 返回值 方法名()
public static <T> T foo(T args);
/**
* 使用泛型方法
* 返回兩個集合的聯合
* @param s1
* @param s2
* @param <E>
* @return
*
* 局限:兩個參數和返回的結果的類型必須全部相同
* 解決方法:使用有限制的通配符
*/
public static <E> Set<E> unionGeneric(Set<E> s1,Set<E> s2){
Set<E> result = new HashSet<>(s1);
result.addAll(s2);
return result;
}
public static <K,V> Map<K,V> newHashMap(){
return new HashMap<K,V>();
}
泛型單例工廠:
public interface UnaryFunction<T>{
T apply(T arg);
}
private static UnaryFunction<Object> IDENTITY_FUNCTION =
new UnaryFunction<Object>() {
@Override
public Object apply(Object arg) {
return arg;
}
};
@SuppressWarnings("unchecked")
public static <T> UnaryFunction<T> identityFunction(){
return (UnaryFunction<T>) IDENTITY_FUNCTION;
}
/**
* 每次都要創建一個,很浪費,而且它是無狀態的.
* 泛型被具體化了,每個類型都需要一個恆等函數,但是它們被擦除後,就只需要一個泛型單例了.
* @param <T>
* @return
*/
public static <T> UnaryFunction<T> identityFunction2(){
return new
UnaryFunction<T>() {
@Override
public T apply(T arg) {
return arg;
}
};
}
遞歸類型限制:
通過某個包含該類型參數本身的表達式來限制類型參數
<T extends Comparable<T>>//針對可以與自身進行比較的每個類型T
參數化類型是不可變的。
雖然String類是Object類的子類,但是List<String>和List<Object>無關
/**
* 棧的實現
* @param <E>
* API:
* public Stack();
* public void push(E e);
* public E pop();
* public boolean isEmpty();
*
* 新增API:
* before:
* public void pushAll(Iterable<E> i);
* public void popAll(Collection<E> c);
* after:
* 使用有限制的通配符類型(bounded wildcard type)
* public void pushAll(Iterable<? extends E> i);
* public void popAll(Collection<? super E> c);
*
*/
class Stack<E>{
private E [] elements;
private static final int INIT_CAPABILITY = 16;
private int size = 0;
@SuppressWarnings("unchecked")
public Stack(){
elements = (E[]) new Object [INIT_CAPABILITY];
}
public void push(E e){
checkCapability();
elements[size++]=e;
}
public E pop(){
if(size==0)
throw new RuntimeException("Empty Stack");
E e = elements[--size];
elements[size]=null;
return e;
}
private void checkCapability() {
if(size==elements.length)
elements = Arrays.copyOf(elements,2*elements.length-1);
}
public boolean isEmpty(){
return size==0;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
String start = super.toString();
sb.append(start);
for(int i = 0 ;i<size;i++){
String s = " ["+elements[i]+"]";
sb.append(s);
}
return sb.toString();
}
//before
// public void pushAll(Iterable<E> i){
// for(E e:i){
// push(e);
// }
// }
// public void popAll(Collection<E> c){
// while (!isEmpty()){
// c.add(pop());
// }
// }
//after
public void pushAll(Iterable<? extends E> i){
for(E e:i){
push(e);
}
}
public void popAll(Collection<? super E> c){
while(!isEmpty()){
c.add(pop());
}
}
Stack<Number> stack= new Stack<>();
Iterable<Integer> integers = Arrays.asList(1,2,3,4,5);
Collection<Object> objectCollection = new LinkedList<>();
//before
// stack.pushAll(integers);//參數類型不對
// stack.popAll(objectCollection);//參數類型不對
//after
stack.pushAll(integers);
System.out.println(stack);
stack.popAll(objectCollection);
System.out.println(stack);
從上面的Demo中我們知道,Java中提供了有限制的通配符類型
來提高API的靈活性。
如
Collection<? extends E>
Collection<? super E>
一般在表示生產者或消費者的輸入參數上使用通配符類型。
PECS:Producer-extends Consumer-super
------------------ * 參數化類型 通配符類型 * T生產者 extends * T消費者 super * ------------------