Java Stack快速指南

2023/06/07

1. 概述

在这篇简短的文章中,我们将介绍java.util.Stack类并开始研究如何使用它。

是一种通用数据结构,表示对象的LIFO(后进先出)集合,允许在恒定时间内压入/弹出元素。

对于新的实现,我们应该支持Deque接口及其实现。Deque定义了一组更完整和一致的LIFO操作。但是,我们可能仍然需要处理Stack类,尤其是在遗留代码中,因此很好地理解它很重要。

2. 创建Stack

让我们首先创建一个空的Stack实例,使用默认的无参数构造函数:

@Test
public void whenStackIsCreated_thenItHasSizeZero() {
    Stack<Integer> intStack = new Stack<>();
    
    assertEquals(0, intStack.size());
}

这将创建一个默认容量为10的Stack。如果添加的元素数量超过栈总大小,它会自动加倍。但是,它的大小在删除元素后永远不会缩小。

3. Stack的同步

Stack是Vector的直接子类;这意味着与其超类类似,它是一个同步实现

但是,并不总是需要同步,在这种情况下,建议使用ArrayDeque。

4. 添加元素到栈

让我们首先使用push()方法将一个元素添加到Stack的顶部-该方法还会返回已添加的元素:

@Test
public void whenElementIsPushed_thenStackSizeIsIncreased() {
    Stack<Integer> intStack = new Stack<>();
    intStack.push(1);
    
    assertEquals(1, intStack.size());
}

使用push()方法与使用addElement()具有相同的效果。唯一的区别是addElement()返回操作的结果,而不是添加的元素

我们也可以一次添加多个元素:

@Test
public void whenMultipleElementsArePushed_thenStackSizeIsIncreased() {
    Stack<Integer> intStack = new Stack<>();
    List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
    
    boolean result = intStack.addAll(intList);
    
    assertTrue(result);
    assertEquals(7, intList.size());
}

5. 从栈中检索

接下来,让我们看看如何获取和删除Stack中的最后一个元素:

@Test
public void whenElementIsPoppedFromStack_thenElementIsRemovedAndSizeChanges() {
    Stack<Integer> intStack = new Stack<>();
    intStack.push(5);

    Integer element = intStack.pop();
    
    assertEquals(Integer.valueOf(5), element);
    assertTrue(intStack.isEmpty());
}

我们还可以在不删除的情况下获取栈的最后一个元素:

@Test
public void whenElementIsPeeked_thenElementIsNotRemovedAndSizeDoesNotChange() {
    Stack<Integer> intStack = new Stack<>();
    intStack.push(5);

    Integer element = intStack.peek();

    assertEquals(Integer.valueOf(5), element);
    assertEquals(1, intStack.search(5));
    assertEquals(1, intStack.size());
}

6. 在栈中搜索元素

6.1 搜索

Stack允许我们搜索一个元素并获取它与顶部的距离:

@Test
public void whenElementIsOnStack_thenSearchReturnsItsDistanceFromTheTop() {
    Stack<Integer> intStack = new Stack<>();
    intStack.push(5);
    intStack.push(8);

    assertEquals(2, intStack.search(5));
}

结果是给定对象的索引。如果存在多个元素,则返回最接近顶部的那个元素的索引。位于堆栈顶部的元素被视为位于位置1。

如果未找到对象,search()将返回-1。

6.2 获取元素索引

要获取Stack上元素的索引,我们还可以使用indexOf()和lastIndexOf()方法:

@Test
public void whenElementIsOnStack_thenIndexOfReturnsItsIndex() {
    Stack<Integer> intStack = new Stack<>();
    intStack.push(5);
    
    int indexOf = intStack.indexOf(5);
    
    assertEquals(0, indexOf);
}

lastIndexOf()将始终找到最接近堆栈顶部的元素的索引。这与search()非常相似-重要的区别是它返回索引,而不是与顶部的距离:

@Test
public void whenMultipleElementsAreOnStack_thenIndexOfReturnsLastElementIndex() {
    Stack<Integer> intStack = new Stack<>();
    intStack.push(5);
    intStack.push(5);
    intStack.push(5);
    
    int lastIndexOf = intStack.lastIndexOf(5);
    
    assertEquals(2, lastIndexOf);
}

7. 从堆栈中删除元素

除了用于删除和检索元素的pop()操作之外,我们还可以使用从Vector类继承的多个操作来删除元素。

7.1 删除指定元素

我们可以使用removeElement()方法删除第一次出现的给定元素:

@Test
public void whenRemoveElementIsInvoked_thenElementIsRemoved() {
    Stack<Integer> intStack = new Stack<>();
    intStack.push(5);
    intStack.push(5);

    intStack.removeElement(5);
    
    assertEquals(1, intStack.size());
}

我们还可以使用removeElementAt()来删除Stack中指定索引下的元素:

@Test
public void whenRemoveElementAtIsInvoked_thenElementIsRemoved() {
    Stack<Integer> intStack = new Stack<>();
    intStack.push(5);
    intStack.push(7);
    
    intStack.removeElementAt(1);
    
    assertEquals(-1, intStack.search(7));
}

7.2 删除多个元素

让我们快速看一下如何使用removeAll() API从Stack中删除多个元素-它将Collection作为参数并从Stack中删除所有匹配的元素:

@Test
public void givenElementsOnStack_whenRemoveAllIsInvoked_thenAllElementsFromCollectionAreRemoved() {
    Stack<Integer> intStack = new Stack<>();
    List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
    intStack.addAll(intList);
    intStack.add(500);

    intStack.removeAll(intList);

    assertEquals(1, intStack.size());
    assertEquals(1, intStack.search(500));
}

也可以使用clear()或removeAllElements()方法从堆栈中删除所有元素;这两种方法的工作原理相同:

@Test
public void whenRemoveAllElementsIsInvoked_thenAllElementsAreRemoved() {
    Stack<Integer> intStack = new Stack<>();
    intStack.push(5);
    intStack.push(7);

    intStack.removeAllElements();

    assertTrue(intStack.isEmpty());
}

7.3 使用过滤器删除元素

我们还可以使用条件从Stack中删除元素。让我们看看如何使用removeIf()来执行此操作,并将过滤器表达式作为参数:

@Test
public void whenRemoveIfIsInvoked_thenAllElementsSatysfyingConditionAreRemoved() {
    Stack<Integer> intStack = new Stack<>();
    List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
    intStack.addAll(intList);
    
    intStack.removeIf(element -> element < 6);
    
    assertEquals(2, intStack.size());
}

8. 遍历堆栈

Stack允许我们同时使用Iterator和ListIterator。主要区别在于第一个允许我们在一个方向上遍历Stack,而第二个允许我们在两个方向上执行此操作:

@Test
public void whenAnotherStackCreatedWhileTraversingStack_thenStacksAreEqual() {
    Stack<Integer> intStack = new Stack<>();
    List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
    intStack.addAll(intList);
    
    ListIterator<Integer> it = intStack.listIterator();
    
    Stack<Integer> result = new Stack<>();
    while(it.hasNext()) {
        result.push(it.next());
    }

    assertThat(result, equalTo(intStack));
}

Stack返回的所有迭代器都是快速失败的。

9. Java Stack的Stream API

Stack是一个集合,这意味着我们可以将它与Java Stream API一起使用。将Stream与Stack一起使用类似于将它与任何其他Collection一起使用

@Test
public void whenStackIsFiltered_allElementsNotSatisfyingFilterConditionAreDiscarded() {
    Stack<Integer> intStack = new Stack<>();
    List<Integer> inputIntList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 9, 10);
    intStack.addAll(inputIntList);

    List<Integer> filtered = intStack
        .stream()
        .filter(element -> element <= 3)
        .collect(Collectors.toList());

    assertEquals(3, filtered.size());
}

10. 总结

本教程是了解Java中Stack这个核心类的快速实用指南。

当然,你可以在Javadoc中探索完整的API。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章