NMP-Study / EffectiveJava2022

Effective Java Study 2022
5 stars 0 forks source link

아이템 52. 다중정의는 신중히 사용하라 #52

Closed okhee closed 1 year ago

windowforsun commented 1 year ago

다중정의 몇줄 요약

다중정의 를 꼭 활용할 필요는 없다. 다중정의 대신 메서드 이름을 다르게 지어주는 방법이나 정적 팩터리 사용을 고민해보자. 왜냐하면 자바가 버전업 이 되가면서 개발자가 다중정의 규칙을 완전하게 이해하기는 쉽지 않기 때문이다. (다중정의 선택 규칙은 매우 매우 복잡하다.)

다중정의(overloading) 메서드 호출에 대한 결정은 컴파일 타임에 결정 된다.

public class OverloadingTest {
    public static String classify(Set<?> s) {
        return "집합";
    }

    public static String classify(List<?> list) {
        return "리스트";
    }

    public static String classify(Collection<?> c) {
        return "그 외";
    }

    public static void main(String[] args) {
        Collection<?>[] collections = {
                new HashSet<String>(),
                new ArrayList<Integer>(),
                new HashMap<String, String>().values()
        };

        for (Collection<?> c : collections) {
            // 집합, 리스트, 그외 X
            // 그외, 그외, 그외 O
            System.out.println(classify(c));
        }
    }
}

재정의(overriding) 은 동적으로 선택되고, 다중정의(overloading) 은 정적으로 선택된다.

public class OverridingTest {
    static class Wine {
        String name() { return "포도주"; }
    }

    static class SparklingWine extends Wine {
        @Override
        String name() { return "발포성 포도주"; }
    }

    static class Champagne extends Wine {
        @Override
        String name() { return "샴페인"; }
    }

    public static void main(String[] args) {
        List<Wine> wineList = List.of(new Wine(), new SparklingWine(), new Champagne());

        for (Wine wine : wineList) {
            // 포도주, 발포성 포도주, 샴페인
            System.out.println(wine.name());
        }
    }
}

다중정의 예시코드 해결방안

 public class OverloadingResolveTest {
    public static String classify(Collection<?> c) {
        return c instanceof Set ? "집합" :
                c instanceof List ? "리스트" : "그 외";
    }

    public static void main(String[] args) {
        Collection<?>[] collections = {
                new HashSet<String>(),
                new ArrayList<Integer>(),
                new HashMap<String, String>().values()
        };

        for (Collection<?> c : collections) {
            // 집합, 리스트, 그외
            System.out.println(classify(c));
        }
    }
}

다중정의가 혼란을 주는 상황은 피해야 한다.

우리에겐 다중정의 대신 메서드 이름을 다르게 지어주는 길이 있다.

public class ObjectOutputStream
    extends OutputStream implements ObjectOutput, ObjectStreamConstants
{
    public void writeBoolean(boolean val) throws IOException {}

    public void writeByte(int val) throws IOException  {}

    public void writeShort(int val)  throws IOException {}

    public void writeChar(int val)  throws IOException {}

    public void writeInt(int val)  throws IOException {}

    public void writeLong(long val)  throws IOException {}

    ...
}

생성자는 정적 팩터리를 사용할 수 있다.

매개변수 수가 같다면, 매개변수가 근본적으로 달라야 한다.

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    public ArrayList(int initialCapacity) {}

    public ArrayList(Collection<? extends E> c) {}
}

오토박싱.. 혼란의 서막

public class AutoboxingTest {
    public static void main(String[] args) {
        Set<Integer> set = new TreeSet<>();
        List<Integer> list = new ArrayList<>();

        for (int i = -3; i < 3; i++) {
            set.add(i);
            list.add(i);
        }

        // set : [-3, -2, -1, 0, 1, 2]
        // list : [-3, -2, -1, 0, 1, 2]

        for (int i = 0; i < 3; i++) {
            set.remove(i);
            list.remove(i);
        }

        // set : [-3, -2, -1]
        // list : [-2, 0, 2] ???????
    }
}

public interface Set<E> extends Collection<E> {
    boolean remove(Object o);
}

public interface List<E> extends Collection<E> {
    boolean remove(Object o);
    E remove(int index);
}
        for (int i = 0; i < 3; i++) {
            set.remove(i);
            list.remove((Integer)i); // or list.remove(Integer.valueOf(i));
        }

람다, 메소드 참조.. 혼란 가중

public class LambdaMethodReferenceTest {
    public static void main(String[] args) {
        // ok
        new Thread(System.out::println).start();

        ExecutorService exec = Executors.newCachedThreadPool();
        // compile error
        exec.submit(System.out::println);
    }
}

public interface ExecutorService extends Executor {
    <T> Future<T> submit(Callable<T> task);
    Future<?> submit(Runnable task);
}
Reference to 'println' is ambiguous, both 'println()' and 'println(boolean)' match

그렇다면 근본적으로 다르다는 것은 무엇인가

String 너마저 ..

 public final class StringBuffer
    extends AbstractStringBuilder
    implements Serializable, Comparable<StringBuffer>, CharSequence
{
    // ...
}

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {

    public boolean contentEquals(StringBuffer sb) {
        return contentEquals((CharSequence)sb);
    }

    public boolean contentEquals(CharSequence cs) {}
}
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {

    public static String valueOf(Object obj) {
        return (obj == null) ? "null" : obj.toString();
    }

    public static String valueOf(char data[]) {
        return new String(data);
    }
}