网站公告列表

  没有公告

加入收藏
设为首页
联系站长
您现在的位置: 网络学院 >> 程序设计 >> Java编程 >> 文章正文
  Java 1.5中的泛型简介(二)            【字体:
Java 1.5中的泛型简介(二)
作者:佚名    文章来源:不详    点击数:    更新时间:2007-9-2    
 
教程摘自IBM DW网站,如有转载,请声明!
编写基本的容器类
此时,您可以开始编写简单的泛型类了。
正在装载数据……
到目前为止,泛型类最常见的用例是容器类(比如集合框架)或者值持有者类(比如
WeakReferenceThreadLocal)。我们来编写一个类,它类似于List,充当一个容器。其中,我们使用泛型来表示这样一个约束,即Lhist的所有元素将具有相同类型。为了实现起来简单,Lhist使用一个固定大小的数组来保存值,并且不接受 null 值。
Lhist类将具有一个类型参数V(该参数是Lhist中的值的类型),并将具有以下方法:
 
public class Lhist<V> {
  public Lhist(int capacity) { ... }
 public int size() { ... }
 public void add(V value) { ... }
 public void remove(V value) { ... }
 public V get(int index) { ... }
}
 
要实例化Lhist,只要在声明时指定类型参数和想要的容量:
 
Lhist<String> stringList = new Lhist<String>(10);
实现构造函数
在实现Lhist类时,您将会遇到的第一个拦路石是实现构造函数。您可能会像下面这样实现它:
 
public class Lhist<V> {
  private V[] array;
 public Lhist(int capacity) {
    array = new V[capacity]; // illegal
 }
}
 
这似乎是分配后备数组最自然的一种方式,但是不幸的是,您不能这样做。具体原因很复杂,当学习到底层细节 一节中的“擦除”主题时,您就会明白。分配后备数组的实现方式很古怪且违反直觉。下面是构造函数的一种可能的实现(该实现使用集合类所采用的方法):
 
public class Lhist<V> {
  private V[] array;
 public Lhist(int capacity) {
    array = (V[]) new Object[capacity];
 }
}
 
另外,也可以使用反射来实例化数组。但是这样做需要给构造函数传递一个附加的参数——一个类常量,比如Foo.class。后面在Class<T> 一节中将讨论类常量。
实现方法
实现Lhist的方法要容易得多。下面是Lhist类的完整实现:
 
public class Lhist<V> {
    private V[] array;
    private int size;
 
    public Lhist(int capacity) {
        array = (V[]) new Object[capacity];
    }
 
    public void add(V value) {
        if (size == array.length)
            throw new IndexOutOfBoundsException(Integer.toString(size));
        else if (value == null)
            throw new NullPointerException();
        array[size++] = value;
    }
 
    public void remove(V value) {
        int removalCount = 0;
        for (int i=0; i<size; i++) {
            if (array[i].equals(value))
                ++removalCount;
            else if (removalCount > 0) {
                array[i-removalCount] = array[i];
                array[i] = null;
            }
        }
        size -= removalCount;
    }
 
    public int size() { return size; }
 
    public V get(int i) {
        if (i >= size)
            throw new IndexOutOfBoundsException(Integer.toString(i));
        return array[i];
    }
}
 
注意,您在将会接受或返回V的方法中使用了形式类型参数V,但是您一点也不知道V具有什么样的方法或域,因为这些对泛型代码是不可知的。
使用Lhist
使用Lhist类很容易。要定义一个整数Lhist,只需要在声明和构造函数中为类型参数提供一个实际值即可:
 
Lhist<Integer> li = new Lhist<Integer>(30);
 
编译器知道,li.get()返回的任何值都将是Integer类型,并且它还强制传递给li.add()li.remove()的任何东西都是Integer。除了实现构造函数的方式很古怪之外,您不需要做任何十分特殊的事情以使Lhist是一个泛型类。
示例代码:
package com.ibm.course.generics;
 
publicclass Lhist<V> {
 
                   privateintsize;
 
                   private V[] array;
 
                   public Lhist(int capacity) {
                                      // The followint initialization method is wrong.
                                      // array = new V[this.capacity];
                                      array = (V[]) new Object[capacity];
                   }
 
                   publicvoid add(V value) {
                                      if (size == array.length)
                                                         thrownew IndexOutOfBoundsException(Integer.toString(size));
                                      elseif (value == null)
                                                         thrownew NullPointerException();
                                      array[size++] = value;
                   }
 
                   publicvoid remove(V value) {
                                      int removalCount = 0;
                                      for (int i = 0; i < size; i++) {
                                                         if (array[i].equals(value))
                                                                            ++removalCount;
                                                         elseif (removalCount > 0) {
                                                                            array[i - removalCount] = array[i];
                                                                            array[i] = null;
                                                         }
                                      }
                                      size -= removalCount;
                   }
 
                   publicint size() {
                                      returnsize;
                   }
 
                   public V get(int i) {
                                      if (i >= size)
                                                         thrownew IndexOutOfBoundsException(Integer.toString(i));
                                      returnarray[i];
                   }
 
                   publicstaticvoid main(String[] args) {
                                      Lhist<Integer> integer = new Lhist<Integer>(10);
                                      for (int i = 0; i < 9; i++) {
                                                         integer.add(new Integer(i));
                                      }
                                      System.out.println(integer.size());
                                      // this statement will cause Runtime exception.
                                      try {
                                                         integer.add(null);
 
                                      } catch (NullPointerException e) {
                                                         e.printStackTrace();
                                      }
                                      integer.add(new Integer(9));
                                      try {
                                                         integer.add(new Integer(1));
                                      } catch (IndexOutOfBoundsException e) {
                                                         e.printStackTrace();
                                      }
                                      integer.remove(new Integer(1));
                                      integer.add(new Integer(1));
                                      for (int i = 0; i < integer.size(); i++) {
                                                         System.out.println(integer.get(i));
                                      }
                   }
}
执行结果:
9
java.lang.NullPointerException
                   at com.ibm.course.generics.Lhist.add(Lhist.java:19)
                   at com.ibm.course.generics.Lhist.main(Lhist.java:54)
java.lang.IndexOutOfBoundsException: 10
                   at com.ibm.course.generics.Lhist.add(Lhist.java:17)
                   at com.ibm.course.generics.Lhist.main(Lhist.java:61)
0
2
3
4
5
6
7
8
9
1
集合类
到目前为止,Java 类库中泛型支持存在最多的地方就是集合框架。就像容器类是 C++ 语言中模板的主要动机一样(参阅附录 A:与 C++ 模板的比较)(尽管它们随后用于很多别的用途),改善集合类的类型安全是 Java 语言中泛型的主要动机。集合类也充当如何使用泛型的模型,因为它们演示了泛型的几乎所有的标准技巧和方言。
所有的标准集合接口都是泛型化的——Collection<V>List<V>Set<V>Map<K,V>。类似地,集合接口的实现都是用相同类型参数泛型化的,所以HashMap<K,V>实现Map<K,V>等。
集合类也使用泛型的许多“技巧”和方言,比如上限通配符和下限通配符。例如,在接口Collection<V>中,addAll方法是像下面这样定义的:
 
interface Collection<V> {
 boolean addAll(Collection<? extends V>);
}
 
该定义组合了通配符类型参数和有限制类型参数,允许您将Collection<Integer>的内容添加到Collection<Number>
如果类库将addAll()定义为接受Collection<V>,您就不能将Collection<Integer>的内容添加到Collection<Number>。不是限制addAll()的参数是一个与您将要添加到的集合包含相同类型的集合,而有可能建立一个更合理的约束,即传递给addAll()的集合的元素适合于添加到您的集合。有限制类型允许您这样做,并且使用有限制通配符使您不需要使用另一个不会用在其他任何地方的占位符名称。
应该可以将addAll()的类型参数定义为Collection<V>。但是,这不但没什么用,而且还会改变Collection接口的语义,因为泛型版本的语义将会不同于非泛型版本的语义。这阐述了泛型化一个现有的类要比定义一个新的泛型类难得多,因为您必须注意不要更改类的语义或者破坏现有的非泛型代码。
作为泛型化一个类(如果不小心的话)如何会更改其语义的一个更加微妙的例子,注意Collection.removeAll()的参数的类型是Collection<?>,而不是Collection<? extends V>。这是因为传递混合类型的集合给removeAll()是可接受的,并且更加限制地定义removeAll将会更改方法的语义和有用性。
其他容器类
除了集合类之外,Java 类库中还有几个其他的类也充当值的容器。这些类包括WeakReferenceSoftReferenceThreadLocal。它们都已经在其包含的值的类型上泛型化了,所以WeakReference<T>是对 T 类型的对象的弱引用,ThreadLocal<T>则是到 T 类型的线程局部变量的句柄。
泛型不止用于容器
泛型最常见最直观的使用是容器类,比如集合类或引用类(比如WeakReference<T>)。Collection<V>中类型参数的含义很明显——“一个所有值都是 V 类型的集合”。类似地,ThreadLocal<T>也有一个明显的解释——“一个其类型是 T 的线程局部变量”。但是,泛型规格说明中没有指定容积。
Comparable<T>Class<T>这样的类中类型参数的含义更加微妙。有时,就像Class<T>中一样,类型变量主要是帮助编译器进行类型推理。有时,就像隐含的Enum<E extends Enum<E>>中一样,类型变量只是在类层次结构上加一个约束。
Comparable<T>
Comparable接口已经泛型化了,所以实现Comparable的对象声明它可以与什么类型进行比较。(通常,这是对象本身的类型,但是有时也可能是父类。)
 
public interface Comparable<T> {
  public boolean compareTo(T other);
}
 
所以Comparable接口包含一个类型参数T,该参数是一个实现Comparable的类可以与之比较的对象的类型。这意味着如果定义一个实现Comparable的类,比如String,就必须不仅声明类支持比较,还要声明它可与什么比较(通常是与它本身比较):
 
public class String implements Comparable<String> { ... }
 
现在来考虑一个二元max()方法的实现。您想要接受两个相同类型的参数,二者都是Comparable,并且相互之间是Comparable。幸运的是,如果使用泛型方法和有限制类型参数的话,这相当直观:
 
public static <T extends Comparable<T>> T max(T t1, T t2) {
 if (t1.compareTo(t2) > 0)
    return t1;
 else
    return t2;
}
 
在本例中,您定义了一个泛型方法,在类型T上泛型化,您约束该类型扩展(实现)Comparable<T>。两个参数都必须是T类型,这表示它们是相同类型,支持比较,并且相互可比较。容易!
更好的是,编译器将使用类型推理来确定当调用max()T的值表示什么意思。所以根本不用指定T,下面的调用就能工作:
 
String s = max("moo", "bark");
 
编译器将计算出T的预定值是String,因此它将进行编译和类型检查。但是如果您试图用不实现Comparable<X>X的参数调用max(),那么编译器将不允许这样做。
Class<T>
Class已经泛型化了,但是很多人一开始都感觉其泛型化的方式很混乱。Class<T>中类型参数T的含义是什么?事实证明它是所引用的类接口。怎么会是这样的呢?那是一个循环推理?如果不是的话,为什么这样定义它?
在以前的 JDK 中,Class.newInstance()方法的定义返回Object,您很可能要将该返回类型强制转换为另一种类型:
 
class Class {
  Object newInstance();
}
 
但是使用泛型,您定义Class.newInstance()方法具有一个更加特定的返回类型:
 
class Class<T> {
  T newInstance();
}
 
如何创建一个Class<T>类型的实例?就像使用非泛型代码一样,有两种方式:调用方法Class.forName()或者使用类常量X.classClass.forName()被定义为返回Class<?>。另一方面,类常量X.class被定义为具有类型Class<X>,所以String.classClass<String>类型的。
Foo.classClass<Foo>类型的有什么好处?大的好处是,通过类型推理的魔力,可以提高使用反射的代码的类型安全。另外,还不需要将Foo.class.newInstance()强制类型转换为Foo
考虑一个方法,它从数据库检索一组对象,并返回JavaBeans 对象的一个集合。您通过反射来实例化和初始化创建的对象,但是这并不意味着类型安全必须完全被抛至脑后。考虑下面这个方法:
 
public static<T> List<T> getRecords(Class<T> c, Selector s) {
 // Use Selector to select rows
 List<T> list = new ArrayList<T>();
 for (/* iterate over results */) {
    T row = c.newInstance();
    // use reflection to set fields from result
    list.add(row); 
  }
 return list;
}
 
可以像下面这样简单地调用该方法:
 
List<FooRecord> l = getRecords(FooRecord.class, fooSelector);
 
编译器将会根据FooRecord.classClass<FooRecord>类型的这一事实,推断getRecords()的返回类型。您使用类常量来构造新的实例并提供编译器在类型检查中要用到的类型信息。
Class<T> 替换 T[]
Collection接口包含一个方法,用于将集合的内容复制到一个调用者指定类型的数组中:
 
public Object[] toArray(Object[] prototypeArray) { ... }
 
toArray(Object[])的语义是,如果传递的数组足够大,就会使用它来保存结果,否则,就会使用反射分配一个相同类型的新数组。一般来说,单独传递一个数组作为参数来提供想要的返回类型是一个小技巧,但是在引入泛型之前,这是与方法交流类型信息最方便的方式。
有了泛型,就可以用一种更加直观的方式来做这件事。不像上面这样定义toArray(),泛型toArray()可能看起来像下面这样:
 
public<T> T[] toArray(Class<T> returnType)
 
调用这样一个toArray()方法很简单:
 
FooBar[] fba = something.toArray(FooBar.class);
 
Collection接口还没有改变为使用该技术,因为这会破坏许多现有的集合实现。但是如果使用泛型从新构建Collection,则当然会使用该方言来指定它想要返回值是哪种类型。
Enum<E>
JDK 5.0 Java 语言另一个增加的特性是枚举。当您使用enum关键字声明一个枚举时,编译器就会在内部为您生成一个类,用于扩展Enum并为枚举的每个值声明静态实例。所以如果您说:
 
public enum Suit {HEART, DIAMOND, CLUB, SPADE};
 
编译器就会在内部生成一个叫做Suit的类,该类扩展java.lang.Enum<Suit>并具有叫做HEARTDIAMONDCLUBSPADE的常量(public static final)成员,每个成员都是Suit类。
Class一样,Enum也是一个泛型类。但是与Class不同,它的签名稍微更复杂一些:
 
class Enum<E extends Enum<E>> { . . . }
 
这究竟是什么意思?这难道不会导致无限递归?
我们逐步来分析。类型参数E用于Enum的各种方法中,比如compareTo()getDeclaringClass()。为了这些方法的类型安全,Enum类必须在枚举的类上泛型化。
所以extends Enum<E>部分如何理解?该部分又具有两个部分。第一部分指出,作为Enum的类型参数的类本身必须是Enum的子类型,所以您不能声明一个类 X 扩展Enum<Integer>。第二部分指出,任何扩展Enum的类必须传递它本身作为类型参数。您不能声明 X 扩展Enum<Y>,即使 Y 扩展Enum
总之,Enum是一个参数化的类型,只可以为它的子类型实例化,并且这些子类型然后将根据子类型来继承方法。幸运的是,在Enum情况下,编译器为您做这些工作,一切都很好。
与非泛型代码相互操作
数百万行现有代码使用已经泛型化的 Java 类库中的类,比如集合框架、ClassThreadLocalJDK 5.0 中的改进不要破坏所有这些代码是很重要的,所以编译器允许您在不指定其类型参数的情况下使用泛型类。
当然,以“旧方式”做事没有新方式安全,因为忽略了编译器准备提供的类型安全。如果您试图将List<String>传递给一个接受List的方法,它将能够工作,但是编译器将会发出一个可能丧失类型安全的警告,即所谓的“unchecked conversion(不检查转换)”警告。
没有类型参数的泛型,比如声明为List类型而不是List<Something>类型的变量,叫做原始类型。原始类型与参数化类型的任何实例化是赋值兼容的,但是这样的赋值会生成 unchecked-conversion 警告。
为了消除一些unchecked-conversion 警告,假设您不准备泛型化所有的代码,您可以使用通配符类型参数。使用List<?>而不使用ListList是原始类型;List<?>是具有未知类型参数的泛型。编译器将以不同的方式对待它们,并很可能发出更少的警告。
无论在哪种情况下,编译器在生成字节码时都会生成强制类型转换,所以生成的字节码在每种情况下都不会比没有泛型时更不安全。如果您设法通过使用原始类型或类文件来破坏类型安全,就会得到与不使用泛型时得到的相同的ClassCastExceptionArrayStoreException
已检查集合
作为从原始集合类型迁移到泛型集合类型的帮助,集合框架添加了一些新的集合包装器,以便为一些类型安全bug 提供早期警告。就像Collections.unmodifiableSet()工厂方法用一个不允许任何修改的Set包装一个现有Set一样,Collections.checkedSet()(以及checkedList()checkedMap())工厂方法创建一个包装器(或者视图)类,以防止您将错误类型的变量放在集合中。
checkedXxx()方法都接受一个类常量作为参数,所以它们可以(在运行时)检查这些修改是允许的。典型的实现可能像下面这样:
 
public class Collections { 
  public static <E> Collection<E> checkedCollection(Collection<E> c, Class<E> type ) {
    return new CheckedCollection<E>(c, type);
  }
 
 private static class CheckedCollection<E> implements Collection<E> {
    private final Collection<E> c;
    private final Class<E> type;
 
    CheckedCollection(Collection<E> c, Class<E> type) {
      this.c = c;
      this.type = type;
    }
 
    public boolean add(E o) {
      if (!type.isInstance(o))
        throw new ClassCastException();
      else
        return c.add(o);
    }
  }
 
擦除
也许泛型最具挑战性的方面是擦除(erasure,这是 Java 语言中泛型实现的底层技术。擦除意味着编译器在生成类文件时基本上会抛开参数化类的大量类型信息。编译器用它的强制类型转换生成代码,就像程序员在泛型出现之前手工所做的一样。区别在于,编译器开始已经验证了大量如果没有泛型就不会验证的类型安全约束。
通过擦除实现泛型的含意是很重要的,并且初看也是混乱的。尽管不能将List<Integer>赋给List<Number>,因为它们是不同的类型,但是List<Integer>List<Number>类型的变量是相同的类!要明白这一点,请评价下面的代码:
 
new List<Number>().getClass() == new List<Integer>().getClass()
 
编译器只为List生成一个类。当生成了List的字节码时,将很少剩下其类型参数的的跟踪。
当生成泛型类的字节码时,编译器用类型参数的擦除替换类型参数。对于无限制类型参数(<V>),它的擦除是Object。对于上限类型参数(<K extends Comparable<K>>),它的擦除是其上限(在本例中是Comparable)的擦除。对于具有多个限制的类型参数,使用其最左限制的擦除。
如果检查生成的字节码,您无法说出List<Integer>List<String>的代码之间的区别。类型限制T在字节码中被T的上限所取代,该上限一般是Object
擦除的含意
擦除有很多初看起来似乎奇怪的含意。例如,因为类只可以实现一个接口一次,所以不能像下面这样定义类:
 
// invalid definition
class DecimalString implements Comparable<String>, Comparable<Integer> { ... }
 
由于擦除,上面的声明没有意义。Comparable的两个实例化是同一个接口,并且它们指定相同的compareTo()方法。不能实现一个方法或接口两次。
擦除的另一个更加烦人的含意是,不能使用类型参数实例化一个对象或数组。这意味着您不能在带有类型参数T的泛型类中使用new T()new T[10]。编译器只是不知道生成什么样的字节码。
该问题有几个应急方案,一般涉及反射和类常量(Foo.class)的使用,但是这二者是很麻烦的。Lhist例子类中的构造函数显示了解决该问题的一种技术(参阅实现构造函数),toArray()的讨论(参阅用 Class<T> 替换 T[])中提供了另一种技术。
擦除的另一个含意是,它使得使用instanceof来测试一个引用是不是参数化类型的一个实例是没有意义的。运行时只是不能区分List<String>List<Number>,所以测试(x instanceof List<String>)是没有任何意义的。
类似地,下面的方法不能提高程序的类型安全:
 
public <T> T naiveCast(T t, Object o) { return (T) o; }
 
编译器将只是发出一个 unchecked 警告,因为它不能确定强制类型转换是否安全。
类型与类
泛型的引入使得 Java 语言中的类型系统更加复杂。以前,该语言具有两种类型——引用类型和基本类型。对于引用类型,类型的概念基本上可以互换,术语子类型子类也可以互换。
随着泛型的引入,类型和类之间的关系变得更加复杂。List<Integer>List<Object>是不同的类型,但是却是相同的类。尽管Integer扩展Object,但是List<Integer>不是List<Object>,并且不能赋给List<Object>或者强制转换成List<Object>
另一方面,现在有了一个新的古怪的类型叫做List<?>,它是List<Integer>List<Object>的父类。并且有一个更加古怪的List<? extends Number>。类型层次的结构和形状也变得复杂得多。类型和类不再几乎是相同的东西了。
协变
正如前面所学的(参阅泛型不是协变的),泛型与数组不同,不是协变的。一个Integer是一个Number,并且一个Integer数组是一个Number数组。因此,您可以随意将Integer[]引用赋给Number[]类型的变量。但是一个List<Integer>不是一个List<Number>,因此,将List<Integer>赋给List<Number>的能力将破坏泛型应该提供的类型检查。
这意味着如果您有一个方法参数是泛型,比如Collection<V>,那么您不能将V的子类的集合传递给该方法。如果您想要自己能够这样做,就必须使用有限制类型参数,比如Collection<T extends V>(或者Collection<? extends V>)。
数组
可以在能够使用非泛型的大多数情形下使用泛型,但是会有一些限制。例如,不能声明泛型数组(除非类型参数是无限制通配符)。下面的代码是非法的:
 
List<String>[] listArray = new List<String>[10]; // illegal
 
允许这样的构造会带来问题,因为 Java 语言中的数组是协变的,而参数化类型不是协变的。因为任何数组类型与Object[]是类型兼容的(一个Foo[]一个Object[]),所以下面的代码将会通过编译而不会发出警告,但是将会在运行时失败,这将破坏这样一个目标,即具有任何可以编译通过但是不会发出unchecked 警告的问题是类型安全的:
 
List<String>[] listArray = new List<String>[10]; // illegal
Object[] oa = listArray;
oa[0] = new List<Integer>();
String s = lsa[0].get(0); // ClassCastException
 
另一方面,如果listArrayList<?>类型的,那么最后一行需要一个显式的强制类型转换。尽管还是会生成运行时错误,但是不会破坏泛型保证提供的类型安全(因为错误是在显式的强制类型转换中)。所以List<?>数组是允许的。
extends的新含意
Java 语言引入泛型之前,extends关键字总是意味着创建一个新的继承自另一个类或接口的类或接口。
引入泛型之后,extends关键字有了另一个含意。将extends用在类型参数的定义中(Collection<T extends Number>)或者通配符类型参数中(Collection<? extends Number>)。
当使用extends来指示类型参数限制时,不需要子类-父类关系,只需要子类型-父类型关系。还要记住,有限制类型不需要是该限制的严格子类型;也可以该限制。换句话说,对于Collection<? extends Number>,您可以赋给它Collection<Number>(尽管Number不是Number的严格子类型)和Collection<Integer>Collection<Long>Collection<Float>等等。
在任何这些含意中,extends右边的类型都可以是参数化类型(Set<V> extends Collection<V>)。
有限制类型
到目前为止,已经看到了一种类型限制 ——上限。指定上限会将类型参数约束为一个给定类型限制的父类型(或者等于这个类型限制),如Collection<? extends Number>中所示。尽管不太常见,但是也可以指定下限,下限写为Collection<? super Foo>。只有通配符可以具有下限。
除了在类型参数上指定类型约束之外,指定限制还有另一个重要的作用。如果知道一个类型T扩展Number,那么Number的方法和域就可以通过类型T的变量被访问到。可能到编译时还不知道T的值是什么,但是至少知道是一个Number
关于哪些类可以充当类型限制存在一些限制。基本类型和数组类型不能用作类型限制(但是数组类型可以用作通配符限制)。任何引用类型(包括参数化类型)都可以用作类型限制。
 
class C <T extends int> // illegal
class C <T extends Foo[]> // illegal
class C <T extends Foo> //legal
class C <T extends Foo<? extends Moo<T>>> //legal
class C <T, V extends T> // legal
 
一个可能使用下限的地方是在一个方法中,该方法从一个集合选择元素,并把所选元素放到另一个集合中。例如:
 
class Bunch<V> {
 public void add(V value) { ... }
 public void copyTo(Collection<? super V>) { ... }
 ...
}
 
copyTo()方法将Bunch的所有值复制到指定的集合中。不是指定它必须是一个Collection<V>,而可以指定它是一个Collection<? super V>,这意味着copyTo()可以将Bunch<String>的内容复制到Collection<Object>Collection<String>,而不只是可以复制到Collection<String>
下限的另一个常见情况是用于Comparable接口。不是指定:
 
public static <T extends Comparable<T>> T max(Collection<T> c) { ... }
 
关于接受什么类型,您可以更灵活一些:
 
public static <T extends Comparable<? super T>> T max(Collection<T> c) { ... }
 
这样,出于一些附加的灵活性,您除了可以传递可与自己比较的类型之外,还可以传递可与它的父类型比较的类型。这对于那些扩展已经Comparable的类的类是有价值的:
 
public class Base implements Comparable<Base> { ... }
public class Child extends Base { }
 
因为Child已经实现Comparable<Base>(继承自父类Base),所以您可以将它传递给上面max()的第二个例子,但是不能传递给第一个例子。
多重限制
一个类型参数可以具有多个限制。当您想要约束一个类型参数比如说同时为ComparableSerializable时,这将很有用。多重限制的语法是用“与”符号分隔限制:
 
class C<T extends Comparable<? super T> & Serializable>
 
通配符类型可以具有单个限制——上限或者下限。一个指定的类型参数可以具有一个或多个上限。具有多重限制的类型参数可以用于访问它的每个限制的方法和域。
类型形参和类型实参
在参数化类的定义中,占位符名称(比如Collection<V>中的V)叫做类型形参(type parameter,它们类似于方法定义中的形式参数。在参数化类的变量的声明中,声明中指定的类型值叫做类型实参(type argument,它们类似于方法调用中的实际参数。但是实际中二者一般都通称为“类型参数”。所以给出定义:
 
interface Collection<V> { ... }
 
和声明:
 
Collection<String> cs = new HashSet<String>();
 
那么,名称 V(它可用于整个Collection接口体内)叫做一个类型形参。在cs的声明中,String的两次使用都是类型实参(一次用于Collection<V>,另一次用于HashSet<V>)。
关于何时可以使用类型形参,存在一些限制。大多数时候,可以在能够使用实际类型定义的任何地方使用类型形参。但是有例外情况。不能使用它们创建对象或数组,并且不能将它们用于静态上下文中或者处理异常的上下文中。还不能将它们用作父类型(class Foo<T> extends T),不能用于instanceof表达式中,不能用作类常量。
类似地,关于可以使用哪些类型作为类型实参,也存在一些限制。类型实参必须是引用类型(不是基本类型)、通配符、类型参数,或者其他参数化类型的实例化。所以您可以定义List<String>(引用类型)、List<?>(通配符)或者List<List<?>>(其他参数化类型的实例化)。在带有类型形参 T 的参数化类型的定义中,您也可以声明List<T>(类型形参)。
 


本文来源:http://blog.csdn.net/sunjavaduke/archive/2007/08/02/1722963.aspx
站内文章搜索 高级搜索
文章录入:admin    责任编辑:admin 
  • 上一篇文章:

  • 下一篇文章:
  • 发表评论】【加入收藏】【告诉好友】【打印此文】【关闭窗口
    最新热点 最新推荐 相关文章
     用java实现web服务器
     用java快速开发linux gu…
     正则表达式分解siemens交…
     [portal参考手册]目录
     jsp中调用oracle存储过程…
  • JSP中JavaBean的生命周期

  • Java Swing实现俄罗斯方块

  • 快速、简便使用AJAX技术的三…

  • java异常处理机制的深入理解

  • [转]Java堆和栈的区别 经典总…

  • 关于java Applet

  • java 设计工厂模式

  • Core Java 之旅

  • 专访Java之父:Java未来的发…

  • 开发手记---JAVA数据库连接池

  •   网友评论:(只显示最新10条。评论内容只代表网友观点,与本站立场无关!)
    网络学院©2007 www.23book.net
    为您提供web编程,vb编程,vc编程,服务器架设管理,数据库设计等方面的知识 站长:David