입력 검증과 반복
센티널·메뉴 반복·형식 및 범위 검증을 결합해 잘못된 입력 뒤에도 계속 동작하는 회원가입 대화를 만듭니다.
대화형 프로그램은 한 번의 정상 입력보다 실패 뒤의 다음 행동이 중요합니다.
숫자가 아닌 나이, 범위 밖 값, 알 수 없는 메뉴를 만났을 때 예외로 끝낼지 같은 메뉴를 다시 보여 줄지 정해야 합니다.
센티널은 일반 데이터와 구분되는 종료 신호입니다.
try/catch의 가장 작은 의미
Integer.parseInt는 문자열이 정수 모양이 아니면 NumberFormatException이라는 실행 중 오류를 알립니다.
try에는 실패할 수 있는 변환을 두고, catch에는 그 오류가 생겼을 때 실행할 문장을 둡니다.
public class ParseOnce {
public static void main(String[] args) {
String raw = "45";
try {
int age = Integer.parseInt(raw);
System.out.println("age=" + age);
} catch (NumberFormatException error) {
System.out.println("숫자를 입력하세요");
}
}
}raw가 "45"이면 try 안의 출력이 실행됩니다.
"forty"로 바꾸면 변환 뒤 문장은 건너뛰고 catch가 실행됩니다.
예외의 계층과 전파 규칙은 ch12에서 깊게 다루며, 여기서는 사용자 입력 한 건이 잘못돼도 대화를 계속하기 위한 최소 형태만 사용합니다.
변환 실패와 입력 복구
import java.util.Scanner;
public final class AbortOnBadNumber {
public static void main(String[] args) {
Scanner scanner = new Scanner("forty\n45\n");
while (scanner.hasNextLine()) {
int age = Integer.parseInt(scanner.nextLine());
System.out.println("saved=" + age);
}
}
}Exception in thread "main" java.lang.NumberFormatException: For input string: "forty"두 번째 줄 45는 정상이어도 첫 줄에서 프로그램이 종료돼 읽히지 않습니다.
한 입력의 사용자 오류를 전체 CLI 장애로 취급한 결과입니다.
import java.util.Scanner;
public final class RetryNumber {
public static void main(String[] args) {
Scanner scanner = new Scanner("forty\n0\n45\n");
while (scanner.hasNextLine()) {
String raw = scanner.nextLine().strip();
try {
int age = Integer.parseInt(raw);
if (age < 14 || age > 120) {
System.out.println("range.error=" + raw);
continue;
}
System.out.println("saved=" + age);
break;
} catch (NumberFormatException error) {
System.out.println("number.error=" + raw);
}
}
}
}number.error=forty
range.error=0
saved=45형식 변환과 값 범위는 서로 다른 실패입니다.
try 블록은 parseInt에 필요한 만큼만 좁게 두고, 범위 검사는 변환 성공 뒤에 둡니다.
정상 값을 저장한 뒤 break로 재입력을 끝냅니다.
센티널 값의 조건
0세를 허용하지 않는다면 나이 입력 0을 종료 신호로 쓸 수 있습니다.
그러나 입력 규칙이 바뀌어 0을 허용하게 되면 센티널과 데이터가 충돌합니다.
명령 문자열 quit처럼 도메인 값과 타입이 다른 신호가 더 분명합니다.
import java.util.Scanner;
public final class SentinelLoop {
public static void main(String[] args) {
Scanner scanner = new Scanner("kim@example.com\nlee@example.com\nquit\nignored@example.com\n");
int count = 0;
while (scanner.hasNextLine()) {
String email = scanner.nextLine().strip();
if (email.equals("quit")) {
break;
}
if (email.isEmpty()) {
continue;
}
count++;
System.out.println("accepted=" + email);
}
System.out.println("count=" + count);
}
}accepted=kim@example.com
accepted=lee@example.com
count=2센티널은 저장 전에 검사하므로 count에 포함되지 않고 뒤의 ignored도 소비하지 않습니다.
메뉴 반복 구조
회원가입은 이메일과 나이를 한 쌍으로 받습니다.
메뉴 선택을 먼저 읽고 등록 메뉴일 때만 상세 입력 두 줄을 소비합니다.
import java.util.Scanner;
public final class MenuConversation {
public static void main(String[] args) {
Scanner scanner = new Scanner(
"1\nkim@example.com\n40\n2\n9\n0\n"
);
int saved = 0;
while (scanner.hasNextLine()) {
String menu = scanner.nextLine().strip();
switch (menu) {
case "1" -> {
String email = scanner.nextLine().strip();
int age = Integer.parseInt(scanner.nextLine().strip());
saved++;
System.out.println("saved=" + email + ":" + age);
}
case "2" -> System.out.println("count=" + saved);
case "0" -> {
System.out.println("bye");
return;
}
default -> System.out.println("unknown.menu=" + menu);
}
}
}
}saved=kim@example.com:40
count=1
unknown.menu=9
bye메뉴 0에서 return하면 switch와 while을 함께 끝냅니다.
메뉴 1의 상세 입력이 부족할 수 있다는 결함은 hasNextLine 검사나 한 줄 명령 형식으로 보완할 수 있습니다.
입력 프로토콜은 소비할 줄 수까지 계약에 포함합니다.
검증과 읽기의 순서
검증 문제를 풀 때 입력 하나를 다음 단계로 보냅니다.
- 줄이 존재하는가?
- 종료 센티널인가?
- 명령 형태와 필요한 항목 수가 맞는가?
- 숫자 문자열을 변환할 수 있는가?
- 변환 값이 14–120 범위인가?
- 모든 조건을 통과한 뒤에만 저장 상태를 바꾼다.
앞 단계가 다음 단계의 안전을 보장합니다.
배열 인덱스를 읽은 뒤 항목 수를 검사하거나 parseInt 뒤에 형식 오류를 안내할 수는 없습니다.
한 줄 입력 반복
import java.util.Scanner;
public final class ValidatedMemberInput {
public static void main(String[] args) {
Scanner scanner = new Scanner(
"register kim@example.com forty\nregister kim@example.com 0\nregister kim@example.com 45\nlist\nquit\n"
);
int count = 0;
int total = 0;
while (scanner.hasNextLine()) {
String line = scanner.nextLine().strip();
if (line.equals("quit")) break;
if (line.equals("list")) {
System.out.println("summary=" + count + ":" + total);
continue;
}
String[] parts = line.split("\\s+");
if (parts.length != 3 || !parts[0].equals("register")) {
System.out.println("command.error");
continue;
}
int age;
try {
age = Integer.parseInt(parts[2]);
} catch (NumberFormatException error) {
System.out.println("number.error");
continue;
}
if (age < 14 || age > 120) {
System.out.println("range.error");
continue;
}
count++;
total += age;
System.out.println("saved=" + parts[1]);
}
}
}number.error
range.error
saved=kim@example.com
summary=1:45현재 count와 total만 남기므로 개별 회원을 다시 출력할 수 없습니다.
다음 문서에서 고정 용량 배열을 만들고 유효한 회원의 위치를 보존합니다.
연습 문제
"x\n13\n121\n60\n"을 읽어 숫자 오류와 범위 오류를 각각 출력한 뒤 60을 받으면 종료하는 MemberAgeRetry를 작성하세요.
해설 보기
import java.util.Scanner;
public final class MemberAgeRetry {
public static void main(String[] args) {
Scanner scanner = new Scanner("x\n13\n121\n60\n");
while (scanner.hasNextLine()) {
String raw = scanner.nextLine().strip();
try {
int age = Integer.parseInt(raw);
if (age < 14 || age > 120) {
System.out.println("range=" + raw);
continue;
}
System.out.println("age=" + age);
break;
} catch (NumberFormatException error) {
System.out.println("number=" + raw);
}
}
}
}number=x
range=13
range=121
age=60변환에 성공한 값만 범위 비교에 도달하고 유효한 첫 값에서 break합니다.