Midnight Coder's Lounge

[Java] Queue의 .add()와 .offer()는 뭐가 다른가요? 본문

Language

[Java] Queue의 .add()와 .offer()는 뭐가 다른가요?

AtomicLiquors 2024. 7. 22. 20:52

 요약 

.add()와 .offer()는 똑같이 Queue에 요소를 삽입하는 연산이다.
Queue에 크기 제한이 있을 경우 동작하는 방식이 다르다.
- .add()는 크기 제한을 넘은 경우 예외를 발생시킨다. 예외처리를 하지 않았다면 프로그램을 비정상적으로 종료한다.
- .offer()는 크기 제한을 넘어도 예외를 발생시키지는 않는다. 대신, 삽입이 되지 못했다는 사실을 사용자가 인지하지 못할 수도 있다.

 

 

 

 사전지식  

예외, 예외처리, 함수의 반환 값(return value)

 

 

 


 

 

 상세 

 

 

".add() 대신 .offer()를 사용하신 이유가 있나요?"

알고리즘 그룹 스터디를 하다가, Queue를 사용하는 문제풀이에 댓글로 문의가 들어와서 글을 작성하게 되었습니다.

사실 평소에 알고리즘을 풀 때는 크게 차이점은 없고, 각자 평소에 익숙한 메서드를 골라서 쓰게 마련입니다.

Queue를 처음 배울 때 한 번 들었을 법도 한데 잘 떠오르지 않아,
이번 기회에 공식 문서를 보고 차이점을 확실히 알아보기로 했습니다.

 

 



[ 공식 문서 ]

Java 공식 문서의 Queue 항목을 보겠습니다. 

(백준에서 많이 사용하는 JDK 11 문서를 보겠습니다.)

읽기 편하시라고 의역을 많이 섞었으니 양해부탁드립니다.

 

 

 

.add()

먼저 .add()의 설명을 볼게요.

 

"현재 큐에 지정한 요소를 삽입하되, 크기 제한에 걸리지 않고 즉시 수행 가능한 경우에 실행합니다.

성공할 경우 true 값을 반환하고, 큐에 빈 공간이 없다면 IllegalStateException을 발생시킵니다."

 

 

크기 제한이 있는 큐가 꽉 찼을 때, add()를 실행하면 'IllegalStateException'이라는 에러를 발생시킨다고 합니다.

 

 


.offer()

다음으로 offer를 봅시다.

"현재 큐에 지정한 요소를 삽입하되, 크기 제한에 걸리지 않고 즉시 수행 가능한 경우에 실행합니다.

크기 제한이 있는 큐를 사용한다면 보통 .add()보다는 이 메서드를 사용하는 것이 더 바람직한데, 
.add()는 똑같은 이유로 원소 삽입에 실패한다면 예외를 발생시킬 수밖에 없기 때문입니다."

 

똑같은 상황일 때 .add()와는 다르게 에러를 발생시키지 않는다고 합니다.

 

실제로 Throws: 아래에 나와 있는, 각 메서드가 발생시키는 에러 목록을 보면

앞서 .add()에는 'IllegalStateException"이 있었지만,
 .offer()에는 없는 것을 확인할 수 있습니다.

 

추가로 문서에서 대놓고 .add()보다는 .offer()가 더 쓰기 좋다고 얘기하는 것도 인상적인 점이네요. 

 

 

https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Queue.html#offer(E)

 

Queue (Java SE 11 & JDK 11 )

A collection designed for holding elements prior to processing. Besides basic Collection operations, queues provide additional insertion, extraction, and inspection operations. Each of these methods exists in two forms: one throws an exception if the opera

docs.oracle.com

 


 

 

[ Java 프로그래밍에서 : .add() ]

사실 알고리즘 문제 풀이를 하면서 큐에 크기 제한을 두고 사용하는 경우는 별로 없을 것입니다.

그렇기 때문에 .add()와 .offer()를 구분할 일은 별로 없을 텐데요.

 

따라서 .add()와 .offer()의 차이를 확실히 체감하기 위해 간단한 Java 프로그램을 하나 짜 보겠습니다.

public class AddOrOffer {
    public static void main(String[] args) {
        /* 크기 제한을 가진 'ArrayBlockingQueue'를 이용해 최대 크기가 5인 큐를 생성합니다. */
        Queue<Integer> que = new ArrayBlockingQueue<>(5);
        Scanner sc = new Scanner(System.in);
        System.out.println("1 이상의 정수를 입력해서 큐에 저장해 주세요.");
        System.out.println("- 큐의 최대 크기는 5입니다.");
        System.out.println("- -1을 입력하면 큐에서 첫 번째 원소를 제거합니다.");
        System.out.println("- 0을 입력하면 큐를 출력하고, 프로그램을 종료합니다.");

        while(true){
            /* 스캐너로 정수를 입력받아 큐에 삽입합니다. */
            int input = sc.nextInt();
            
            /* -1을 입력한 경우 큐의 맨 첫번째 원소를 제거합니다. */
            if(input == -1){
                int removedItem = que.poll();
                System.out.printf("큐에서 원소 제거됨 : %d\n", removedItem);
                continue;
            }

            /* 0을 입력한 경우 반복문을 종료합니다. */
            if(input == 0)
                break;

            que.add(input);
        }

        System.out.println("큐 상태 : " + que);
    }
}

 

큐에 원하는 숫자를 입력하고, 프로그램 정상 종료 시 큐에 입력한 숫자들을 출력하는 프로그램입니다.

큐의 최대 크기는 5입니다.

하지만 최대 크기를 넘었을 때 .add()와 .offer()가 어떻게 동작하는지 알고 싶기 때문에

일부러 5개가 넘는 숫자들을 입력해 보겠습니다.

 

 

먼저 .add()를 사용했을 때입니다. 1부터 6까지 숫자를 넣을 텐데, 

여섯 번째 숫자를 넣는 순간 예외가 발생하면서 프로그램이 종료되어 버립니다.

 

입력값

1
2
3
4
5
6

 


출력 결과

Exception in thread "main" java.lang.IllegalStateException: Queue full
	at java.util.AbstractQueue.add(AbstractQueue.java:98)
	at java.util.concurrent.ArrayBlockingQueue.add(ArrayBlockingQueue.java:312)
	at Queue.AddOrOffer.main(AddOrOffer.java:31)

Process finished with exit code 1

 

"Queue full"이라는 메시지와 함께 IllegalStateException이 발생한 것을 확인할 수 있습니다.



[문제점]

우리가 작성한 프로그램에서 -1을 입력하면 큐에 들어있던 항목을 하나 뺄 수 있습니다.

그럼에도 불구하고, 한 번 예외가 발생해 버리면 아무것도 하지 못하고 프로그램이 종료되고 맙니다.

 

 

 

[보완 방법]

이런 문제를 보완하기 위해 IllegalException에 대한 예외처리로 프로그램이 계속 실행될 수 있도록 유지해 줄 수 있겠죠.

다음과 같이 que.add(input) 부분을 try-catch문으로 감싸서, .add() 메서드에서 발생 가능한 예외를 대비해 주었습니다.

try {
    que.add(input);
} catch (IllegalStateException e) {
    System.err.println("에러 : 큐가 가득 찼습니다!");
}

 

그리고 다시 똑같이 1, 2, 3, 4, 5, 6을 입력해 주면,

6번째 숫자를 입력했을 때 전처럼 프로그램이 종료되지 않고,
대신 다음과 같이 예외상황에 대한 메시지를 확인할 수 있게 됩니다.

 

1
2
3
4
5
6
에러 : 큐가 가득 찼습니다!

 

 

그러면 프로그램 사용자는 큐의 항목을 제거하는 커맨드를 실행하고,
원하는 결과를 얻을 때까지 프로그램을 실행할 수 있게 됩니다.

처음에 넣었던 1과 2를 빼고 큐에 3, 4, 5, 6, 7을 넣고 싶다면 다음과 같은 흐름으로 실행이 될 수 있겠네요.

1
2
3
4
5
6
에러 : 큐가 가득 찼습니다!
-1 // 원소 제거 커맨드
큐에서 원소 제거됨 : 1
6
7
에러 : 큐가 가득 찼습니다!
-1 // 원소 제거 커맨드
큐에서 원소 제거됨 : 2
7
0 // 종료 커맨드
큐 상태 : [3, 4, 5, 6, 7]

 

 


[ Java 프로그래밍에서 : .offer() ]

.offer()는 어떨까요? 프로그램을 즉시 종료하지 않으니 더 좋기만 한 걸까요?

개인적으로 경우에 따라 .offer()를 사용하는 것이 더 나쁜 선택이 될 수도 있다고 봅니다.

 

맨 처음에 사용한 코드에서 que.add(input)만 que.offer(input)으로 바꿔봅시다.

que.offer(input);

 

 

입력값

1
2
3
4
5
6
7
8
9
10
0 // 종료 커맨드

 

이번에는 아까와 다르게 1, 2, 3, 4, 5, 6...

여섯 번째 숫자까지 넣어도 아무런 메시지도 없고, 프로그램도 종료되지 않고 잘 돌아가네요.

숫자를 계속 넣다보니 재미가 들려서, 큐에 7, 8, 9, 10... 까지 넣고 싶은 만큼 숫자를 넣고

프로그램을 종료해 보겠습니다.



출력 결과

큐 상태 : [1, 2, 3, 4, 5]

 

...왜 6, 7, 8, 9, 10은 큐에 안 들어가고 1, 2, 3, 4, 5만 들어간 거죠?

 

아! 큐의 최대 크기가 5였다는 걸 깜빡했네요.

숫자를 실컷 넣는 동안 아무도 얘기를 안 해 줘서 전혀 몰랐는걸요.

 

 

[문제점]

이런 식으로 아무런 예외를 발생시키지 않는 경우에는

사용자가 전혀 의도하지 않은 결과를 낳을 수 있고,

프로그램은 아무 말 없이 멀쩡하게 실행되고 있으니 이런 문제를 아무도 모르게 될 위험이 존재하는 것입니다.

프로그램이 갑자기 깨진다면 최소한 뭔가 문제가 있다는 것은 인지할 수 있죠.

그런 의미에서 예외가 없다면 프로그램이 갑자기 종료되는 것보다도 나쁜 안티패턴을 낳을 수도 있는 겁니다.

 

 

[보완 방법]

 

다시 공식 문서 내용을 보고 옵시다.

.add()나 .offer()는 boolean 타입을 반환하는 메서드입니다.

Queue에 요소 삽입이 성공했다면 true를, 실패했다면(+그 과정에서 예외를 일으키지 않았다면) false를 반환합니다.

큐가 꽉 찼을 때 .offer()의 반환 값이 false가 된다는 점을 이용해서 코드를 다음과 같이 고쳐볼 수 있겠습니다.

boolean result = que.offer(input);
if (!result) {
    System.err.println("에러 : 큐가 가득 찼습니다!");
}

 

 

.offer()의 삽입 연산이 실패하고 false를 반환했을 때, 

if문이 발생해서 메시지를 출력합니다.

그렇게 하면 아래와 같이 사용자가 원하는 결과를 얻을 수 있도록 행동을 수정하게 유도할 수 있을 겁니다.

1
2
3
4
5
6
에러 : 큐가 가득 찼습니다!
-1 // 원소 제거 커맨드
큐에서 원소 제거됨 : 1
6
7
에러 : 큐가 가득 찼습니다!
-1 // 원소 제거 커맨드
큐에서 원소 제거됨 : 2
7 
-1 // 원소 제거 커맨드
큐에서 원소 제거됨 : 3
-1 // 원소 제거 커맨드
큐에서 원소 제거됨 : 4
-1 // 원소 제거 커맨드
큐에서 원소 제거됨 : 5
8
9
10
0 // 종료 커맨드
큐 상태 : [6, 7, 8, 9, 10]

 



 마치며  

이상으로 Queue의 .add()와 .offer()의 차이점, 실제 프로그래밍에 적용했을 때를 상정한 간단한 예시를 다뤄보았습니다.

이해가 잘 안 되시거나 정정할 내용이 있다면 댓글로 피드백 부탁드립니다.

 

 

 

Comments