15泛型_15.2简单泛型

15.2 简单泛型

有许多原因促成了泛型的出现,而最引人往目的一个原因,就是为了创造容器类。容器,就是存放要使用的对象的地方.数组也是如此,不过与简单的数组相比,容器类更加灵活,具备更多不同的功能。事实上,所有的程序,在运行时都要求你持有一大堆对象,所以,容器类算得上最具重用性的类库之一。

我们先来看看一个只能持有单个对象的类。当然了,这个类可以明确指定其持有的对象的类型:

  1. //: generics/Holder1.java
  2. class Automobile {}
  3. public class Holder1 {
  4. private Automobile a;
  5. public Holder1(Automobile a) { this.a = a; }
  6. Automobile get() { return a; }
  7. } ///:~

不过,这个类的可重用性就不怎么样了,它无法特有其他类型的任何对象。我们可不希望为碰到的每个类型都编写一个新的类。

在Javaa SE5之前,我们可以让这个类直接持有Object类型的对象:

  1. //: generics/Holder2.java
  2. public class Holder2 {
  3. private Object a;
  4. public Holder2(Object a) { this.a = a; }
  5. public void set(Object a) { this.a = a; }
  6. public Object get() { return a; }
  7. public static void main(String[] args) {
  8. Holder2 h2 = new Holder2(new Automobile());
  9. Automobile a = (Automobile)h2.get();
  10. h2.set("Not an Automobile");
  11. String s = (String)h2.get();
  12. h2.set(1); // Autoboxes to Integer
  13. Integer x = (Integer)h2.get();
  14. }
  15. } ///:~

现在,Holder2可以存储任何类型的对象,在这个例子中,只用了一个Hlolder2对象,却先后三次存储了三种不同类型的对象。

有些情况下,我们确实希望容器能够同时持有多种类型的对象。但是,通常而言,我们只会使用容器来存储一种类型的对象。泛型的主要目的之一就是用来指定容器要持有什么类型的对象。而且由编译器来保证类型的正确性。

因此,与其使用Object,我们更喜欢暂时不指定类型,而是稍后再决定具体使用什么类型。要达到这个目的,需要使用类型参数,用尖括号括住,放在类名后面。然后在使用这个类的时候,再用实际的类型替换此类型参数。在下面的例子中,T就是类型参数:

  1. //: generics/Holder3.java
  2. public class Holder3<T> {
  3. private T a;
  4. public Holder3(T a) { this.a = a; }
  5. public void set(T a) { this.a = a; }
  6. public T get() { return a; }
  7. public static void main(String[] args) {
  8. Holder3<Automobile> h3 =
  9. new Holder3<Automobile>(new Automobile());
  10. Automobile a = h3.get(); // No cast needed
  11. // h3.set("Not an Automobile"); // Error
  12. // h3.set(1); // Error
  13. }
  14. } ///:~

现在,当你创建Holder3对象时,必须指明想持有什么类型的对象,将其置于尖括号内。就像main()中那样。然后,你就只能在Holder3中存入该类型(或其子类,因为多态与泛型不冲突)的对象了。并且,在你从Holder3中取出它持有的对象时,自动地就是正确的类型。

这就是Java泛型的核心概念:告诉编译器想使用什么类型,然后编译器帮你处理一切细节。

一般而言,你可以认为泛型与其他的类型差不多,只不过它们碰巧有类型参数罢了。稍后我们会看到,在使用泛型时,我们只需指定它们的名称以及类型参数列表即可。

15.2.1 一个元组类库

仅一次方法调用就能返回多个对象,你应该经常需要这样的功能吧。可是return语句只允许返回单个对象,因此,解决办法就是创建一个对象,用它来持有想要返回的多个对象。当然,可以在每次需要的时候,专门创建一个类来完成这样的工作。可是有了泛型。我们就能够一次性地解决该问题,以后再也不用在这个问题上浪费时间了。同时,我们在编译期就能确保类型安全。

这个概念称为元组(tuple),它是将一组对象直接打包存储干其中的一个单一对象。这个容器对象允许谈取其中元素,但是不允许向其中存放新的对象。(这个概念也称为数据传这对象或信使。)

通常,元组可以具有任意长度,同时,元组中的对象可以是任意不同的类型。不过,我们希望能够为每一个对象指明其类型,并且从容器中读取出来时,能够得到正确的类型。要处理不同长度的问题,我们需要创建多个不同的元组。下面的程序是一个2维元组,它能够持有两个对象:

  1. //: net/mindview/util/TwoTuple.java
  2. package net.mindview.util;
  3. public class TwoTuple<A,B> {
  4. public final A first;
  5. public final B second;
  6. public TwoTuple(A a, B b) { first = a; second = b; }
  7. public String toString() {
  8. return "(" + first + ", " + second + ")";
  9. }
  10. } ///:~

构造器捕获了要存储的对象,而toString()是一个便利函数,用来显示列表中的值。注意,元组隐含地保持了其中元素的次序。

第一次阅读上面的代码时,你也许会想,这不是违反了Java编程的安全性原则吗?first和second应该声明为private,然后提供getFirst()和getSecond()之类的访问方法才对呀?让我们仔细看看这个例子中的安全性:客户端程序可以读取first和second对象,然后可以随心所欲地使用这两个对象。但是,它们却无法将其他值赋予first或second。因为final声明为你买了相同的安全保险,而且这种格式更简洁明了。

还有另一种设计考虑,即你确实希望允许客户端程序员改变first办second所引用的对象。然而,采用以上的形式无疑是更安全的做法,这样的话,如果程序员想要使用具有不同元素的元组,就强制要求他们另外创建一个新的TwoTuple对象。

我们可以利用继承机制实现长度更长的元组。从下面的例子中可以看到,增加类型参数是件很简单的事情:

  1. //: net/mindview/util/ThreeTuple.java
  2. package net.mindview.util;
  3. public class ThreeTuple<A,B,C> extends TwoTuple<A,B> {
  4. public final C third;
  5. public ThreeTuple(A a, B b, C c) {
  6. super(a, b);
  7. third = c;
  8. }
  9. public String toString() {
  10. return "(" + first + ", " + second + ", " + third +")";
  11. }
  12. } ///:~

为了使用元组,你只需定义一个长度适合的元组,将其作为方法的返回值,然后在return语句中创建该元组,井返回即可。

  1. //: generics/TupleTest.java
  2. import net.mindview.util.*;
  3. class Amphibian {}
  4. class Vehicle {}
  5. public class TupleTest {
  6. static TwoTuple<String,Integer> f() {
  7. // Autoboxing converts the int to Integer:
  8. return new TwoTuple<String,Integer>("hi", 47);
  9. }
  10. static ThreeTuple<Amphibian,String,Integer> g() {
  11. return new ThreeTuple<Amphibian, String, Integer>(
  12. new Amphibian(), "hi", 47);
  13. }
  14. static
  15. FourTuple<Vehicle,Amphibian,String,Integer> h() {
  16. return
  17. new FourTuple<Vehicle,Amphibian,String,Integer>(
  18. new Vehicle(), new Amphibian(), "hi", 47);
  19. }
  20. static
  21. FiveTuple<Vehicle,Amphibian,String,Integer,Double> k() {
  22. return new
  23. FiveTuple<Vehicle,Amphibian,String,Integer,Double>(
  24. new Vehicle(), new Amphibian(), "hi", 47, 11.1);
  25. }
  26. public static void main(String[] args) {
  27. TwoTuple<String,Integer> ttsi = f();
  28. System.out.println(ttsi);
  29. // ttsi.first = "there"; // Compile error: final
  30. System.out.println(g());
  31. System.out.println(h());
  32. System.out.println(k());
  33. }
  34. } /* Output: (80% match)
  35. (hi, 47)
  36. ([email protected], hi, 47)
  37. ([email protected], [email protected], hi, 47)
  38. ([email protected], [email protected], hi, 47, 11.1)
  39. *///:~

由于有了泛型,你可以很容易地创建元组,令其返回一组任意类型的对象。而你所要做的,只是编写表达式而已。

通过ttsi.first=“there”语句的错误,我们可以看出,final明确实能够保护public元素,在对象被构造出来之后,声明为final的元素便不能被再赋予其他值了。

在上面的程序中,new表达式确实有点罗嗦。本章稍后会介绍,如何利用泛型方法简化这样的表达式。

15.2.2 一个堆栈类

接下来我们看一个稍微复杂一点的例子:传统的下推堆栈。在第11章中,我们看到,这个堆栈是作为net.mindview.util.Stack类,用一个LinkedList实现的。在那个例子中,LinkedList本身已经具备了创建堆栈所必需的方法,而Stack也可以通过两个泛型的类Stack<T>LinkedList<T>的组合来创建。在那个示例中,我们可以看出,泛型类型也就是另一种类型罢了(稍候我们会一些例外的情况)。

现在我们不用LinkedList,来实现自己的内部链式存储机制:

  1. public class LinkedStack<T> {
  2. private static class Node<U> {
  3. U item;
  4. Node<U> next;
  5. Node() { item = null; next = null; }
  6. Node(U item, Node<U> next) {
  7. this.item = item;
  8. this.next = next;
  9. }
  10. boolean end() { return item == null && next == null; }
  11. }
  12. private Node<T> top = new Node<T>(); // End sentinel
  13. public void push(T item) {
  14. top = new Node<T>(item, top);
  15. }
  16. public T pop() {
  17. T result = top.item;
  18. if(!top.end())
  19. top = top.next;
  20. return result;
  21. }
  22. public static void main(String[] args) {
  23. LinkedStack<String> lss = new LinkedStack<String>();
  24. for(String s : "Phasers on stun!".split(" "))
  25. lss.push(s);
  26. String s;
  27. while((s = lss.pop()) != null)
  28. System.out.println(s);
  29. }
  30. }
  31. /* Output:
  32. stun!
  33. on
  34. Phasers
  35. */

内部类Node也是一个泛型,它拥有自己的类型参数。

这个例子使用了一个末端哨兵(end sentinel)来判断堆栈何时为空。这个末端哨兵是在构造LinkedStack时创建的。然后,每调用一次push()方法,就会创建一个Node<T>对象,并将其链接到前一个Node<T>对象。当你调用pop()方法时,总是返top.item,然后丢弃当前top所指的Node<T>,并将top转移到下一个Node<T>,除非你已经碰到了末端哨兵,这时候就不再移动top了。如果已经到了末端,客户端程序还继续调用pop()方法,它只能得到null,说明堆栈已经空了。

15.2.3 RandomList

作为容器的另一个例子,假设我们需要一个持有特定类型对象的列表,每次调用其上的select()方法时,它可以随机地选取一个元素。如果我们希望以此构建一个可以应用干各种类型的对象的工具,就需要使用泛型:

  1. //: generics/RandomList.java
  2. import java.util.*;
  3. public class RandomList<T> {
  4. private ArrayList<T> storage = new ArrayList<T>();
  5. private Random rand = new Random(47);
  6. public void add(T item) { storage.add(item); }
  7. public T select() {
  8. return storage.get(rand.nextInt(storage.size()));
  9. }
  10. public static void main(String[] args) {
  11. RandomList<String> rs = new RandomList<String>();
  12. for(String s: ("The quick brown fox jumped over " +
  13. "the lazy brown dog").split(" "))
  14. rs.add(s);
  15. for(int i = 0; i < 11; i++)
  16. System.out.print(rs.select() + " ");
  17. }
  18. } /* Output:
  19. brown over fox quick quick dog brown The brown lazy brown
  20. *///:~

来自为知笔记(Wiz)

时间: 2024-10-15 05:29:13

15泛型_15.2简单泛型的相关文章

关于C#泛型作用的简单说明

泛型:即通过参数化类型来实现在同一份代码上操作多种数据类型.泛型编程是一种编程范式,它利用“参数化类型”将类型抽象化,从而实现更为灵活的复用. C#泛型的作用概述 C#泛型赋予了代码更强的类型安全,更好的复用,更高的效率,更清晰的约束. 在一个方法中,一个变量的值是可以作为参数,但其实这个变量的类型本身也可以作为参数.泛型允许我们在调用的时候再指定这个类型参数是什么.在.net中,泛型能够给我们带来的两个明显的好处是--类型安全和减少装箱.拆箱. 在一个方法中,一个变量的值是可以作为参数,但其实

编写高质量代码改善C#程序的157个建议[优先考虑泛型、避免在泛型中声明静态成员、为泛型参数设定约束]

前言 泛型并不是C#语言一开始就带有的特性,而是在FCL2.0之后实现的新功能.基于泛型,我们得以将类型参数化,以便更大范围地进行代码复用.同时,它减少了泛型类及泛型方法中的转型,确保了类型安全.委托本身是一种引用类型,它保存的也是托管堆中对象的引用,只不过这个引用比较特殊,它是对方法的引用.事件本身也是委托,它是委托组,C#中提供了关键字event来对事件进行特别区分.一旦我们开始编写稍微复杂的C#代码,就肯定离不开泛型.委托和事件.本章将针对这三个方面进行说明. 这里也有一篇之前我对泛型的简

Java泛型和集合之泛型介绍

在声明一个接口和类的时候可以使用尖括号带有一个或者多个参数但是当你在声明属于一个接口或者类的变量的时候或者你在创建一个类实例的时候需要提供他们的具体类型.我们来看下下面这个例子 List<String>words = new ArrayList<String>(); words.add("Hello "); words.add("world!"); String s = words.get(0)+words.get(1); assert s.

C#泛型集合与非泛型集合_Felix(转自新浪博客)

在.NET FCL为我们提供了很多集合类型,是编程中非常有力的工具.泛型集合主要在System.Collections.Generic名称空间中,而非泛型集合主要在System.Collections,首先抛出结论:如果在C#2.0版本以上,尽量使用泛型集合类,而不使用非泛型集合类.因为,1.泛型编程是从c#2.0开始才被.net支持的.2.泛型集合在性能和类型安全方面优于非泛型集合. 非泛型集合-System.Collections名字空间中的类主要包括ArrayList, Hashtable

编写高质量代码改善C#程序的157个建议——建议20:使用泛型集合代替非泛型集合

建议20:使用泛型集合代替非泛型集合 在建议1中我们知道,如果要让代码高效运行,应该尽量避免装箱和拆箱,以及尽量减少转型.很遗憾,在微软提供给我们的第一代集合类型中没有做到这一点,下面我们看ArrayList这个类的使用情况: ArrayList al=new ArrayList(); al.Add(0); al.Add(1); al.Add("mike"); foreach (var item in al) { Console.WriteLine(item); } 上面这段代码充分演

安装ChemOffice 15.1就是这么简单

化学绘图软件ChemDraw出了最新版ChemOffice 15.1了,其下有三个不同组件,其中ChemDraw15.1 Pro使用范围最广.当我们下载完软件后就需要对其进行安装,一般按照流程来就没有什么大的问题,但是一些新手用户对一些细节不是很了解,下面我们就来和大家分享一下其实安装ChemOffice 15.1就是这么简单? 第一步:安装ChemDraw的第一步当然是获取ChemDraw15.1 Pro安装包,为了避免在安装或使用过程中发生莫名其妙的异常情况,建议从ChemDraw中文官网获

.NET泛型01,为什么需要泛型,泛型基本语法

.NET泛型或许是借鉴于C++泛型模版,借助它可以实现对类型的抽象化.泛型处理,实现了类型和方法之间的解耦.一个最经典的运用是在三层架构中,针对不同的领域模型,在基接口.基类中实现针对各个领域模型的泛型处理. 本篇主要包括:■ 为什么需要泛型    ※ 不用泛型    ※ 使用泛型    ※ 泛型的运行时本质■ 泛型语法■ 典型的泛型类 为什么需要泛型 不用泛型 来看一个比较类型的方法. public class Calculator { public static bool AreEqual(

Java 泛型(一) 泛型使用基础

泛型Generics 所谓泛型,就是变量类型的参数化. 泛型是JDK1.5中一个最重要的特征.通过引入泛型,我们将获得编译时类型的安全和运行时更小的抛出ClassCastException的可能. 在JDK1.5中,你可以声明一个集合将接收/返回的对象的类型. 使用泛型时如果不指明参数类型,即泛型类没有参数化,会提示警告,此时类型为Object. 为什么使用泛型 使用泛型的典型例子,是在集合中的泛型使用. 在使用泛型前,存入集合中的元素可以是任何类型的,当从集合中取出时,所有的元素都是Objec

PD 15.1 安装 破解 , 简单使用 (一对多,多对多关系生成sql脚本)

  1:运行  PowerDesigner15_Evaluation.exe 默认   2: 安装完毕后,不要执行,下面我们进行 破解 把 PowerDesigner15汉化+注册补丁  下的所有文件,覆盖 PD的安装目录下的文件 然后我们打开 PD,点击 帮助 –> About  看到下面窗口中红色方框,就表示已经破解 + 汉化成功 PD 15.1 安装 破解 , 简单使用 (一对多,多对多关系生成sql脚本)