如何在Java中设计一个类型安全堆栈,以防止POP出现在空列表中?

如何在Java中设计一个类型安全堆栈,以防止POP出现在空列表中?,java,generics,data-structures,types,type-safety,Java,Generics,Data Structures,Types,Type Safety,这是这两个问题的一个分支: 我想用Java实现类型安全的数据结构,以防止不必要的操作。例如,如果编译器知道我有一个空堆栈的实例,它应该不允许我在空堆栈上调用pop 例如,我将如何在Java中实现这样一个(通用)堆栈?请参见下面基于.net代码的Java实现 如果客户端尝试弹出太远,编译器将发出未定义的方法错误。例如,发出如下调用: new EmptyStack().push(1.pop().getTop() 将导致调用getTop()时出现未定义的方法错误 类GenericStack{ @测试公

这是这两个问题的一个分支:

我想用Java实现类型安全的数据结构,以防止不必要的操作。例如,如果编译器知道我有一个空堆栈的实例,它应该不允许我在空堆栈上调用pop


例如,我将如何在Java中实现这样一个(通用)堆栈?

请参见下面基于.net代码的Java实现

如果客户端尝试弹出太远,编译器将发出未定义的方法错误。例如,发出如下调用:

new EmptyStack().push(1.pop().getTop()

将导致调用
getTop()
时出现未定义的方法错误

类GenericStack{
@测试公共无效测试(){
最终IStack堆栈=新的清空堆栈();
assertEquals(新整数(1),stack.push(1.getTop());
assertEquals(新整数(2),stack.push(1.push(2.getTop());
assertEquals(新整数(1),stack.push(1.push(2.pop().getTop());
}
接口IStack{
INonEmptyStack推送(tx);
}
接口IEmptyStack扩展了IStack
{
@覆盖INonEmptyStack推送(TX);
}
接口INonEmptyStack
扩展IStack
{
T getTop();
tspop();
@覆盖INonEmptyStack
推力(tx);
}
类EmptyStack实现了IEMPyStack{
@覆盖公共INonEmptyStack推送(tx){
返回新的非空堆栈(x,this);
}
}
类NonEmptyStack扩展对象
实现INonEmptyStack{
stackBeneathTop的私人最终TSTACK;
私人决赛T-top;
非空堆栈(T形顶部、T形顶部){
this.top=top;
this.stackBeneathTop=stackBeneathTop;
}
@重写公共T getTop(){
返回顶部;
}
@覆盖公共TSPOP(){
返回栈顶;
}
@覆盖公共INonEmptyStack
推力(T x){
返回
新的非空堆栈(x,this);
}
}
//应@TacticalCoder的请求,以下客户端代码演示了
//此实现的一些好处(和限制)。
@测试公共void testRandomPopper(){
IStack stack=randomPopper(新的清空堆栈(),20);
//此断言将在.3^20次运行中失败1次
assertTrue(INonEmptyStack的堆栈实例);
assertFalse(IEmptyStack的堆栈实例);
}
公共IStack随机波普尔(IStack s,最终整数N){
IStack堆栈;
如果(N)maybeEmptyStack=stack;
assertFalse(可能是IEmptyStack的mptystack实例);
//下面的cast应该发出警告!不发出警告并发出运行时错误。
IEmptyStack definitelyemptycstack=(IEmptyStack)可能是mptystack;
assertFalse(IEmptyStack的definiteEmptyStack实例);
}
公共IStack DRAISTACK(IStack堆栈){
对于(;INonEmptyStack;的堆栈实例)
stack=((INonEmptyStack)stack.Pop();
返回栈;
}
}

换句话说,对于空的堆栈,您需要一个单独的类型。扩展来说,对于只有一个元素的堆栈,您需要一个单独的类型,因为弹出需要返回一个
EmptyStack
。依此类推……推论:您必须在编译时知道堆栈的长度,除非您愿意执行强制转换,否则哪种类型f违背了目的。@glenviewjeff:从你的问题中,我可以告诉你,你肯定想读“Tony Morris”的博客(他是IBM JVM工程师……他有很强的自我意识,但他非常优秀,这绝对是最好的编码博客之一).他在博客上写函数式编程:大部分是Haskell,但也有一些关于Java的精彩文章。这里有一个挑战:写一个完全安全的Tic Tac Toe,如果你试图做一个无效的移动,就会抛出编译时错误。s/erros/errors/(该死的评论只能编辑五分钟;)@TacticalCoder感谢你的博客——我会去看看!删除没有时间限制,所以我通常只删除错误的评论,然后创建新的评论并进行更正。但是这个堆栈不实用,对吗?你说,如何进行for循环(比如20次)它在每次传递时都会在堆栈上推送一个数字,并且有一次机会从树中弹出一些东西。然后在完成循环后,弹出所有剩余的数字?可以这样做吗?(不使用任何instanceof/reflection技巧)@TacticalCoder如何使用instanceof“技巧”使用它有什么问题?还有,当你写“从树中拿出一次机会从堆栈中弹出一些东西”时,你想说什么我的意思是,看到你想要达到的目标,使用instanceOf是不干净的。你先做instanceOf,然后做casting,这会有点挫败你尝试做的事情的全部目的。我关于随机推或弹出堆栈的观点很简单,你在这里写的堆栈似乎根本不实用。你说什么你想做的事是可以做到的,但我认为扭曲泛型不是一条路。我将添加一个我自己的答案来说明我的想法。你写道“如果客户端尝试弹出太远,编译器将发出错误”…但据我所知,zs只在“手动”时起作用从源代码中推送和弹出。如果以编程方式将/pop推送到堆栈中,我看不出它起作用。这就是为什么,在我自己的解决方案/答案中,作为测试,我确实从for循环内部随机将/pop推送到我创建的堆栈中。在我的解决方案中,编译器不允许您编译一个试图弹出emp的程序ty stack(您的要求),我可以通过编程将/pop推送到堆栈中/从堆栈中弹出。@TacticalCoder请查看答案代码末尾附加的解决方案。如果您试图在未使用
instanceof
测试对象的情况下强制转换对象,编译器将向您发出警告,并且可以将其配置为
class GenericStack {
   @Test public void test() {
      final IStack<Integer> stack = new EmptyStack<Integer>();
      assertEquals(new Integer(1), stack.push(1).getTop());
      assertEquals(new Integer(2), stack.push(1).push(2).getTop());
      assertEquals(new Integer(1), stack.push(1).push(2).pop().getTop());
   }

   interface IStack<T> { 
      INonEmptyStack<T, ? extends IStack<T>> push(T x);
   }

   interface IEmptyStack<T> extends IStack<T>
   {
       @Override INonEmptyStack<T, IEmptyStack<T>> push(T x);
   }

   interface INonEmptyStack<T, TStackBeneath extends IStack<T>> 
      extends IStack<T>
   {
       T getTop();
       TStackBeneath pop();
       @Override INonEmptyStack<T, INonEmptyStack<T, TStackBeneath>> 
          push(T x);
   }

   class EmptyStack<T> implements IEmptyStack<T> {
      @Override public INonEmptyStack<T, IEmptyStack<T>> push(T x) {
         return new NonEmptyStack<T, IEmptyStack<T>>(x, this);
      }
   }

   class NonEmptyStack<T, TStackBeneath extends IStack<T>> extends Object 
      implements INonEmptyStack<T, TStackBeneath> {
      private final TStackBeneath stackBeneathTop;
      private final T top;

      NonEmptyStack(T top, TStackBeneath stackBeneathTop) {
         this.top = top;
         this.stackBeneathTop = stackBeneathTop;
      }

      @Override public T getTop() {
         return top;
      }

      @Override public TStackBeneath pop() {
         return stackBeneathTop;
      }

      @Override public INonEmptyStack<T, INonEmptyStack<T, TStackBeneath>> 
         push(T x) {
         return 
            new NonEmptyStack<T, INonEmptyStack<T, TStackBeneath>>(x, this);
      }
   }

   // The following client code at the request of @TacticalCoder demonstrates
   // some of the benefits (and limitations) of this implementation.

   @Test public void testRandomPopper() {
      IStack<?> stack = randomPopper(new EmptyStack<Integer>(), 20);
      // This assertion will fail 1 out of .3^20 runs 
      assertTrue(stack instanceof INonEmptyStack<?,?>); 
      assertFalse(stack instanceof IEmptyStack<?>); 
   }

   public IStack<Integer> randomPopper(IStack<Integer> s, final int N) {
      IStack<Integer> stack;
      if(N<1)
         return s;
      stack = s.Push(1);
      for (int i = 1; i < N; i++) {
         INonEmptyStack<Integer,?> tStack = stack.Push(i+1);
         if(Math.random()<0.3) {
            stack = tStack.Pop();            
         } else {
            stack = tStack;
         }
      }
      return stack;
   }

   @Test public void testDrainStack() {
      IStack<Integer> stack = randomPopper(new EmptyStack<Integer>(), 20);
      IStack<?> maybeEmptyStack = drainStack(stack);
      assertTrue(maybeEmptyStack instanceof IEmptyStack);
      IEmptyStack<?> definitelyEmptyStack = (IEmptyStack<?>) maybeEmptyStack;
      assertTrue(definitelyEmptyStack instanceof IEmptyStack<?>); 
   }

   @Test public void testCastNonEmptyStackToEmptyStack() {
      IStack<Integer> stack = randomPopper(new EmptyStack<Integer>(), 20);
      IStack<?> maybeEmptyStack = stack;
      assertFalse(maybeEmptyStack instanceof IEmptyStack);
      // Below cast should issue warning!  Doesn't and issues runtime error.
      IEmptyStack<?> definitelyEmptyStack = (IEmptyStack<?>) maybeEmptyStack;
      assertFalse(definitelyEmptyStack instanceof IEmptyStack<?>); 
   }

   public IStack<?> drainStack(IStack<?> stack) {
      for (;stack instanceof INonEmptyStack<?,?>;)
         stack = ((INonEmptyStack<?,?>) stack).Pop();
      return stack;
   }
}