Jdk5.0新特性Generic Types(泛型)
副标题#e#
1. 先容
2.界说简朴Java泛型
其实Java的泛型就是建设一个用范例作为参数的类。就象我们写类的要领一样,要领是这样的method(String str1,String str2 ),要领中参数str1、str2的值是可变的。而泛型也是一样的,这样写class Java_Generics<K,V>,这里边的K和V就象要领中的参数str1和str2,也是可变。下面看看例子:
import java.util.Hashtable;
class TestGen0<K,V>{
public Hashtable<K,V> h=new Hashtable<K,V>();
public void put(K k, V v) {
h.put(k,v);
}
public V get(K k) {
return h.get(k);
}
public static void main(String args[]){
TestGen0<String,String> t=new TestGen0<String,String>();
t.put("key", "value");
String s=t.get("key");
System.out.println(s);
}
}
正确输出:value
这只是个例子,不外看看是不是建设一个用范例作为参数的类,参数是K,V,传入的“值”是String范例。这个类他没有特定的待处理惩罚型别,以前我们界说好了一个类,在输入参数有所牢靠,是什么型此外有要求,可是此刻编写措施,完全可以不拟定参数的范例,详细用的时候来确定,增加了措施的通用性,像是一个模板。
3. 泛型通配符
首先,下面是一个例子,浸染是打印出一个荟萃中的所有元素,我们首先用老版本jdk1.4的编码法则,代码如下:
void printColleciton(Collection c){
iterator i = c.iterator();
for (k = 0; k < c.size();k++){
System.out.pritnln(i.next();
}
然后,我们用jdk5.0泛型来重写上面这段代码(轮回的语法是新版本的语法):
void printCollection(Colleciton<Object> c){
for(Object e : c){
System.out.print(e);
}
}
这个新版本并不比老版本的好几多,老版本可以用任意一种荟萃范例作为参数来挪用,而新版本仅仅持有Collection<Object>范例,Colleciton<Object>并不是任意范例的Collection的超类。
那么什么是所有Colleciton范例的超范例呢?它是Collection<?>这样一个范例,读作“未知Colleciton”。它的意思是说Colleciton的元素范例可以匹配任意范例,我们把它称作通配符范例,我们这样写:
void printCollection(Colleciton<?> c){
for (Object e: c){
System.out.println(e);
}
}
此刻我们用任意范例的荟萃来挪用它了,需要留意的是内部要领printColleciton(),我们任可以从c中来读出元素,而且这些元素是Object范例,并且是安详的,因为无论荟萃中是什么范例,它总包罗Object,可是将任意工具加到荟萃中是不安详的:
Colleciton<?> c = new ArrayList<String>();
c.add(new Object());//编译时错误
由于我们不知道c持有的是什么范例的元素,我们不能加object到荟萃中去。add()要领用范例E作为参数,(荟萃的元素范例)卖力正的参数范例是?的时候,它代表的是一些未知范例。任何通报给add()要领的参数必需是这个未知范例的子范例。由于我们不知道未知范例,所以我们通报给它任何对象。主要的破例是null,它是每一个范例的成员。
另一方面,假定给一个List<?>,我们挪用get()而且充实操作功效。功效范例是未知范例。可是我老是知道它是一个Object,因此分派一个从get()取出来的功效到一个object的变量是安详的,可能作为一个参数通报到一个需要object范例的处所。
#p#副标题#e#
3.1有限制的通配符
思量一个绘图的应用措施,这个措施可以或许画长方形、圆等范例,为了在措施中暗示这样的图形,你可以界说一个范例的条理布局:
public abstract class Shape{ public abstract void draw(Canvas c); } public class Circle extends Shape{ private int x,y,radius; public void draw(Canvas c){} } public class Rectangle extends Shape{ private int x,y,width,height; public void draw(Canvas c){ } } //这些类能被画在画布上: public class Canvas{ public void draw(Shape s){ s.draw(this); } } 任何绘图的行动的都包括一些图形,假设他们被暗示在一个list中,在Canvas中它将会有一个很利便的要领来画他们:
public void drawAll(List<Shape> shapes){
for(Shape s :shapes){
s.draw(this);
}
}
此刻范例法则说,要领drawAll()只能在真正的Shape范例的List上被挪用,而它的子类无法挪用,譬喻List<Circle>上被挪用。这是很不幸的。由于所有的要领确实从List中读出Shape,所以它仅能在List<Object>上被挪用,下面我们改后的代码可以在任意范例的Shape上被挪用:
public void drawAll(List< ? extends Shape>{ }
#p#分页标题#e#
这里有一个很小的差异是,我们已经用List<? extends Shape>替换了List<Object>,此刻drawAll()要领可以接管任意的Shape的子类了,我们虽然可以在List<Circle>上挪用。
<? extends Class>是一种限制通配符范例,它可以接管所有<Class>以及Class的子范例。然而挪用价钱是,只读会见,无法向shapes中添加元素。像凡是一样,利用通配符带来的机动性将支付价钱,譬喻,下面是不答允的:
public void addRectangle(List<? extends Shape> shapes){
shapes.add(0,new Rectangle());//编译时错误
}
限制性通配符的一个例子是,是一小我私家口普查的例子,我们假设数据是由一个名字映射一小我私家,名字是字符串,人(可以是Person,或是它的子类Driver),Map<k,v>是一个泛型的例子,它拥有两个参数,暗示为一个KEY和value的映射MAP
再次留意正规参数的定名法则,K代表key,V代表value
public class Census{
public static void addRegistry(Map<String ? extends Person> Registry){ }
}
Map<String,Driver> allDrivers =;
census.addResigtry(allDrivers);
编写泛型类要留意:
1) 在界说一个泛型类的时候,在 “<>”之间界说形式范例参数,譬喻:“class TestGen<K,V>”,个中“K” , “V”不代表值,而是暗示范例。
2) 实例化泛型工具的时候,必然要在类名后头指定范例参数的值(范例),一共要有两次书写。譬喻:
TestGen<String,String> t=new TestGen<String,String>();
3) 泛型中<K extends Object>,extends并不代表担任,它是范例范畴限制。
4.泛型与数据范例转换
4.1. 消除范例转换
上面的例子各人看到什么了,数据范例转换的代码不见了。在以前我们常常要书写以下代码,如:
import Java.util.Hashtable;
class Test {
public static void main(String[] args) {
Hashtable h = new Hashtable();
h.put("key", "value");
String s = (String)h.get("key");
System.out.println(s);
}
}
这个我们做了范例转换,是不是感受很烦的,而且强制范例转换会带来潜在的危险,系统大概会抛一个ClassCastException异常信息。在JDK5.0中我们完全可以这么做,如:
import Java.util.Hashtable;
class Test {
public static void main(String[] args) {
Hashtable<String,Integer> h = new Hashtable<String,Integer> ();
h.put("key", new Integer(123));
int s = h.get("key").intValue();
System.out.println(s);
}
}
这里我们利用泛化版本的HashMap,这样就不消我们来编写范例转换的代码了,范例转换的进程交给编译器来处理惩罚,是不是很利便,并且很安详。上面是String映射到String,也可以将Integer映射为String,只要写成HashTable<Integer,String> h=new HashTable<Integer,String>();h.get(new Integer(0))返回value。公然很利便。
4.2 自动解包装与自动包装的成果
从上面有没有看到有点别扭啊,h.get(new Integer(123))这里的new Integer(123);好烦的,在JDK5.0之前我们只能忍着了,此刻这种问题已包办理了,请看下面这个要领。我们传入一个int这一根基型别,然后再将i的值直接添加到List中,其实List是不能储存根基型此外,List中应该存储工具,这里编译器将int包装成Integer,然后添加到List中去。接着我们用List.get(0);来检索数据,并返回工具再将工具解包装成int。恩,JDK5.0给我们带来更多利便与安详。
public void autoBoxingUnboxing(int i) {
ArrayList<Integer> L= new ArrayList<Integer>();
L.add(i);
int a = L.get(0);
System.out.println("The value of i is " + a);
}
4.3 限制泛型中范例参数的范畴
也许你已经发此刻TestGen<K,V>这个泛型类,个中K,V可以是任意的型别。也许你有时候呢想限定一下K和V虽然范畴,怎么做呢?看看如下的代码:
class TestGen2<K extents String,V extends Number>
{
private V v=null;
private K k=null;
public void setV(V v){
this.v=v;
}
public V getV(){
return this.v;
}
public void setK(K k){
this.k=k;
}
public V getK(){
return this.k;
}
public static void main(String[] args)
{
TestGen2<String,Integer> t2=new TestGen2<String,Integer>();
t2.setK(new String("String"));
t2.setV(new Integer(123));
System.out.println(t2.getK());
System.out.println(t2.getV());
}
}
#p#分页标题#e#
上边K的范畴是<=String ,V的范畴是<=Number,留意是“<=”,对付K可以是String的,V虽然也可以是Number,也可以是Integer,Float,Double,Byte等。看看下图也许能直观些请看上图A是上图类中的基类,A1,A2别离是A的子类,A2有2个子种别离是A2_1,A2_2。
然后我们界说一个受限的泛型类class MyGen<E extends A2>,这个泛型的范畴就是上图中兰色部门。
这个是单一的限制,你也可以对型别多重限制,如下:
class C<T extends Comparable<? super T> & Serializable>
我们来阐明以下这句,T extends Comparable这个是对上限的限制,Comparable< super T>这个是下限的限制,Serializable是第2个上限。一个指定的范例参数可以具有一个或多个上限。具有多重限制的范例参数可以用于会见它的每个限制的要领和域。
5.泛型要领
思量写一个持有数组范例工具和一个荟萃工具的要领,把数组里的所有工具都放到
荟萃里。第一个措施为:
static void fromArrayToColleciton(Object[]a,Collection<?> c){
for (Object o : a){
c.add(o);//编译时错误
}
}
到此刻为止,你大概学会制止开始的错误而去利用Collection<Object>作为荟萃参数的范例,你大概会心识到利用Colleciton<?>将不会事情。
办理这个问题的要领是利用泛型要领,GENERIC METHODS,就像范例声明、要领声明一样,就是被一个或更多的范例参数参数化。
static <T> void fromArrayToCollection(T[]a,Collection<T> c){
for(T o :a){
c.add(o);//正确
}
}
我们可以用任意范例的荟萃挪用这个要领,他的元素范例是数组元素范例的超范例。
Object[] oa = new Object[100];
Collection <Object> co = new ArrayList<Object>();
fromArrayToCollection(oa,co);//T 被认为是Object范例
String[] sa = new String[100];
Colleciton<String> cs = new ArrayList<String>();
fromArrayToCollection(sa,cs);//T被认为是String范例
fromArrayToCollection(sa,co);//T 被认为是Object范例
Integer[] is = new Integer[100];
Float[] fa = new Float[100];
Number[] na = new Number[100];
Collection<Number> cn = new ArrayList<Number>();
fromArrayToCollection(is,cn);//Number
fromArrayToCollection(fa,cn);//Number
fromArrayToCollection(na,cn);//Number
fromArrayToCollection(na,co);//Object
fromArrayToCollection(na,cs);//编译时错误
我们不必给一个泛型要领通报一个真正的范例参数,编译器会揣度范例参数.一个问题呈现了,什么时候利用泛型要领,什么时候使通配符范例,为了答复这些问题,我们从Colleciton库中看一下几个要领:
interface Collection<E>{
public boolean containsAll(Collection<?> c);
public boolean addAll(Collection<? extends E> c);
}
利用泛型要领的形式为:
interface Collection<E>{
public <T> boolean containsAll(Collection<T> c);
public <T extends E> boolean addAll(Collection<T> c);
}
无论如何,在ContainAll和addAll中,范例参数T仅被利用一次。返回范例不依赖于范例参数,也不依赖要领中的任何参数。这汇报我范例参数正被用于多态,它的影响仅仅是答允差异的实参在差异的挪用点被利用。
泛型要领答允范例参数被用于表达在一个或更多参数之间可能要领中的参数、返回范例的依赖。假如没有如此依赖,泛型要领就不能被利用。大概一前一厥后连系利用泛型和通配符,这里有个例子:
class Collections{
public static <T> void copy(List<T> dest,List<? extends T> src){
}
}
留意两个参数之间的依赖,任何从原list的工具复制,必需分派一个方针LIST元素的范例T,于是Src的元素范例大概是任何T的子范例。我们不必在意在COPY的表达中,暗示依赖利用一个范例参数,可是是利用一个通配符。
下面我们不利用通配符来重写上面的要领:
class Collections{
public static <T,S extends T>
void copy(List<T> dest,List<S> src){
}
}
这很是好,可是第一个范例参数既在dst中利用,也在第二个范例参数中利用,S自己就被利用了一次。在范例src中,没有什么范例依赖它。这是一个符号我们可以用通配符来替换它。利用通配符比显示的声明范例参数越发清楚和准确。所以有大概推荐利用通配符。
通配符也有优势,可以在要领之外来利用,作为字段范例、局部变量和数组。
这里有一个例子。
#p#分页标题#e#
返回到我们绘图的例子,假设我们要保持一个绘图请求的汗青,我们可以在Shape类内部用一个静态变量来保持汗青。用drawAll()存储它到来的参数到汗青字段。
static List<List<? extends Shape>> history =
new ArrayList<List<? extends Shape>>();
public void drawAll(List<? extends Shape> shapes){
history.addLast(shapes);
for (Shape s : shapes){
s.draw(this);
}
}