Skip to content

아이템 53. 가변인수는 신중히 사용하라 - Fin #127

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
Irisation23 opened this issue Feb 18, 2023 Discussed in #124 · 0 comments
Open

아이템 53. 가변인수는 신중히 사용하라 - Fin #127

Irisation23 opened this issue Feb 18, 2023 Discussed in #124 · 0 comments
Assignees
Labels
8장 메서드 이펙티브 자바 8장 (메서드)

Comments

@Irisation23
Copy link
Member

Discussed in https://github.com/orgs/Study-2-Effective-Java/discussions/124

Originally posted by bunsung92 February 14, 2023
📝구성

Table of contents generated with markdown-toc

0. 들어가기에 앞서 🤔

가변인수?

  • 등장 배경

자바 5버전 이전에는 가변 인수 처리를 위해서 배열[]오버로딩을 사용해서 처리했다.
예를 들어, 여러 개의 문자열을 출력하는 printStrings() 메서드를 구현한다면, 다음과 같은 방식으로 구현할 수 있다.

  • 배열 버전
// Java 5 이전 버전에서는 가변인수를 지원하지 않으므로 배열을 이용하여 여러 개의 인수를 전달했다.
public void printStrings(String[] strings) {
    for (int i = 0; i < strings.length; i++) {
        System.out.print(strings[i]);
    }
}
  • 메서드 오버로딩 버전
// Java 5 이전 버전에서는 가변인수를 지원하지 않으므로 메서드 오버로딩을 이용하여 여러 버전의 메서드를 구현했다.
public void printStrings(String s1) {
    System.out.print(s1);
}

public void printStrings(String s1, String s2) {
    System.out.print(s1);
    System.out.print(s2);
}

public void printStrings(String s1, String s2, String s3) {
    System.out.print(s1);
    System.out.print(s2);
    System.out.print(s3);
}

// ... More Method

Java 5 버전 이후 가변인수가 도입된 이후에도 여전히 위의 방법을 사용할 수 있지만, 가변인수를 사용하면 배열이나 메서드 오버로딩을 이용한 구현보다 간결하고 가독성이 높은 코드를 작성할 수 있게 되었다.

// 가변인수를 이용하여 구현한 printStrings 메서드
public void printStrings(String... strings) {
    for (int i = 0; i < strings.length; i++) {
        System.out.print(strings[i]);
    }
}

근데 쓰라고 만들어 놓은 메서드를 왜 신중히 사용하라고 하는걸까?
이제 알아보도록 하자.

1. 가변인수를 신중히 사용해야 하는 이유 ✨

위에서 만든 printStrings에 추가 요구사항이 들어왔다.
최소 인자 한개를 두지 않으면 예외를 처리 해 달라는 것이다.

  • 가변인수 메서드를 호출하면 먼저 인수의 갯수와 같은 배열을 만들고 인수들을 배열에 저장해 가변인수 메서드에 전달한다.
    public void printStrings(String... strings) {
        if (strings.length < 1) {
            throw new IllegalArgumentException("인수 1개 이상 필요.");
        }

        for (int i = 0; i < strings.length; i++) {
            System.out.print(strings[i]);
        }
    }

printStrings("foo", "bar", "barz");
pirntStrings("foo", "bar");
pirntStrings(); // 에러 발생

해당 코드는 원하는 결과를 도출하는데 문제점이 있다.

바로 컴파일 단계를 넘어서 런타임시점에 에러가 발생한다는 점이다.

이를 해결하기 위한 비책은 기본 파라미터와 가변인자 파라미터를 중복으로 두는 방법이다.
코드로 이를 설명하자면 아래와 같다.

    public void printStrings(String firstString, String... strings) {
        for (int i = 0; i < strings.length; i++) {
            System.out.print(strings[i]);
        }
    }

printStrings("foo", "bar", "barz");
pirntStrings("foo", "bar");
pirntStrings(); // 컴파일 에러 발생

런타임 시점에 에러를 던져 처리하는 것보다 코드가 더 명확해졌다. 그리고 코드를 작성하는 시점에서 빠르게 에러 사항을 감지할 수 있다.

하지만 이는 성능에 민감한 사항에서 걸림돌이 될 수 있다.
위에서 말했던처럼 가변인수 메서드는 호출될 때마다 배열을 새로 하나 할당하고 초기화 한다.

이말을 다시 풀어보자면 가변인수의 호출없이 코드를 사용하게 된다면 필요없는 배열의 할당과 초기화가 일어나는 것이다.

위의 코드를 개발하던 개발자는 메서드의 호출 횟수를 체크하게 되었다.
메서드 호출을 확인 해 보니 해당 메서드는 1개에서 3개까지 호출하는 경우가 95% 이상이였고, 나머지 5%만 가변인자 메서드를 사용하는것을 알게 되었다.

리팩토링을 감행하고자보니 오버로딩이 적합해 보였다.

public void printStrings(String firstString) { }
public void printStrings(String firstString, String secondString) { }
public void printStrings(String firstString, String secondString, String thirdString) { }
public void printStrings(String firstString, String secondString, String thirdString, String... strings) { }

실제로 자바 코드에서는 EnumSet 의 정적 팩터리도 이 기법을 사용해 열거 타입 집합 생성 비용을 최소화 하고 있다.

public static <E extends Enum<E>> EnumSet<E> of(E e) {
    EnumSet<E> result = noneOf(e.getDeclaringClass());
    result.add(e);
    return result;
}
public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2) {
    EnumSet<E> result = noneOf(e1.getDeclaringClass());
    result.add(e1);
    result.add(e2);
    return result;
}

public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3) {
    EnumSet<E> result = noneOf(e1.getDeclaringClass());
    result.add(e1);
    result.add(e2);
    result.add(e3);
    return result;
}

public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, E e4) {
    EnumSet<E> result = noneOf(e1.getDeclaringClass());
    result.add(e1);
    result.add(e2);
    result.add(e3);
    result.add(e4);
    return result;
}

public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, E e4,
                                                    E e5) {
    EnumSet<E> result = noneOf(e1.getDeclaringClass());
    result.add(e1);
    result.add(e2);
    result.add(e3);
    result.add(e4);
    result.add(e5);
    return result;
}

@SafeVarargs
public static <E extends Enum<E>> EnumSet<E> of(E first, E... rest) {
    EnumSet<E> result = noneOf(first.getDeclaringClass());
    result.add(first);
    for (E e : rest)
        result.add(e);
    return result;
}

2. 핵심 정리 📚

  • 인수 개수가 일정하지 않은 메서드를 정의해야 할때 가변인수를 고려하자.
  • 메서드 정의에 필수 매개변수는 앞에 두고, 가변매개 변수를 뒤에 두자.
  • 성능도 체킹해 볼 수있다면 더 좋다.

3. 회고 🧹

2022.02.18

  • 큰 문제 없었던 파트였다.
  • 과거에 메서드 오버로딩이 귀찮아서 사용했던 경험이 있는데, 실질적인 동작이 배열을 만들어서 사용한다는 점은 이번에 알게 된 사실이였다.
  • EnumSet 은 여러번 강조되고 있는걸 보니, 저자가 굉장히 심혈을 기울였다는것을 알게 되었다.

참조 자료
https://github.com/Meet-Coder-Study/book-effective-java/blob/main/8%EC%9E%A5/53_%EA%B0%80%EB%B3%80%EC%9D%B8%EC%88%98%EB%8A%94_%EC%8B%A0%EC%A4%91%ED%9E%88_%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC_%EA%B9%80%EC%84%B8%EC%9C%A4.md

@Irisation23 Irisation23 added the 8장 메서드 이펙티브 자바 8장 (메서드) label Feb 18, 2023
@Irisation23 Irisation23 self-assigned this Feb 18, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
8장 메서드 이펙티브 자바 8장 (메서드)
Projects
None yet
Development

No branches or pull requests

1 participant