입력 검증과 반복
센티널·메뉴 반복·형식 및 범위 검증을 결합해 잘못된 입력 뒤에도 계속 동작하는 회원가입 대화를 만듭니다.
대화형 프로그램은 한 번의 정상 입력보다 실패 뒤의 다음 행동이 중요합니다.
숫자가 아닌 나이, 범위 밖 값, 알 수 없는 메뉴를 만났을 때 예외로 끝낼지 같은 메뉴를 다시 보여 줄지 정해야 합니다.
센티널은 일반 데이터와 구분되는 종료 신호입니다.
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에 필요한 만큼만 좁게 두고, 범위 검사는 변환 성공 뒤에 둡니다.
유효한 값을 saved= 문자열로 출력한 뒤 break로 재입력을 끝냅니다. 이 작은 예제는 회원 배열이나 파일에 기록하지 않습니다. 출력과 저장 상태의 차이는 뒤의 배열 예제에서 확인합니다.
센티널 값의 조건
0세를 허용하지 않는다면 나이 입력 0을 종료 신호로 쓸 수 있습니다.
그러나 입력 규칙이 바뀌어 0을 허용하게 되면 센티널과 데이터가 충돌합니다.
명령 문자열 quit처럼 도메인 값과 타입이 다른 신호가 더 분명합니다.
import java.util.Scanner;
public final class SentinelLoop {
public static void main(String[] args) {
Scanner scanner = new Scanner("[email protected]\n[email protected]\nquit\n[email protected]\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);
}
}[email protected]
[email protected]
count=2센티널은 저장 전에 검사하므로 count에 포함되지 않고 뒤의 ignored도 소비하지 않습니다.
quit는 소비되지만 저장되지 않고 뒤 입력은 남는다
두 이메일을 읽어 count를 증가시키고 quit은 저장 전에 소비하여 종료하며 ignored 이메일을 남깁니다. 처리된 접두부와 미소비 꼬리가 단계마다 달라집니다.
두 이메일은 count를 증가시키지만 quit는 소비한 즉시 break하며 뒤의 [email protected]은 읽히지 않는다.
INPUT · STATE BOUNDARY
quit까지 소비하고, 뒤 입력은 읽지 않습니다
두 이메일은 개수를 늘립니다. quit 줄은 읽은 즉시 break하므로 개수에 포함되지 않으며 ignored 줄은 Scanner에 남습니다.
| 입력 순서 | 소비 여부 | 처리·출력 | count |
|---|---|---|---|
[email protected] | 읽음 | [email protected] | 1 |
[email protected] | 읽음 | [email protected] | 2 |
quit | 읽음 | 즉시 break · accepted 출력 없음 | 2 유지 |
[email protected] | 읽지 않음 | 처리하지 않음 | 2 유지 |
시작
count=0. 다음 줄은[email protected]입니다.첫 이메일 소비
[email protected]을 출력하고count=1입니다.둘째 이메일 소비
[email protected]을 출력하고count=2입니다.quit 소비 후 즉시 종료
break합니다.quit는 개수에 포함하지 않아count=2이며[email protected]\n은 읽지 않고 남깁니다.
반복 뒤 마지막 출력은 count=2입니다. 개별 이메일을 보존하는 배열은 아직 없습니다. SentinelLoop는 quit를 읽은 직후 반복을 끝내며, 그 뒤에 남은 입력을 처리하지 않습니다.
이 예의 조건
- quit 뒤 ignored 줄은 읽지 않는다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 2 | count 0→Kim 1→Lee 2→quit 2 즉시 break; ignored 미소비 영역은 제어 흐름과 연결하지 않음 |
| 구분 | 내용 |
|---|---|
| 설명 | count 0→Kim 1→Lee 2→quit 2 즉시 break; ignored 미소비 영역은 제어 흐름과 연결하지 않음 |
| 전체 읽기 정보 | quit는 소비되지만 저장되지 않고 뒤 입력은 남는다 SentinelLoop의 소비된 세 줄과 남은 ignored 입력 두 이메일은 count를 증가시키지만 quit는 소비한 즉시 break하며 뒤의 [email protected]은 읽히지 않는다. INPUT · STATE BOUNDARY quit까지 소비하고, 뒤 입력은 읽지 않습니다 두 이메일은 개수를 늘립니다. quit 줄은 읽은 즉시 break하므로 개수에 포함되지 않으며 ignored 줄은 Scanner에 남습니다. SentinelLoop · 초기 count=0 입력 순서 소비 여부 처리·출력 count [email protected] 읽음 [email protected] 1 [email protected] 읽음 [email protected] 2 quit 읽음 즉시 break · accepted 출력 없음 2 유지 [email protected] 읽지 않음 처리하지 않음 2 유지 시작 count=0 . 다음 줄은 [email protected] 입니다. 첫 이메일 소비 [email protected] 을 출력하고 count=1 입니다. 둘째 이메일 소비 [email protected] 을 출력하고 count=2 입니다. quit 소비 후 즉시 종료 break 합니다. quit 는 개수에 포함하지 않아 count=2 이며 [email protected]\n 은 읽지 않고 남깁니다. 반복 뒤 마지막 출력은 count=2 입니다. 개별 이메일을 보존하는 배열은 아직 없습니다. SentinelLoop 는 quit 를 읽은 직후 반복을 끝내며, 그 뒤에 남은 입력을 처리하지 않습니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 입력 전 회원 개수를 0으로 시작합니다. |
|
|
| 첫 이메일 [email protected]을 읽고 승인합니다. |
|
|
| 둘째 이메일 [email protected]을 읽고 승인합니다. |
|
|
| 센티널 종료 quit을 읽은 뒤 break합니다. |
|
|
quit는 소비되지만 저장되지 않고 뒤 입력은 남는다
두 이메일을 읽어 count를 증가시키고 quit은 저장 전에 소비하여 종료하며 ignored 이메일을 남깁니다. 처리된 접두부와 미소비 꼬리가 단계마다 달라집니다.
두 이메일은 count를 증가시키지만 quit는 소비한 즉시 break하며 뒤의 [email protected]은 읽히지 않는다.
INPUT · STATE BOUNDARY
quit까지 소비하고, 뒤 입력은 읽지 않습니다
두 이메일은 개수를 늘립니다. quit 줄은 읽은 즉시 break하므로 개수에 포함되지 않으며 ignored 줄은 Scanner에 남습니다.
| 입력 순서 | 소비 여부 | 처리·출력 | count |
|---|---|---|---|
[email protected] | 읽음 | [email protected] | 1 |
[email protected] | 읽음 | [email protected] | 2 |
quit | 읽음 | 즉시 break · accepted 출력 없음 | 2 유지 |
[email protected] | 읽지 않음 | 처리하지 않음 | 2 유지 |
시작
count=0. 다음 줄은[email protected]입니다.첫 이메일 소비
[email protected]을 출력하고count=1입니다.둘째 이메일 소비
[email protected]을 출력하고count=2입니다.quit 소비 후 즉시 종료
break합니다.quit는 개수에 포함하지 않아count=2이며[email protected]\n은 읽지 않고 남깁니다.
반복 뒤 마지막 출력은 count=2입니다. 개별 이메일을 보존하는 배열은 아직 없습니다. SentinelLoop는 quit를 읽은 직후 반복을 끝내며, 그 뒤에 남은 입력을 처리하지 않습니다.
이 예의 조건
- quit 뒤 ignored 줄은 읽지 않는다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 2 | count 0→Kim 1→Lee 2→quit 2 즉시 break; ignored 미소비 영역은 제어 흐름과 연결하지 않음 |
| 구분 | 내용 |
|---|---|
| 설명 | count 0→Kim 1→Lee 2→quit 2 즉시 break; ignored 미소비 영역은 제어 흐름과 연결하지 않음 |
| 전체 읽기 정보 | quit는 소비되지만 저장되지 않고 뒤 입력은 남는다 SentinelLoop의 소비된 세 줄과 남은 ignored 입력 두 이메일은 count를 증가시키지만 quit는 소비한 즉시 break하며 뒤의 [email protected]은 읽히지 않는다. INPUT · STATE BOUNDARY quit까지 소비하고, 뒤 입력은 읽지 않습니다 두 이메일은 개수를 늘립니다. quit 줄은 읽은 즉시 break하므로 개수에 포함되지 않으며 ignored 줄은 Scanner에 남습니다. SentinelLoop · 초기 count=0 입력 순서 소비 여부 처리·출력 count [email protected] 읽음 [email protected] 1 [email protected] 읽음 [email protected] 2 quit 읽음 즉시 break · accepted 출력 없음 2 유지 [email protected] 읽지 않음 처리하지 않음 2 유지 시작 count=0 . 다음 줄은 [email protected] 입니다. 첫 이메일 소비 [email protected] 을 출력하고 count=1 입니다. 둘째 이메일 소비 [email protected] 을 출력하고 count=2 입니다. quit 소비 후 즉시 종료 break 합니다. quit 는 개수에 포함하지 않아 count=2 이며 [email protected]\n 은 읽지 않고 남깁니다. 반복 뒤 마지막 출력은 count=2 입니다. 개별 이메일을 보존하는 배열은 아직 없습니다. SentinelLoop 는 quit 를 읽은 직후 반복을 끝내며, 그 뒤에 남은 입력을 처리하지 않습니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 입력 전 회원 개수를 0으로 시작합니다. |
|
|
| 첫 이메일 [email protected]을 읽고 승인합니다. |
|
|
| 둘째 이메일 [email protected]을 읽고 승인합니다. |
|
|
| 센티널 종료 quit을 읽은 뒤 break합니다. |
|
|
메뉴 반복 구조
회원가입은 이메일과 나이를 한 쌍으로 받습니다.
메뉴 선택을 먼저 읽고 등록 메뉴일 때만 상세 입력 두 줄을 소비합니다.
import java.util.Scanner;
public final class MenuConversation {
public static void main(String[] args) {
Scanner scanner = new Scanner(
"1\n[email protected]\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);
}
}
}
}[email protected]:40
count=1
unknown.menu=9
bye메뉴 0에서 return하면 switch와 while을 함께 끝냅니다.
메뉴 1의 상세 입력이 부족할 수 있다는 결함은 hasNextLine 검사나 한 줄 명령 형식으로 보완할 수 있습니다.
입력 프로토콜은 소비할 줄 수까지 계약에 포함합니다.
검증과 읽기의 순서
검증 문제를 풀 때 입력 하나를 다음 단계로 보냅니다.
- 줄이 존재하는가?
- 종료 센티널인가?
- 명령 형태와 필요한 항목 수가 맞는가?
- 숫자 문자열을 변환할 수 있는가?
- 변환 값이 14–120 범위인가?
- 모든 조건을 통과한 뒤에만 저장 상태를 바꾼다.
앞 단계가 다음 단계의 안전을 보장합니다.
항목 수를 확인한 뒤에만 배열 원소를 읽습니다. parseInt에서 발생한 형식 오류는 catch로 안내하고, 변환에 성공한 값만 범위 검사로 넘깁니다.
register 검증을 모두 통과한 뒤에만 누적 상태가 바뀐다
형태 검사→정수 변환→14–120 범위 검사 모두를 통과한 뒤에만 count와 total을 변경합니다. 각 거절 지점에서 상태를 보존하는 실제 입력 게이트입니다.
quit와 list를 먼저 처리한 뒤 남은 명령은 형태, 정수 변환, 14–120 범위를 모두 통과해야 count와 total을 바꾼다.
INPUT · STATE BOUNDARY
검증 실패는 상태를 보존하고 다음 입력으로
quit와 list를 먼저 처리한 뒤, 남은 한 줄은 명령 형태·정수 변환·나이 범위를 모두 통과해야 count와 total을 바꿉니다.
범위: quit·list를 처리한 뒤 남은 한 줄
명령 형태
split("\\s+")뒤 항목이 3개이고 첫 항목이register인지 확인합니다. 아니면command.error후 다음 입력으로 갑니다.숫자 변환
Integer.parseInt(parts[2])가NumberFormatException을 던지면number.error후 다음 입력으로 갑니다.범위 검사
14 <= age && age <= 120인지 확인합니다. 아니면range.error후 다음 입력으로 갑니다.상태 반영
count++,total += age를 실행하고saved=<email>을 출력한 뒤 다음 입력으로 갑니다.
세 오류 경로는 count와 total을 바꾸지 않습니다. quit뿐 아니라 EOF로 hasNextLine()==false가 되어도 반복이 끝납니다.
이 예의 조건
- quit·list 처리 뒤 register 후보 검사이며 saved는 출력입니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 전체 읽기 정보 | register 검증을 모두 통과한 뒤에만 누적 상태가 바뀐다 ValidatedMemberInput의 register 후보 검증과 다음 입력으로의 복귀 quit와 list를 먼저 처리한 뒤 남은 명령은 형태, 정수 변환, 14–120 범위를 모두 통과해야 count와 total을 바꾼다. INPUT · STATE BOUNDARY 검증 실패는 상태를 보존하고 다음 입력으로 quit와 list를 먼저 처리한 뒤, 남은 한 줄은 명령 형태·정수 변환·나이 범위를 모두 통과해야 count와 total을 바꿉니다. 범위: quit · list 를 처리한 뒤 남은 한 줄 명령 형태 split("\\s+") 뒤 항목이 3개이고 첫 항목이 register 인지 확인합니다. 아니면 command.error 후 다음 입력으로 갑니다. 숫자 변환 Integer.parseInt(parts[2]) 가 NumberFormatException 을 던지면 number.error 후 다음 입력으로 갑니다. 범위 검사 14 <= age && age <= 120 인지 확인합니다. 아니면 range.error 후 다음 입력으로 갑니다. 상태 반영 count++ , total += age 를 실행하고 saved=<email> 을 출력한 뒤 다음 입력으로 갑니다. 세 오류 경로는 count 와 total 을 바꾸지 않습니다. quit 뿐 아니라 EOF로 hasNextLine()==false 가 되어도 반복이 끝납니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 명령 형태 parts 3개와 register 이름을 검사합니다. |
|
|
| 숫자와 범위 parseInt 후 14–120 범위를 검사합니다. |
|
|
| 성공 갱신 검증을 통과한 age에 대해서만 count++와 total+=age를 수행합니다. |
|
|
register 검증을 모두 통과한 뒤에만 누적 상태가 바뀐다
형태 검사→정수 변환→14–120 범위 검사 모두를 통과한 뒤에만 count와 total을 변경합니다. 각 거절 지점에서 상태를 보존하는 실제 입력 게이트입니다.
quit와 list를 먼저 처리한 뒤 남은 명령은 형태, 정수 변환, 14–120 범위를 모두 통과해야 count와 total을 바꾼다.
INPUT · STATE BOUNDARY
검증 실패는 상태를 보존하고 다음 입력으로
quit와 list를 먼저 처리한 뒤, 남은 한 줄은 명령 형태·정수 변환·나이 범위를 모두 통과해야 count와 total을 바꿉니다.
범위: quit·list를 처리한 뒤 남은 한 줄
명령 형태
split("\\s+")뒤 항목이 3개이고 첫 항목이register인지 확인합니다. 아니면command.error후 다음 입력으로 갑니다.숫자 변환
Integer.parseInt(parts[2])가NumberFormatException을 던지면number.error후 다음 입력으로 갑니다.범위 검사
14 <= age && age <= 120인지 확인합니다. 아니면range.error후 다음 입력으로 갑니다.상태 반영
count++,total += age를 실행하고saved=<email>을 출력한 뒤 다음 입력으로 갑니다.
세 오류 경로는 count와 total을 바꾸지 않습니다. quit뿐 아니라 EOF로 hasNextLine()==false가 되어도 반복이 끝납니다.
이 예의 조건
- quit·list 처리 뒤 register 후보 검사이며 saved는 출력입니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 전체 읽기 정보 | register 검증을 모두 통과한 뒤에만 누적 상태가 바뀐다 ValidatedMemberInput의 register 후보 검증과 다음 입력으로의 복귀 quit와 list를 먼저 처리한 뒤 남은 명령은 형태, 정수 변환, 14–120 범위를 모두 통과해야 count와 total을 바꾼다. INPUT · STATE BOUNDARY 검증 실패는 상태를 보존하고 다음 입력으로 quit와 list를 먼저 처리한 뒤, 남은 한 줄은 명령 형태·정수 변환·나이 범위를 모두 통과해야 count와 total을 바꿉니다. 범위: quit · list 를 처리한 뒤 남은 한 줄 명령 형태 split("\\s+") 뒤 항목이 3개이고 첫 항목이 register 인지 확인합니다. 아니면 command.error 후 다음 입력으로 갑니다. 숫자 변환 Integer.parseInt(parts[2]) 가 NumberFormatException 을 던지면 number.error 후 다음 입력으로 갑니다. 범위 검사 14 <= age && age <= 120 인지 확인합니다. 아니면 range.error 후 다음 입력으로 갑니다. 상태 반영 count++ , total += age 를 실행하고 saved=<email> 을 출력한 뒤 다음 입력으로 갑니다. 세 오류 경로는 count 와 total 을 바꾸지 않습니다. quit 뿐 아니라 EOF로 hasNextLine()==false 가 되어도 반복이 끝납니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 명령 형태 parts 3개와 register 이름을 검사합니다. |
|
|
| 숫자와 범위 parseInt 후 14–120 범위를 검사합니다. |
|
|
| 성공 갱신 검증을 통과한 age에 대해서만 count++와 total+=age를 수행합니다. |
|
|
한 줄 입력 반복
import java.util.Scanner;
public final class ValidatedMemberInput {
public static void main(String[] args) {
Scanner scanner = new Scanner(
"register [email protected] forty\nregister [email protected] 0\nregister [email protected] 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
[email protected]
summary=1:45실패한 입력은 count와 total을 그대로 남긴다
숫자 오류와 범위 오류 뒤 count/total은 0을 유지하고 정상 입력에서 1/45가 되며 list와 quit은 바꾸지 않습니다. 같은 반복의 상태 추적입니다.
숫자와 범위 오류는 상태를 보존하고 45의 정상 등록만 count 1과 total 45를 만든다. list와 quit는 누적 상태를 바꾸지 않는다.
INPUT · STATE BOUNDARY
다섯 입력 뒤 바뀌는 상태와 유지되는 상태
숫자·범위 오류는 count=0과 total=0을 유지합니다. 나이45의 정상 입력만 상태를 갱신하며 list와 quit는 값을 바꾸지 않습니다.
| 소비한 입력 | 관찰 출력 | 처리 후 count | 처리 후 total |
|---|---|---|---|
register [email protected] forty | number.error | 0 | 0 |
register [email protected] 0 | range.error | 0 | 0 |
register [email protected] 45 | [email protected] | 1 | 45 |
list | summary=1:45 | 1 | 45 |
quit | 출력 없음 · 반복 종료 | 1 | 45 |
count=0, total=0에서 시작
register [email protected] forty출력:
number.error· 처리 후count=0, total=0register [email protected] 0출력:
range.error· 처리 후count=0, total=0register [email protected] 45출력:
[email protected]· 처리 후count=1, total=45list출력:
summary=1:45· 처리 후count=1, total=45quit출력:
출력 없음 · 반복 종료· 처리 후count=1, total=45
여기서 저장되는 상태는 count와 total뿐입니다. saved= 출력이 개별 회원 배열을 보존한다는 뜻은 아닙니다.
이 예의 조건
- 모든 register의 이메일은 [email protected]이며 배열 저장은 없습니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 2 | 숫자 오류 0/0→범위 오류 0/0→45 등록 1/45→list 1/45→quit 1/45, saved는 출력이며 회원 배열 저장 아님 |
| 구분 | 내용 |
|---|---|
| 설명 | 숫자 오류 0/0→범위 오류 0/0→45 등록 1/45→list 1/45→quit 1/45, saved는 출력이며 회원 배열 저장 아님 |
| 전체 읽기 정보 | 실패한 입력은 count와 total을 그대로 남긴다 ValidatedMemberInput의 다섯 입력 뒤 count와 total 상태 숫자와 범위 오류는 상태를 보존하고 45의 정상 등록만 count 1과 total 45를 만든다. list와 quit는 누적 상태를 바꾸지 않는다. INPUT · STATE BOUNDARY 다섯 입력 뒤 바뀌는 상태와 유지되는 상태 숫자·범위 오류는 count=0과 total=0을 유지합니다. 나이 45의 정상 입력만 상태를 갱신하며 list와 quit는 값을 바꾸지 않습니다. ValidatedMemberInput · 초기 count=0, total=0 소비한 입력 관찰 출력 처리 후 count 처리 후 total register [email protected] forty number.error 0 0 register [email protected] 0 range.error 0 0 register [email protected] 45 [email protected] 1 45 list summary=1:45 1 45 quit 출력 없음 · 반복 종료 1 45 count=0, total=0 에서 시작 register [email protected] forty 출력: number.error · 처리 후 count=0, total=0 register [email protected] 0 출력: range.error · 처리 후 count=0, total=0 register [email protected] 45 출력: [email protected] · 처리 후 count=1, total=45 list 출력: summary=1:45 · 처리 후 count=1, total=45 quit 출력: 출력 없음 · 반복 종료 · 처리 후 count=1, total=45 여기서 저장되는 상태는 count 와 total 뿐입니다. saved= 출력이 개별 회원 배열을 보존한다는 뜻은 아닙니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 실패 두 입력 forty 변환 오류와 0 범위 오류를 처리합니다. |
|
|
| 45 등록 유효한 45를 누적합니다. |
|
|
| 조회와 종료 list는 summary 1:45, quit은 출력 없이 끝냅니다. |
|
|
실패한 입력은 count와 total을 그대로 남긴다
숫자 오류와 범위 오류 뒤 count/total은 0을 유지하고 정상 입력에서 1/45가 되며 list와 quit은 바꾸지 않습니다. 같은 반복의 상태 추적입니다.
숫자와 범위 오류는 상태를 보존하고 45의 정상 등록만 count 1과 total 45를 만든다. list와 quit는 누적 상태를 바꾸지 않는다.
INPUT · STATE BOUNDARY
다섯 입력 뒤 바뀌는 상태와 유지되는 상태
숫자·범위 오류는 count=0과 total=0을 유지합니다. 나이45의 정상 입력만 상태를 갱신하며 list와 quit는 값을 바꾸지 않습니다.
| 소비한 입력 | 관찰 출력 | 처리 후 count | 처리 후 total |
|---|---|---|---|
register [email protected] forty | number.error | 0 | 0 |
register [email protected] 0 | range.error | 0 | 0 |
register [email protected] 45 | [email protected] | 1 | 45 |
list | summary=1:45 | 1 | 45 |
quit | 출력 없음 · 반복 종료 | 1 | 45 |
count=0, total=0에서 시작
register [email protected] forty출력:
number.error· 처리 후count=0, total=0register [email protected] 0출력:
range.error· 처리 후count=0, total=0register [email protected] 45출력:
[email protected]· 처리 후count=1, total=45list출력:
summary=1:45· 처리 후count=1, total=45quit출력:
출력 없음 · 반복 종료· 처리 후count=1, total=45
여기서 저장되는 상태는 count와 total뿐입니다. saved= 출력이 개별 회원 배열을 보존한다는 뜻은 아닙니다.
이 예의 조건
- 모든 register의 이메일은 [email protected]이며 배열 저장은 없습니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 2 | 숫자 오류 0/0→범위 오류 0/0→45 등록 1/45→list 1/45→quit 1/45, saved는 출력이며 회원 배열 저장 아님 |
| 구분 | 내용 |
|---|---|
| 설명 | 숫자 오류 0/0→범위 오류 0/0→45 등록 1/45→list 1/45→quit 1/45, saved는 출력이며 회원 배열 저장 아님 |
| 전체 읽기 정보 | 실패한 입력은 count와 total을 그대로 남긴다 ValidatedMemberInput의 다섯 입력 뒤 count와 total 상태 숫자와 범위 오류는 상태를 보존하고 45의 정상 등록만 count 1과 total 45를 만든다. list와 quit는 누적 상태를 바꾸지 않는다. INPUT · STATE BOUNDARY 다섯 입력 뒤 바뀌는 상태와 유지되는 상태 숫자·범위 오류는 count=0과 total=0을 유지합니다. 나이 45의 정상 입력만 상태를 갱신하며 list와 quit는 값을 바꾸지 않습니다. ValidatedMemberInput · 초기 count=0, total=0 소비한 입력 관찰 출력 처리 후 count 처리 후 total register [email protected] forty number.error 0 0 register [email protected] 0 range.error 0 0 register [email protected] 45 [email protected] 1 45 list summary=1:45 1 45 quit 출력 없음 · 반복 종료 1 45 count=0, total=0 에서 시작 register [email protected] forty 출력: number.error · 처리 후 count=0, total=0 register [email protected] 0 출력: range.error · 처리 후 count=0, total=0 register [email protected] 45 출력: [email protected] · 처리 후 count=1, total=45 list 출력: summary=1:45 · 처리 후 count=1, total=45 quit 출력: 출력 없음 · 반복 종료 · 처리 후 count=1, total=45 여기서 저장되는 상태는 count 와 total 뿐입니다. saved= 출력이 개별 회원 배열을 보존한다는 뜻은 아닙니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 실패 두 입력 forty 변환 오류와 0 범위 오류를 처리합니다. |
|
|
| 45 등록 유효한 45를 누적합니다. |
|
|
| 조회와 종료 list는 summary 1:45, quit은 출력 없이 끝냅니다. |
|
|
현재 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합니다.