Java

[Effective Java] 8-2. 메서드

noahkim_ 2024. 12. 31. 22:38

조슈아 블로크 님의 "Effective Java" 책을 정리한 포스팅 입니다.


1. 다중정의는 신중히 사용하라

다중정의한 메서드는 정적으로 선택됨

오버로딩의 호출 메서드는 컴파일 타임에 정해짐
public class CollectionClassifier {
    public static String classify(Set<?> s) { return "set"; }
    public static String classify(List<?> list) { return "list"; }
    public static String classify(Collection<?> c) { return "collection"; }    
    
    public static void main(String[] args) {
    	CollectionClassifier classifier = new CollectionClassifier();
        
        Collection<?>[] list = new {new HashSet<>(), new ArrayList<>()};
    
    	for (Collection<?> c : list) {
            System.out.println(classifier.classify(c); // collection
        }
    }
}
  • 런타임에는 매번 타입이 달라지지만 호출할 메서드를 결정하지 못함

 

해결책
public class CollectionClassifier {
    public static String classify(Collection<?> c) { 
    	return  c instanceof Set ? "set" :
    	    	c instanceof List ? "list" : "collection";         
    }    
    
    public static void main(String[] args) {
    	CollectionClassifier classifier = new CollectionClassifier();
        
        Collection<?>[] list = new {new HashSet<>(), new ArrayList<>()};
    
    	for (Collection<?> c : list) {
            System.out.println(classifier.classify(c); 
        }
        
        // set
        // list
    }
}
  • instanceof 활용

 

혼동을 피하기 위해 매개변수 수가 같은 다중정의는 만들지 말자

정적 팩터리 사용하기 (생성자)
메서드 이름을 다르게 짓기
public class ObjectOutputStream
    extends OutputStream implements ObjectOutput, ObjectStreamConstants {
    
    public void write(byte[] buf) throws IOException {
        bout.write(buf, 0, buf.length, false);
    }
    
    public void writeBoolean(boolean val) throws IOException {
        bout.writeBoolean(val);
    }

    public void writeByte(int val) throws IOException  {
        bout.writeByte(val);
    }
    
    public void writeShort(int val)  throws IOException {
        bout.writeShort(val);
    }
}

 

타입이 서로 연관되지 않다면, 매개변수 수가 같은 다중정의는 런타임만 신경쓰면 됨

 

ArrayList(int), ArrayList(Collection)
  • 인자의 타입이 서로 연관이 없어 혼동이 일어나지 않음

 

타입이 서로 연관되어 있다면, 매개변수 수가 같은 다중정의는 구체 타입으로 호출됨

Collection.remove(Object), Collection.remove(Integer)
public class OverloadingExample {
    public static void remove(Integer i) {
        System.out.println("remove(Integer) called with: " + i);
    }

    public static void remove(Object o) {
        System.out.println("remove(Object) called with: " + o);
    }

    public static void main(String[] args) {
        int number = 5;
        remove(number); // int -> Integer (autoboxing) -> remove(Integer) 호출
    }
}
  • int 값이 온다면, 더 구체적인 Collection.remove(Integer)가 호출됨

 

서로 다른 함수형 인터페이스 더라도 같은 위치의 인수를 받아서는 안됨

ExecutorService.submit
ExecutorService exec = Executors.newCachedThreadPool();
exec.submit(System.out::println);
  • 다중정의 메서드를 메서드 참조 시, 부정확하게 참조될 수 있음
    • Runnable, Callable<T> 를 받을 수 있음
    • System.out.println()은 여러 시그니처를 가지고 있으므로 현재 정보로는 타입 추론이 어려움
  • 어느 다중정의로 구현되는지 알 수 없어 예외가 발생함

 

해결책: 명시적으로 전달하기
exec.submit(() -> System.out.println());
exec.submit((Runnable) System.out::println);

 

 

2. 가변인수는 신중히 사용하라

가변 인수

  • 명시한 타입의 인수를 0개 이상 받을 수 있음
  • 인수의 개수와 길이가 같은 배열을 만들고 이 인수들을 배열에 저장하여 가변인수 메서드에 건네줌

 

예외 상황

인수가 1개 이상을 강제해야 하는 메서드
  • 메서드 몸체 내에서 런타임 에러가 발생함
  • 이를 위해 유효성 검사를 명시적으로 해야 함

 

해결책: 매개변수 2개 받기
static int min(int firstArg, int... remainingArgs) {}
  • 평범함 매개변수 받기
  • 가변인수 매개변수 받기

 

3. null이 아닌, 빈 컬렉션이나 배열을 반환하라

null을 반환하면 안되는 이유

사용하기 어려움
List<String> result = getList();
if (result != null) {
    for (String item : result) {
        System.out.println(item);
    }
}
  • 호출하는 쪽에서 항상 null check 해야 함

 

오류 가능성 증가
List<String> result = getList();
for (String item : result) { // NPE 가능성 있음
    System.out.println(item);
}
  • null check 누락 시, NPE 발생

 

빈 불변 컬렉션 반환

Collections.emptyList() 활용
  • 수정 불가
  • 읽기 전용

 

3. 옵셔널 반환은 신중히 하라

Optional<T>

  • 값이 존재할 수도, 존재하지 않을수도 있는 상황에서 안전하게 처리하기 위한 컨테이너
  • null을 직접 다루는 대신, 값이 없을 가능성을 명시적으로 처리할 수 있음
    • T 타입 참조 (not null) or 아무것도 담지 않음
  • 불변 컬렉션

 

주요 메서드

get()
Optional<String> optional = Optional.of("Hello");
String value = optional.get(); // "Hello"
  • 값이 존재하면 받아올 수 있음
  • 값이 없을 경우 사용 시, NoSuchElementException 발생

 

orElse("기본값")
Optional<String> optional = Optional.empty();
String value = optional.orElse("Default Value"); // "Default Value"
  • 값이 없을 경우 기본값 설정

 

orElseGet(Supplier)
Optional<String> optional = Optional.empty();
String value = optional.orElseGet(() -> "Generated Value"); // "Generated Value"
  • 값이 없을 경우, Supplier에서 제공한 값 반환
  • 예외 시에 발생하므로, 지연 연산이 가능함

 

orElseThrow(Supplier)
Optional<String> optional = Optional.empty();
String value = optional.orElseThrow(() -> new IllegalArgumentException("No value present"));
// throws IllegalArgumentException
  • 값이 없을 경우, 지정된 예외 던짐

 

주의사항

컬렉션은 옵셔널에 담으면 안됨
래퍼타입은 옵셔널에 담으면 안됨
  • Optional은 단일 값에만 의미가 있음