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; } } }