본문 바로가기
개발/트러블슈팅

Java Scanner의 nextLine을 한 번 더 호출하면 입력이 사라지는 이유

by char_lie 2026. 10. 17.
반응형

nextInt 뒤에 nextLine을 ‘한 번 더 소비’하면 빈 문자열 문제는 사라져도, 같은 줄에 있던 실제 이름을 버릴 수 있습니다. 숫자와 이름이 같은 줄인지, 다음 줄인지 입력 계약부터 확인하세요. nextLine은 의미 없는 개행만 버리는 함수가 아니라 현재 위치부터 줄 끝까지의 내용을 돌려주는 함수입니다.

이미 Java 기본 구조와 Scanner 입력에서 빈 nextLine의 원인을 확인했다면, 이번에는 그 해결책을 무조건 적용했을 때 생기는 데이터 손실을 점검합니다. Temurin OpenJDK 21.0.11에서 같은 줄·다음 줄·CRLF·빈 줄·입력 끝을 실행해 비교했습니다.

1. 소비했다고 버린 문자열에 이름이 들어 있을 수 있습니다

import java.util.Scanner;

public class InputContract {
    static void probe(String label, String input) {
        try (Scanner sc = new Scanner(input)) {
            int age = sc.nextInt();
            String rest = sc.hasNextLine() ? sc.nextLine() : "<EOF>";
            String next = sc.hasNextLine() ? sc.nextLine() : "<EOF>";
            System.out.println(label + ": age=" + age
                + ", rest=[" + rest + "], next=[" + next + "]");
        }
    }
    public static void main(String[] args) {
        probe("same-line", "3 Alice\nBob\n");
        probe("next-line", "3\nAlice\n");
        probe("CRLF", "3\r\nAlice\r\n");
        probe("blank-line", "3\n\nAlice\n");
        probe("end-of-input", "3");
    }
}
same-line: age=3, rest=[ Alice], next=[Bob]
next-line: age=3, rest=[], next=[Alice]
CRLF: age=3, rest=[], next=[Alice]
blank-line: age=3, rest=[], next=[]
end-of-input: age=3, rest=[<EOF>], next=[<EOF>]

같은 줄 입력 3 Alice에서는 rest가 빈 문자열이 아니라 앞 공백을 포함한 ‘ Alice’입니다. 이 값을 버리고 다음 nextLine을 이름으로 저장하면 원래 이름 Alice 대신 다음 줄 Bob을 읽습니다. 숫자 다음에 항상 빈 문자열이 나온다는 설명은 입력 형태를 생략한 것입니다.

숫자 토큰을 읽은 뒤 같은 줄에 남은 입력

2. 다음 줄을 읽는 규칙은 다음 줄 계약에만 적용합니다

입력이 ‘첫 줄 정수 하나, 둘째 줄 이름’으로 고정되어 있다면 nextInt 뒤의 nextLine은 첫 줄의 남은 부분을 소비하는 데 쓸 수 있습니다. 하지만 첫 줄의 나머지 부분을 버려도 되는 계약이어야 합니다. 예제의 CRLF도 다음 줄 이름 Alice를 읽지만, 빈 줄 사례는 둘째 줄 자체가 빈 문자열입니다.

빈 줄을 모두 건너뛰거나 trim 결과가 비었으면 계속 읽는 방식은 빈 문자열이 유효한 입력인 계약을 바꿉니다. ‘같은 줄 나머지가 비었으면 다음 줄로 넘어가라’는 자동 추측도, 한 줄 형식이 고정된 데이터에서 빈 이름을 다음 레코드로 대체할 수 있습니다. 공백 제거 여부도 필드의 규칙에 맞춰 결정하세요.

소비용 읽기로 같은 줄의 유효 데이터를 버리는 위험

3. 줄 단위 입력으로 통일하면 경계가 분명해집니다

import java.util.Scanner;

public class WholeLine {
    public static void main(String[] args) {
        try (Scanner sc = new Scanner("3\nAlice\n")) {
            if (!sc.hasNextLine()) {
                throw new IllegalArgumentException("missing age line");
            }
            int age = Integer.parseInt(sc.nextLine().strip());
            if (!sc.hasNextLine()) {
                throw new IllegalArgumentException("missing name line");
            }
            String name = sc.nextLine();
            System.out.println("age=" + age + ", name=[" + name + "]");
        }
    }
}
age=3, name=[Alice]

이 코드는 첫 줄 전체를 정수 필드로 읽어 변환하고, 다음 줄 전체를 이름으로 받는 계약입니다. 이름에는 strip을 적용하지 않았으므로 공백을 보존합니다. 같은 줄에 3 Alice를 넣으면 정수 변환에 실패합니다. 오류를 억지로 숨기는 대신, 코드와 입력 계약이 맞지 않는다는 사실을 드러냅니다.

같은 줄 입력과 다음 줄 입력의 계약 비교

4. 입력 끝과 잘못된 숫자는 별도 오류입니다

첫 실험의 입력 끝 사례에서는 숫자는 읽었지만 이름 줄이 없습니다. hasNextLine이 false이면 이름을 읽으려고 nextLine을 호출하지 않고 입력 부족을 처리해야 합니다. 대화형 콘솔에서는 hasNextLine도 입력을 기다릴 수 있으므로 예제처럼 이미 준비된 문자열을 읽는 상황과 구분하세요.

nextInt는 정수가 아닌 토큰이나 int 범위를 벗어난 값에 InputMismatchException을 낼 수 있고, 더 읽을 입력이 없으면 NoSuchElementException을 낼 수 있습니다. Integer.parseInt는 잘못된 정수 문자열이나 범위 초과에 NumberFormatException을 낼 수 있습니다. 이런 오류는 줄 경계 문제와 원인이 다르므로 ‘nextLine 한 줄 추가’로 해결되지 않습니다.

입력 오류를 좁히는 점검 순서

입력 예시 그대로 재현 → 같은 줄·다음 줄 계약 확인 → 버리는 문자열 출력 → 빈 줄·EOF·숫자 오류 구분 순서로 보세요. 이미 소비한 나머지 줄을 다시 복원할 수는 없습니다. 먼저 읽기 단위를 정하고, 유효한 값을 버리지 않는지 작은 입력으로 확인하는 것이 핵심입니다.

반응형

댓글