今天在网上闲逛时看到了这样一个言论,说“Java的Stack类实现List接口的设计是个笑话”。
 
当然作者这篇文章的重点不是这个,原本我也只是一笑置之,然而看评论里居然还有人附和,说“Java那种Stack的设计作为笑话,差不多可以算公案了”,我就有点不淡定了,为什么、什么时候“作为笑话”的并且“差不多可以算公案”了呢?
 
因此我决定写一篇文章来谈谈这个问题。
 
Java中Stack类的声明
 
首先我们来看看Java中Stack类的声明:
 
public class Stack<E> extends Vector<E>
 
Vector:
 
public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable
 
也就是说,Stack定义了标准的push/pop等操作,同时也实现了List和RandomAccess接口。List接口在Java中表示的是一个顺序列表,RandomAccess则在顺序访问的基础上又加上了“随机访问”的约定。一般来说,随机访问的时间复杂度为O(1)。例如数组就是一个“顺序且随机访问”的列表,而链表则是一个“顺序非随机访问”的列表。
 
接口是什么
 
狭义地讲,接口就是一个类所定义的方法(方法名、参数、返回值)。一个类提供了Foo方法,其他类就可以调用它。广义上讲,接口可以理解为一个子系统和其他子系统之间为了交互所做的约定。这里的子系统可能是类,也可以是模块,或其他任何能够可以跟外界产生交互的实体。
 
注意:在Java中,我们可以使用Interface来定义一个接口,Interface不做任何事情,仅仅用来为实现提供“规范”,因此Interface比抽象类更纯粹,抽象层次往往也要更高一些。但是请注意这里的Interface和泛指的“接口”不同,Interface是Java为了更好地支持“针对接口编程”而提供的一种语言机制,即使不用Interface我们也可以很好地做到“针对接口编程”。
 
如果一个类完全遵守一个接口,就可以说这个类“实现”了这个接口。实现一个接口在语言层面是任意的,因为语言只能对语法进行检查,而无法对语意进行检查。例如你可以让People类实现Plant接口,于是这个人就可以随时被当作一颗植物来使用(植物人?)。对动态语言来说,这种情况更加明显:只要一个对象表现出了某种行为特征,那么这个对象就可以被当作另一个对象来使用。人们给动态语言的这种特性起了一个形象化的名字:鸭子类型(Duck Typing)。也就是说,只要一个对象能够“呱呱叫”,那么就完全可以把它当成一只鸭子。
 
但是从设计者的角度来看,一个类实现哪些接口,除了要符合语法之外,更应该符合语意,这样才能让使用这个类的人不至于产生迷惑。
 
因此,“Stack类应不应该实现List接口”是一个语意问题而不是语法问题。要回答这个问题,首先要了解Stack是什么,以及在哪些场景下需要用到它。
 
Stack是什么
 
Stack,栈,是一种所有程序员都很熟悉的数据机构。栈通常是“后进先出”的(LIFO)。作为一个惯例,栈通常具有名为push和pop的2个操作,push操作将一个元素存入栈中(压入),pop操作则相反,将一个元素从栈顶取出(弹出)。
 
栈的应用
 
多数情况用到栈时,仅仅将其当作一个“栈”来使用即可,即只需使用push/pop操作,例如可以用栈来检查括号匹配问题,或计算后缀表达式的值。
 
另一个例子是计算机中的过程调用(procedure call)。在调用一个过程(也称为子过程、函数)之前,需要先将当前帧指针、返回地址压入栈中,然后依次压入各参数,最后执行call指令跳转到目标过程的起始地址继续执行。其栈结构如下图所示。
 
 
从过程中返回和调用时正好相反:先从栈上将各参数和临时变量弹出(一般直接丢弃),然后执行ret指令跳转到返回地址所在的位置继续执行。
 
随机访问栈
 
到目前为止,程序对栈的操作还只是标准的push/pop操作。但在一个过程的内部,除了要用到push/pop操作之外,可能还需要对栈做随机访问(类似于数组的访问方式)。以上图为例,过程Q中的代码可能要访问参数y1,但又不能丢掉y2,因此不能使用pop操作。
 
事实上,在所有基于栈来实现过程调用的计算机中,编译器都是使用随机访问的方式操作栈上的参数和临时变量的,只有在call和ret时才需要对栈进行push/pop操作。此时的栈对过程来说更像是一个数组。例如过程Q可能通过下面的方式来访问y1:
 
*(SP + )
这里SP是栈指针,它指向栈顶,并且假设y2占用1个机器字长(例如在32位系统中4个字节为一个机器字长)。另外之所以是加1而不是减1,是因为栈是由高地址向低地址增长的,因此y2的地址比y1的小。
 
从这个例子可以看出,根据使用场景的不同,栈有时也需要像数组一样的随机访问方式,在这种情况下,实际上是把栈看成了一个数组。
 
Java的Stack类实现List到底是不是一个笑话
 
从上面的例子可以看出,Stack实现List和RandomAccess接口是完全合理的。如果你需要把它当成一个纯粹的栈来使用,你只需将其看成一个普通的“Stack”实例即可。但如果你需要对其进行随机访问,你可以随时把它当成一个“RandomAccess的List”来使用。这两种情况都有可能会遇到。
 
所以我看了上面的文章和评论后对“Stack实现List是一个笑话”感到非常疑惑,更不明白在什么时候这已经成为了所谓的“公案”了。在我看来,这非常合理,也不会对我正确使用Stack产生任何不良影响。
 
周星驰在《唐伯虎点秋香》里对夺命书生说:谁说没有枪头就捅不死呢?这里借用一下:谁说Stack实现List是个笑话,只是你了解的还不够而已。
 
 
 
 
 

Java的Stack类实现List接口真的是个笑话吗的更多相关文章

  1. java.util.Stack类中的peek()方法

    java.util.stack类中常用的几个方法:isEmpty(),add(),remove(),contains()等各种方法都不难,但需要注意的是peek()这个方法. peek()查看栈顶的对 ...

  2. Java实现Stack类

    Java实现Stack类 import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Sc ...

  3. java集合类——Stack类

    查看java的API文档,Stack继承Vector类. 栈的特点是后进先出. API中Stack自身的方法不多,基本跟栈的特点有关. import java.util.Stack; public c ...

  4. java.util.Stack类简介

    Stack是一个后进先出(last in first out,LIFO)的堆栈,在Vector类的基础上扩展5个方法而来 Deque(双端队列)比起Stack具有更好的完整性和一致性,应该被优先使用 ...

  5. java.util.Stack类中 empty() 和 isEmpty() 方法的作用

    最近在学习算法和数据结构,用到Java里的Stack类,但程序运行结果一直和我预料的不一样,网上也没查清楚,最后查了API,才搞明白. java.util.Stack继承类 java.util.Vec ...

  6. 探Java多线程Thread类和Runnable接口之间的联系

    首先复习一下Java多线程实现机制,Java实现多线程方法有如下这么几种: 1.继承了(extends)Thread类 2.实现了(implements)Runnable接口 也就是说  有如下两种情 ...

  7. java.util.Stack类简介(栈)

    Stack是一个后进先出(last in first out,LIFO)的堆栈,在Vector类的基础上扩展5个方法而来 Deque(双端队列)比起stack具有更好的完整性和一致性,应该被优先使用 ...

  8. Java中的栈:java.util.Stack类

    public class Stack<E>extends Vector<E>Stack 类表示后进先出(LIFO)的对象堆栈.它通过五个操作对类 Vector 进行了扩展 ,允 ...

  9. java:使用匿名类直接new接口

    java中的匿名类有一个倍儿神奇的用法,见下面代码示例: package contract; public interface ISay { void sayHello(); } 上面是一个简单的接口 ...

随机推荐

  1. 十大最佳Leap Motion体感控制器应用

    十大最佳Leap Motion体感控制器应用   Leap Motion Controller也许还没有准备好大规模的发售,但是毫无疑问,这款小巧的动作捕捉器是我们见过的最酷的设备之一.这款设备的硬件 ...

  2. Unity3D for VR 学习(1): 又一个新玩具 暴风魔镜 4(Android)

    2016年伊始,有了VR虚拟现实硬件设备:  暴风魔镜4–好奇者的新玩具 . 2015年下半年的朋友圈中各种VR.AR的新闻层次不穷,搞的我也心痒痒的:好歹咱也是职业的Unity3D程序员,高大上的O ...

  3. git使用经验(一)

    在使用Git Push代码到数据仓库时,提示如下错误: [remote rejected] master -> master (branch is currently checked out) ...

  4. 「Python」7个不一样的代码写法

    打印index 对于一个列表,或者说一个序列我们经常需要打印它的index,一般传统的做法或者说比较low的写法: 更优雅的写法是多用enumerate 两个序列的循环 我们会经常对两个序列进行计算或 ...

  5. React读取Excel——js-xlsx 插件的使用

    介绍 SheetJS js-xlsx 是一款能够读写多种格式表格的插件,浏览器支持良好,并且能在多个语言平台上使用,目前在 github 上有 12602 个 star, 刚好项目中遇到了前端解析 e ...

  6. NOIP模拟赛17

    5分.... T1 LOJ 计算几何瞎暴力 维护以下操作: 1.序列末尾加一个数 2.序列全体从小到大排序 3.查询区间和 4.序列全体异或一个数k 序列全体异或一个数,很明显是trie树 那么序列全 ...

  7. UVA 11982 Fantasy Cricket

    https://vjudge.net/problem/UVA-11982 题意: 给出一个包含’U’, ‘D’, ‘E’的字符串, ’U’ 表示需要把这个字符向后移动, ’D’表示需要把这个字符向前移 ...

  8. HDU 1431 思维 基础数论

    找范围内回文素数,最大到1e8,我就是要枚举回文串,再判素数,然后因为这种弱智思路死磕了很久题目. /** @Date : 2017-09-08 15:24:43 * @FileName: HDU 1 ...

  9. Productivity tips, tricks and hacks for academics (2015 edition)

    Productivity tips, tricks and hacks for academics (2015 edition) Contents Jump to: My philosophy: Op ...

  10. 动态加载js和css的jquery plugin

    一个简单的动态加载js和css的jquery代码,用于在生成页面时通过js函数加载一些共通的js和css文件. //how to use the function below: //$.include ...