[Java] Scanner 그리고 Buffer 비우기
📝 개요
Java 를 공부하며, 계산기를 만드는 프로젝트를 진행 중에 에러를 해결하는 과정 중 알게 된 사실을 정리하고자 한다.
🔥 문제 상황
계산기의 기능은 숫자 2개와 연산자를 사용자에게 입력받아 사칙연산을 할 수 있는 간단한 기능이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
ArithmeticCalculator calc = new ArithmeticCalculator();
while (true) {
try {
System.out.print("첫 번째 숫자를 입력하세요: ");
double num1 = sc.nextDouble();
System.out.print("두 번째 숫자를 입력하세요: ");
double num2 = sc.nextDouble();
System.out.print("사칙연산 기호를 입력하세요(+,-,*,/): ");
char symbol = sc.next().charAt(0);
double result = calc.calculate(num1, num2, symbol);
System.out.println("결과: " + result);
System.out.println("더 계산하시겠습니까? (exit 입력 시 종료)");
if (sc.next().equalsIgnoreCase("exit")) break;
} catch (InputMismatchException e) {
System.out.println("유효한 숫자를 입력해주세요.");
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
} catch (ArithmeticException e) {
System.out.println(e.getMessage());
} catch (Exception e) {
System.out.println("알 수 없는 오류가 발생했습니다.");
}
}
sc.close();
}
}
첫 번째 숫자에 문자열을 입력 후 Enter 키를 눌렀더니
해당 메시지가 무한으로 출력되는 현상이 발생했다.
👀 원인
Scanner 클래스는 입력 버퍼를 사용하여 데이터를 읽는다.
nextDouble() 메서드는 입력된 문자열을 숫자로 변환하려고 시도하는데, 만약 문자열이 숫자가 아닌 경우 InputMismatchException 예외를 발생시킨다.
이 예외가 발생하면, Scanner의 입력 버퍼에 해당 문자열이 남아있게 된다.
이후 nextDouble()을 다시 호출하면, 여전히 같은 문자열을 읽으려고 시도하게 되어 무한 루프가 발생한다.
💡 설명
물이 담긴 빨대컵
Scanner를 물이 담긴 빨대컵이라고 상상해보자.
- 컵(버퍼): 사용자가 키보드로 입력한 모든 내용(글자, 숫자, 엔터 키)이 잠시 저장되는 공간
- 빨대(메서드):
sc.next(),sc.nextDouble(),sc.nextLine()같은 메서드들이 빨대 역할을 하며, 이 빨대로 컵에 담긴 물(입력)을 가져온다.
문제의 핵심은 각 빨대가 물을 마시는 방식이 다르다는 것
sc.nextDouble(),sc.nextInt(),sc.next()- 이 빨대들은 토큰(Token) 단위로 물을 마신다. 토큰은 공백(스페이스, 탭, 엔터)으로 구분되는 단어 같은 것
- 문제점: 이 빨대들은 토큰만 마시고, 토큰 뒤에 따라오는 나머지 물(특히 엔터 키
\n에 해당하는 줄바꿈 문자)은 컵(버퍼)에 그대로 남겨둔다.
예시: 사용자가 키보드로
123을 입력하고Enter키를 눌렀다고 가정- 컵에는
123\n(123 뒤에 줄바꿈 문자) 이 담긴다. sc.nextDouble()이123을 읽어간다.- 컵에는
\n이 남는다.
sc.nextLine()- 이 빨대는 줄(Line) 단위로 물을 마신다.
- 현재 커서 위치부터 다음 줄바꿈 문자(
\n)가 나올 때 까지 모든 물을 마시고,\n자신도 마시고 버린다.
문제 발생 시나리오
- 코드:
double firstNumber = sc.nextDouble(); - 사용자 입력:
abc(그리고Enter키) - 컵(버퍼) 상태:
abc\n이 담긴다. sc.nextDouble()동작abc는 숫자가 아니므로InputMismatchException예외 발생
- 예외 발생 후
abc\n은 여전히 컵에 남아있게 된다.
sc.nextDouble()은 예외를 던지고 입력을 소비하지 않기 때문
catch블록 진입try-catch블록에 의해inputMismatchException예외가 잡히고, 오류 메시지가 출력된다.
while루프 반복- 다시 루프의 처음으로 돌아가 첫 번째 숫자를 입력받는 코드를 실행
- 문제 발생 ❗️
sc.nextDouble()은 컵에 남아있던abc를 다시 만나 또 다시 예외를 던지고, 이 과정이 반복된다.
사용자는 아무것도 입력하지 않았는데도 무한히 오류 메시지가 출력되는 현상이 발생한다.
✅ 해결
sc.nextLine() 사용하여 버퍼 비우기
1
2
3
4
5
6
7
try {
double num1 = sc.nextDouble();
// ...
} catch (InputMismatchException e) {
System.out.println("유효한 숫자를 입력해주세요.");
sc.nextLine(); // 버퍼 비우기
// ...
시나리오
- 사용자 입력:
abc(Enter) -> 컵에abc\n sc.nextDouble()예외 발생.abc\n잔류catch블록 진입. 오류 메세지 출력sc.nextLine()실행- 컵에 남아있던
abc\n모두 마시고 버퍼를 비운다. (컵이 비워짐)
while루프 반복.sc.nextDouble()이 실행- 정상 작동
- 컵이 비어있으므로
sc.nextDouble()은 새로운 사용자 입력을 기다린다.
무한 반복 문제가 해결