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

안동민 개발노트

본문 시작
10장 : 래퍼·열거형·시간 API

System·Math·Random 기본 도구

프로세스 환경, 수학 계산, 재현 가능한 의사 난수를 각각 작은 예제로 구분합니다.

표준 라이브러리는 자주 필요한 기능을 이미 구현해 제공합니다.

System은 현재 Java 프로세스의 입출력과 환경에, Math는 상태 없는 수치 계산에, Random은 의사 난수 순서에 접근합니다.

세 도구는 편리하지만 역할이 다르므로 한 객체처럼 묶어 생각하지 않습니다.

이전 문서에서 기본형과 래퍼를 배웠고, 이 문서가 끝나면 실행 환경의 값·결정적 계산·외부에서 주어지는 난수를 구분할 수 있습니다.

타입 자체를 조사하는 Class와 리플렉션은 ch28에서 처음부터 배웁니다.


System의 입출력과 속성

System.out은 일반 출력, System.err는 오류 출력, System.in은 표준 입력입니다.

System.getProperty는 JVM이 시작될 때 정해진 실행 환경의 속성을 읽습니다.

src/SystemBasics.java — 정상 최소 예제
public class SystemBasics {
    public static void main(String[] args) {
        System.out.println("java=" + System.getProperty("java.version"));
        System.out.println("os=" + System.getProperty("os.name"));
        System.out.println("separator=" + System.lineSeparator().length());
        System.err.println("diagnostic=sample");
    }
}

Java 버전과 운영체제 이름은 컴퓨터마다 다릅니다.

프로그램의 정답으로 문자열 전체를 고정하지 말고 실제로 필요한 조건만 검사합니다.

업무 메서드 곳곳에서 전역 환경을 직접 읽으면 입력이 숨으므로, 시작점에서 읽어 필요한 객체에 값으로 전달하는 편이 좋습니다.


경과 시간 측정

System.nanoTime()은 두 시점 사이의 경과 시간을 재는 기준입니다.

달력 시각이나 파일에 저장할 현재 날짜로 해석하지 않습니다.

src/ElapsedTime.java
public class ElapsedTime {
    public static void main(String[] args) {
        long start = System.nanoTime();

        long total = 0;
        for (int number = 1; number <= 100_000; number++) {
            total += number;
        }

        long elapsed = System.nanoTime() - start;
        System.out.println("total=" + total);
        System.out.println("elapsed-nonnegative=" + (elapsed >= 0));
    }
}

경과 나노초의 실제 숫자는 실행마다 달라집니다.

짧은 코드 한 번의 시간으로 성능 결론을 내리면 JIT 준비, 운영체제 스케줄링, 다른 프로세스의 영향을 분리하지 못합니다.

정확한 벤치마크는 ch38에서 다룹니다.


Math의 결정적 계산

Math의 정적 메서드는 같은 입력에 같은 계산 결과를 냅니다.

src/MathBasics.java
public class MathBasics {
    public static void main(String[] args) {
        int postCount = 47;
        int pageSize = 25;

        int pages = (int) Math.ceil((double) postCount / pageSize);
        int bounded = Math.min(Math.max(postCount, 1), 600);
        int distance = Math.abs(postCount - 50);

        System.out.println("pages=" + pages);
        System.out.println("bounded=" + bounded);
        System.out.println("distance=" + distance);
    }
}
pages=2
bounded=47
distance=3

ceil은 올림, min·max는 작은 값과 큰 값을 선택하고 abs는 절댓값을 구합니다.

금액처럼 정확한 십진 계산이 필요한 값에는 부동소수점 double보다 BigDecimal 같은 별도 표현을 검토합니다.


Random과 시드

의사 난수 생성기는 내부 상태에서 다음 값을 계산합니다.

생성할 때 같은 시드를 주면 같은 구현에서 같은 순서를 다시 얻을 수 있어 예제를 재현하기 쉽습니다.

src/SeededPicker.java
import java.util.Random;

public class SeededPicker {
    public static void main(String[] args) {
        String[] categories = {"notice", "question", "free"};
        Random random = new Random(42);

        int first = random.nextInt(categories.length);
        int second = random.nextInt(categories.length);

        System.out.println("first=" + categories[first]);
        System.out.println("second=" + categories[second]);
    }
}

nextInt(bound)는 0 이상 bound 미만을 반환하므로 배열 인덱스로 사용할 수 있습니다.

배열 길이가 0이면 유효한 범위가 없으므로 호출 전에 입력을 확인해야 합니다.

고정 시드는 디버깅과 예제 재현에 적합하지만 비밀번호 재설정 토큰이나 인증 코드를 만들기에는 예측 가능합니다.

보안 목적에는 SecureRandom을 사용하고 운영 정책에 맞는 엔트로피와 저장 방식을 정합니다.


배열 복사

System.arraycopy는 한 배열의 연속 구간을 다른 배열로 복사합니다.

시작 위치와 길이의 단위는 원소 개수입니다.

src/ArrayCopyBasics.java
import java.util.Arrays;

public class ArrayCopyBasics {
    public static void main(String[] args) {
        int[] source = {20, 30, 40, 50};
        int[] target = new int[2];

        System.arraycopy(source, 1, target, 0, 2);
        System.out.println(Arrays.toString(target));
    }
}
[30, 40]

잘못된 시작 위치나 너무 긴 길이는 배열 범위 예외를 만듭니다.

단순 반복문이 더 읽기 쉬운 작은 변환이라면 반복문을 사용하고, 연속 구간 복사라는 의도가 분명하면 arraycopy 또는 Arrays.copyOfRange를 선택합니다.


도구 선택 기준

질문도구
프로세스의 출력·속성이 필요한가System
같은 입력의 수치 결과가 필요한가Math
난수 순서가 필요한가Random
예측하기 어려운 보안 난수가 필요한가SecureRandom
타입의 필드·메서드를 실행 중 조사하는가ch28의 Class·리플렉션

연습 문제

1부터 100 사이의 게시글 수를 무작위로 하나 고르고, 페이지당 25개씩 보여 줄 때 몇 페이지가 필요한지 올림해 출력하세요.

Random(7)을 사용해 반복 실행 결과를 재현하고, 결과가 항상 1~100 범위인지 Math.min·Math.max 없이 조건문으로 확인하세요.