??xml version="1.0" encoding="utf-8" standalone="yes"?>91亚洲欧美,国产精品视频久久一区,久久蜜桃精品http://www.aygfsteel.com/jiangshachina/category/27393.html同是Java爱好者,盔R何必曾相识Q?lt;br>    a cup of Java, cheers!zh-cnThu, 29 Nov 2007 01:40:09 GMTThu, 29 Nov 2007 01:40:09 GMT60Java Tutorials -- Concurrency(?http://www.aygfsteel.com/jiangshachina/archive/2007/10/28/156522.htmlSha JiangSha JiangSun, 28 Oct 2007 11:51:00 GMThttp://www.aygfsteel.com/jiangshachina/archive/2007/10/28/156522.htmlhttp://www.aygfsteel.com/jiangshachina/comments/156522.htmlhttp://www.aygfsteel.com/jiangshachina/archive/2007/10/28/156522.html#Feedback1http://www.aygfsteel.com/jiangshachina/comments/commentRss/156522.htmlhttp://www.aygfsteel.com/jiangshachina/services/trackbacks/156522.html阅读全文

Sha Jiang 2007-10-28 19:51 发表评论
]]>
Java Tutorials -- Generics(?http://www.aygfsteel.com/jiangshachina/archive/2007/06/20/125293.htmlSha JiangSha JiangWed, 20 Jun 2007 03:11:00 GMThttp://www.aygfsteel.com/jiangshachina/archive/2007/06/20/125293.htmlhttp://www.aygfsteel.com/jiangshachina/comments/125293.htmlhttp://www.aygfsteel.com/jiangshachina/archive/2007/06/20/125293.html#Feedback13http://www.aygfsteel.com/jiangshachina/comments/commentRss/125293.htmlhttp://www.aygfsteel.com/jiangshachina/services/trackbacks/125293.htmlJava Tutorials -- Generics
Java Generics伴随JDK 5.0发布到现在已l超q?q半了,但目前还没有?非常q泛"地应用,我也一直没有进行过pȝ的学习。最q用Thinking in Java(4th)?a >Java TutorialsҎ型进行了专门的学习?/span>本文是对Java Tutorials?a >Generics一?/a>的翻译。其实关于Java Generics的文章已是汗牛充栋,之所以将q篇译文攑֜此处Q也是对自己学习的一U鼓励吧。该文的读者应该只有我一人,但仍然希望对其他朋友有所助益?/span>(2007.07.10最后更?

1 介绍
    JDK 5.0引进了几UJavaE序设计语言的新扩展。其中之一Q就是对泛型的引入?br />     本次体验只是Ҏ型的介绍。你可能通过其它的语aQ特别是C++ TemplateQ已l对泛型的结构有些熟悉了。如果是q样的话Q你看到它们的怼点和重要的不同点。如果你对从别处看到的这U似曄识的l构不熟悉的话,那就更好了,你可以从头开始,以避免不得不忘却一些误解?br />     泛型允许你抽象出cd。最普通的例子是容器cdQ如集合框架(Collection)中的那些cR?br />     下面是一个此cȝ性的典型使用Q?br />     List myIntList = new LinkedList(); // 1
    myIntList.add(new Integer(0)); // 2
    Integer x = (Integer) myIntList.iterator().next();  // 3        
    W三行的强制cd转换有点烦h。基本上Q程序员知道到底是什么类型的数据被放到这个特定的List中了。然而,q个强制cd转换是必需的。编译器只能保证q代器将q回的是一个对象。ؓ了确保一个类型ؓInteger的变量x是类型安全的Q这个强制类型{换是需要的?br />     当然Q这个强制类型{换ƈ不会造成混ؕ。它仍然可能会造成一个运行时错误Q可能是q序员的失误而生的?br />     那么E序员如何才能准地表达他们的本意,使得一个List被限制ؓ只能包含某个特定cd的数据呢Q这正是泛型背后的核心思想。下面的E序片断是前qC子的泛型版:
    List<Integer> myIntList = new LinkedList<Integer>(); // 1'
    myIntList.add(new Integer(0)); // 2'
    Integer x = myIntList.iterator().next(); // 3'
注意变量myIntList的类型声明。它不是指定了一个Q意的ListQ而是指定了一个Integer对象的ListQ写作List<Integer>。我们说QList是一个拥有类型参敎ͼ在此处就是IntegerQ的泛型接口。当创徏q个List对象Ӟ我们也指定了一个类型参数?br />     再次注意Q原来行3的的强制cd转换已经不需要了?br />     现在你可能会x们所已经完成的就是移除了那个混ؕ(强制cd转换)。我们在?处就使Integer成ؓ一个类型参敎ͼ而不是在?处进行强制类型{换。这儿就有一个很大的不同。在~译Ӟ~译器就能够查程序中的类型是否正。当我们说myIntList在声明时使用了类型List<Integer>Q那么就是告诉我们myIntList变量在Q何时间和M地点所包含的类型必LIntegerQƈ且编译器会确保这一炏V相反地Q强制类型{换只是告诉我们在代码中的某个独立的地方程序员所期望的情况而以?br />     在实际情况下Q特别是在大型应用中Q泛型可以提高程序的可读性和鲁棒性?br />
2 定义单的泛型
    下面是java.util包中List和Iterator接口定义的简短摘要:
    public interface List <E>{
        void add(E x);
        Iterator<E> iterator();
    }

    public interface Iterator<E>{
        E next();
        boolean hasNext();
    }
除了角括号中的内容,我们对这D代码应该比较熟悉了。这些是List和Iterator接口的Ş式类型参数的声明?br />     cd参数的用可以诏I于整个泛型声明Q用在那些你以后想用普通类型的地方(但有一些重要的U束Q详?良好的打?一??br />     ?介绍"一节中Q我们知道了使用了泛型类型声明的List接口的调用方法,如List<Integer>。在q个调用(一般就是调用一个参数化的类?中,所有Ş式类型参?x处的E)出现的地斚w被实际的cd参数(x处的Integer)替换了?br />     你可能会惛_List<Integer>表示一U由Integerl一C替E之后的新的List版本Q?br />     public interface IntegerList {
        void add(Integer x);
        Iterator<Integer> iterator();
    }
q种直觉是有助益的,但那也是误解?br />     说它是有助益的,是因为参数类型List<Integer>实际上所使用的方法看h是像那U扩展?br />     说它是误解,是因为泛型的声明实没有用那U方式进行扩展。ƈ不存在那些代码的多个复本Q在源文件中、二q制文g中、硬盘中、内存中都没有这些复本。如果你是C++E序员,你将会发现这与C++ Template非常的不同?br />     泛型cd的声明绝对只会被~译一ơ,然后q入一个class文g中,像一个普通的cL接口声明一栗?br />     cd参数cM于方法或构造器中的普通参数。它非常像一个方法拥有一个Ş式值参敎ͼq个参数描述了可以出现在该处的值的cdQ泛型声明也有一个Ş式类型参数。当一个方法被调用Ӟ一个真实的的参C替换形式参数Q然后这个方法会q行评估。当一个泛型声明被调用Ӟ一个真实的cd参数也会替代形式cd参数?br />     需要注重一个命名规范。我们推荐你使用叫v来尽量精?如果可能的话Q最好是单个字母)的名字作为Ş式类型参数。最好避免用小写字母,q样可以很ҎC普通的cd接口中区分出形式cd参数。如上述例子中,很多容器cd使用E代表容器中的元素(element)?br />
3 泛型与子c?/span>
    让我们测试一下你Ҏ型的理解。下面的代码片断是合法的吗?
    List<String> ls = new ArrayList<String>(); // 1
    List<Object> lo = ls; // 2
    W一行肯定是合法的。这个问题狡猄部分是在W二行。这个问题可归结为:一个String对象的List也是Object对象的List吗?大部分h都会本能的回{到Q是的!
    那好Q来看看下面几行Q?br />     lo.add(new Object()); // 3
    String s = ls.get(0); // 4: 试图一个Object对象赋值给一个String变量Q?br />     此处我们已经别名化了ls和lo。通过别名lo讉KlsQ一个String对象的ListQ我们可以向其中插入L对象。但ls不能包含除String对象外的其它对象Q则当我们试图从中获得些什?Object对象)Ӟ我们会感到非常的惊讶?br />     译者:上面q段话的意思是_如果上述4行代码都成立的话Q那么就会我们感到很惊讶、很困惑。lo的类型是List<Object>Q那么可以放入Q意的Object到这个List中;而ls的类型是List<String>Q即只能攑օString对象。但lo引用的对象实际上是ArrayList<String>的对象,卛_能存放String对象Q所以上面的例子会人感到很困惑?br />     当然QJava~译器会Lq一切的发生--W二行将会导致一个编译时错误?br />     一般地Q如果Foo是Bar的子cd(子类或子接口)Q且G是某个泛型类型声明,那么G<Foo>q不是G<Bar>的子cd。这可能是当你学习泛型时所遇到的最困难的问题,因ؓq违反了我们Ҏ蒂固的直觉?br />     我们不能假设集成对象们不会改变。我们的直觉可能会导致我们静态地思考这些问题?br />     例如Q如果机动R理?Department of Motor Vehicles, DMV)向h口调查局(Census Bureau)提交了一l司机的名单Q这会被看成是合理的Q因为我们认为List<Driver>是List<Person>的子cd(假设Driver是Person的子cd)。实际上被提交的只是司机注册表的副本。否则,人口调查局也可以把那些不是司机的h也加入到q个名单 (List)中,q就会破坏DMV的记录?br />     Z应对q种情况Q有必要考虑更ؓҎ的泛型cd。我们到目前为止所看到的规则实在是太具限制性了?br />
4 通配W?/span>
    考虑q样一个问题,写一个程序打印出一个集合对象中的所有元素。下面的E序可能是你用老版Java语言所写的Q?br />     void printCollection(Collection c) {
        Iterator i = c.iterator();
        for (k = 0; k < c.size(); k++) {
            System.out.println(i.next());
        }
    }
    q儿有一个不成熟的对泛型应用的尝?q且使用了新的foreach循环语法)Q?br />     void printCollection(Collection<Object> c) {
        for (Object e : c) {
            System.out.println(e);
        }
    }
    q个问题是新版的程序ƈ不比旧版的程序更有用。反之,旧版的程序能够作为参数被Mcd的集合对象调用,新版的程序只能用于Collection<Object>Q而这U情况已l被我们证明了,它ƈ不是所有集合类型的类?br />     那么什么才是所有集合对象的类呢?它应该写作Collection<?>(叫作"collection of unknowQ未知的集合")Q这U集合类型的元素才可能配|Q何类型。很明显Q它被称作通配W类型。我们可以这样写Q?br />     void printCollection(Collection<?> c) {
        for (Object e : c) {
            System.out.println(e);
        }
    }
    然后我们可以用M集合cd来调用这个方法了。注意printCollectionҎ的内部,我们仍然可以从c中读取它的元素,q可这些元素赋值给Objectcd的变量?br />         Collection<?> c = new ArrayList<String>();
        c.add(new Object()); // Compile time error
    ׃不知道c中元素的cd是什么,我们不能向它里面d元素。addҎ接受cdE的参敎ͼ卌个集合对象元素的cd。当然实际的cd参数??"Ӟ它表C某个未知的cd。Q何我们要d入的参数都将不得不是未知cd的子cd。由于我们不知道q个cd是什么,所以我们不能传入Q何类型。唯一的例外是 "null"Qnull是每个类型的成员(译者:null是每U类型的子类型??br />     另一斚wQ给Z个List<?>Q我们就能调用getҎq用得到的l果。所得结果的cd是未知的Q但我们d以知道它是一?Object对象。因此将由getҎ得到的结果赋予一个Objectcd的变量,或是它作ؓ一个参C入一个期望获得Objectcd对象的地方,都是完全的?br />     有边界的通配W?/span>
    考虑q样的一个简单的l图E序Q它可以l制诸如矩Ş和环形之cȝ形状。ؓ了用程序来描述q些形状Q你可能是会下面那样定义一l类Q?br />     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) {
            ...
        }
    }
    q些cd以被l在一个画?canvas)上:
    public class Canvas {
        public void draw(Shape s) {
            s.draw(this);
        }
    }
    Ml制动作通常都会包含一lŞ状。假设用List来表C它们,那么为方便v见,Canvas需要有一个方法去l制所有的形状Q?br />     public void drawAll(List<Shape> shapes) {
        for (Shape s: shapes) {
            s.draw(this);
        }
    }
    现在Q规则要求drawAllҎ只能用于仅包含Shape对象的ListQ例如它不能用于List<Circle>。但不幸的是Q由于所有的Ҏ所做的只是从List中读取Shape对象Q所以它也需要能用于List<Circle>。我们所惌的就是这个方法能够接受所有的 Shapecd?br />     public void drawAll(List<? extends Shape> shapes) {
        ...
    }
    q儿是一个很但很重要的区别Q我们已l用List<? extends Shape>代替了List<Shape>。现在,drawAllҎ可以接受Shape的Q何子cd象的List了?br />     List<? extends Shape>是有边界的通配W的一个例子。问??)代表未知cdQ就如我们之前所看到的这个通配W一栗然而,在这个例子中Q我们这个未知类型实际上是Shapecȝ子类?注:它可以是Shapecd本nQ无需按字面上的意义一定说是Shape子类)?br />     一般地Q在使用通配W时要付Z些弹性方面的代h。这个代价就是,马上向该Ҏ体中写入Shapecd的对象是非法的。例如,下面的代码是不被允许的:
    public void addRectangle(List<? extends Shape> shapes) {
        shapes.add(0, new Rectangle()); // Compile-time error!
    }
    你应该会指出Z么上面的代码是不能被接受的。shaps.add的第二个参数?? extends Shape"--一个未知的Shape子类Q由于我们不知道它会是哪个ShapecdQ不知道它的类是否是RectangleQ它可能是,也可能不?Rectangle的超c,所以当传递一个Rectangle对象Qƈ不安全?br />    
有边界的通配W正是上一节中DMV向h口调查局提交数据的例子所需要的。我们的例子假设那些数据是由姓名(用字W串表示)Ch(用Person或其子类Q如 DriverQ的引用cd表示)的映表C。Map<K, V>是包含两个类型参数的例子Q这两个cd参数分别表示映射中的键与倹{?br />     再次注意形式cd参数的命名规?-K代表键,V代表倹{?br />     public class Census {
        public static void addRegistry(Map<String, ? extends Person> registry) {
    }
    ...

    Map<String, Driver> allDrivers = ... ;
    Census.addRegistry(allDrivers);

5 泛型Ҏ
    考虑写一个方法,它包含一个Object数据和一个集合对象,它的作用是将数组中的对象全部插入到集合对象中。下面是W一ơ尝试:
    static void fromArrayToCollection(Object[] a, Collection<?> c) {
        for (Object o : a) {
            c.add(o); // Compile time error
        }
    }
    到现在ؓ此,你要学会避免新手所犯的错误--试Collection<Object>作ؓq个集合的类型参数。你可能认识或没认识C?Collection<?>也不能完成工作。回忆一下,你不能将对象挤入一个未知类型的集合对象中?br />     处理q些问题的方法是使用泛型Ҏ。就像类型的声明一PҎ的声明也可以泛型?-卻I用一个或多个参数d数化q个Ҏ?br />     static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
        for (T o : a) {
            c.add(o); // Correct
        }
    }
    我们能够调用Lcd的集合对象中的方法,只要q个集合对象中的元素是数l类型中元素的超cd?br />     Object[] oa = new Object[100];
    Collection<Object> co = new ArrayList<Object>();
    fromArrayToCollection(oa, co); // T inferred to be Object

    String[] sa = new String[100];
    Collection<String> cs = new ArrayList<String>();
    fromArrayToCollection(sa, cs); // T inferred to be String
    fromArrayToCollection(sa, co); // T inferred to be Object

    Integer[] ia = new Integer[100];
    Float[] fa = new Float[100];
    Number[] na = new Number[100];
    Collection<Number> cn = new ArrayList<Number>();
    fromArrayToCollection(ia, cn); // T inferred to be Number
    fromArrayToCollection(fa, cn); // T inferred to be Number
    fromArrayToCollection(na, cn); // T inferred to be Number
    fromArrayToCollection(na, co); // T inferred to be Object

    fromArrayToCollection(na, cs); // compile-time error
    注意我们q不需要传递一个确切的cdl泛型方法。编译器会根据准的参数的类型帮我们推断出实际类型参数。编译器通常会推断出大部分的特定cd参数Q这׃得对Ҏ的调用是cd正确的?br />     产生了一个问题:什么时候我应该使用泛型ҎQ什么时候我应用使用通配W类型?Z理解{案Q让我们试一些集合框架类库中的方法:
    interface Collection<E> {
        public boolean containsAll(Collection<?> c);
        public boolean addAll(Collection<? extends E> c);
    }
    我们可能使用下面的泛型方法替换上面的E序Q?br />     interface Collection<E> {
        public <T> boolean containsAll(Collection<T> c);
        public <T extends E> boolean addAll(Collection<T> c);
        // Hey, type variables can have bounds too!
    }
    然而,在两个containAll和addAllҎ中,cd参数T只被使用了一ơ。返回类型既不依赖类型参敎ͼ也不需要传递其它的参数l这个方?在本例中Q只不过是一个实参Ş?。这告诉我们该实参用于多态;它的仅有的作用就是允许该Ҏ的多U不同的实参能够应用于不同的调用炏V?br />     泛型Ҏ允许cd参数用于描述一个或多个实参的类型对于该Ҏ?或它的返回g间依赖关pR如果没有这U依赖关p,那么׃应该使用泛型Ҏ?br />     一前一后的使用泛型Ҏ和通配W是可能的,下面的方法Collections.copy()pCq一点:    class Collections {
        public static <T> void copy(List<T> dest, List<? extends T> src) {
        ...
    }
    注意q两个参数的cd之间的依赖关pRQ何复制于源表scr的对象对于目标表dest中元素的cdT都必L可赋值的。所以src元素的类型肯定是T的Q何子cd--我们不用兛_q些。复制方法的{֐使用一个类型参数描qCq种依赖关系Q但通配W用于第二个参数中元素的cd?br />     我们也可以用另一U方法来书写q个Ҏ的签名,q种Ҏ完全不需要用通配W:
    class Collections {
        public static <T, S extends T>
               void copy(List<T> dest, List<S> src) {
        ...
    }
    q很好,但当W一个类型参数在cddest和第二个cd的限度中都用了ӞS它那本n只被使用了一ơ,是在src的类型中--没Q何其它的东西再依赖于它了。这是一个我们要以用通配W替换S的一个信受用通配W比昄的声明类型变量更加清晰、更加精,所以在M可能的时候通配W是首选?br />     通配W也有它的优点,它可以被用于Ҏ{֐的外面,以作为字D늚cdQ局部变量或数组。下面就是这L一个例子?br />     回到我们l制形状的那个例子,假设我们想维护一个绘制Ş状请求的历史记录。我们可以将q个历史记录l护在类Shape内部的一个静态变量,让drawAllҎ它自己获得的实?卌求绘制的形状)加入历史字段中?br />     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);
        }
    }
    最后,仍然让我们再ơ注意类型变量的命名规范。我们一般用T表示cdQ只要无需再区别Q何其它的特定cd。这U情늻常用于泛型方法中。如果有多个cd参数Q我可以使字母表中邻qT的其它字母,例如S。如果在一个泛型类中有一个泛型方法,那么Z避免hQ一个好的习惯是不要使泛型类和泛型方法有相同名字的类型参数。这也适用于嵌套泛型类?br />
6 与遗留代码交?/span>
    到现在ؓ止,我们的例子是假设处于一U理想的状况Q即每个人都在用JavaE序设计语言的支持泛型的最新版?br />     唉,但现实ƈ非如此。数以百万行计的代码是用Java语言的早期版本写的,而且也不可能在一夜之间就它们{换到新版中?br />     E后Q在"使用泛型转化遗留代码"q一节中Q我们将解决你的旧代码转换C用泛型这个问题。在本节Q我们将x一个简单的问题Q遗留代码与泛型代码之间如何交互Q这个问题含有两个部分:在泛型代码内部用遗留代码;在遗留代码内部用泛型代码?br />     作ؓ一个例子,假设你想使用包com.Fooblibar.widgets。分支Fooblibar.com*商用在一个资产管理系l中Q这个系l的_֍如下所C:
    package com.Fooblibar.widgets;

    public interface Part { ...}

    public class Inventory {
    /**
     * Adds a new Assembly to the inventory database.
     * The assembly is given the name name, and consists of a set
     * parts specified by parts. All elements of the collection parts
     * must support the Part interface.
    **/
        public static void addAssembly(String name, Collection parts) {...}
        public static Assembly getAssembly(String name) {...}
    }

    public interface Assembly {
        Collection getParts(); // Returns a collection of Parts
    }
现在Q你要添加一些新的代码ƈ使用上述API。比较好的是Q要保你一直能够用适当的实参去调用addAssemblyҎ--卻I你传入的集合对象必须是装有Part对象的集合对象。当Ӟ泛型最适合做这些了Q?br />     package com.mycompany.inventory;

    import com.Fooblibar.widgets.*;

    public class Blade implements Part {
        ...
    }

    public class Guillotine implements Part {
    }

    public class Main {
        public static void main(String[] args) {
            Collection<Part> c = new ArrayList<Part>();
            c.add(new Guillotine()) ;
            c.add(new Blade());
            Inventory.addAssembly("thingee", c);
            Collection<Part> k = Inventory.getAssembly("thingee").getParts();
        }
    }
当我们调用addAssemblyҎӞ该方法希望第二个参数的类型是Collection。该参数的实际类型是Collection< Part>。这是正的Q但是什么呢Q毕竟,大部分的Collection是不能包含Part对象的,因ؓ一般来_~译器无法知道该 Collection所表示的是哪种对象的集合对象?br />     在合适的泛型代码中,Collection一直跟随着一个类型参数。当一个像Collectionq样的泛型类型在被用时没有提供cd参数Q就被称之ؓ原生cd(Raw Type)?br />     大多Ch的每一直觉认ؓCollection是Collection<Object>。然而,按我们之前所说的Q在需要Collection<Object>的地方用Collection<Part>q不是安全的?br />     但请{等Q那也不对!x对getParts对象的调用,它要q回一个Collection对象(实际上是一个引用变?。然后这个对象被赋于变量kQk?Collection<Part>cd。如果调用该Ҏ而返回的l果是一个Collection<?>对象Q该赋值操作也生错误?br />     事实上,该赋值操作是合法的,它会生一个未查的警告。这个警告是必要的,因ؓ事实上编译器q不能保证它的正性。我们没办法?getAssemblyҎ中的遗留代码以保证返回的集合对象Part对象的集合。被用于该代码的cd是CollectionQ能够合法的向这U?Collection中插入Q何类型的对象?br />     那么q还应该是一个错误吗Q就理论上而言Q是的;但就实际上而言Q如果泛型代码是Z调用遗留代码Q那么就不得不允怺。对于你Q一个程序员Q会对这U情冉|到满意的Q赋值是安全的,因ؓgetAssermblyҎ的规则告诉我们它q回q回的是 Part对象的CollectionQ即使该Ҏ的签名ƈ没有表明q一炏V?br />     所以原生类型非常像通配W类型,但它们不会被做严格的cd查。这是经q深思熟虑之后的l果Q是Z允许泛型代码能够与之前已存在的代码交互用?br />     用泛型代码调用遗留代码是天生危险的;一旦你在泛型代码中混合了非泛型的遗留代码,那么泛型cdpȝ通常都无法提供完全的保证。然而,q仍然比你不使用泛型要好些。至你知道最l这些代码是一致的?br />     到那儿已经有了很多的非泛型代码Q然后又有了泛型代码的时候,那么无法避免的情况就是不得不混合它们?br />     如果你发C必须混合使用遗留代码和泛型代码,请密切注意未查的警告。要谨慎地思考你如何再才能证明那些被l出了危险警告的代码是安全的?br />     当你l箋犯错误,且代码造成的警告确实不是类型安全的Q什么事情将发生呢?让我们看看这L一U情c在q个处理q程中,我们观察编译器所做的事情?br />
擦除和翻?/span>
    public String loophole(Integer x) {
        List<String> ys = new LinkedList<String>();
        List xs = ys;
        xs.add(x); // Compile-time unchecked warning
        return ys.iterator().next();
    }
此处Q我们已l别名化了String的List和一个普通的老版的List。我们向q个List xs插入一个Integer对象Qƈ试图抽取一个String对象。这昄是错的。如果我们忽略警告ƈ试执行q段代码Q它在我们试图使用错误cd的地方上p|?br />     public String loophole(Integer x) {
        List ys = new LinkedList;
        List xs = ys;
        xs.add(x);
        return(String) ys.iterator().next(); // run time error
    }
当我们从q个List中抽取一个元素,q试囑ְ它当作String对象而把它{换成StringӞ我们得C个ClassCastException的异常。完全相同的情况也发生在了loopholeҎ的泛型版中?br />     q种情况的原因就是泛型是由Java~译器作ZU叫?擦除(Erasure)"的最前到后的机制实现的。你(几乎)可以把它惛_ZU?源代码对源代?(source-to-source)的翻译,q就是ؓ何loophole的泛型版被{换成了非泛型版了?br />     l果QJava虚拟机的cd安全和完整性再也不处于危险中了Q甚臛_遇到到未查的警告时也一栗?br />     基本圎ͼErasure去除(或者说"擦除")了所有的泛型信息。所有的在角括号中的cd信息都被抛弃了,所以,如像List<String> q样的参数化cd被{化成了List。所有保持对cd变量使用的地斚w被类型变量的高层限度cd(一般就是Object)替换了。ƈ且,无论何时产生的结果都不是cd正确的,一个向适当的类型的强制cd转换被插入了其中?br />     对Erasure的全部细节的描述出了本教程的范_但我们给出的单描q离真实情况q不太远。了解一些这斚w的知识是有益的,特别是如果你惛_一些更加老练的泛型应用,如把已有的API转换C用泛型时(详见"使用泛型转化遗留代码")Q或者只是想理解Z么它们会是这U情c?br />
    在遗留代码中使用泛型代码
    现在让我们思考一个颠倒的例子。想像Foolibar.com选择泛型去{化了它们的APIQ但他们的一些客LE序q没有{化。所以这些代码看h像:
    package com.Fooblibar.widgets;

    public interface Part {
        ...
    }

    public class Inventory {
    /**
     * Adds a new Assembly to the inventory database.
     * The assembly is given the name name, and consists of a set
     * parts specified by parts. All elements of the collection parts
     * must support the Part interface.
    **/
        public static void addAssembly(String name, Collection<Part> parts) {...}
        public static Assembly getAssembly(String name) {...}
    }

    public interface Assembly {
        Collection<Part> getParts(); // Returns a collection of Parts
    }
客户端程序看h像:
    package com.mycompany.inventory;

    import com.Fooblibar.widgets.*;

    public class Blade implements Part {
    ...
    }

    public class Guillotine implements Part {
    }

    public class Main {
        public static void main(String[] args) {
            Collection c = new ArrayList();
            c.add(new Guillotine()) ;
            c.add(new Blade());
            Inventory.addAssembly("thingee", c); // 1: unchecked warning}
            Collection k = Inventory.getAssembly("thingee").getParts();
        }
    }
    q些客户端代码是在泛型生之前写成的Q但它用了包com.Fooblibar.widgets和集合框架类库,q两者都在用泛型。客L中对泛型cd的用得它们成Z原生(Raw Type)cd?br />     代码?产生了一个未查的警告Q因Z个原生Collection被传入了一个期望是Collection<Part>出现的地方,而且~译器无法保证这个原生Collection真的是Part对象的Collection?br />     作ؓ一U可选的ҎQ你可以这些代码作为Java 1.4的源代码q行~译Q这p保证不会出现警告。但q样的话Q你不能用到JDK 5.0中Q何新的语aҎ?br />     --------------------------------------------------------------------------
    注意Q?Fooblibar.com"是一个纯属虚构的公司Q目的仅仅只是ؓ了本文中的例子。Q何公司或机构、Q何健在或已故的个Z此有关的话,U属巧合?br />     译者:看来老外做事情十分}慎,对于q种"问?我们又怎么会如此郑重其事的发表一个声明呢?br />
7 良好的打?/span>
    一个泛型类被它的所有应用共?br />     下面的代码片断是打印Z么呢Q?br />     List <String> l1 = new ArrayList<String>();
    List<Integer> l2 = new ArrayList<Integer>();
    System.out.println(l1.getClass() == l2.getClass());
    你可能会被引诱得说是falseQ但你错了。打印的是trueQ因Z个泛型类的所有实际拥有相同的q行时类Q而不它们具体的cd参数?br />     实Q对一个类的泛型所做的事实是q个泛型cd它所有可能的cd参数都有相同的行为;相同的这个类可以被视为它有很多不同的cd?br />     同样的结果,泛型cM的静态变量和Ҏ也被该类的所有实例共享。这是Z么在一个静态方法或初始化器中、在一个静态变量的声明或初始化器中引用cd变量是非法的?br />
    Cast和Instanceof
    一个泛型类被它的所有实例共享的另一个隐含意义就是,如果某个实例是这个泛型类的一U特定类型的实例Q那么通常情况下请求这个类的实例是无意义的Q?br />     Collection cs = new ArrayList<String>();
    if (cs instanceof Collection<String>) { ...} // Illegal.
    cM圎ͼ如下面这个强制类型{?br />     Collection<String> cstr = (Collection<String>) cs; // Unchecked warning,
    会报一个未查的警告Q因不应该是q行时系l将要ؓ你检查的事情?br />     对类型变量也是如?br />     <T> T badCast(T t, Object o) {return (T) o; // Unchecked warning.
    }
    cd变量在运行时q不存在。这意味着在时间和I间上,它们都不可能避免地无法生作用。不q的是,q也意味着你不能可靠地在强制类型{换中使用它们?br />
    数组
    一个数l对象中元素的类型不会是一个类型变量或参数化的cdQ除非它是一?非受限的)通配W类型。你可以声明数组cd的元素类型是一个类型变量或参数化的cdQ但数组对象本n不行?br />     q很烦hQ但却是真的。该U束寚w免如下例子中的情冉|有必要的Q?br />     List<String>[] lsa = new List<String>[10]; // Not really allowed.
    Object o = lsa;
    Object[] oa = (Object[]) o;
    List<Integer> li = new ArrayList<Integer>();
    li.add(new Integer(3));
    oa[1] = li; // Unsound, but passes run time store check
    String s = lsa[1].get(0); // Run-time error: ClassCastException.
如果允许有参数化cd的数l,上面的例子将会通过~译且不报Q何未查的警告Q然而会在运行时p|。我们已l知道设计泛型的主要目的是Zcd安全。特别地_Java语言被设计ؓQ如果你的整个程序用javac -source 1.5q行~译时没有报M未检查的警告Q那么这个程序就是类型安全的?br />     然而,你仍然可以用通配W数l。这儿有上面代码的两个变U。第一个变U放弃用参数化cd的数l对象和参数化类型元素。这h们ؓ了在数组外得到String对象不得不在昄C用强制类型{换?br />     List<?>[] lsa = new List<?>[10]; // OK, array of unbounded wildcard type.
    Object o = lsa;
    Object[] oa = (Object[]) o;
    List<Integer> li = new ArrayList<Integer>();
    li.add(new Integer(3));
    oa[1] = li; // Correct.
    String s = (String) lsa[1].get(0); // Run time error, but cast is explicit.
在第二个变种中,我们限制了数l对象的创徏Q这个数l的元素的类型被参数化了Q但仍然要将一个参数化的元素类型用于这个数l。这是合法的Q但产生一个未查的警告。确实,q段代码是不安全的,甚至会导致一个错误?br />     List<String>[] lsa = new List<?>[10]; // Unchecked warning. This is unsafe!
    Object o = lsa;
    Object[] oa = (Object[]) o;
    List<Integer> li = new ArrayList<Integer>();
    li.add(new Integer(3));
    oa[1] = li; // Correct.
    String s = lsa[1].get(0); // Run time error, but we were warned.
译者:Ҏ我的试(JDK 1.5.0_11)Q?List<String>[] lsa = new List<?>[10]"q一句无法通过~译Q理׃很直?cd不匹配,不能List<?>[]转化为List< String>[]"?br />     cM圎ͼ试图创徏一个元素类型是cd变量的数l对象会D一个运行时错误Q?br />     <T> T[] makeArray(T t) {
        return new T[100]; // Error.
    }
    因ؓcd变量在运行时q不存在Q这没有办法确定数l的实际cd?br />     围绕着q些限制的工作方法是使用了将cd面量当作q行时类型标记的机制Q该机制在下一?cd面量作ؓq行时标?中进行叙q?br />
8 cd面量作ؓq行时标?/span>
    JDK 5.0的变量之一是java.lang.Class也被泛型化了。这是一个不容器c而在其它地方使用泛型机制的有例子?br />     既然ClasscL一个类型参数TQ你可能会问Q这个T代表什么?它代表这个Class对象表示的类型?br />     例如QString.class的类型是Class<String>Q而Serializable.class的类型就是Class<Serializable>。这U机制用于提高在你的反射E序中的cd安全性?br />     特别圎ͼ׃ClasscM的方法netInstance现在是返回一个TQ这样当你在使用反射机制创徏对象时能够得到更加精的cd?br />     例如Q假设你需要一个执行数据库查询的工h法,l入的是SQL字符Ԍq回的是数据库中匚w该查询语a的对象的集合?br />     一U方法就是显C地传入一个工厂对象中Q所写的代码像Q?br />     interface Factory<T> { T make();}

    public <T> Collection<T> select(Factory<T> factory, String statement) {
        Collection<T> result = new ArrayList<T>();
        /* Run sql query using jdbc */
        for (/* Iterate over jdbc results. */) {
            T item = factory.make();
            /* Use reflection and set all of item's fields from sql results. */
            result.add(item);
        }
        return result;
    }
你可以像下面那么样去调用
    select(new Factory<EmpInfo>(){ public EmpInfo make() {
                                   return new EmpInfo();
                                   }}
          , "selection string");
你也可以声明一个EmpInfoFactorycd支持Factory接口
    class EmpInfoFactory implements Factory<EmpInfo> {
        ...
        public EmpInfo make() { return new EmpInfo();}
    }
然后像下面那样去调用?br />     select(getMyEmpInfoFactory(), "selection string");
q个解决Ҏ最l还需要:
    * 在调用点使用冗长的匿名工厂类Q?br />     * 或者,为每个被使用的类型声明一个工厂类Qƈ这个工厂类的实例传递到调用点,但这U方法有点不自然?br /> 可以很自然地类字面量用作工厂对象,q个工厂E后可被反射机制使用。现在这个程?不用泛型)可以写ؓQ?br />     Collection emps = sqlUtility.select(EmpInfo.class, "select * from emps");
    ...
    public static Collection select(Class c, String sqlStatement) {
        Collection result = new ArrayList();
        /* Run sql query using jdbc. */
        for (/* Iterate over jdbc results. */ ) {
            Object item = c.newInstance();
            /* Use reflection and set all of item's fields from sql results. */
            result.add(item);
        }
        return result;
    }
可是Q这不能l我们一个所期望的精类型的集合。既然Class是泛型的Q我们可以用下面的代替写法Q?br />     Collection<EmpInfo> emps =
                          sqlUtility.select(EmpInfo.class, "select * from emps");
    ...
    public static <T> Collection<T> select(Class<T> c, String sqlStatement) {
        Collection<T> result = new ArrayList<T>();
        /* Run sql query using jdbc. */
        for (/* Iterate over jdbc results. */ ) {
            T item = c.newInstance();
            /* Use reflection and set all of item's fields from sql results. */
            result.add(item);
        }
        return result;
    }
上面的程序以一U类型安全的Ҏl了我们_cd的集合?br />     类字面量作行时标记的技术被认ؓ十分狡猾。例如,Z操作AnnotationQ这U技术在新API中被扩展使用了?br />
9 通配W的更多味
    在本节,我们考虑一些更高的通配W用法。我们已l看了几个受限的通配W用于读取数据结构时例子。现在反q来x一个只可写的数据结构。接口Sink是这U类型的一个简单的例子Q?br />     interface Sink<T> {
        flush(T t);
    }
我们可以惛_它作ؓ一个范例用于下面的代码。方法writeAll被设计ؓh集合coll中的所有元素到Sink的实例snk中,q返回最后一个被h的元素?br />     public static <T> T writeAll(Collection<T> coll, Sink<T> snk) {
        T last;
        for (T t : coll) {
            last = t;
            snk.flush(last);
        }
        return last;
    }
    ...
    Sink<Object> s;
    Collection<String> cs;
    String str = writeAll(cs, s); // Illegal call.
已l写出来的,对writeAllҎ的调用是非法的,׃无法推断出有效的cd实参QString或Object都不是T的合适类型,因ؓCollection的元素和Sink必须是相同的cd?br />     我们可以通过修改writeAll的方法签名来修正q个错误Q如下所C,使用了通配W:
    public static <T> T writeAll(Collection<? extends T>, Sink<T>) {...}
    ...
    String str = writeAll(cs, s); // Call is OK, but wrong return type.
该调用是合法的,但赋值是错的Q是׃q回cd被推断成了ObjectQ因为T匚ws的类型,但s的类型是Object?br />     该解x案用了一U我们尚未见q的受限通配WŞ式:有一个较低限度的通配W。语?? super T"表示未知cd是T的超cd(或者是T本nQ记住,类型关pLҎ的)?br />     public static <T> T writeAll(Collection<T> coll, Sink<? super T> snk) {
        ...
    }
    String str = writeAll(cs, s); // Yes!
使用了这U语法,Ҏ的调用就是合法的Qƈ且被推断的类型正如所愿是String?br />     现在让我们{向更为实际的例子。java.util.TreeSet<E>表示了一个排序了的以cd为E的对象作为元素的树。构造一?TreeSet对象的方法之一是传递一个Comparator对象l这个构造器。该Comparator对象被用于Ҏ期望的规则对TreeSet中的元素q行排序?br />     TreeSet(Comparator<E> c)
    Comparator接口是必ȝQ?br />     interface Comparator<T> {
        int compare(T fst, T snd);
    }
假设我们惛_Z个TreeSet<String>对象Qƈ传入一个合适的比较器对象。我们就需要一个能比较String?Comparator对象Q一个Comparator<String>可以做刎ͼ但一个Comparator<Object> 对象也能做到。然而,我们不能调用上面Comparator<Object>所提供的构造器?br />     TreeSet(Comparator<? super E> c)
上述代码允许适用的比较器被用?br />     作ؓ最后一个低位受限通配W的例子Q让我们看看Collections.maxҎQ该Ҏq回一个集合中的极大元素。ؓ了让max文g能够工作Q集合中所有的传入该集合的元素都必dCComparable接口。此外,它们怺之间必须是可被比较的?br />     在第一ơ尝试创个方法后有如下结果:
    public static <T extends Comparable<T>>
            T max(Collection<T> coll)
卻Iq个Ҏ有一个某cdT的集合对象,T的实例之间可以进行比较,该方法ƈq回一个该cd的元素。然而,q个E序实现h太受限制了。看看是Z么,考虑一个对象,它能与Q意对象进行比较:
    class Foo implements Comparable<Object> {
        ...
    }
    Collection<Foo> cf = ... ;
    Collections.max(cf); // Should work.
Collection cf中的每个元素都能与该集合中的其它元素q行比较Q因为每个这L元素都是一个Foo的实例,而Foo的实例能够与L对象q行比较Q则与另一个Foo 对象比较那就更没问题了。然而,使用前面的方法签名,我们可以发现上面Ҏ法max的调用会被拒l。被推断出的cd必须是FooQ但Fooq没有实?Comparable<Foo>?br />     没有必要_地要求T与它自己的实例进行比较。所有被要求的是T的实例能够与它的某个类型的实例q行比较。这p我们有了如下代码Q?br />     public static <T extends Comparable<? super T>>
            T max(Collection<T> coll)
注意到Collections.max真实的方法签名更难以理解。我们将在下一?遗留代码{化到使用泛型"中再讲述它。这个适用于几乎Q何一?Comprarable应用的理论是打算能用于Q意的cdQ你L想用Comprarable<? super T>?br />     一般地Q如果你的API只是类型参数T作ؓcd变量使用Q那应该利于低位受限通配W?? super T)。相反地Q如果这个API只需q回TQ你p使用高位受限通配W?? extends T)以给q个API的客LE序更大的灵zL?br />
通配W捕?br />     到目前ؓ此,下面的程序应该更清晰些:
    Set<?> unknownSet = new HashSet<String>();
    ...
    /** Add an element  t to a Set s. */
    public static <T> void addToSet(Set<T> s, T t) {
        ...
    }
但下面的调用是非法的?br />     addToSet(unknownSet, "abc"); // Illegal.
传入该方法的一个精的Set是一个String的Setq没有媄响;问题在于作ؓ实参传入表达式的是一个未知类型的SetQ这q不能保证它一定就是String或其它Q何特定类型的Set?br />     现在考虑下面的代码:
    class Collections {
        ...
        <T> public static Set<T> unmodifiableSet(Set<T> set) {
            ...
        }
    }
    ...
    Set<?> s = Collections.unmodifiableSet(unknownSet); // This works! Why?
看v来它应该不被允许Q然而,看看q个Ҏ的调用,它确实是安全的而可以允许这么做。毕竟,unmodifiableSetҎ可用于Q何类型的SetQ而不这个Set中的元素的类型?br />     因ؓq种情况发生地相Ҏ较频J,所以有一个特D的规则允许q些在一个非常特D的环境中的代码是合法的Q在q个环境中这些代码被证明是安全的。这个名?通配W捕?的规则允许编译器通配W的未知cd作ؓcd实参推断到泛型方法中?br />
10 遗留代码{化ؓ使用泛型
    早先Q我们展CZ新、老代码之间如何交互。现在是时候看?泛型?老代码这个困隄问题了?br />     如果你决定将老代码{换成使用泛型Q你需要仔l考虑如何M改你的API?br />     你需要确定泛型化的API不会造成q度的限Ӟ它必能l箋地支持API原先的功能。再ơ考虑一些来自于java.util.Collection中的例子。没有用泛型的API看v来像Q?br />     interface Collection {
        public boolean containsAll(Collection c);
        public boolean addAll(Collection c);
    }
一U自然的泛型化尝试可能像下面那样Q?br />     interface Collection<E> {

        public boolean containsAll(Collection<E> c);
        public boolean addAll(Collection<E> c);
    }
肯定是类型安全的了,但它q没有实现该API之前的功能。containsAllҎ用于M引入的集合对象,如果引入的集合真C包含E的实例时Q该Ҏ才会成功。但是:
    * 引入集合的静态类型可能有所不同Q或许是因ؓ调用者不知道传入的集合对象的准确cdQ或者可能是因ؓ它是一个Collection<S>Q而S是E的子cd?br />     * 能够合法C用一个不同的cd的集合调用containsAllҎ则最为理想了。这U方法应该能工作Qƈ返回false?br />     在这个例子中的addAllҎQ我们应该能够加入由M由E的子cd的实例组成的集合对象。我们在"泛型Ҏ"q一节中已经看过了如何正地处理此类情况?br />     你也需要保证修改后的API要保持与老的客户端程序的二进制兼Ҏ。这暗C着"擦除"后的API必须与以前的非泛型化API相同。在大部分例子中Q这自然会引用争吵,但也有一些精妙的例子。我们将试我们已经遇到q的最_֦例子中的一个,即Collections.max()Ҏ。根据我们在"通配W的更多乐趣"一节所看到的,一个模p的maxҎ{֐是:
    public static <T extends Comparable<? super T>>
            T max(Collection<T> coll)
除了擦除后的{֐之外Q这些都很好Q?br />     public static Comparable max(Collection coll)
q与max之前的方法签名不同:
    public static Object max(Collection coll)
当然可以q样指定maxҎ的签名,但这没有什么用。所有老的调用Collections.maxҎ的二q制class文g都依赖于q回cd为Object的方法的{֐?br />     通过昄地在限度中ؓ形式cd参数T指定一个超c,我们能够强制q个擦除产生不同的结果?br />     public static <T extends Object & Comparable<? super T>>
            T max(Collection<T> coll)
q是一个单个类型参数有多个限度的例子,使用语法"T1 & T2 ... & Tn"。有多个限度的类型变量是被认为是限度中所有类型的一个子cd。当使用多限度时Q限度中W一个被提及的类型将作ؓ该类型变量被擦除后的cd?br />     最后,我们应该回想到maxҎ只需从输入的Collection中进行读取操作,所以这适合于T的Q何子cd的集合?br />     q就把我们带入到JDK中该Ҏ的真实签名中Q?br />     public static <T extends Object & Comparable<? super T>>
            T max(Collection<? extends T> coll)
在实践中产生如此晦ӆ的应用是十分|见的,但是当{换现有APIӞ专家型的cd设计者们应该要准备着去进行非常细致地地思考?br />     另一个问题需要密切关注的是"协变q回"Q即在一个子cd中精gq回cd。你不需要在老的API中用这个特性。ؓ了找到原因,让我们看一个例子?br />     假设你原先的API是如下Ş式:
    public class Foo {
        public Foo create() {
            ...
        } // Factory. Should create an instance of whatever class it is declared in.
    }

    public class Bar extends Foo {
        public Foo create() {
            ...
        } // Actually creates a Bar.
    }
Z利用"协变q回"Q你它修改为:
    public class Foo {
        public Foo create() {
            ...
        } // Factory. Should create an instance of whatever class it is declared in.
    }

    public class Bar extends Foo {
        public Bar create() {
            ...
        } // Actually creates a Bar.
    }
现在假设有一个像下面那样写的你代码的W三方客LE序Q?br />     public class Baz extends Bar {
        public Foo create() {
            ...
        } // Actually creates a Baz.
    }
    Java 虚拟Z直接支持有着不同q回cd的方法的覆盖Q该Ҏ由~译器支持。因此,除非Bazc被重新~译Q否则它不能正常地覆盖Bar的createҎ。另外,Baz不得不被修改,因ؓq些代码如前面所写的那样被拒l?-Baz中的createҎq回cdq不是Bar中createҎq回cd的子cd?br />     译者:Ҏ我的试(JDK 1.5.0_11)QBazcM的createҎ无法通过~译Q理由就是Baz.createҎ与Bar.createҎ的返回不兼容Q返回类型须是BarQ而不是Foo?br />
致谢
    Erik Ernst, Christian Plesner Hansen, Jeff Norton, Mads Torgersen, Peter von der Ahe和Philip Wadler教程提供了材料?br />     感谢David Biesack, Bruce Chapman, David Flanagan, Neal Gafter, Orjan Petersson, Scott Seligman, Yoshiki Shibata和Kresten Krab Thorup教程的早期版本所提出的富有h值的反馈。向我忘记列出来的每个h道歉?/span>

Sha Jiang 2007-06-20 11:11 发表评论
]]>
վ֩ģ壺 | Ƹ| ϰˮ| | | | | | | | ຣʡ| | | | Ȫ| ˫Ѽɽ| ʻ| | Ͼ| ͩ| ԫ| ̫| ɽ| | | ̶| | ɽ| | | ̺| | ²| | | | ƽɽ| | | Զ| |