DevSoupe
  • 🙂welcome
    • Hi there, I'm Seth!
  • ✍️blog
    • macOS 개발 환경 설정
    • SDKMAN!으로 자바 버전 관리
    • 도커 환경에서 MySQL 설치 및 접속
    • 도커를 사용하여 GitLab Runner 구성
    • 스프링부트 H2 DB mem 모드 사용시 테이블 접근
    • Git 서브모듈 삭제
  • 📚Book
    • 자바에서 코틀린으로 (코틀린으로 리팩터링하기)
      • 1장 - 소개
      • 2장 - 자바 프로젝트에서 코틀린 프로젝트
      • 3장 - 자바 클래스에서 코틀린 클래스로
      • 4장 - 옵셔널에서 널이 될 수 있는 타입으로
    • 오브젝트 (코드로 이해하는 객체지향 설계)
    • 이펙티브 코틀린
      • 1장 - 안정성
        • 아이템 1 - 가변성을 제한하라
        • 아이템 2 - 변수의 스코프를 최소화하라
        • 아이템 3 - 최대한 플랫폼 타입을 사용하지 말라
        • 아이템 4 - inferred 타입으로 리턴하지 말라
        • 아이템 5 - 예외를 활용해 코드에 제한을 걸어라
        • 아이템 6 - 사용자 정의 오류보다는 표준 오류를 사용하라
        • 아이템 7 - 결과 부족이 발생할 경우 null과 Failure를 사용하라
        • 아이템 8 - 적절하게 null을 처리하라
        • 아이템 9 - use를 사용하여 리소스를 닫아라
        • 아이템 10 - 단위 테스트를 만들어라
  • 🧑‍🏫Seminar
    • 우아한 모노리스
    • 우아한 객체지향
    • 점진적 추상화
  • 🌎English
    • 영어 피트니스 50일의 기적 ①
      • PART 1 워밍업
        • 1. '말문 트기'란?
      • PART 2 말문 트기 훈련 코스
        • DAY 1
          • STEP 1. Do (~해, ~하지 마)
          • STEP 2. Do + 말늘리기
    • 라이브 아카데미 토들러
      • Lesson 001 - 기본적인 문장 구성하기
      • Lesson 002 - 문장 만들기
Powered by GitBook
On this page
  • 없음을 표현하기
  • 옵셔널에서 널 가능성으로 리팩터링하기
  • 코틀린다운 코드로 리팩터링하기
  • 다음으로 나아가기
  1. Book
  2. 자바에서 코틀린으로 (코틀린으로 리팩터링하기)

4장 - 옵셔널에서 널이 될 수 있는 타입으로

4장 - 옵셔널에서 널이 될 수 있는 타입으로를 정리한 내용입니다.

널 참조 발명을 실수라고 생각할 수도 있지만 소프트웨어 시스템에서는 무언가가 없다는 사실을 기술해야 할 필요가 있다.

없음을 표현하기

  • 널 가능성은 코틀린에서 가장 매력적인 기능이다.

  • 이전 자바에서는 @Nullable @NotNullable 어노테이션의 도움을 받았고, 8부터는 Optional 타입을 사용한다.

  • 코틀린은 널을 포용하지만 자바와의 호환성을 유지하기 위해 일관된 형태로 완벽하게 처리하지는 않는다.

  • Map.get()은 값이 없을때 null을 리턴하지만 List.get(), Iterable.first()는 Exception을 던진다.

  • 코틀린에서는 래퍼 타입인 Optional을 사용하는 대신 언어에서 제공하는 널 가능성을 사용하는 편이 좋다.

  • 코틀린 타입 시스템에서 T는 T?의 하위 타입이지만 T는 Optional<T>의 하위 타입이 아니다.

  • 널 가능성 사용시 String과 String? 관계에서 String?을 String으로 바꿔도 클라이언트 코드가 깨지지 않는다.

  • String과 Optional<String>은 위의 변경시 클라이언트 코드가 깨지게 되어 변경이 쉽지 않다.

옵셔널에서 널 가능성으로 리팩터링하기

public class Legs {

    public static Optional<Leg> findLongestLegOver(
        List<Leg> legs,
        Duration duration
    ) {
        Leg result = null;
        for (Leg leg : legs) {
            if (isLongerThan(leg, duration))
                if (result == null ||
                    isLongerThan(leg, result.getPlannedDuration())
                ) {
                    result = leg;
                }
        }
        return Optional.ofNullable(result);
    }

    private static boolean isLongerThan(Leg leg, Duration duration) {
        return leg.getPlannedDuration().compareTo(duration) > 0;
    }
}
public class LongestLegOverTests {

    private final List<Leg> legs = List.of(
        leg("one hour", Duration.ofHours(1)),
        leg("one day", Duration.ofDays(1)),
        leg("two hours", Duration.ofHours(2))
    );
    private final Duration oneDay = Duration.ofDays(1);

    @Test
    public void is_absent_when_no_legs() {
        assertEquals(
            Optional.empty(),
            findLongestLegOver(emptyList(), Duration.ZERO)
        );
    }

    @Test
    public void is_absent_when_no_legs_long_enough() {
        assertEquals(
            Optional.empty(),
            findLongestLegOver(legs, oneDay)
        );
    }

    @Test
    public void is_longest_leg_when_one_match() {
        assertEquals(
            "one day",
            findLongestLegOver(legs, oneDay.minusMillis(1))
                .orElseThrow().getDescription()
        );
    }

    @Test
    public void is_longest_leg_when_more_than_one_match() {
        assertEquals(
            "one day",
            findLongestLegOver(legs, Duration.ofMinutes(59))
                .orElseThrow().getDescription()
        );
    }

    private static final Leg leg(String description, Duration duration) {
        var start = ZonedDateTime.ofInstant(
            Instant.ofEpochSecond(ThreadLocalRandom.current().nextInt()),
            ZoneId.of("UTC"));
        return new Leg(description, start, start.plus(duration));
    }
}
  • Legs와 LongestLegOverTests 자바 코드를 툴을 사용해 코틀린으로 변환한다.

object Legs {

    @JvmStatic
    fun findLongestLegOver(
        legs: List<Leg>,
        duration: Duration
    ): Optional<Leg> {
        var result: Leg? = null
        for (leg in legs) {
            if (isLongerThan(leg, duration))
                if (result == null ||
                    isLongerThan(leg, result.plannedDuration))
                    result = leg
        }
        return Optional.ofNullable(result)
    }

    private fun isLongerThan(leg: Leg, duration: Duration): Boolean {
        return leg.plannedDuration.compareTo(duration) > 0
    }
}
class LongestLegOverTests {
    private val legs = List.of(
        leg("one hour", Duration.ofHours(1)),
        leg("one day", Duration.ofDays(1)),
        leg("two hours", Duration.ofHours(2))
    )
    private val oneDay = Duration.ofDays(1)

    @Test
    fun is_absent_when_no_legs() {
        Assertions.assertEquals(
            Optional.empty<Any>(),
            findLongestLegOver(emptyList(), Duration.ZERO)
        )
    }

    @Test
    fun is_absent_when_no_legs_long_enough() {
        Assertions.assertEquals(
            Optional.empty<Any>(),
            findLongestLegOver(legs, oneDay)
        )
    }

    @Test
    fun is_longest_leg_when_one_match() {
        Assertions.assertEquals(
            "one day",
            findLongestLegOver(legs, oneDay.minusMillis(1))
                .orElseThrow().description
        )
    }

    @Test
    fun is_longest_leg_when_more_than_one_match() {
        Assertions.assertEquals(
            "one day",
            findLongestLegOver(legs, Duration.ofMinutes(59))
                .orElseThrow().description
        )
    }

    private fun leg(description: String, duration: Duration): Leg {
        val start = ZonedDateTime.ofInstant(
            Instant.ofEpochSecond(ThreadLocalRandom.current().nextInt().toLong()),
            ZoneId.of("UTC"));
        return Leg(description, start, start.plus(duration));
    }
}

이터레이션과 for 루프

코틀린에서는 Iterable이 아닌 타입을 for 루프에 사용할 수 있다.

  • Iterable를 확장한 타입

  • Iterator를 반환하는 iterator() 메서드를 제공하는 타입 (hasNext(): Boolean, next(): T 제공)

  • Iterator를 반환하는 T.iterator() 확장 함수가 영역 안에 정의된 T 타입

두번째, 세번째는 Iterable 취급을 해주진 않아 map, reduce 확장 함수를 사용할 수는 없다.

코틀린다운 코드로 리팩터링하기

다음으로 나아가기

Last updated 8 months ago

📚