🐾
개발자국
  • 🐶ABOUT
  • 🚲프로그래밍
    • 객체 지향 프로그래밍
    • 오브젝트
      • 1장: 객체, 설계
      • 2장: 객체지향 프로그래밍
      • 3장: 역할, 책임, 협력
      • 4장: 설계 품질과 트레이드오프
      • 5장: 책임 할당하기
      • 6장: 메시지와 인터페이스
      • 7장: 객체 분해
      • 8장: 의존성 관리하기
      • 9장: 유연한 설계
      • 10장: 상속과 코드 재사용
      • 11장: 합성과 유연한 설계
      • 12장: 다형성
      • 13장: 서브클래싱과 서브타이핑
      • 14장: 일관성 있는 협력
      • 15장: 디자인 패턴과 프레임워크
    • 도메인 주도 개발 시작하기
      • 1장: 도메인 모델 시작하기
      • 2장: 아키텍처 개요
      • 3장: 애그리거트
      • 4장: 리포지토리와 모델 구현
      • 5장: 스프링 데이터 JPA를 이용한 조회 기능
      • 6장: 응용 서비스와 표현 영역
      • 7장: 도메인 서비스
      • 8장: 애그리거트 트랜잭션 관리
      • 9장: 도메인 모델과 바운디드 컨텍스트
      • 10장: 이벤트
      • 11장: CQRS
    • 클린 아키텍처
      • 만들면서 배우는 클린 아키텍처
        • 계층형 아키텍처의 문제와 의존성 역전
        • 유스케이스
        • 웹 어댑터
        • 영속성 어댑터
        • 아키텍처 요소 테스트
        • 경계 간 매핑 전략
        • 애플리케이션 조립
        • 아키텍처 경계 강제하기
        • 지름길 사용하기
        • 아키텍처 스타일 결정하기
    • 디자인 패턴
      • 생성(Creational) 패턴
        • 팩토리 패턴
        • 싱글톤 패턴
        • 빌더 패턴
        • 프로토타입 패턴
      • 행동(Behavioral) 패턴
        • 전략 패턴
        • 옵저버 패턴
        • 커맨드 패턴
        • 템플릿 메서드 패턴
        • 반복자 패턴
        • 상태 패턴
        • 책임 연쇄 패턴
        • 인터프리터 패턴
        • 중재자 패턴
        • 메멘토 패턴
        • 비지터 패턴
      • 구조(Structural) 패턴
        • 데코레이터 패턴
        • 어댑터 패턴
        • 퍼사드 패턴
        • 컴포지트 패턴
        • 프록시 패턴
        • 브리지 패턴
        • 플라이웨이트 패턴
      • 복합 패턴
  • 시스템 설계
    • 1. 사용자 수에 따른 규모 확장성
    • 2. 개략적 규모 추정
    • 3. 시스템 설계 접근법
    • 4. 처리율 제한 장치
    • 5. 안정 해시
    • 6. 키-값 저장소
    • 7. 유일한 ID 생성기
    • 8. URL 단축기
    • 9. 웹 크롤러
    • 10. 알림 시스템
    • 11. 뉴스 피드
    • 12. 채팅 시스템
    • 13. 검색어 자동완성
    • 14. 유튜브 스트리밍
    • 15. 구글 드라이브
    • ⭐️. 캐싱 전략
    • ⭐️. 재고 시스템으로 알아보는 동시성이슈 해결방법
    • ⭐️. 실습으로 배우는 선착순 이벤트 시스템
  • 🏝️자바
    • 자바의 내부 속으로
      • Java 언어의 특징
      • JDK
      • JVM
        • 메모리 관리
        • Garbage Collector
          • 기본 동작
          • Heap 영역을 제외한 GC 처리 영역
          • (WIP) GC 알고리즘
        • 클래스 로더
      • 자바 실행 방식
      • 메모리 모델과 관리
      • 바이트 코드 조작
      • 리플렉션
      • 다이나믹 프록시
      • 어노테이션 프로세서
    • 자바의 기본
      • 데이터 타입, 변수, 배열
    • 이펙티브 자바
      • 2장: 객체의 생성과 파괴
        • item 1) 생성자 대신 정적 팩토리 메서드를 고려하라
        • item2) 생성자에 매개변수가 많다면 빌더를 고려하라
        • item3) private 생성자나 열거 타입으로 싱글톤임을 보증하라
        • item4) 인스턴스화를 막으려면 private 생성자를 사용
        • item5) 자원을 직접 명시하는 대신 의존 객체 주입 사용
        • item6) 불필요한 객체 생성 지양
        • item7) 다 쓴 객체는 참조 해제하라
        • item8) finalizer와 cleaner 사용 자제
        • item9) try-with-resources를 사용하자
      • 3장: 모든 객체의 공통 메서드
        • item 10) equals는 일반 규약을 지켜 재정의 하자
        • item 11) equals 재정의 시 hashCode도 재정의하라
        • item 12) 항상 toString을 재정의할 것
        • item 13) clone 재정의는 주의해서 진행하라
        • item 14) Comparable 구현을 고려하라
      • 4장: 클래스와 인터페이스
        • item 15) 클래스와 멤버의 접근 권한을 최소화하라
        • item 16) public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라
        • item 17) 변경 가능성을 최소화하라
        • item 18) 상속보다는 컴포지션을 사용하라
        • item 19) 상속을 고려해 설계하고 문서화하고, 그러지 않았다면 상속을 금지하라
        • item 20) 추상 클래스보다는 인터페이스를 우선하라
        • item 21) 인터페이스는 구현하는 쪽을 생각해 설계하라
        • item 22) 인터페이스는 타입을 정의하는 용도로만 사용하라
        • item 23) 태그 달린 클래스보다는 클래스 계층구조를 활용하라
        • item 24) 멤버 클래스는 되도록 static으로 만들라
        • item 25) 톱레벨 클래스는 한 파일에 하나만 담으라
      • 5장: 제네릭
        • item 26) 로 타입은 사용하지 말 것
        • item 27) unchecked 경고를 제거하라
        • item 28) 배열보다 리스트를 사용하라
        • item 29) 이왕이면 제네릭 타입으로 만들라
        • item 30) 이왕이면 제네릭 메서드로 만들라
        • item 31) 한정적 와일드카드를 사용해 API 유연성을 높이라
        • item 32) 제네릭과 가변 인수를 함께 사용
        • item 33) 타입 안전 이종 컨테이너를 고려하라
      • 6장: 열거 타입과 어노테이션
        • item 34) int 상수 대신 열거 타입을 사용하라
        • item 35) ordinal 메서드 대신 인스턴스 필드를 사용하라
        • item 36) 비트 필드 대신 EnumSet을 사용하라
        • item 37) ordinal 인덱싱 대신 EnumMap을 사용하라
        • item 38) 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라
        • item 39) 명명 패턴보다 어노테이션을 사용하라
        • item 40) @Override 어노테이션을 일관되게 사용하라
        • item 41) 정의하려는 것이 타입이라면 마커 인터페이스를 사용하라
      • 7장: 람다와 스트림
        • item 42) 익명 클래스보다는 람다를 사용하라
        • item 43) 람다보다는 메서드 참조를 사용하라
        • item 44) 표준 함수형 인터페이스를 사용하라
        • item 45) 스트림은 주의해서 사용하라
        • item 46) 스트림에서는 부작용 없는 함수를 사용하라
        • item 47) 반환 타입으로는 스트림보다 컬렉션이 낫다
        • item 48) 스트림 병렬화는 주의해서 적용하라
      • 8장: 메서드
        • item 49) 매개변수가 유효한지 검사하라
        • item 50) 적시에 방어적 복사본을 만들라
        • item 51) 메서드 시그니처를 신중히 설계하라
        • item 52) 다중정의는 신중히 사용하라
        • item 53) 가변인수는 신중히 사용하라
        • item 54) null이 아닌, 빈 컬렉션이나 배열을 반환하라
        • item 55) 옵셔널 반환은 신중히 하라
        • item 56) 공개된 API 요소에는 항상 문서화 주석을 작성하라
      • 9장: 일반적인 프로그래밍 원칙
        • item 57) 지역 변수의 범위를 최소화하라
        • item 58) 전통적인 for문보다 for-each문을 사용하기
        • item 59) 라이브러리를 익히고 사용하라
        • item 60) 정확한 답이 필요하다면 float, double은 피하라
        • item 61) 박싱된 기본타입보단 기본 타입을 사용하라
        • item 62) 다른 타입이 적절하다면 문자열 사용을 피하라
        • item 63) 문자열 연결은 느리니 주의하라
        • item 64) 객체는 인터페이스를 사용해 참조하라
        • item 65) 리플렉션보단 인터페이스를 사용
        • item 66) 네이티브 메서드는 신중히 사용하라
        • item 67) 최적화는 신중히 하라
        • item 68) 일반적으로 통용되는 명명 규칙을 따르라
      • 10장: 예외
        • item 69) 예외는 진짜 예외 상황에만 사용하라
        • item 70) 복구할 수 있는 상황에서는 검사 예외를, 프로그래밍 오류에는 런타임 예외를 사용하라
        • item 71) 필요 없는 검사 예외 사용은 피하라
        • item 72) 표준 예외를 사용하라
        • item 73) 추상화 수준에 맞는 예외를 던지라
        • item 74) 메서드가 던지는 모든 예외를 문서화하라
        • item 75) 예외의 상세 메시지에 실패 관련 정보를 담으라
        • item 76) 가능한 한 실패 원자적으로 만들라
        • item 77) 예외를 무시하지 말라
      • 11장: 동시성
        • item 78) 공유 중인 가변 데이터는 동기화해 사용하라
        • item 79) 과도한 동기화는 피하라
        • item 80) 스레드보다는 실행자, 태스크, 스트림을 애용하라
        • item 81) wait와 notify보다는 동시성 유틸리티를 애용하라
        • item 82) 스레드 안전성 수준을 문서화하라
        • item 83) 지연 초기화는 신중히 사용하라
        • item 84) 프로그램의 동작을 스레드 스케줄러에 기대지 말라
      • 12장: 직렬화
        • item 85) 자바 직렬화의 대안을 찾으라
        • item 86) Serializable을 구현할지는 신중히 결정하라
        • item 87) 커스텀 직렬화 형태를 고려해보라
        • item 88) readObject 메서드는 방어적으로 작성하라
        • item 89) 인스턴스 수를 통제해야 한다면 readResolve보다는 열거 타입을 사용하라
        • item 90) 직렬화된 인스턴스 대신 직렬화 프록시 사용을 검토하라
    • 모던 자바 인 액션
      • 1장: 자바의 역사
      • 2장: 동작 파라미터화
      • 3장: 람다
      • 4장: 스트림
      • 5장: 스트림 활용
      • 6장: 스트림으로 데이터 수집
      • 7장: 병렬 데이터 처리와 성능
      • 8장: 컬렉션 API 개선
      • 9장: 람다를 이용한 리팩토링, 테스팅, 디버깅
      • 10장: 람다를 이용한 DSL
      • 11장: null 대신 Optional
      • 12장: 날짜와 시간 API
      • 13장: 디폴트 메서드
      • 14장: 자바 모듈 시스템
      • 15장: CompletableFuture와 Reactive 개요
      • 16장: CompletableFuture
      • 17장: 리액티브 프로그래밍
      • 18장: 함수형 프로그래밍
      • 19장: 함수형 프로그래밍 기법
      • 20장: 스칼라 언어 살펴보기
    • 자바의 이모저모
      • Javax
      • Objects
      • NIO
      • Thread
      • Concurrent
        • Atomic
        • Executor, ExecutorService
        • Interrupt
      • Assertions
    • Netty
      • 네티 맛보기
      • 네티의 주요 특징
      • 채널 파이프라인
      • 이벤트 루프
      • 바이트 버퍼
      • 부트스트랩
      • 네티 테스트
      • 코덱
      • 다양한 ChannelHandler와 코덱
      • 웹소켓
      • UDP 브로드캐스팅
    • 자바 병렬 프로그래밍
      • 2장: 스레드 안전성
      • 15장: 단일 연산 변수와 논블로킹 동기화
  • 🏖️코틀린
    • 코틀린 인 액션
      • 코틀린 언어의 특징
      • 코틀린 기초
      • 함수 정의와 호출
      • 클래스, 객체, 인터페이스
      • 람다
      • 타입 시스템
      • 연산자 오버로딩과 기타 관례
      • 고차 함수
      • 제네릭스
      • 어노테이션과 리플렉션
      • DSL 만들기
  • 🌸스프링
    • Spring Core
      • Cron Expression
      • Bean
        • Lifecycle
        • Aware
    • Spring MVC
    • Spring Security
      • 로그인 처리
      • 로그아웃 처리
      • JWT 인증 방식
      • 메소드별 인가 처리
    • Spring Data
      • Pageable
      • Spring Data Couchbase
      • Spring Data Redis
        • Serializer
    • Spring REST Docs
    • Spring Annotations
    • Spring Cloud
      • Service Discovery
      • API Gateway
      • Spring Cloud Config
      • MicroService Communication
      • Data Synchronization
    • Test
      • 테스트 용어 정리
      • JUnit
      • Spring Boot Test
      • Mockito
    • QueryDSL
      • 프로젝트 환경설정
      • 기본 문법
      • 중급 문법
      • 순수 JPA와 QueryDSL
      • 스프링 데이터 JPA와 QueryDSL
    • Lombok
      • @Data
      • @Builder
      • Log Annotations
  • 🕋DB
    • MySQL
      • CentOS7에서 MySQL 8 버전 설치하기
    • MongoDB
      • 
    • Redis
      • Sentinel
      • Cluster
      • Transaction
      • 자료구조
        • String
        • List
        • Set
        • Hash
        • Bitmaps
        • SortedSet
      • Lettuce 단일 서버, 클러스터 서버, 풀링 사용 방법
  • 📽️인프라
    • 리눅스
      • 주요 명령어 모음
    • Docker
      • Docker
      • Docker Compose
      • Docker Swarm
      • Docker Network
      • Linux에서 root 아닌 유저로 docker 실행하기
    • Kubernetes
      • 기초 개념
      • Pod
      • Configuration
      • ReplicationSet
      • Network
      • ConfigMap & Secret
      • Volume, Mount, Claim
      • Controller
      • Multi Container Pod
      • StatefulSet & Job
      • Rollout & Rollback
      • Helm
      • 개발 워크플로우와 CI/CD
      • Container Probes
      • Resource Limit
      • Logging & Monitoring
      • Ingress
      • Security
      • Multi Node/Architecture Cluster
      • Workload & Pod management
      • CRD & Operator
      • Serverless Function
      • K8S Cheat Sheet
    • Kafka
      • 카프카 개요
      • 카프카 설치 및 실습
      • Kafka Broker
      • Topic, Partition, Record
      • Producer
      • Consumer
      • Kafka Streams
      • Kafka Connect
      • MirrorMaker
  • AWS
    • AWS Console / CLI / SDK
    • IAM
    • EC2
      • EC2 Advanced
    • ELB / ASG
    • RDS / Aurora / ElastiCache
    • DynamoDB
    • DocumentDB / Neptune / Keyspaces / QLDB / Timestream
    • Route 53
    • Beanstalk
    • Solution Architect
    • S3
      • 보안
    • CloudFront
    • Global Accelerator
    • AWS Storage
    • Messaging
    • Container
    • Serverless
    • Data Analysis
    • Machine Learning
    • Monitoring
    • Security
    • VPC
    • Data Migration
    • 기타 서비스
  • 🏔️CS
    • 운영 체제
      • Introduction
      • System Structures
      • Process
      • Synchronization
      • Muitithreaded Programming
      • Process Scheduling
      • Memory Management
      • Virtual Memory
    • 네트워크
      • 네트워크 기초
      • 네트워크 통신 방식
      • OSI 7계층
        • 1계층: 물리계층
        • 2계층: 데이터 링크 계층
        • 3계층: 네트워크 계층
        • 4계층: 전송 계층
        • 5계층: 세션 계층
        • 6계층: 표현 계층
        • 7계층: 응용 계층
      • TCP/IP 스택
      • ARP
      • 데이터 크기 조절
      • WDM
      • NAT
      • DNS
      • DHCP
      • VPN
      • 네이글 알고리즘
      • 서버 네트워크
      • 네트워크 보안
        • 보안의 기본
        • 보안 장비
      • 이중화
    • 데이터베이스
      • 트랜잭션
    • 컴퓨터 구조
      • 개요
      • Instruction Set Architecture
      • Procedure Call & Return
      • Linking
      • Pipeline
      • Memory Hierarchy
      • Virtual Memory
      • Interrupt / Exception, IO
    • 자료 구조
      • Array
      • List
      • Map
      • Set
      • Queue
      • PriorityQueue
      • Stack
    • 웹 기술
      • HTTP
        • 쿠키와 세션
  • 🪂Big Data
    • Apache Hadoop
  • 🕹️ETC
    • Git
      • 내부 구조
      • 내가 자주 사용하는 명령어 모음
      • Commit Convention
    • 이력서 작성하기
    • Embedded
      • 라즈베리파이에서 네오픽셀 적용기
    • 기술블로그 모음집
Powered by GitBook
On this page
  • 널 가능성
  • ?. 호출 연산자
  • ?: 엘비스 연산자
  • as?
  • !! 단언문
  • let 함수
  • 지연 초기화
  • 널이 될 수 있는 타입 확장
  • 제네릭과 널 가능성
  • 자바와 널 가능성
  • 원시 타입
  • 숫자 변환
  • 원시 타입 리터럴
  • Any, Any?
  • Unit 타입
  • Nothing 타입
  • 컬렉션과 배열
  • 널 가능성과 컬렉션
  • 읽기 전용 / 변경 가능한 컬렉션
  • 코틀린 컬렉션과 자바
  • 컬렉션을 플랫폼 타입으로 다루기
  • 배열
  1. 코틀린
  2. 코틀린 인 액션

타입 시스템

널 가능성

  • null이 될 수 있는지 여부를 타입 시스템에 추가하여 컴파일러 단에서 미리 감지해 실행 시점에 발생할 수 있는 예외 가능성을 줄일 수 있다.

  • 기본적으로 null 또는 null이 될 수 있는 인자를 넘기지 못하도록 되어있다.

fun strLen(s: String) = s.length
  • null을 인자로 받을 수 있게 하려면 타입 뒤에 물음표를 명시해야 한다.

fun strLen(s: String?) = s.length
  • 널이 될 수 있는 값은 널이 될 수 없는 타입에 할당할 수 없다.

val x: String? = null
val y: String = x // ERROR: Type mismatch: inferred type is String? but String was expected
  • 널이 아님이 확실한 영역에서는 해당 값을 널이 될 수 없는 타입처럼 사용할 수 있다. 예를 들어 아래와 같이 널인지 값을 검사한 후에 사용한다면 널이 될 수 없는 타입처럼 사용할 수 있다.

fun strLen(s: String?) : Int =
    if (s != null) s.length else 0
  • 타입이란 어떤 값들이 들어올 수 있는지와 타입에 대해 수행할 수 있는 연산의 종류를 결정한다.

  • String 타입과 double 타입은 각각 할당 가능한 값과 실행할 수 있는 연산이 다르다. 하지만 null 값이 해당 타입에 할당될 경우 일반적인 값과 달리 실행할 수 있는 연산이 없어지게 된다.

  • 코틀린에서는 널이 될 수 있는 타입과 널이 될 수 없는 타입을 구분하여 각 타입의 값에 어떤 연산이 가능한 지 쉽게 파악하도록 한다. 또한 이를 컴파일 타임에 검증하게 된다.

?. 호출 연산자

  • null 검사와 메서드 호출을 한 번에 수행한다.

  • 만약 호출 대상 객체가 null이라면 메서드를 수행하는 대신 null을 바로 반환한다.

  • 예를 들어 s?.toUpperCase() 는 if (s != null) s.toUpperCase else null 과 같다. 또한 반환 결과가 널이 될 수 있는 타입인 String?이 된다.

  • 프로퍼티를 읽거나 쓸 때에도 ?. 연산자를 사용할 수 있다.

fun managerName(employee: Employee): String? = employee.manager?.name
  • ?. 연산자 호출을 연쇄적으로 하여 여러 객체의 null 검사를 쉽게 수행할 수 있다.

fun Person.countryName(): String {
   val country = this.company?.address?.country
   return if (country != null) country else "Unknown"
}

?: 엘비스 연산자

  • null 대신 사용할 디폴트 값을 지정할 때 사용하는 연산자이다.

  • 아래는 s가 null이 아니면 그대로 변수에 할당하고, null이면 ""를 변수에 할당하는 예시이다.

val t: String = s ?: ""
  • 코틀린에서는 return이나 throw 등의 연산도 식이다. 따라서 엘비스 연산자의 우항에 return, throw 등의 연산을 넣을 수 있다.

  • 아래는 사람 객체에 연관된 회사의 주소가 없다면 예외를 발생시키는 예시이다.

fun printShippingLabel(person: Person) {
    val address = person.company?.address
      ?: throw IllegalArgumentException("No address")
    with (address) {
        println(streetAddress)
        println("$zipCode $city, $country")
    }
}

as?

  • 어떤 값을 지정한 타입으로 캐스트할 수 있다면 변환하고, 불가능하면 null을 반환한다.

  • 아래 예제에서는 인자를 Any 타입으로 받았을 때 as? 를 통해 타입 캐스팅을 하고, 만약 변환에 실패해 null이 반환되면 ?: 연산자로 검사한다.

fun equals(o: Any?): Boolean {
       val otherPerson = o as? Person ?: return false
       
       return otherPerson.firstName == firstName &&
              otherPerson.lastName == lastName
}

!! 단언문

  • 널이 될 수 있는 타입의 값을 다룰 때 널이 될 수 없는 타입으로 변환하는 연산자이다.

  • 널에 대해 !!를 적용하면 NPE가 발생한다.

  • 아래와 같이 널이 될 수 있는 값에 !!를 적용하면 널일 때는 NPE를 발생시키고, 널이 아닐 때에는 값을 적용시킨다.

fun ignoreNulls(s: String?) {
    val sNotNull: String = s!!
    println(sNotNull.length)
}
  • 널이 될 수 있는 타입에 항상 널이 아닌 값이 들어오는 경우 유용하게 사용할 수 있다.

  • 널에 대해 !! 를 사용하여 예외가 발생할 때 어떤 식에서 발생했는지에 대한 정보가 없기 때문에 여러 !! 단언문을 한 줄에 쓰지 않는 것이 좋다.

person.company!!.address!!.country 

let 함수

  • let 함수는 자신의 수신 객체를 인자로 전달받은 람다에 넘긴다.

  • 널이 아닌 타입을 인자로 받는 함수에 널이 될 수 있는 타입을 넘길 때 안전한 호출 구문과 함께 let 함수를 사용하면, 널이 아닌 타입으로 바꾸어 람다에 전달하게 된다.

  • 아래와 같이 ?. 호출 연산자로 let 함수를 호출하면 email 값이 null이 아닐 때에만 람다가 수행된다.

email?.let { email -> sendEmailTo(email) }
email?.let { sendEmailTo(it) }
  • 아래와 같이 함수의 결과를 변수에 저장할 필요 없이 바로 let 메서드를 통해 null이 아니면 람다를 수행하도록 할 수 있다.

getTheBestPersonInTheWorld()?.let { sendEmailTo(it.email) }

지연 초기화

  • 널이 될 수 없는 프로퍼티에 대해서는 생성자가 아닌 별도 메서드에서 지연 초기화가 불가능하다.

  • 따라서 지연 초기화 이후에 항상 널이 될 수 없는 필드를 널이 가능한 타입으로 두고 접근할 때 마다 널 검사를 하거나 !! 연산자를 붙여야 한다.

  • lateinit 변경자를 붙이면 프로퍼티를 나중에 초기화할 수 있다.

  • 만약 lateinit 프로퍼티에 초기화 되기 전 접근하면 예외가 발생한다.

  • 아래와 같이 JUnit을 사용할 때 myService 필드를 널이 될 수 없는 필드로 두고 lateinit 변경자를 붙인다. 그렇게 되면 최초에는 널로 할당되고, @Before 함수에서 해당 필드가 초기화된다.

class MyTest {
    private lateinit var myService: MyService

    @Before fun setUp() {
        myService = MyService() 
    }

    @Test fun testAction() {
        Assert.assertEquals("foo", myService.performAction())
     }
}

널이 될 수 있는 타입 확장

  • 널이 될 수 있는 타입에 대한 확장 함수를 정의하면 null 값을 다루는 강력한 도구로 활용될 수 있다.

  • 코틀린에서는 String? 타입의 수신 객체에 대해 호출할 수 있는 isNullOrEmpty, isNullOrBlank 함수가 제공된다.

fun String?.isNullOrBlank(): Boolean =
    this == null || this.isBlank()
  • 널을 검사하는 기능을 담은 확장 함수를 직접 작성할 수도 있다. 이 때 this는 널이 될 수 있으므로 명시적으로 널 여부를 검사해야 한다.

제네릭과 널 가능성

  • 함수나 클래스의 모든 타입 파라미터는 기본적으로 널이 될 수 있다.

  • 따라서 타입 파라미터를 사용할 때에는 널이 아닌지 확인한 후 사용해야 한다.

fun <T> printHashCode(t: T) {
    println(t?.hashcode())
}
  • 널이 될 수 없도록 타입 상한을 지정하면 널 검사를 하지 않아도 된다.

fun <T: Any> printHashCode(t: T) {
    println(t.hashcode())
}

자바와 널 가능성

  • 자바에서는 JSR-305 표준이나 jetbrains 어노테이션 등에서 제공하는 @Nullable, @NotNull어노테이션으로 널이 가능한지 아닌지 표현할 수 있다.

  • 코틀린에서는 이 어노테이션에 따라 널 가능 타입인지 확인한다.

  • 코틀린이 널 관련 정보를 알 수 없는 타입은 플랫폼 타입이 된다. 플랫폼 타입은 널이 될 수 있는 타입으로 처리해도 되고 널이 될 수 없는 타입으로 처리해도 된다. 즉, 모든 연산의 책임을 사용자에게 두게 된다.

val s: String? = person.name
val s2: String = person.name
  • 플랫폼 타입은 코틀린에서 생성할 수는 없으며, 널 가능성을 알지 못한다는 의미에서 ! 가 붙는다. 예를 들어 자바에서 선언된 String 타입에 대해 코틀린이 널 가능성을 알 수 없다면 String! 타입으로 두게 된다.

  • 코틀린에서 자바 클래스를 상속받아 메서드를 오버라이드할 때 파라미터와 반환타입을 널이 될 수 있는 타입으로 선언할지 널이 될 수 없는 타입으로 선언할 지 정할 수 있다.

  • 아래는 java에 정의된 StringProcessor 인터페이스를 kotlin으로 상속받는 예제로, 메서드 파라미터를 널이 될 수 없는 String 타입으로 둘 수도 있고 널이 될 수 있는 String? 타입으로 둘 수도 있다.

interface StringProcessor {
    void process(String value);
}
class StringPrinter: StringProcessor {
    override fun process(value: String) {
        println(value)
    }
}

class NullableStringPrinter: StringProcessor {
    override fun process(value: String?) {
        if (value != null) {
            println(value)
        }
    }
}

원시 타입

  • 코틀린은 자바와 달리 원시 타입과 래퍼 타입을 구분하지 않는다.

  • 코틀린의 Int 타입은 기본적으로 자바의 원시 타입인 int로 컴파일된다. 단, 컬렉션과 같은 제네릭 클래스를 사용할 경우 자바의 래퍼 타입인 Integer로 컴파일된다.

  • 아래와 같이 널 값이나 널이 될 수 있는 타입을 사용하지 않더라도 Integer 타입 컬렉션으로 컴파일된다.

val listOfInts = listOf(1, 2, 3)
  • 코틀린에서 널이 될 수 있는 타입 중 Int?, Boolean? 과 같은 타입은 int, boolean으로 컴파일될 수 없으므로 Integer, Boolean과 같은 래퍼 타입으로 컴파일된다.

숫자 변환

  • 한 타입의 숫자를 다른 타입의 숫자로 자동 변환하지 않는다.

  • 코드에서 동시에 여러 숫자 타입을 사용할 때 예상치 못한 동작을 피하기 위해 항상 변수를 명시적으로 변환해 사용해야 한다.

val x = 1
println(x.toLong() in listOf(1L, 2L, 3L)

원시 타입 리터럴

  • 숫자 리터럴 종류는 아래와 같다.

    • Long 타입 리터럴: L 접미사를 붙인다. ex) 123L

    • Double 타입 리터럴: 표준 부동소수점 표기법을 사용한다. ex) 0.12, 1.2e-10

    • Float 타입 리터럴: f나 F 접미사를 붙인다. ex) 123.4f

    • 16진 리터럴: 0x나 0X 접두사를 붙인다. ex) 0x123CB, 0xbcdL

    • 2진 리터럴: 0b나 0B 접두사를 붙인다. ex) 0b0000101

  • 숫자 리터럴 중간에 _ 을 넣어 단위를 구분하기 쉽도록 할 수 있다. ex) 1_000_000

  • 숫자 리터럴 사용 시 보통 변환 함수를 호출할 필요가 없다. 타입이 정해진 변수에 대입하거나 함수 인자로 넘기면 자동으로 변환 함수가 호출될 것이다.

  • 산술 연산자의 경우 오버로딩이 되어 있어 여러 타입을 받아들일 수 있다.

val b: Byte = 1
val l = b + 1L

Any, Any?

  • 자바에서 Object가 모든 클래스의 최상위 타입이듯, 코틀린에서는 Any 타입은 널이될 수 없는 모든 타입의 최상위 타입이다.

  • 자바의 원시 타입은 Object 타입이 아니지만 코틀린에서는 Int, Double 등 모든 타입이 Any 타입이다.

  • Any 타입은 널이 될 수 없으므로 null이 들어갈 수 없다.

  • 널을 포함하는 모든 값을 대입하려면 Any? 타입을 사용해야 한다.

  • 내부적으로 Any 타입은 자바의 Object와 대응된다. Any 타입은 toString, equals, hashcode 메서드를 사용할 수 있지만 wait, notify 등 Object 클래스의 다른 메서드는 사용할 수 없다. 따라서 해당 메서드를 사용하고자 한다면 Object 타입으로 캐스팅해야 한다.

Unit 타입

  • 코틀린 함수를 자바의 void 메서드처럼 사용하려 할 때 Unit 타입을 반환하도록 하면 된다.

  • Unit이란 단 하나의 인스턴스만 갖는 타입을 의미한다.

  • Unit 타입은 모든 기능을 갖는 일반적인 타입이고, Unit이라는 값을 가진다.

  • 아래 두 함수는 반환값이 없다는 의미를 동일하게 가진다.

fun f(): Unit {
    // ...
}

fun f() {
    // ...
}
  • 제네릭 파라미터를 반환하는 함수를 오버라이드하면서 Unit을 반환할 수 있다.

  • 아래는 제네릭 타입의 결과를 반환하지 않고 싶을 때 Unit 타입을 사용하는 예제이다. process 메서드는 결국 Unit 타입을 반환해야 하지만, 컴파일러가 자동으로 return Unit 를 넣어준다.

interface Processor<T> {
    fun process() : T
}
    
class NoResultProcessor : Processor<Unit> {
    override fun process() {
        // ...
    }
}

Nothing 타입

  • 아무 값도 포함하지 않으며 함수의 반환 타입이나 반환 타입으로 쓰일 타입 파라미터로 사용되는 타입이다.

  • 컴파일러는 Nothing이 반환 타입인 함수가 정상 종료되지 않는다는 것을 알고, 함수를 호출하는 코드를 분석할 때 사용한다.

  • 아래와 같이 코틀린 테스트 시 제공되는 fail함수는 Nothing 타입을 반환한다. 컴파일러는 company.address가 널이라면 예외가 발생한다는 사실을 파악하고 address 값이 널이 아님을 추론한다.

fun fail(message: String) : Nothing {
    throw IllegalStateException(message)
}

val address = company.address ?: fail("No address")

컬렉션과 배열

널 가능성과 컬렉션

  • 컬렉션 타입 인자 뒤에 ?를 붙이면 컬렉션의 원소로 널을 저장할 수 있다. 이 때 리스트 자체는 널이 될 수 없다.

fun searchAll(list: List<Int?>): Boolean {
}
  • 아래와 같이 사용하면 리스트 필드 자체에 널을 저장할 수 있다. 하지만 컬렉션 원소로 널을 저장할 수는 없다.

fun searchAll(list: List<Int>?): Boolean {
}
  • 코틀린 표준 라이브러리에서는 filterNotNull이라는 함수를 제공해 null 원소들을 제외한 원소들만 구할 수 있다.

val validNumbers = numbers.filterNotNull() // List<Int> 타입이다.

읽기 전용 / 변경 가능한 컬렉션

  • 코틀린 컬렉션에는 Collection 인터페이스와 MutableCollection 인터페이스가 존재한다.

  • Collection 인터페이스에는 읽기 전용 연산들이 존재하며, MutableCollection 인터페이스는 Collection 인터페이스를 확장하고 원소 추가/제거 연산들을 가진다.

  • 코드에서는 되도록 읽기 전용 인터페이스를 사용하고 필요할 때에만 변경 가능한 인터페이스를 사용해야 한다.

  • 원본 컬렉션의 변경을 막기 위해 다른 함수에 전달할 때 복사본을 넘길 수도 있다.

fun<T> copyElements(source: Colleciton<T>, target: MutableCollection<T>) {
    for (item in source) {
        target.add(item)
    }
}

fun main(args: Array<String>) {
    val source: Collection<Int> = arrayListOf(3, 5, 7)
    val target: MutableCollection<Int> = arrayListOf(1)
    
    copyElements(source, target)
}
  • 읽기 전용 컬렉션이라고 해서 변경 불가능한 컬렉션인 것은 아니다. 다른 참조에서 MutableCollection 타입으로 사용할 수도 있기 때문이다.

코틀린 컬렉션과 자바

  • 다음은 코틀린 컬렉션 인터페이스의 계층 구조이다.

  • 읽기 전용, 변경 가능 인터페이스의 기본 구조는 자바 컬렉션 인터페이스를 그대로 옮겨 놓은 형태이다.

  • 코틀린에서는 자바의 ArrayList, HashSet 클래스를 MutableList, MutableSet을 상속받은 것처럼 취급한다.

  • 컬렉션 생성 함수는 아래와 같이 나뉘게 된다.

컬렉션 타입
읽기 전용 타입
변경 가능 타입

List

listOf

mutableListOf, arrayListOf

Set

setOf

mutableSetOf, hashSetOf, linkedSetOf, sortedSetOf

Map

mapOf

mutableSetOf, hashMapOf, linkedSMapOf, sortedMapOf

  • 자바는 읽기 전용 컬렉션과 변경 가능 컬렉션을 구분하지 않으므로, 코틀린에서 읽기 전용 컬렉션을 자바 메서드 인자로 넘길 때 변경 가능해진다는 문제가 있다.

  • 널이 아닌 원소로 이뤄진 컬렉션 타입을 자바 메서드 인자로 넘길 때에도 마찬가지로 컬렉션에 널이 들어갈 가능성이 있다.

  • 이러한 문제를 해결하는 방법은 따로 없고, 사용자가 올바른 파라미터 타입을 넘겨 혼돈이 없도록 해야 한다.

컬렉션을 플랫폼 타입으로 다루기

  • 코틀린은 자바에서 선언한 컬렉션 타입 변수를 플랫폼 타입으로 본다.

  • 따라서 읽기 전용 컬렉션이나 변경 가능한 컬렉션 중 원하는 형태로 사용할 수 있다.

  • 컬렉션 타입이 자바 메서드 시그니처에 있고 이를 코틀린에서 오버라이드하려 하면 마찬가지로 읽기 전용 컬렉션이나 변경 가능한 컬렉션 중 어떤 타입을 사용할 지 정해야 한다.

  • 컬렉션이 널이 될 수 있는지, 원소로 널이 들어올 수 있는지, 오버라이드하는 메서드가 컬렉션을 변경하는지 등 자바에서 인터페이스나 클래스가 어떤 맥락에서 사용되는지 확인해 적절한 타입을 정해야 한다.

배열

  • 타입 파라미터를 받으며 자바의 배열과 호환된다.

  • arraysOf 함수에 여러 원소를 인자로 넣어 배열을 만들 수 있다.

  • arraysOfNulls 함수에 정수 값을 인자로 넘겨 원하는 개수만큼 null이 담긴 배열을 만들 수 있다.

  • 배열 크기와 람다를 인자로 받아 배열 원소를 초기화해주는 생성자도 존재한다.

val letters = Array<String>(26) { i -> ('a' + i).toString() }
  • 코틀린에서 배열이 사용되는 상황은 다음과 같다.

    • 배열을 인자로 받는 자바 메서드 호출

    • vararg 인자를 받는 코틀린 함수 호출

  • 컬렉션을 배열로 바꾸려면 toTypedArray 함수를 사용할 수 있다.

  • 아래는 vararg 인자를 넘기기 위해 리스트 컬렉션을 배열로 변경한 후 스프레드 연산자 * 를 사용한 예제이다.

val strings = listOf("a", "b", "c")
println("%s/%s/%s".format(*strings.toTypedArray()))
  • Array<Int>는 제네릭 타입이기 때문에 자바의 Integer[]로 변환된다.

  • int[]와 같은 원시 타입 배열로 변환되도록 하려면 IntArray, CharArray, BooleanArray와 같이 원시 타입 배열을 위한 별도 클래스를 사용해야 한다.

  • 원시 타입 배열을 만드는 방법은 아래 세 가지가 있다.

    • 생성자에 size 인자를 입력해 고정 크기 배열을 만들 수 있다.

    • 팩토리 함수를 사용해 여러 원소를 인자로 넣어 배열을 만들 수 있다.

    • 크기와 람다를 인자로 받는 생성자로 배열을 만들 수 있다.

val arr1 = IntArray(5)
val arr2 = IntArrayOf(0, 0, 0, 0, 0)
val arr3 = IntArray(5) { i -> (i+1) * (i+1) }
  • forEachIndexed 함수를 사용해 배열의 인덱스와 원소를 순회할 수 있다.

fun main(args: Array<String>) {
    args.forEachIndexed { index, element ->
        println("Argument $index is: $element")
    }
}
Previous람다Next연산자 오버로딩과 기타 관례

Last updated 8 months ago

🏖️