JAVA

Java에서 Stack Class는 함정카드다.

킹명주 2024. 6. 13. 23:54

프로젝트를 진행하다가 Last In First Out과 같은 자료구조를 활용한 경험이 있다.

그래서 Stack을 사용하려고 했는데, 얼핏 누군가가 나에게 "Java Stack은 지양해주세요." 라는 말을 했던 것 같다.. 

 

Java에서 Stack 사용을 왜 지양해야하는지, 공부했던 내용을 공유하고자 한다.


Stack은 무엇인가요?

출처: https://ko.wikipedia.org

 

스택에 대한 내용은 이미 잘 아시겠지만~ 리마인드 차원에서 간단하게 짚고 넘어가보자!

스택은 한 쪽 끝에서만 자료를 넣거나 뺄 수 있는 선형 구조이며 Last In First Out 매커니즘을 가지고 있다.

 

Java에서 간단한 Stack 사용법을 보자면..

import java.util.Stack;

// init
Stack<Integer> st = new Stack<>();
// push
st.push(1);
st.push(2);
// pop & print
while(!st.empty()){
    System.out.printf("%d ", st.pop());
}

 

요런식으로 사용할 수 있다. (이것만 알아도 stack 완전 정복~)

 


Stack Class의 문제점

 

 

그래서 왜 Why Java에서 Stack 사용을 지양해야할까? 필자는 크게 2가지 문제가 있다고 생각한다.

 

1. LIFO 구조를 장담할 수 있나?

Stack은 Vector의 특징을 그대로 받아서 사용하고 있다. 그러므로 중간 삽입/삭제, set을 활용하여 임의로 값을 조작할 수도 있다.

// init
Stack<Integer> st = new Stack<>();
// push
st.push(1);
st.push(2);
// vector add
st.add(0, 3);
// vector set
st.set(0, 999);
while(!st.empty()){
    int t = st.pop();
    System.out.printf("%d ", t);
}

(사실 이 문제는 대안으로 소개할 Deque도 동일하다..)

 

2. Vector로부터 상속받은 구조 ⭐⭐

Stack class를 살펴보면

package java.util;

/**
 * The {@code Stack} class represents a last-in-first-out
 * (LIFO) stack of objects. It extends class {@code Vector} with five
 * operations that allow a vector to be treated as a stack. 
 */
public class Stack<E> extends Vector<E> {

    public Stack() {
    }

Vector를 통해 상속받은 것을 확인할 수 있다. 여기서 의문인 것은 Vector를 통해 상속받은 것이 왜 문제가 될까?

이를 알기 위해서는 우리의 주적인 Vector에 대해 알고 있어야 한다.

 

 

Step1) Vector란?

출처: https://www.geeksforgeeks.org/implementing-sorted-vector-in-java

 

고대유물과 같은 Vector는 List 역할을 수행할 수 있는데 List 기능을 가지고 있는 Vector가 무엇이 문제일까..?

 

Step2) Vector 파해치기

Vector class를 살펴보자.

    public synchronized E get(int index) {
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);

        return elementData(index);
    }

    /**
     * Replaces the element at the specified position in this Vector with the
     * specified element.
     *
     * @param index index of the element to replace
     * @throws ArrayIndexOutOfBoundsException if the index is out of range
     *         ({@code index < 0 || index >= size()})
     * @since 1.2
     */
    public synchronized E set(int index, E element) {
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

get, set 역할을 하는 모든 메서드에 synchronized 키워드가 선언되어 잇는 것을 확인할 수 있다.  Vector의 장점이자 단점인데,, 당연하게도 Single Thread 환경에서는 가장 큰 단점이 될 것이다.

동기화를 통해 멀티 쓰레드 환경에서 안전하게 사용할 수 있다. (완전히 Thread-Safety라고 할 수 없지만..)

 

⇒ 단일쓰레드 환경에서?

Vector<Integer> vector = new Vector<>();
for (int i = 0; i < 10000000; i++) {
    vector.add(i);
}

ArrayList<Integer> arrayList = new ArrayList<>();
for (int i = 0; i < 10000000; i++) {
    arrayList.add(i);
}

결과는~~

이처럼 Vector는 항상 동기화를 진행하기 때문에 오버헤드와 시간 소모가 발생할 수 있다.

그러므로 Vector의 특징을 그대로 물려받은 Stack을 많이 썼다가.. Oops~

 


Stack의 대안을 내놓으시오

 

 

이제 stack의 문제점을 파악했을 것이라고 생각한다! 그래서 Stack을 못쓰면 어떤걸 쓰라는 것일까?

 

https://docs.oracle.com/javase/8/docs/api/java/util/Stack.html

 

Stack (Java Platform SE 8 )

The Stack class represents a last-in-first-out (LIFO) stack of objects. It extends class Vector with five operations that allow a vector to be treated as a stack. The usual push and pop operations are provided, as well as a method to peek at the top item o

docs.oracle.com

위 주소와 Stack class의 주석을 해석해보자면, Stack보다 Deque 사용을 우선적으로 고려하라는 뜻이다.

그런데.. 왜 Deque가 대안일까?

 

일단, Deque는 Duoble-Ended Queue을 줄임말로 큐의 양쪽에 데이터를 넣고 뺄 수 있는 자료구조로 Stack 용도로 충~~분히 활용할 수 있다. 필자가 생각할 때, 공식문서에서도 deque를 사용하라는 이유는 크게 2가지가 있다.

 

1. Interface

//stack
public class Stack<E> extends Vector<E> { ...}

//Deque
public interface Deque<E> extends Queue<E>, SequencedCollection<E>

Stack의 경우 class로 되어있지만 Deque의 경우 interface로 선언이 되어있는데 그 덕분에 ArrayDeque, LinkedList에서 Deque 인터페이스를 구현하여 유연하게 사용하고 있다. Stack의 경우 단일상속만 가능하기 때문에 유연성 측면에서 현저히 떨어진다.

 

2. 성능

Deque의 경우 동기화를 수행하지 않기 때문에 오버헤드, 속도 측면에서 더 나은 성능을 보여줄 수 있다.

 

 


오늘의 한줄평

 

Stack은 지양하고 Deque 사용을 지향하자.