今天在网上闲逛时看到了这样一个言论,说“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. struts2(s2-052)远程命令执行漏洞复现

    漏洞描述: 2017年9月5日,Apache Struts发布最新安全公告,Apache Struts2的REST插件存在远程代码执行的高危漏洞,该漏洞由lgtm.com的安全研究员汇报,漏洞编号为C ...

  2. css美化Div边框的样式实例

    很多时候如果不是用了很多样式,很难把边框修饰得好看,看了一篇博文,觉得真的挺漂亮,也挺好看. 转载的博文地址 将这段美化的css代码 border:1px solid #96c2f1;backgrou ...

  3. Codeforces 864E dp

    题意: 房间着火了,里面有n件物品,每件物品有营救需要的时间t,被烧坏的最晚时间d,他的价值p,问能得到的最大价值,并且输出营救出来的物品编号 代码: //必然是先救存活时间短的即d小的,所以先排个序 ...

  4. bzoj 1004 组合

    代码: //根据Burnside定理:有m个置换k钟颜色,所有本质不同的染色方案数就是每种置换的不变元素的个数的平均数.所谓不变元素就是一种染色方案 //经过置换变换后和之前一样.所以现在就是要求不变 ...

  5. Qt ------ 我定义的规则 之 对象命名规则

    类型 + 特性,比如  button_closeLigth 非公有的变量前面要加上小写m_ (指的修饰符为private时) 静态变量前面加上小写s_ 其它变量以小写字母开头 静态变量全大写 (sta ...

  6. linux 文件IO

    1.文件描述符 (1)文件描述符的本质是一个数字,这个数字本质上是进程表中文件描述符表的一个表项,进程通过文件描述符作为index去索引查表得到文件表指针,再间接访问得到这个文件对应的文件表.(2)文 ...

  7. python---Scrapy模块的使用(二)

    出处:http://www.cnblogs.com/wupeiqi/ 一:去除重复URL scrapy默认使用 scrapy.dupefilter.RFPDupeFilter 进行去重,相关配置有: ...

  8. JS中的new操作符原理解析

    var Person = function(name){ this.name = name; } Person.prototype.sayHello = function() { console.lo ...

  9. MongoDB-3.4集群搭建:分片

    概念 集群拥有三个节点: 分片(sharding),分发路由(query routers)和配置服务器 (config server) Shard 分片是存储了一个集合部分数据的MongoDB实例,每 ...

  10. ASP.Net中表单POST到其他页面的方法

    在ASP中,我们通常把表单提交到另外一个页面(接受数据页面).但是在ASP.NET中,服务端表单通常都是提交到本页面的,如果我设置 form1.action="test.aspx" ...