본문으로 건너뛰기
안동민 개발노트 아이콘

안동민 개발노트

본문 시작
3장 : 입력·배열·메서드

배열 기반 회원가입

용량과 저장 개수를 구분하고 합계·평균·최솟값·최댓값을 계산해 배열 기반 회원가입을 완성합니다.

배열을 저장소로 쓰려면 원소를 넣는 것만으로는 부족합니다.

비어 있는 칸과 실제 회원을 구분하고, 용량을 넘지 않게 추가하며, 저장된 범위만 조회하고 집계해야 합니다.

이 절에서는 상품 관리 문제에서 자주 쓰는 고정 용량 배열 패턴을 회원가입에 적용합니다.


lengthsize 구분

lab/LengthInsteadOfSize.java
public final class LengthInsteadOfSize {
    public static void main(String[] args) {
        String[] emails = new String[4];
        int[] age = new int[4];
        int size = 0;

        emails[size] = "kim@example.com";
        age[size] = 40;
        size++;

        emails[size] = "lee@example.com";
        age[size] = 50;
        size++;

        int total = 0;
        for (int index = 0; index < age.length; index++) {
            total += age[index];
            System.out.println(emails[index] + "=" + age[index]);
        }
        System.out.println("average=" + total / age.length);
    }
}
잘못된 결과
kim@example.com=40
lee@example.com=50
null=0
null=0
average=22

예외는 발생하지 않지만 결과는 틀렸습니다.

배열의 기본값 null0이 존재하지 않는 두 회원처럼 출력되고, 평균의 분모도 용량 4가 되어 45가 아니라 22가 됩니다.

반복 범위와 평균 분모에는 size를 사용해야 합니다.

length는 추가 가능 여부를 판단하는 용량입니다.

if (size >= emails.length) {
    System.out.println("저장 공간이 가득 찼습니다.");
} else {
    emails[size] = "park@example.com";
    age[size] = 35;
    size++;
}

두 병렬 배열에 값을 모두 쓴 다음 마지막에 size를 증가시킵니다.

중간에 검증이 실패했는데 size부터 증가시키면 빈 회원이 생깁니다.


저장 범위 집계

src/SummaryWithSize.java
public final class SummaryWithSize {
    public static void main(String[] args) {
        int[] age = new int[5];
        int size = 3;
        age[0] = 40;
        age[1] = 50;
        age[2] = 30;

        int total = 0;
        for (int index = 0; index < size; index++) {
            total += age[index];
        }

        double average = size == 0 ? 0.0 : (double) total / size;
        System.out.println("total=" + total);
        System.out.println("average=" + average);
    }
}
total=120
average=40.0

정수끼리 나누면 소수 부분이 먼저 버려집니다.

(double) total / size처럼 피연산자 하나를 실수로 변환해야 실수 나눗셈이 됩니다.

size가 0이면 나눗셈 자체가 불가능하므로 평균의 정책을 정해야 합니다.

여기서는 회원 없음의 평균을 0.0으로 표시했지만, 화면에 회원 없음을 출력하거나 계산을 건너뛰는 편이 더 정확할 수도 있습니다.

합계가 커질 수 있는 영역에서는 int total 대신 long total을 고려합니다.

회원 나이 몇 건에서는 int가 충분하지만, 단위를 밀리초로 바꾸거나 장기간 데이터를 합치면 범위를 검토해야 합니다.

자료형 선택도 요구사항의 일부입니다.


최솟값과 최댓값 초기화

최솟값을 0으로 초기화하면 모든 회원 나이가 양수일 때 최솟값이 계속 0으로 남습니다.

0은 저장된 회원이 아니라 개발자가 만든 가짜 값입니다.

src/MemberExtremes.java
public final class MemberExtremes {
    public static void main(String[] args) {
        String[] emails = {"kim@example.com", "lee@example.com", "park@example.com", null};
        int[] age = {40, 55, 25, 0};
        int size = 3;

        if (size == 0) {
            System.out.println("회원 없음");
            return;
        }

        int minIndex = 0;
        int maxIndex = 0;
        for (int index = 1; index < size; index++) {
            if (age[index] < age[minIndex]) {
                minIndex = index;
            }
            if (age[index] > age[maxIndex]) {
                maxIndex = index;
            }
        }

        System.out.println("min=" + emails[minIndex] + ":" + age[minIndex]);
        System.out.println("max=" + emails[maxIndex] + ":" + age[maxIndex]);
    }
}
min=park@example.com:25
max=lee@example.com:55

값만 기억하지 않고 인덱스를 기억하면 같은 위치의 email도 함께 찾을 수 있습니다.

같은 최댓값이 여러 개일 때 >는 먼저 나온 회원을 유지하고 >=는 나중 회원 정보로 교체합니다.

어느 쪽이 맞는지는 동점 정책에 따라 정합니다.

빈 배열과 회원이 0개인 배열은 다릅니다.

new int[5]는 원소 다섯 개가 있지만 size가 0이면 저장된 회원은 없습니다.

최솟값·최댓값 계산 전에 size를 검사해야 index 0 접근도 피할 수 있습니다.


회원 관리 패턴

상품 관리 예제는 보통 상품명과 가격을 병렬 배열에 넣고, 추가와 목록 출력을 반복합니다.

회원가입도 이메일과 나이를 병렬 배열로 관리하므로 구조가 같습니다.

상품 관리회원가입공통 불변식
상품명 배열이메일 배열같은 index의 문자열이 이름 역할
가격 배열회원 나이 배열같은 index의 숫자가 속성 역할
상품 수회원 수0 <= size <= length
재고 초과 방지회원 용량 초과 방지추가 전에 capacity 검사

도메인 단어는 다르지만 저장 알고리즘은 재사용할 수 있습니다.

다만 상품 가격에는 0이 허용될 수 있고 회원 나이에는 양수만 허용한다는 식으로 검증 규칙은 도메인에 맞게 달라집니다.


배열 기반 명령 흐름

다음 예제는 입력을 문자열로 고정해 추가, 목록, 요약 흐름을 재현합니다.

실제 콘솔로 바꾸려면 new Scanner(System.in)만 사용하면 됩니다.

src/ArrayMemberRegistry.java
import java.util.Scanner;

public final class ArrayMemberRegistry {
    public static void main(String[] args) {
        Scanner scanner = new Scanner("add\nkim@example.com\n40\nadd\nlee@example.com\n50\nlist\nsummary\nquit\n");
        String[] emails = new String[3];
        int[] age = new int[3];
        int size = 0;
        boolean running = true;

        while (running && scanner.hasNextLine()) {
            String command = scanner.nextLine().trim();

            switch (command) {
                case "add" -> {
                    if (size >= emails.length) {
                        System.out.println("full");
                        break;
                    }

                    String email = scanner.nextLine().trim();
                    int value = Integer.parseInt(scanner.nextLine().trim());
                    if (email.isEmpty() || value < 14 || value > 120) {
                        System.out.println("invalid");
                        break;
                    }

                    emails[size] = email;
                    age[size] = value;
                    size++;
                    System.out.println("added");
                }
                case "list" -> {
                    for (int index = 0; index < size; index++) {
                        System.out.println((index + 1) + ". " + emails[index] + "=" + age[index]);
                    }
                }
                case "summary" -> {
                    int total = 0;
                    for (int index = 0; index < size; index++) {
                        total += age[index];
                    }
                    double average = size == 0 ? 0.0 : (double) total / size;
                    System.out.println("total=" + total + ", average=" + average);
                }
                case "quit" -> running = false;
                default -> System.out.println("unknown");
            }
        }
    }
}
added
added
1. kim@example.com=40
2. lee@example.com=50
total=90, average=45.0

이 프로그램이 지켜야 할 핵심 불변식은 네 가지입니다.

  1. size는 0보다 작지 않고 배열 길이보다 크지 않습니다.
  2. 0부터 size - 1까지는 두 배열 모두 유효한 값이 있습니다.
  3. size부터 length - 1까지는 사용하지 않는 영역입니다.
  4. 검증을 통과한 회원만 저장하고 그때만 size를 증가시킵니다.

현재는 모든 로직이 main 안에 있어 길고, 같은 집계가 다른 메뉴에 추가되면 중복될 수 있습니다.

다음 절에서 추가·출력·집계를 메서드로 분리해 변경 이유가 다른 코드를 떼어 냅니다.


용량 초과 처리

검사 없이 네 번째 원소를 넣으면 즉시 실패합니다.

lab/CapacityOverflow.java
public final class CapacityOverflow {
    public static void main(String[] args) {
        String[] emails = new String[2];
        int size = 0;

        emails[size++] = "kim@example.com";
        emails[size++] = "lee@example.com";
        emails[size++] = "park@example.com";
    }
}
실패 관찰
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 2 out of bounds for length 2

예외 메시지는 문제가 생긴 인덱스 2와 배열 길이 2를 함께 보여 줍니다.

그러나 사용자 입력 때문에 프로그램이 예외로 종료되게 두기보다, 추가 전에 공간을 확인해 full이라는 도메인 결과로 바꾸는 편이 낫습니다.


연습 문제

이메일과 나이 배열, size가 주어졌을 때 30세 이상인 회원의 개수, 합계, 평균을 출력하세요.

조건을 만족하는 회원이 하나도 없으면 평균 대신 none을 출력합니다.

해설 보기
src/QualifiedSummary.java
public final class QualifiedSummary {
    public static void main(String[] args) {
        int[] age = {20, 45, 30, 60, 0};
        int size = 4;
        int count = 0;
        int total = 0;

        for (int index = 0; index < size; index++) {
            if (age[index] >= 30) {
                count++;
                total += age[index];
            }
        }

        System.out.println("count=" + count);
        System.out.println("total=" + total);
        if (count == 0) {
            System.out.println("average=none");
        } else {
            System.out.println("average=" + (double) total / count);
        }
    }
}
count=3
total=135
average=45.0

평균의 분모는 전체 size가 아니라 조건을 통과한 count입니다.

필터링과 집계를 함께 할 때 어떤 집합의 평균인지 끝까지 일치시켜야 합니다.