空间站

北极心空

  BlogJava :: 首页 :: 联系 :: 聚合  :: 管理
  15 Posts :: 393 Stories :: 160 Comments :: 0 Trackbacks

                                                                   (一)
JDK 5.0
(也叫做 Java 5.0 或者 Tiger Java 来了一些大的化。其中最重要的化就是引入了泛型 —— 支持定义带有抽象型参数的些参数由您在例化指定。泛型提高大型程序的型安全和可维护来了很大的潜力。

泛型与 JDK 5.0 中其他几个新的言特性相互作,包括增 for (有叫做 foreach 或者 for/in )、枚enumeration)和自装箱(autoboxing)。

本教程解了在 Java 言中引入泛型的机,详细了泛型的法和语义,并述了如何在自己的中使用泛型。

本教程针对中高 Java 开发,他想要了解针对泛型的新言支持是如何工作的。本教程假定者熟悉在 Java 言中开发接口和,并且具有基本的面向设计技能。

泛型言特性只在 JDK 5.0 及以后版本中可用。如果您是基于早的 JDK 版本开发软件,那在迁移到 JDK 5.0 或以后版本之前,将无法在代中使用泛型特性。

了使用泛型,必具有一个 JDK 5.0 开发环境。可以从 Sun 公司的 Web 站点免 JDK 5.0 (http://java.sun.com/j2se/1.5.0/index.jsp)

是泛型?

泛型(Generic type 或者 generics)是 Java 言的型系的一种扩展,以支持建可以按行参数化的。可以把型参数看作是使用参数指定的型的一个占位符,就像方法的形式参数是运行时传递的占位符一

可以在集合框架(Collection framework)中看到泛型的机。例如,Map 您向一个 Map 添加任意象,即使最常的情况是在定映射(map)中保存某个特定型(比如 String)的象。

Map.get() 被定义为返回 Object,所以一般必 Map.get() 转换为期望的型,如下面的代所示:

Map m = new HashMap();

m.put("key", "blarg");

String s = (String) m.get("key");

程序通过编译,必 get() 转换为 String,并且希望果真的是一个 String。但是有可能某人已映射中保存了不是 String 西,这样,上面的代将会抛出 ClassCastException

理想情况下,您可能会得出这样一个点,即 m 是一个 Map,它将 String 映射到 String 可以您消除代中的转换,同时获得一个附加的检查层该检查层可以防止有人将错误类型的保存在集合中。就是泛型所做的工作。

泛型的好

Java 言中引入泛型是一个大的功能增。不仅语言、型系编译器有了大的化,以支持泛型,而且类库行了大翻修,所以多重要的,比如集合框架,都已泛型化的了。这带来了很多好

型安全。泛型的主要目是提高 Java 程序的型安全。通知道使用泛型定量的型限制,编译器可以在一个高得多的程度上验证类型假。没有泛型,些假就只存在于程序头脑中(或者如果幸运的存在于代中)。

Java 程序中的一流行技是定义这样的集合,即它的元素或是公共型的,比如“String 列表”或者“String String 的映射”。量声明中捕获这一附加的型信息,泛型允许编译些附加的束。错误现在就可以在编译时被捕了,而不是在运行当作 ClassCastException 展示出来。将检查从运行挪到编译时有助于您更容易找到错误,并可提高程序的可靠性。

消除转换泛型的一个附是,消除源代中的转换使得代更加可,并且减少了出机会。

尽管减少转换可以降低使用泛型的代罗嗦程度,但是声明泛型量会来相罗嗦。比下面两个代例子。

不使用泛型:

List li = new ArrayList();

li.put(new Integer(3));

Integer i = (Integer) li.get(0);

使用泛型:

List<Integer> li = new ArrayList<Integer>();

li.put(new Integer(3));

Integer i = li.get(0);

简单的程序中使用一次泛型量不会降低罗嗦程度。但是于多次使用泛型量的大型程序来可以累起来降低罗嗦程度。

潜在的性能收益。泛型为较大的来可能。在泛型的初始实现中,编译器将转换(没有泛型的,程序会指定转换)插入生成的字节码中。但是更多型信息可用于编译未来版本的 JVM 来可能。

由于泛型的实现方式,支持泛型(几乎)不需要 JVM 文件更改。所有工作都在编译器中完成,编译器生成似于没有泛型(和转换所写的代,只是更能确保型安全而已。


                                                                           (二)

泛型用法的例子

泛型的许多最佳例子都来自集合框架,因为泛型让您在保存在集合中的元素上指定类型约束。考虑这个使用 Map 类的例子,其中涉及一定程度的优化,即 Map.get() 返回的结果将确实是一个 String

Map m = new HashMap();

m.put("key", "blarg");

String s = (String) m.get("key");

如果有人已经在映射中放置了不是 String 的其他东西,上面的代码将会抛出 ClassCastException。泛型允许您表达这样的类型约束,即 m 是一个将 String 键映射到 String 值的 Map。这可以消除代码中的强制类型转换,同时获得一个附加的类型检查层,这个检查层可以防止有人将错误类型的键或值保存在集合中。

下面的代码示例展示了 JDK 5.0 中集合框架中的 Map 接口的定义的一部分:

public interface Map<K, V> {

 public void put(K key, V value);

 public V get(K key);

}

注意该接口的两个附加物:

类型参数 K V 在类级别的规格说明,表示在声明一个 Map 类型的变量时指定的类型的占位符。

get()put() 和其他方法的方法签名中使用的 K V

为了赢得使用泛型的好处,必须在定义或实例化 Map 类型的变量时为 K V 提供具体的值。以一种相对直观的方式做这件事:

Map<String, String> m = new HashMap<String, String>();

m.put("key", "blarg");

String s = m.get("key");

当使用 Map 的泛型化版本时,您不再需要将 Map.get() 的结果强制类型转换为 String,因为编译器知道 get() 将返回一个 String

在使用泛型的版本中并没有减少键盘录入;实际上,比使用强制类型转换的版本需要做更多键入。使用泛型只是带来了附加的类型安全。因为编译器知道关于您将放进 Map 中的键和值的类型的更多信息,所以类型检查从执行时挪到了编译时,这会提高可靠性并加快开发速度。

向后兼容

Java 语言中引入泛型的一个重要目标就是维护向后兼容。尽管 JDK 5.0 的标准类库中的许多类,比如集合框架,都已经泛型化了,但是使用集合类(比如 HashMap ArrayList)的现有代码将继续不加修改地在 JDK 5.0 中工作。当然,没有利用泛型的现有代码将不会赢得泛型的类型安全好处。


                                                                       (三)

型参数

在定泛型或声明泛型,使用尖括号来指定形式型参数。形式型参数与实际类型参数之似于形式方法参数与实际方法参数之系,只是型参数表示型,而不是表示

泛型中的型参数几乎可以用于任何可以使用名的地方。例如,下面是 java.util.Map 接口的定的摘

public interface Map<K, V> {

 public void put(K key, V value);

 public V get(K key);

}

Map 接口是由两个型参数化的,两个型是键类 K 值类 V。(不使用泛型)将会接受或返回 Object 的方法在在它的方法名中使用 K V,指示附加的束位于 Map 明之下。

当声明或者例化一个泛型的,必指定型参数的

Map<String, String> map = new HashMap<String, String>();

注意,在本例中,必指定两次型参数。一次是在声明 map ,另一次是在选择 HashMap 的参数化以便可以例化正确型的一个

编译器在遇到一个 Map<String, String> 型的,知道 K V 在被 String,因此它知道在这样量上 Map.get() 将会得到 String 型。

除了异常型、枚或匿名内部以外,任何都可以具有型参数。

命名型参数

推荐的命名定是使用大写的个字母名称作为类型参数。 C++ 定有所不同(参 A:与 C++ 模板的比),并反映了大多数泛型将具有少量型参数的假定。于常的泛型模式,推荐的名称是:

K —— ,比如映射的

V —— ,比如 List Set 的内容,或者 Map 中的

E —— 异常

T ——泛型。

泛型不是协变

于泛型的混淆,一个常的来源就是假像数协变的。其不是协变的。List<Object> 不是 List<String> 的父型。

如果 A B,那 A 的数也是 B 的数,并且完全可以在需要 B[] 的地方使用 A[]

Integer[] intArray = new Integer[10]; 

Number[] numberArray = intArray;

上面的代是有效的,因一个 Integer 一个 Number,因而一个 Integer 一个 Number 。但是于泛型来说则不然。下面的代是无效的:

List<Integer> intList = new ArrayList<Integer>();

List<Number> numberList = intList; // invalid

最初,大多数 Java 程序员觉缺少协变人,或者甚至是“坏的(broken)”,但是之所以这样有一个很好的原因。如果可以将 List<Integer> 赋给 List<Number>,下面的代就会背泛型应该提供的型安全:

List<Integer> intList = new ArrayList<Integer>();

List<Number> numberList = intList; // invalid

numberList.add(new Float(3.1415));

intList numberList 都是有名的,如果允,上面的代就会您将不是 Integers 西放 intList 中。但是,正如下一屏将会看到的,您有一个更加灵活的方式来定泛型。


                                                                               (四)

型通配符

您具有方法:

void printList(List l) {

 for (Object o : l)

    System.out.println(o);

}

上面的代 JDK 5.0 编译,但是如果试图 List<Integer> 用它,会得到警告。警告是因,您将泛型(List<Integer>传递给一个只承将它当作 List(所的原始型)的方法,将破坏使用泛型的型安全。

如果试图编写像下面这样的方法,那将会怎么样

void printList(List<Object> l) {

 for (Object o : l)

    System.out.println(o);

}

它仍然不会通过编译,因一个 List<Integer> 不是一个 List<Object>(正如前一屏泛型不是协变中所学的)。才真正 —— 在您的泛型版本没有普通的非泛型版本有用!

解决方案是使用型通配符:

void printList(List<?> l) {

 for (Object o : l)

    System.out.println(o);

}

上面代中的号是一个型通配符。它作“号”。List<?> 是任何泛型 List 的父型,所以您完全可以将 List<Object>List<Integer> List<List<List<Flutzpah>>> 传递给 printList()

型通配符的作用

型通配符中引入了型通配符,这让您可以声明 List<?> 型的量。您可以对这样 List 做什呢?非常方便,可以从中索元素,但是不能添加元素。原因不是编译器知道哪些方法修改列表哪些方法不修改列表,而是(大多数)化的方法比不化的方法需要更多的型信息。下面的代码则工作得很好:

List<Integer> li = new ArrayList<Integer>();

li.add(new Integer(42));

List<?> lu = li;

System.out.println(lu.get(0));

么该能工作呢? lu编译器一点都不知道 List 型参数的。但是编译器比较聪明,它可以做一些型推理。在本例中,它推断未知的型参数必须扩 Object。(个特定的推理没有太大的跳,但是编译器可以作出一些非常令人佩服的型推理,后面就会看到(在层细节中)。所以它 List.get() 并推断返回 Object

另一方面,下面的代不能工作:

List<Integer> li = new ArrayList<Integer>();

li.add(new Integer(42));

List<?> lu = li;

lu.add(new Integer(43)); // error

在本例中, lu编译器不能 List 型参数作出足够严密的推理,以确定将 Integer 传递给 List.add() 型安全的。所以编译器将不允这么做。

以免您仍然认为编译器知道哪些方法更改列表的内容哪些不更改列表内容,注意下面的代将能工作,因它不依编译器必知道 lu 型参数的任何信息:

List<Integer> li = new ArrayList<Integer>();

li.add(new Integer(42));

List<?> lu = li;

lu.clear();

泛型方法 

(在型参数中)您已看到,通的定中添加一个形式型参数列表,可以将泛型化。方法也可以被泛型化,不管它在其中的是不是泛型化的。

泛型在多个方法间实束。在 List<V> 中,型参数 V get()add()contains() 等方法的名中。当建一个 Map<K, V> 型的,您就在方法之宣称一个束。您传递给 add() 将与 get() 返回的型相同。

似地,之所以声明泛型方法,一般是因您想要在方法的多个参数之宣称一个。例如,下面代中的 ifThenElse() 方法,根据它的第一个参数的布尔值,它将返回第二个或第三个参数:

public <T> T ifThenElse(boolean b, T first, T second) {

 return b ? first : second;

}

注意,您可以 ifThenElse(),而不用式地告诉编译器,您想要 T 的什么值编译器不必式地被告知 T 将具有什么值;它只知道都必相同。编译器允用下面的代,因为编译器可以使用型推理来推断出,替 T String 足所有的束:

String s = ifThenElse(b, "a", "b");

似地,您可以用:

Integer i = ifThenElse(b, new Integer(1), new Integer(2));

但是,编译器不允下面的代,因没有型会足所需的束:

String s = ifThenElse(b, "pi", new Float(3.14));

选择使用泛型方法,而不是将 T 添加到呢?(至少)有两情况应该这样做:

当泛型方法是静这种情况下不能使用类类型参数。

T 上的于方法真正是局部的意味着没有在相同的另一个方法名中使用相同 T 束。通使得泛型方法的型参数于方法是局部的,可以化封闭类型的名。

有限制

在前面泛型方法的例子中,型参数 V 是无束的或无限制的型。有没有完全指定型参数,需要对类型参数指定附加的束。

例子 Matrix ,它使用型参数 V参数由 Number 来限制:

public class Matrix<V extends Number> { ... }

编译器允 Matrix<Integer> Matrix<Float> 型的量,但是如果您试图 Matrix<String> 型的量,会出现错误型参数 V 被判断 Number 限制在没有型限制,假设类型参数由 Object 限制。就是前一屏泛型方法中的例子,允 List.get() List<?> 返回 Object,即使编译器不知道型参数 V
posted on 2008-10-24 09:35 芦苇 阅读(504) 评论(0)  编辑  收藏 所属分类: JAVA

只有注册用户登录后才能发表评论。


网站导航: