본문 바로가기

Reading/Effective Java

[Effective-Java] Item 59.라이브러리를 익히고 사용하라

Random


Random 섹터에서 이슈는 아래와 같이 정리했습니다.

 

 

[아이템 59] Random 클래스의 nextInt()와 URL 예제 · Issue #15 · Reading-Effective-Java/community

p351. 앞의 두가지 문제점에서 n이 크지않은 2의 제곱수와 2의 제곱수가 아닐때 평균적으로 더 자주 반환된다고 나와있는데 n을 Integer.MAX_VALUE로 설정하거나 /2, /3을 했을 때는 중간값에 거의 수렴

github.com

 

0부터 n까지 수 사이에서 무작위 정수 하나를 생성하려 한다고 해보자. 그러면 다음과 같이 만들어 볼 수 있을 것이다.

 

static Random rnd = new Random();

static int random(int n) {
return Math.abs(rnd.nextInt()) % n;
}

 

하지만 다음 코드는 세 가지 문제를 가진다.

 

1. n이 그리크지 않은 2의 제곱수라면 같은 수열이 반복된다(상단 이슈 참고)

 

2. n이 2의 제곱수가 아니라면 몇몇 숫자가 평균적으로 더 자주 반환되고, n이 클수록 더 두드러진다(상단 이슈 참고)

 

public static void main(String[] args) {
	int n = 2 * (Integer.MAX_VALUE / 3);
	int low = 0;
	for (int i = 0; i < 1000000; i++)
		if (rnd.nextInt(n) < n/2)
			low++;
	System.out.println(low);
}

 

n을 설정하고 무작위로 백만개를 생성해서 중간 값보다 작은게 몇개인지 출력해보자.

random 메소드가 이상적이라면 약 50만개에 가까워야하지만, 666,666에 가까운 값을 얻는다.

 

3. 지정한 범위 바깥의 수가 종종 튀어나온다.

nextInt()가 Integer.MIN_VALUE를 반환하면 Math.abs를 이용해 음수가 아닌 정수로 매핑하는 과정에서 부합하는 수가 없다.

따라서 나머지 연산자는 음수를 반환해버린다. 예기치않은 실패의 현상 재현도 쉽지 않다.

 

 

이 결함을 해결하기 위해 의사난수 생성기, 정수론, 2의 보수 계산 등을 알아야하지만 이를 해결한 메소드가 이미 존재한다.

Random.nextInt(int)가 이 문제들을 해결했다. 매개변수로 bound 값을 넘기면 적절한 랜덤 수가 반환된다.(상단 이슈 참고)

구현에 검증이 된 라이브러리고 수백만 개발자가 사용했지만 버그가 보고된 적 없고 발견되더라도 다음 릴리즈에서 수정될 것이다.

 

자바 7부터는 ThreadLocalRandom으로 대체하면 잘 작동하고, Random보다 더 고품질의 무작위 수를 생성하고 속도도 더 빠르다.

포크-조인 풀이나 병렬 스트림에서는 SplittableRandom을 사용하면 되고,

시드 보안을 확실하게 하고 싶다면 SecureRandom을 사용하자. 속도는 더 느리지만 더 많은 경우의 수를 가지고 있다는 장점이 있다.

 

 

표준 라이브러리 사용의 이점


1. 코드를 작성한 전문가의 지식과 우리보다 앞서 사용한 다른 프로그래머들의 경험을 활용할 수 있다.

 

2. 핵심적인 일과 크게 관련없는 문제를 해결하느라 시간을 허비하지 않아도 된다.

 

3. 따로 노력하지 않아도 성능이 지속해서 개선된다.

사용자가 많고, 업계 표준 벤치마크를 사용해 성능을 확인해서 더 나은 방법을 모색하며 다시 작성되어성능이 극적으로 개선되기도 한다.

 

4. 기능이 점점 많아진다.

라이브러리에 부족한 점이 있다면 논의가 계속 될거고 다음 릴리즈에 해당 기능이 추가되곤 한다.

 

5. 작성한 코드가 많은 사람에게 낯익은 코드가 된다.

자연스레 다른 개발자들이 더 읽기 좋고, 유지보수하기 쉬우며 재활용하기 쉬운 코드가 된다.

 

라이브러리 알아두기


 

메이저 릴리스마다 수많은 기능이 라이브러리에 추가된다.[참고: JDK 10]

지정한 URL의 내용을 가져오는 명령줄 애플리케이션은 다음과 같다. 이전에는 작성하기 까다로웠지만 자바 9에서 InputStream에 추가된 transfer 메소드를 사용하면 쉽게 구현할 수 있다.

 

public static void main(String[] args) throws IOException {
    try (InputStream in = new URL(args[0]).openStream()) {
        in.transferTo(System.out);
    }
}

 

 

 

라이브러리 사용의 대안


1. 라이브러리는 매년 아주 빠르게 성장하고 있으니 모든 기능을 요약하긴 어렵고, 원하는 기능이 아니면 대안을 사용하자. 

다음과 같은 우선순위로 알아두자.

  • java.lang, java.util, java.io와 하위 패키지
  • 컬렉션 프레임워크와 스트림 라이브러리
  • java.tuil.concurrent의 동시성 기능 - 멀티스레드 프로그래밍 작업을 단순화해주는 고수준 편의 기능

2. 고품질의 서드파티 라이브러리 사용 ex) 구글의 구아바 라이브러리

3. 직접 구현

 

누군가 이미 구현한 라이브러리가 있다면 찾아서 쓰자. 우리가 작성한 코드보다 품질이 좋고, 점차 개선될 가능성이 크다.