의미있는 코드 Style
- Java에서 인스턴스를 생성하는 방법
- 1. 생성자. Constructor Method 이용
- 2. 정적 팩토리 메서드: Static Factory 이용
- 장점
- 이름을 가질 수 있다. (의미 전달)
- 반환될 객체의 특성을 쉽게 묘사.
- 반면에 생성자는 클래스명과 같기 때문에.
- 호출될 때마다 인스턴스를 새로 생성하지는 않아도 된다. (싱글톤)
- 시간이 지나고 프로젝트가 커지면 메모리 관리를 해야함.
- 반환 타입의 하위 타입 객체를 반환할 수 있다. (다형성)
- 입력 매개변수에 따라 매번 다른 클래스의 인스턴스를 반환 가능 (OCP)
- 상위 클래스를 반환해도, 하위 클래스를 반환해도 된다.
- 정적 팩토리 메서드를 작성하는 시점에는 반환할 클래스의 객체가 존재하지 않아도 된다.
- 인터페이스로만 존재하고, 구현체로 존재하지 않아도 된다.
- 이름을 가질 수 있다. (의미 전달)
- 단점
- 상속할 수 없다.
- 기본 생성자의 접근 제한자가 private로 선언되기 때문에
- 정적 팩토리 메서드 찾기가 어렵다
- 일반적인 생성자는 클래스명과 동일한 이름이지만, 정적 팩토리 메서드는 다른 메서드 명을 사용해서 이런 코드가 익숙치 않은 동료 개발자는 인스턴스 생성 메서드를 찾기 어려움
- 이름 짓기
- from: 매개변수를 하나 받아서 해당 타입의 인스턴스를 반환하는 형변환 메서드
- Date d = Date.from(instance);
- of: 여러 매개변수를 받아 적합한 타입의 인스턴스를 반환하는 집계 메서드/ Entity -> DTO, DTO -> Entity
- Set<Developer> dev = EnumSet.of(CHOI, DONG, JUN);
- valueOf: 어떤 값으로 해당하는 객체를 만들 때.
- Boolean isTrue = Boolean.valueOf("true")
- from: 매개변수를 하나 받아서 해당 타입의 인스턴스를 반환하는 형변환 메서드
- 상속할 수 없다.
- 장점
정적 팩토리 메서드 예시
@Getter
public class PaymentRequestDto {
private final String pgCorpName;
private PaymentRequestDto(String name) {
this.pgCorpName = PgCorp.valueOf(name.toUpperCase()).toString().toLowerCase();
}
public static PaymentRequestDto of(String pgCorpName) {
return new PaymentRequestDto(pgCorpName);
}
}
Builder 패턴
- 생성자와 정적 팩토리의 한계: 선택적 매개변수가 많을 때 생성자와 정적 팩토리는 적절히 대응하기 어렵다.
- 메서드를 호툴하는 과정에서 파라미터 순서가 중요하고 누락될 수 있는 휴먼 에러를 내포
- 가독성이 떨어짐
- Builder 패턴을 사용하면 이러한 문제를 해결 가능
- 방법
- Builder 패턴을 클래스 내부에서 직접 구현
- Lombok에서 제공하는 어노테이션으로 적용
- 방법
직접 구현
public class NutritionFacts {
private final int serviceSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;
public static class Builder {
private final int servingSize;
private final int servings;
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;
public Builder(int servingSize, int servings) {
this.serviceSize = serviceSize;
this.servings = servings;
}
public Builder calories(int calories) {
this.calories = calories;
return this;
}
public Builder fat(int fats) {
this.fat = fats;
return this;
}
...
}
private NutritionFacts(Builder builder) {
serviceSize = builder.serviceSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
...
}
}
이렇게 구현되고, Builder는 inner class로, 또한, 생성자는 private으로
Lombok 어노테이션 사용
@Builder
@RequiredArgsConstructor
public class TossApproveMessage extends CommonApproveMessage {
private final String paymentKey;
private final String orderId;
private final int totalAmount;
}
두가지 방법 중 적절히 선택해야함.
불변 객체
- 불변식 (Invariant)
- 프로그램이 실행되는 동안 또는 정해진 기간 동안 반드시 만족해야 하는 조건
- 변경을 허용할 수는 있으나, 주어진 조건 내에서만 허용한다
- 리스트의 경우, size는 반드시 0 이상이어야 하고, 한 순간이라도 음수 값이 될 수 없다.라는 조건식이 List.size()의 불변식
- 불변 클래스 선언 방법
- 클래스에 final 붙이기
- 클래스의 모든 필드에 final 붙이기
- 항상 설계자의 의도대로 행동하도록
- private 필드
- 클래스의 확장을 막기
- private만 쓰고, static factory
- 상속을 하면 설계자의 의도를 망가뜨릴 수도
- 불변 객체 특징
- 단순함 (예상대로 동작하니까)
- 자유롭게 공유할 수 있고 동일한 불변 객체 간의 내부 데이터를 공유할 수 있다
- 그 자체로 실패 원자성을 제공한다.
- 미처 생각하지 못한 타입이나 범위의 값이 나오지 않는다.
람다와 스트림
- 동작 파라미터화
- 메서드를 메서드의 파라미터로 전달하는 방식
- new Predicate<>(), new Runnable
- 하지만, 함수형 파라미터를 잘 사용하지 않음.
- 가독성이 나빠서
- 비즈니스 로직과 관련 없는 부가적인 코드가 많아서
- 람다
- 익명 함수 기능
- (int arg1, String arg2) -> {System.out.println("Two arguments " + arg1 + " and " + arg2);}
- 함수를 값으로 취급할 수 있다.
- 가독성이 좋아진다.
- 대표적으로 Consumer와 Predicate
메서드를 함수처럼 선언할 수 있고, 변수에 할당 가능
Predicate<Apple> filterByRed = (Apple a) -> "RED".equals(a.getColor());
Function<String, Integer> getLength1 = (String s) -> s.length();
Function<String, Integer> getLength2 = String::length;
함수형 인터페이스라는 Context에서 람다 표현식을 사용 가능
filterBy(apples, (Apple apple) -> "RED".equals(apple.getColor()));
filterBy(apples, (a) -> a.getWeight() > 15);
- 유효한 람다 표현식
- 람다 표현식의 문법은 유연함
(String s) -> s.length(); //s변수의 문자열 길이를 int 형으로 리턴
(String s) -> String::length; //s변수의 문자열 길이를 int 형으로 리턴
(Apple a) -> a.getWeight > 25; // 25보다 크면 true, 작으면 false 리턴
(Apple a, Apple b) -> a.getWeight().compare(b.getWeight());
(a,b) -> a.getWeight().compare(b.getWeight()); //Apple 타입 생략 가능
() -> 25 //그냥 25 리턴
(Apple a1, Apple a2) -> {
System.out.println(a1);
System.out.println(a2);
}
- 람다식 메서드 참조
- 실행하려는 메서드를 참조해서 매개 변수와 리턴 타입을 알아내어, 람다식에서 불필요한 선언부를 생략 가능
- 연산자는 ::
.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에서 추출된 연속된 요소
- 컬렉션 처리를 도와줌
- 스트림이라는 특정 파이프라인을 통해서 리스트에 있는 요소를 연속적으로 흘려 보낸다.
- 특징
- 선언형 코드를 작성 가능
- 여러 중간 연산을 연결해서 복잡한 데이터 처리 파이프 라인을 만들 수 있다.
- 메서드 체이닝
- lazy evaluation
- 멀티쓰레드로 구현하지않아도 멀티쓰레드로 구현됨.
- 지연 연산
- 스트림에는 중간 연산자와 최종 연산자가 있음
- 중간 연산자의 리턴 값은 다른 스트림.
- 최종 연산자에서만 결과를 도출
- 외부 반복이 아닌 내부 반복
- 처리가 계속해서 되고, 최종 연산자에서 결과가 나옴
- 함수들
- map()
- 함수를 컬렉션의 요소에 하나씩 적용
- filter()
- 필터에 통과되는 것만 이후 동작 적용
- sorted()
- map()
- null로 인해 발생하는 문제
- 에러의 근원
- NPE
- 아무런 의미가 없다
- null 처리를 위한 if-else 문으로 가독성이 떨어진다
- 자바 철학에 위배가 된다
- null만 Pointer 개념을 가진다.
- 타입 시스템에 구멍이 생긴다.
- 올바른 null 처리 방법
- 타입 시스템을 이용해서 값이 없는 경우, null이 아닌 빈 값을 갖도록 하고, 값이 있는 경우, 주어진 형식에 맞는 값을 갖도록 하는 방식이 좋다.
- Optional<T>
- T 타입의 값을 캡슐화
- 값이 존재하면 그 값을 감싸지만, 값이 존재하지 않는 경우, null이 아닌 Optional.Empty 값으로 감싼다.
- Empty 값으로 Optional 생성
- Optional.empty();
- 값이 null이 아닐 경우, Optional 생성
- Optional<String> pgCorpName = Optional.of(paymentRequestDto.getPgCorpName());
- 값이 null이 될 수 있는 Optional 생성
- Optional<String> pgCorpName = Optional.ofNullable(paymentRequestDto.getPgCorpName());
- 유용한 Method
- isPresent()
- Optional 값이 존재하면 true, 그렇지 않으면 false
- 성능상 이슈가 있어서 지양
- T get()
- 값이 존재하면 반환, 그렇지 않으면 NoSuchElementException을 던짐
- orElse(T other)
- Optional이 값을 포함하지 않고 있을 때 Default 값을 제공할 수 있다.
- orElseGet(Supplier<? extends T> other)
- Optional 값이 없을 때만 Supplier가 실행됨
- orElseThrow(() -> Exception 명)
- isPresent()
- 에러의 근원
Auto Boxing/Unboxing
- 기본형 타입과 래퍼 클래스 간의 형 변환을 자동으로 처리해주는 기능
- Auto Boxing: Integer.valueOf(int value)
- Auto Unboxing: (Integer object).intValue()
- 개발자의 편의성과 가독성에는 도움이 되지만, 성능 문제를 일으키는 숨은 요인 중 하나
- stream을 사용하면 해결 가능
728x90
'Java' 카테고리의 다른 글
[프로그래머스] Lv3. 이중우선순위 (0) | 2024.10.28 |
---|---|
[프로그래머스] Lv2. 타겟 넘버 / JAVA (0) | 2024.10.28 |
[프로그래머스] Lv1. 로또의 최고 순위와 최저 순위 / JAVA (0) | 2024.10.19 |
[프로그래머스] Lv1. 약수의 개수와 덧셈 / JAVA (0) | 2024.10.19 |
[프로그래머스] Lv1. 옹알이(2) / JAVA (0) | 2024.10.19 |