项目29:偏爱泛型类型

ITEM 29: FAVOR GENERIC TYPES
  通常,参数化声明并使用JDK提供的泛型类型和方法并不太难。编写自己的泛型类型有点困难,但是值得学习如何编写泛型类型。
考虑第7项中的简单(玩具)堆栈实现:

// Object-based collection - a prime candidate for generics
public class Stack {
  private Object[] elements;
  private int size = 0;
  private static final int DEFAULT_INITIAL_CAPACITY = 16;
  public Stack() {
    elements = new Object[DEFAULT_INITIAL_CAPACITY];
  }
  public void push(Object e) { ensureCapacity(); elements[size++] = e;}
  public Object pop() { 
    if (size == 0)
      throw new EmptyStackException();
    Object result = elements[--size];
    elements[size] = null; // Eliminate obsolete reference return result;
  }
  public boolean isEmpty() { return size == 0;}
  private void ensureCapacity() { 
    if (elements.length == size)
      elements = Arrays.copyOf(elements, 2 * size + 1); 
  }
}

  这个类一开始就应该是参数化的,但是由于它不是参数化的,所以我们可以在事后对它进行泛化。换句话说,我们可以在不损害原始非参数化版本客户机的情况下对其进行参数化。按照目前的情况,客户机必须转换从堆栈弹出的对象,这些转换可能在运行时失败。泛型类的第一步是向其声明中添加一个或多个类型参数。在本例中,有一个类型参数,表示堆栈的元素类型,该类型参数的常规名称是E (Item 68)。
  下一步是用适当的类型参数替换类型对象的所有用法,然后尝试编译生成的程序:

// Initial attempt to generify Stack - won't compile! 
public class Stack<E> {
  private E[] elements;
  private int size = 0;
  private static final int DEFAULT_INITIAL_CAPACITY = 16;
  public Stack() {
    elements = new E[DEFAULT_INITIAL_CAPACITY];
  }
  public void push(E e) { ensureCapacity(); elements[size++] = e;}
  public E pop() { 
    if (size == 0)
      throw new EmptyStackException();
    E result = elements[--size];
    elements[size] = null; // Eliminate obsolete reference return result;
  }
  ... // no changes in isEmpty or ensureCapacity 
}

  您通常会得到至少一个错误或警告,这个类也不例外。幸运的是,这个类只生成一个错误:
“Stack.java:8: generic array creation elements = new E[DEFAULT_INITIAL_CAPACITY];”
  正如 item 28 中所解释的,您不能创建一个不可具体化类型的数组,比如E。每次编写由数组支持的泛型类型时都会出现这个问题。有两种合理的方法来解决它。第一个解决方案直接绕过了泛型数组创建的禁令:创建一个对象数组并将其转换为泛型数组类型。现在,编译器将发出警告,而不是错误。这个用法是合法的,但它不是(一般)类型安全:
“Stack.java:8: warning: [unchecked] unchecked cast found: Object[], required: E[] elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];”
  编译器可能无法证明您的程序是类型安全的,但您可以。您必须说服自己,未检查的强制转换不会损害程序的类型安全性。所涉及的数组(元素)存储在一个私有字段中,从来没有返回给客户机或传递给任何其他方法。数组中存储的惟一元素是传递给 push 方法的元素,类型为 E,因此未选中的强制转换不会造成任何危害。
  一旦您证明了未选中的强制转换是安全的,就将警告抑制在尽可能小的范围内(item 27)。在本例中,构造函数只包含未选中的数组创建,因此在整个构造函数中禁用警告是合适的。通过添加一个注释,Stack 可以干净地编译,您可以使用它而不需要显式的强制转换或担心 ClassCastException:

// The elements array will contain only E instances from push(E).
// This is sufficient to ensure type safety, but the runtime 
// type of the array won't be E[]; it will always be Object[]! @SuppressWarnings("unchecked")
public Stack() {
  elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}

  消除堆栈中通用数组创建错误的第二种方法是将字段元素的类型从E[]更改为Object[]。如果你这样做,你会得到一个不同的错误:
“Stack.java:19: incompatible types found: Object, required: E. E result = elements[--size];”
  您可以通过将从数组中检索到的元素强制转换为E,将此错误转换为警告,但是您将得到警告:
“Stack.java:19: warning: [unchecked] unchecked cast found: Object, required: E
E result = (E) elements[--size];”
  因为E是不可具体化的类型,所以编译器无法在运行时检查转换。同样,您可以很容易地向自己证明未选中的强制转换是安全的,因此适当地抑制警告。根据 item 27 的建议,我们只对包含未选中强制类型转换的赋值取消警告,而不是对整个pop方法:

// Appropriate suppression of unchecked warning
public E pop() { 
  if (size == 0)
    throw new EmptyStackException();
  // push requires elements to be of type E, so cast is correct
  @SuppressWarnings("unchecked") 
  E result = (E) elements[--size];
  elements[size] = null; // Eliminate obsolete reference
  return result; 
}

  消除泛型数组创建的两种技术都有其追随者。第一个更容易读:数组被声明为E[]类型,清楚地表明它只包含E实例。它也更简洁:在一个典型的泛型类中,从数组中读取代码中的许多点;第一种技术只需要一次转换(在创建数组的地方),而第二种技术在每次读取数组元素时都需要单独的转换。因此,第一种技术更可取,在实践中也更常用。但是,它确实会造成堆污染(item 32):数组的运行时类型与其编译时类型不匹配(除非E恰好是Object)。这使得一些程序员非常反感,他们选择了第二种技术,尽管在这种情况下堆污染是无害的。
  下面的程序演示了通用堆栈类的使用。程序以相反的顺序打印命令行参数并转换为大写。对栈中弹出的元素调用 String 的 toUpperCase 方法不需要显式的强制转换,自动生成的强制转换保证成功:

// Little program to exercise our generic Stack
public static void main(String[] args) { 
  Stack<String> stack = new Stack<>();
  for (String arg : args) 
    stack.push(arg);
  while (!stack.isEmpty()) 
    System.out.println(stack.pop().toUpperCase());
}

  上面的例子可能与 item 28 相矛盾, item 28 鼓励使用列表而不是数组。在泛型类型中使用列表并不总是可行或可取的。Java本身不支持列表,因此一些泛型类型(如ArrayList)必须在数组之上实现。其他泛型类型(如 HashMap)是在数组之上实现的,用于提高性能。
  绝大多数泛型类型与我们的堆栈示例相似,因为它们的类型参数没有限制:您可以创Stack<Object>, Stack<int[]>, Stack<List<String>> 或者其他类型的 Stack 。注意,您不能创建原始类型的堆栈:尝试创建堆栈或堆栈将导致编译时错误。这是Java泛型类型系统的一个基本限制。您可以使用装箱的基本类型(第61项)来解决这个限制。
  有一些泛型类型限制其类型参数的允许值。例如,考虑java.util.concurrent.DelayQueue,它的声明如下:
class DelayQueue<E extends Delayed> implements BlockingQueue<E>
类型参数列表(<E extends Delayed>)要求实际的类型参数E是java.util.concurrent.Delayed 的子类型。这允许 DelayQueue 实现及其客户端利用对DelayQueue 元素的延迟方法,而不需要显式的强制转换或 ClassCastException 的风险。类型参数 E 称为有界类型参数。注意,子类型关系被定义为每个类型都是它自己的子类型[JLS, 4.10],因此创建一个 DelayQueue 是合法的。
  总之,泛型类型比需要在客户机代码中强制转换的类型更安全、更容易使用。当您设计新类型时,请确保它们可以在没有此类强制转换的情况下使用。这通常意味着使类型泛型。如果您有任何应该是泛型的现有类型,但没有泛型,则对它们进行泛型。这将使这些类型的新用户的生活更容易,而不会破坏现有的客户端(第26项)。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,277评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,689评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,624评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,356评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,402评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,292评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,135评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,992评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,429评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,636评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,785评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,492评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,092评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,723评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,858评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,891评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,713评论 2 354

推荐阅读更多精彩内容

  • 对象的创建与销毁 Item 1: 使用static工厂方法,而不是构造函数创建对象:仅仅是创建对象的方法,并非Fa...
    孙小磊阅读 1,982评论 0 3
  • 泛型代码可以确保你写出灵活的,可重用的函数和定义出任何你所确定好的需求的类型。你的可以写出避免重复的代码,并且用一...
    iOS_Developer阅读 798评论 0 0
  • 本文大量参考Thinking in java(解析,填充)。 定义:多态算是一种泛化机制,解决了一部分可以应用于多...
    谷歌清洁工阅读 462评论 0 2
  • 在一个刷着奶黄色漆的屋子里靠墙围着一圈椅子 上面坐着一圈小动物 具体说是一圈小兔子 有小灰兔 小花兔 小白兔 小黄...
    EvelynSu阅读 719评论 0 0
  • 我是今天早上得知自己快要死掉的。 梦里有个声音对我说:“你马上要死了,过完今天就要死了。” 我惊醒,看了看手机,显...
    空碗儿阅读 724评论 0 0