1.定義泛型接口和類
泛型接口:
public interface List<E>
{
void add(E x);
Iterator<E> iterator();
...
}
public interface Iterator<E>
{
E next();
boolean hasNext();
...
}
public interface Map<K,V>
{
Set<K,V> keySet();
V put(K key,V value);
...
}
使用泛型的類:
import java.util.*;
class Genericclass<T>
{
private T info;
public Genericclass(){}
public Genericclass(T t)
{//使用泛型的構造器
this.info=t;
}
public void setinfo(T info)
{
this.info=info;
}
public T getinfo()
{
return this.info;
}
public static void main(String[] args)
{
//調用使用泛型的構造器
Genericclass<String> gs=new Genericclass<>("123");
System.out.println(gs.getinfo());
Genericclass<Double> gd=new Genericclass<>(3.45);
System.out.println(gd.getinfo());
}
}
2.從泛型類派生子類
import java.util.*;
class Genericclass<T>
{
private T info;
public Genericclass(){}
public Genericclass(T t)
{//使用泛型的構造器
this.info=t;
}
public void setinfo(T info)
{
this.info=info;
}
public T getinfo()
{
return this.info;
}
}
/*定義Genericclass的子類
使用public class A1 extends Genericclass<String>
或者public class A1 extends Genericclass方式,不能使用
public class A1 extends Genericclass<T>,即必須指定具體的類型或者什麼都不指定
*/
public class A1 extends Genericclass<String>
{
public A1(String s)
{
super(s);
}
public String getinfo()
{
return "子類:"+super.getinfo();
}
public static void main(String[] args)
{
//調用使用泛型的構造器
A1 a1=new A1("123");
System.out.println(a1.getinfo());
}
}
3.類型通配符
3.1爲什麼使用類型通配符?如下:
import java.util.*;
class ArrErr
{
public void test(List c)
{
for(int i=0;i<c.size();i++)
{
System.out.println(c.get(i));
}
}
/*public void test(List<Object> c)
{
for(int i=0;i<c.size();i++)
{
System.out.println(c.get(i));
}
}*/
public static void main(String[] args)
{
List<String> strList=new ArrayList<>();
ArrErr ae=new ArrErr();
/*調用第一個test時不會有編譯問題,但調用第二個時會產生編譯錯誤
由於List<String>不是List<Object>的子類,二者不能進行轉換
而一般情況下List是泛型接口,使用時爲了能夠知道其中元素的類型
往往使用第二種方法定義,這就限制了第二種方法的使用,必須完全轉爲Object
類型才能使用
*/
ae.test(strList);
System.out.println("Hello World!");
}
}
3.2使用類型通配符
通過?符號作爲參數進行傳遞,即可以與任何類型進行匹配的類型。則上述的test函數可改爲:
public void test(List<?> c)
{
for(int i=0;i<c.size();i++)
{
System.out.println(c.get(i));
}
}
//這種定義只能訪問c中的元素,不能修改和添加
List<?> c= new ArrayList<String>();
因爲此時不知道c集合中元素的類型,只表明它是各種泛型List的父類,即可以將List<String>類型的參數傳遞給List<?>類型的參數。
一般通配符是和泛型結合使用,如下(使用通配符的上限):
public void test(List<? extends E> c)
{
for(int i=0;i<c.size();i++)
{
System.out.println(c.get(i));
}
}
傳遞給該函數的參數是泛型參數E的子類,或者在泛型方法中使用。
4.泛型方法
4.1爲什麼使用泛型方法?如下:
import java.util.*;
class GenMethod
{
/*將一個Object數組的元素添加到Collection集合中
static void fromarraytocollection(Object[] a,Collection<Object> c)
{
for(Object o:a)
{
c.add(o);
}
}*/
//使用泛型方法定義
static <T> void fromarraytocollection(T[] a,Collection<T> c)
{
for(T o:a)
{
c.add(o);
}
}
public static void main(String[] args)
{
String[] str={"a","b"};
List<String> slist=new ArrayList<>();
GenMethod gm=new GenMethod();
/*使用第一個方法時,下面將會出現編譯錯誤,因爲List<String>不是
Collection<Object>的子類,不能進行轉換也不能使用通配符?,
因爲需要向c中添加元素,?不知道元素的類型
*/
fromarraytocollection(str,slist);
Object[] obj=new Object[100];
List<Object> lo=new ArrayList<>();
fromarraytocollection(obj,lo);
}
}
4.2泛型方法的定義方式:修飾符 <T,S> 返回值類型 方法名(形參列表){}。
大多數情況下可以使用泛型方法來代替類型通配符,如下:
public interface Collection<E>
{
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
...
}
//轉爲泛型方法爲:
public interface Collection<E>
{
<T> boolean containsAll(Collection<T> c);
<T extends E> boolean addAll(Collection<T> c);
...
}
注意:如果一個方法的類型形參(a)的類型或返回值的類型依賴於另一個形參(b)的類型,則形參(b)的類型聲明不應該使用通配符,因爲如果b的類型不確定,則a的類型也不確定,只能使用類型形參,即泛型方法。
4.3設定通配符的下限
爲什麼要設置下限?如下:
public static<T> T copy(Collection<T> dest,Collection<? extends T> src)
{
T last=null;
for(T t:src)
{
last=t;
dest.add(last);
}
/*返回一個目標集合類型的元素,但實際傳遞的元素類型爲T的子類,此時就會忘記原集合的類型
而src原類型必須是T的子類,所以需要使用<? extends T>的方法定義
*/
return last;
}
//使用下述代碼訪問時,則會出現編譯錯誤
List<Number> lm=new ArrayList<>();
List<Integer> li=new ArrayList<>();
Integer last=copy(lm,li);//此時想反悔Integer類型的參數,但函數中返回的是Number類型
則只需將上述方法改爲:public static<T> T copy(Collection<? super T> dest,Collection<T> src)
即將dest類型使用通配符表示,設置其下限必須是T類型,及其父類類型。
注:泛型方法可進行方法重載,如:
public static<T> void copy(Collection<T> dest,Collection<? extends T> src);
public static<T> T void copy(Collection<? super T> dest,Collection<T> src);
5.擦出和轉換
當把一個具有泛型信息的對象賦值給另一個沒有泛型信息的變量時,所有尖括號之間的類型信息都被扔到,比如把一個List<String>類型轉爲List,則該List集合元素的類型都轉爲類型變量的上限,無法進行還原。比如:
A<Integer> a=new A<>(5);
Integer as=a.get();//獲取5的值
A b=a;
Object ob=b.get();//此時b只知道a中的5被轉爲Object類型
//無法再將b中的5隱式轉爲Integer
Integer ib=b.get();
但下面的這種方式是正確的:
List<Integer> li=new ArrayList<>();
List l=li;
List<String> ls=l;//可以轉換,但當訪問ls中的數組時,會出現運行時異常
6.泛型和數組
java5中的泛型有一個重要的原則:如果一段代碼在編譯時沒有提出"[unchecked]"未經檢查的異常,則在運行時也不會拋出該異常。
import java.util.*;
class GenArray
{
public static void main(String[] args)
{
下面這句在編譯時會引發類型異常
List<String>[] lsa=new ArrayList[10];//不能使用List<String>[] lsa==new List<String>[10],編譯時不會引發異常,運行時引發異常,未被java設計原則
Object[] oa =(Object[])lsa;
List<Integer> li=new ArrayList<Integer>();
li.add(new Integer(3));
oa[1]=li;
String s=lsa[1].get(0);//在運行時將會引發類型異常,不能講Object再轉爲String
//可將第一句改爲如下形式
List<?>[] lsa =new ArrayList<?>[10];
//在進行轉換時使用下述方法,則不會出現任何錯誤
Object target=lsa[1].get(0);
if(target instanceof String)
{
String s=(String)target;
}
}
}