의미 있는 코드 Style
Constructor VS Static Factory Method
Java에서는 인스턴스 생성하는 방법은 대략 2가지 정도 된다.
첫 번째는 생정자를 이용하는 방법이고,
두 번째는 정적 팩토리 메서드를 사용하는 방법이다.
정적 팩토리 메서드의 장점
- 이름을 가질 수 있다. (메서드 이름 자체로 의미 전달이 된다.)
- 생성자에 넘기는 매개변수와 생성자 자체만으로는 반환될 객체의 특성을 제대로 설명하지 못한다. 반면 정적 팩토리는 이름만 잘 지으면 반환될 객체의 특성을 쉽게 묘사할 수 있다.
- 호출될 때마다 인스턴스를 새로 생성하지는 않아도 된다. (싱글턴, 메모리 효율)
- 불변 클래스는 인스턴스를 미리 만들어 놓거나 새로 생성한 인스턴스를 캐싱하여 재활용하는 식으로 불필요한 객체 생성을 피할 수 있다.
- 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다. (다형성)
- 입력 매개변수에 따라 매번 다른 클래스의 인스턴스를 반환할 수 있다. (SOLID의 OCP)
- 정적 팩토리 메서드를 작성하는 시점에는 반환할 클래스가 실제로 존재하지 않아도 된다. (예를 들어 인터페이스를 반환한다면 실제 구현체가 없어도 선언에 문제가 없다.)
정적 팩토리 메서드의 단점
- 상속할 수 없다.
- 기본 생성자의 접근 제한자가 private으로 선언되기 때문에 상속할 수 없다.
- 정적 팩토리 메서드 찾기가 어렵다.
- 일반적인 생성자와 다른 메서드명을 사용하기 때문에, 이러한 코드가 익숙하지 않은 동료 개발자는 인스턴스를 생성하는 역할을 하는 메서드 찾기가 쉽지 않을 것이다.
- 따라서 일반적으로 사용하는 형태를 알아두면 좋다.
아래는 예시다.
from: 매개변수 하나 받아서 해당 타입의 인스턴스를 반환하는 형변환 메서드
Date d = Date.from(instant);
of: 여러 매개변수를 받아 적합한 타입의 인스턴스를 반환하는 집계 메서드Set<Developer> dev = EnumSet.of(a, b, c);
valueOf:Boolean isTrue = Boolean.valueOf("true");
이런 느낌.
Builder 패턴
생성자와 정적 팩토리의 한계
선택적 매개변수가 많을 때 정적 팩토리와 생성자는 적절히 대응하기 어렵다.
Why?
- 메서드를 호출하는 과정에서 파라미터를 할당할 때 매개변수의 순서를 틀리거나 누락할 수 있다는 휴먼 에러를 내포하고 있다.
- 가독성이 매우 현저하게 떨어진다.
How?
- Builder패턴을 클래스 내부에서 직접 구현하기
- Lombok에서 제공하는 어노테이션으로 적용하는 방식
불변 객체
불변식(Invariant)
프로그램이 실행되는 동안 또는 정해진 기간 동안 반드시 만족해야 하는 조건을 말한다. 다시 말해 변경을 허용할 수 있으나, 주어진 조건 내에서만 허용한다는 뜻이다.
예를 들면, “리스트의 경우 size의 크기는 반드시 0이상이어야 하고 한 순간이라도 음수 값이 될 수 없다.”라는 조건식이 List.size()의 불변식이다.
불변 클래스 선언하는 법
- final 클래스
- 클래스의 모든 필드를 final로 선언 한다. 단, 예외도 존재한다.
- private 필드
- 클래스의 확장(extends)을 막는다: only private, Static Factory (상속을 하는 순간, 설계자의 의도를 망가트린다.)
불변 객체의 특징
- 불변 객체는 단순하다. (예상대로 동작할 것이므로)
- 불변 객체는 자유롭게 공유할 수 있고 동일한 불변 객체 간의 내부 데이터를 공유할 수 있다.
- 불변 객체는 그 자체로 실패 원자성을 제공한다. (원자성: 성공하면 새로운 값 반환, 실패하면 오류 반환)
람다와 스트림
동작 파라미터화
동작(Method)를 Method의 파라미터로 전달하는 방식을 말한다.
람다
Java8 부터 추가된 기능으로, 익명 함수를 말한다.
(int arg1, String arg2) -> {System.out.println("Two arguments" + args1 + "and " + arg2;}
람다가 왜 중요할까?
- Java 8부터 함수를 값으로 취급할 수 있다. (1급 객체)
- 가독성이 좋아진다.
어떻게 사용하는 게 좋을까?
- 메서드를 함수처럼 선언할 수 있고 변수에 할당할 수 있다.
- 함수형 인터페이스라는 문맥에서 람다 표현식을 사용할 수 있다.
유효한 람다 표현
람다 표현식의 문법은 굉장히 유연하다.
(String s) -> s.length(); (String s) -> String::length; (Apple a) -> a.getWeight() > 25; (Apple a, Apple b) -> a.getWeight().compare(b.getWeight());
람다식 메서드 참조
실행하려는 메서드를 참조해서 매개 변수와 리턴 타입을 알아내어, 람다식에서 불필요한 선언부를 생략할 수 있는 문법을 말한다.
.map( (String s) -> s.length();) .map(String::length); (Apple a, Apple b) -> a.getWeight().compare(b.getWeight()); (a, b) -> a.getWeight().compare(b.getWeight()); .comparing(Apple::getValue) .comparing(apple -> apple.getWeight().getValue()) .comparing(Apple::getValue);
스트림
데이터 처리 연산을 지원하도록 Source Data에서 추출된 연속된 요소
스트림의 특징
- 선언형 코드를 작성할 수 있다. (여러 변화하는 요구사항에 대응하기가 좋다.)
- 여러 중간 연산을 연결해서 복잡한 데이터 처리 파이프라인을 만들 수 있다. (메서드 체이닝)
- 외부 반복이 아닌 내부 반복자를 사용한다.
- 스트림 메서드
index.stream().sorted((o1, o2) -> o1.compareTo(o2)); index.sort((o1, o2) -> o2.compareTo(o2)); index.sort(Integer::compareTo);
null로 인해 발생하는 문제
- 에러의 근원
- 아무런 의미가 없다.
- null 처리를 위한 if-else문으로 가독성이 떨어진다. (null 검증 코드는 가독성을 떨어뜨린다.)
- 자바 철학에 위배가 된다. (null만 포인터 개념을 가진다.)
- 형식 시스템에 구멍이 생긴다.
올바른 null 처리 방법
Type 시스템을 이용해서 값이 없는 경우, null이 아닌 빈 값(Empty)을 갖도록 하고 값이 있는 경우, 주어진 형식에 맞는 값을 갖도록 하는 방식이 좋다.
Optional<T>
Optional<T>는 T타입의 값을 캡슐화하는 클래스이다. 값이 존재하는 경우, 그 값을 감싸지만, 값이 존재하지 않는 경우, null이 아닌 Optional.Empty 값으로 감싼다.
Optional 사용법
- Empty 값으로 Optional 생성
- Optional.of()
- Optional.ofNullable()
Optional 유용한 Method
- isPresent()
- Optional 값이 존재하는 경우, true, 아니면 false를 리턴한다.
- 공식문서상으로는 성능상 문제가 있다고 한다.
- T get();
- 값이 존재하면 값을 반환하고, 그렇지 않다면 NoSuchElementException을 던진다.
- orElse(T other);
- Optional이 값을 포함하지 않고 있을 때 Default 값을 제공할 수 있다.
- orElseGet()
- Optional이 값을
Auto Boxing/Unboxing의 문제
개발자의 편의성과 가독성에는 도움이 되지만, 성능 문제를 일으키는 숨은 요인 중 하나다.
Collection 프레임워크를 사용하는 것보다 stream을 사용하는 것이 성능 상 이득이다.