Java语言基础特性—第一部分(下)

你可以通过指定extends后接类型名称来提供通配符的上界。同样的,你可以通过指定super后接类型名称来提供通配符的下界。这些限定限制了可以作为实际类型参数传入的类型。

在例子中,你可以把? extends String理解为任何String或其子类的实际类型参数。同样的,你可以把? super String理解为任何String或其父类的实际类型参数。因为String是final的,这意味着它不能被继承,只有源列表为String对象,目标列表为String或Object对象能够传入作为参数,这样用处不大。

你可以使用泛型方法来完全解决这个问题,它是一个有类型实现参数的类或接口方法。泛型方法支持下面的语法:

    <formalTypeParameterList> returnType
    identifier(parameterList)

泛型方法的形参列表在它的返回类型之前。它包含类型参数和可选的上界。类型参数可以作为返回类型使用,并且可以出现在参数列表中。

清单9展示了怎么定义和调用泛型copy()方法

Listing 9. GenDemo.java (version 5)

    import java.util.ArrayList;
    import java.util.List;
    
    public class GenDemo
    {
       public static void main(String[] args)
       {
          List<Integer> grades = new ArrayList<Integer>();
          Integer[] gradeValues =
          {
             new Integer(96),
             new Integer(95),
             new Integer(27),
             new Integer(100),
             new Integer(43),
             new Integer(68)
          };
          for (int i = 0; i < gradeValues.length; i++)
             grades.add(gradeValues[i]);
          List<Integer> failedGrades = new ArrayList<Integer>();
          copy(grades, failedGrades, new Filter<Integer>()
                                     {
                                        public boolean accept(Integer grade)
                                        {
                                           return grade.intValue() <= 50;
                                        }
                                     });
          for (int i = 0; i < failedGrades.size(); i++)
             System.out.println(failedGrades.get(i));
       }
    
       static <T> void copy(List<T> src, List<T> dest,
    Filter<T> filter)
       {
          for (int i = 0; i < src.size(); i++)
             if (filter.accept(src.get(i)))
                dest.add(src.get(i));
       }
    }
    
    interface Filter<T>
    {
       boolean accept(T o);
    }

清单9中我定义了一个<T> void copy(List<T> src, List<T> dest, Filter<T> filter)泛型方法。编译器注意到srcdestfilter参数的类型都包含类型参数T。这意味着在方法调用中必须传入同样的实际类型参数,而编译器会在调用中获取参数。

如果你编译清单9(javac GenDemo.java)并运行程序(java GenDemo),你应该可以看到下面的输出:

    27
    43

Java语言中关于泛型最有争议的是什么?

虽然泛型本身并不具争议,但它在Java语言中的特殊实现却是。泛型是作为消除转换的语法糖的编译时特性来实现的。编译器会在编译源码后丢弃泛型类型或泛型的形参类型列表。这个“丢弃”行为称为擦除(erasure)。其他在泛型中关于擦除的例子包含:在代码类型不正确时,插入时可以自动转换为合适的类型;通过上界(例如Object)来替换类型参数。

更多关于泛型的讨论

泛型不只因为擦除而备受争议。看一下StackOverflow.com的“为什么我们抱怨Java关于泛型的实现很糟糕”主题的讨论,包含了通配符很难理解和事实上泛型并不直接值类型(例如,你不能指定List<int>)。

使用擦除会有下面的几个限制:

  • instanceof并不能用于参数化类型,只有一种情况是例外的。这个例外就是无界的通配符。例如,你不能指定Set<Shape> shapes = null; if (shapes instanceof ArrayList<Shape>){}。相反,你需要把对instanceof表达式修改为shapes instanceof ArrayList<?>,这种就是无界的通配符。或者,你可以指定shapes instanceof ArrayList,这使用的是原生类型(通常也是推荐使用的做法)。

  • 编译器把泛型代码转换为非泛型代码,并保存在class文件中。一些开发人员指出擦除会使得你不能通过反射取得泛型信息,因为它们并不保存在class文件中。开发人员Jakob Jenkov在“Java 反射:泛型”中指出一些泛型信息会被保存在class文件中的情况,并且这些信息可以通过反射来访问。

  • 你不能在创建数组的表达式中使用类型参数;例如,elements = new E[size];。如果你这样做,编译器会报告泛型数组创建错误信息。

鉴于擦除的限制,你会奇怪为什么泛型要通过擦除来实现。原因很简单:Java编译器被重构来使用擦除,因此泛型代码可以跟那些非泛型的遗留代码进行交互。没有这个向后兼容性,遗留代码在支持泛型的Java编译器上编译时将会报错。

第一部分总结

Java语言已经添加了许多新特性。在这篇文章中,我展示了怎么使用断言来增强你在代码正确性上的信心,和如何使用泛型来消除ClassCastException。通过使用断言和泛型,你可以编写更可靠的代码,并且使你的代码在运行时的错误降到最低,当然,也减少面对生气的客户时的头痛了。

Java 5 是Java平台历史上的一个重大发布,虽然泛型比其他特性都更具争议,但它却比其他都更加重要。我的下篇文章将会介绍另外7个在Java5时加入的必要的特性:类型安全的枚举,注解,自动装箱和拆箱,加强的循环,静态引入,可变参数,协变返回类型。在那之前,下载这篇文章的源代码,它包含了更多的关于断言和泛型的提示和例子。

Jeff Friesen是一个自由职业导师和侧重Java和Android的软件开发人员。除了为Apress写Java和Android书籍,Jeff为JavaWorld,informITJava.netDevSourceSitePoint写了大量的关于Java和其他技术的文章。你可以通过他在TutorTutor.ca的网站联系到他

了解更多主题相关

下载文章的源代码
阅读Angelika Langer的Java Generics FAQs,那里有着大量的关于Java语言泛型的信息和观点。

对于想学习Java语言和它的备受争议的特性的,Langer的文章理解闭包的争论(2008.6 JavaWorld)对比了Java 7语言中的三个关于添加闭包和lambda表达式的初始提议。

可以查看“Java反射:泛型”(Jakob Jenkov, Jenkov.com)关于泛型反射和某些情况下可以在运行时获取泛型信息的讨论。
Java无痛并发编程,第一部(2013.6):介绍了Executor框架,同步类型和Java并发集合包。

Java无痛并发编程,第二部(2013.8):介绍了锁,原子变量和fork/join操作,还附加了Java8中关于java.util.concurrent的修改概述。
跟上Java Date和Time API(2013.4):介绍了Java8的JSR310:Date 和Time API,并且展示了你最有可能使用的java.time系列类的使用。
JavaWorld中更多关于Java集合框架的文章:
Java集合框架从零开始(1998.11 Dan Becker):这篇文章介绍了集合刚引入java时的历史。
Java集合中的省时习惯(2013.9 Java Q&A blog):展望未来,Jeff Friesen回答一些当前使用Java集合的常见问题。

原文链接: javaworld 翻译: ImportNew.com - 陈 晓舜
译文链接: http://www.importnew.com/11317.html
[ 转载请保留原文出处、译者和译文链接。]

关于作者: 陈 晓舜

(新浪微博:@shun记忆

查看陈 晓舜的更多文章 >>



可能感兴趣的文章

发表评论

Comment form

(*) 表示必填项

2 条评论

  1. 莫测 说道:

    “一些开发人员指出擦除会使得你不能通过反射取得泛型信息,因为它们并不保存在class文件中。” – - – 对此表示疑问

    Thumb up 0 Thumb down 0

跳到底部
返回顶部