본문 바로가기

Kotlin

자바 개발자를 위한 코틀린 입문

1. 코틀린에서 변수와 타입, 연산자를 다루는 방법

1.1 코틀린에서 변수를 다루는 법

//java
long number1 = 10L;

final long number2 = 10L;

//kotlin
var number1 = 10L
val number2 = 10L

코틀린에서 primitive 타입

//java
long number1 = 10L;

Long number3 = 1_000L;

//kotlin
var number1 = 10L
var number3 = 1_000L

  • 코틀린은 내부적으로 primitive 타입으로 바꿔서 똑똑하게 계산해준다.
  • 프로그래머가 boxing과 unboxing을 고려하지 않아도 될 정도로 자동으로 처리해준다.

코틀린에서 nullable

  • 코틀린에서는 기본적으로 모든 변수는 null이 들어갈 수 없다.
var number1 = 10L
number1 = null // 에러

var number3 : Long? = 1_000
number3 = null // 정상

객체 인스턴스화

  • 코틀린에서는 객체 인스턴스화할 때 new를 붙이지 않는다.

1.2 코틀린에서 null을 다루는 방법

  • 코틀린에서는 null이 들어갈 수 있는 타입은 완전히 다르게 간주된다.
    • 함수에서 한 번 null check를 하는 코드를 작성하면 그 후에는 컴파일러가 non-null임을 알 수 있다.
  • null이 아닌 경우에만 호출되는 Safe call (?.)이 있다.
  • null인 경우에만 호출되는 Elvis 연산자 (?:)가 있다.
  • null이 절대 아닐때 사용할 수 있는 널 아님 단언(!!)이 있다.
// Java
boolean startsWithA1(String str){
    if (str == null) {
        throw new IllegalArgumentException("null이 들어왔습니다.");
    }
    return str.startsWith("A");
}

Boolean startsWithA2(String str) {
    if (str == null) {
        return null;
    }
    return str.startsWith("A");
}

boolean startsWithA3(String str) {
    if (str == null) {
        return false;
    }
    return str.startsWith("A");
}

//Kotlin
fun startsWithA1(str: String?): Boolean {
    return str?.startsWith("A")
        ?: throw java.lang.IllegalArgumentException("null이 들어왔습니다.")
}

fun startsWithA2(str: String?): Boolean? {
    return str?.startsWith("A")
}

fun startsWithA3(str: String?): Boolean {
    return str?.startsWith("A") ?: false
}

1.3 코틀린에서 Type을 다루는 방법

기본 타입

Byte, Short, Int, Long, Float, Double

코틀린에서는 선언된 기본값을 보고 타입을 추론한다. 코틀린에서 타입변환은 명시적으로 to변환타입 함수를 사용하여 이루어져야한다. (Java의 암시적 변환과는 다르다.) ex) toLong, toString

타입 캐스팅

  • is, !is, as, as?

kotlin 새로운 타입

  • Any
    • 자바의 Object 역할 (모든 객체의 최상위 타입)
  • Unit
    • 자바의 void와 동일한 역할
    • void와 다르게 그 자체로 타입 인자로 사용 가능하다.
  • Nothing
    • 함수가 정상적으로 끝나지 않았다는 사실을 표현하는 역할
    • 무조건 예외를 반환하는 함수, 무한 루프 함수

String interpolation, String Indexing

//kotlin
prinln("이름 : ${person.name}")

  • 문자열을 가공할 때 ${} 를 사용할 수 있다.
  • """ : 여러 줄의 문자열을 표현 가능
  • 문자열의 특정 문자 가져오기
    • val str = "ABC"
    • str\[0\], str\[1\]

1.4 코틀린에서 연산자를 다루는 방법

단항 연산자 / 산술 연산자

  • 자바와 동일하다

비교연산자와 동등성, 동일성

  • < >= <=
  • 코틀린에서는 비교연산자를 사용하게 되면 compareTo를 자동으로 호출해준다.
  • 코틀린에서 동일성은 === 동등성(equalsTo를 자동으로 호출해준다.)은 ==

논리 연산자 / 코틀린에 있는 특이한 연산자

  • Java와 동일하다.
  • in / !in
    • 컬렉션이나 범위에 포함되어 있다. 포함되어 있지 않다.
  • a..b
    • a부터 b까지의 범위 객체를 생성한다.
  • a\[i\]
    • a의 특정 index에 접근

연산자 오버로딩

  • Kotlin에서는 객체마다 연산자를 직접 정의할 수 있다.

2. 코틀린에서 코드를 제어하는 방법

코틀린에서 제어문을 다루는 방법

  • if문
  • Expression과 Statement
  • switch와 when

if/ if-else

  • Java와 문법이 동일하다.

Expression & Statement

  • Statement: 프로그램의 문장, 하나의 값으로 도출되지 않음
  • Expression: 하나의 값으로 도출되는 문장

자바에서는 if문은 Statement이다. 하지만 Kotlin에서 if/else문은 Expression 이다. Kotlin에서는 if/else를 Expression으로 사용할 수 있기 때문에 삼항연산자가 없다.

Switch & when

  • Java의 Switch문은 Kotlin에서 when으로 대체되었다. when은 더 강력한 기능을 갖는다.
//java
switch(score /10){
	case 9:
		return "A";
	case 8:
		return "B";
	case 7:
		return "C";
	default:
		return "D";
}

//kotlin
when(score/10){
	9 -> "A"
	8 -> "B"
	7 -> "C"
	else -> "D"
}

  • when절은 다음의 두 가지 경우로 사용할 수 있다.
    • 값으로 분기
    • 조건절을 직접 명시
when (값) {
조건부 -> 어떠한 구문
else -> 어떠한 구문
}

when{
}

코틀린에서 반복문을 다루는 방법

  • for-each
//kotlin
for(number in numbers){
	println(number)
}

  • 전통적인 for
//kotlin
for( i in 1..3){
	println(i)
}

for(i in 3 downTo 1){
	println(i)
}

for(i in 1..5 step 2){
	println(i)
}

  • Progression과 Range
  • ..연산자 : 범위를 나타내는 연산자
    • 등차수열을 만드는 코드
    • downTo, step 도 함수다! (중위함수)
    • 코틀린에서 전통적인 for문은 등차수열을 사용한다.
  • While
    • 자바와 동일

코틀린에서 예외를 다루는 방법

try catch finally 구문

//kotlin
fun parseIntOrThrow(str: Striing): Int{
	try{
		return str.toInt()
	} catch(e: NumberFormatException){
		throw IllegalArgumentException("주어진 ${str}는 숫자가 아닙니다.")
	}
}

  • try catch 구문도 expression이다.
//kotlin
fun parseIntOrThrowV2(str: Striing): Int?{
	return try{
		str.toInt()
	} catch(e: NumberFormatException){
		null
	}
}

Checked Exception and Unchecked Exception

  • Kotlin에서는 Checked Exception과 Unchecked Exception을 구분하지 않는다.
  • 모든 예외는 Unchekced 이다.
//java
public void filePrinter() throws IOException {
    File currentFile = new File(".");
    System.out.println(currentFile.getAbsoluteFile());
    File file = new File(currentFile.getAbsolutePath() + "/a.txt");
    BufferedReader bufferedReader = new BufferedReader(new FileReader(file));
    System.out.println(bufferedReader.readLine());
    bufferedReader.close();
}

//kotlin
fun filePrinter() {
    val file = File(".")
    val currentFile = File(file.absolutePath + "/a.txt")
    val bufferedReader = BufferedReader(FileReader(currentFile))
    println(bufferedReader.readLine())
    bufferedReader.close()
}

try with resources

  • kotlin에서는 try with resources 구문이 없어지고 use라는 inline 함수를 사용한다.
//java
public void readFile(String path) throws IOException {
    try(BufferedReader bufferedReader = new BufferedReader(new FileReader(path))){
        System.out.println(bufferedReader.readLine());
    }
}

//kotlin
fun readFile(path: String) {
    BufferedReader(FileReader(path)).use { reader ->
        println(reader.readLine())
    }
}

코틀린에서 함수를 다루는 방법

함수 선언 문법

  • 함수가 하나의 결과값이면 block 대신 = 사용 가능
  • 함수는 클래스 안에 있을 수도, 파일 최상단에 있을 수도 있다. 또한, 한 파일 안에 여러 함수들이 있을 수도 있다.
// java
public int max(int a, int b){
	if(a > b){
		return a;
	}
	return b;
}

// kotlin
fun max(a: Int, b: Int) = if (a > b) else b

default parameter

  • 밖에서 파라미터를 넣어주지 않으면 기본값을 사용한다.
    • Java에서는 overloading을 사용해서 함수를 구현해주어야한다.
//java
public void repeat(String str, int num, boolean useNewLine){
	for(int i = 1; i <= num; i++){
		if(useNewLine){
			System.out.println(str);
		} else {
			System.out.print(str);
		}
	}
}

//kotlin
fun repeat(str: String,
			num: Int = 3,
			useNewLine: Boolean = true){
	for(i in 1..num){
		if(useNewLine){
			println(str)
		} else {
			print(str)
		}
	}

}

named argument

  • 위의 코틀린 함수 Repeat에서 num은 default로 사용하고 useNewLine만 인자로 주고싶다면?
    • repeat("Hello world", useNewLine = false)
    • builder를 직접 만들지 않았지만 마치 builder를 쓰는 것과 같은 효과를 가질 수 있다.

같은 타입의 여러 파라미터 받기 (가변인자)

//java
public static void printAll(String... strings){
	for(String str: strings){
		System.out.println(str);
	}
}
printAll("A", "B", "C");
String[] array = new String[]{"A", "B", "C"};
printAll(array)

//kotlin
fun printAll(vararg strings: String) {
	for(str in strings){
		println(str)
	}
}
printAll("A", "B", "C")
val array = arrayOf("A", "B", "C")
printAll(*array) // *은 spread 연산자

3. 코틀린에서의 OOP

코틀린에서 클래스를 다루는 방법

클래스와 프로퍼티

class Person constructor(name: String, age: Int){
	val name = name;
	var age = age
}

  • 코틀린에서는 필드가 있으면 getter와 setter를 자동으로 만들어 준다.
  • constructor를 명시적으로 적지 않아도 된다.
  • class의 필드 선언과 생성자를 동시에 선언할 수 있다.
class Person(
	val name: String,
	var age: Int)

  • .필드를 통해 getter와 setter에 바로 접근 가능

생성자와 init

  • Person 객체의 생성자에서 나이를 검증하는 로직을 추가
  • 생성자 추가
class Person(
	val name: String,
	var age: Int){

	init {
		if(age < 0){
			throw IllegalArgumentException("나이는 ${age}일 수 없습니다.")
		}
	}

	constructor(name: String): this(name, 1)
}

  • 주 생성자 (primary constructor) : 클래스의 선언과 함께 만들어지는 생성자
  • 부 생성자 (secondary constructor) : 있을수도 없을수도 있다. 최종적으로 주 생성자를 this로 호출해야한다.
    • body를 가질 수 있다.
  • 코틀린에서는 부 생성자보다는 default parameter를 쓰는것을 권장한다.

커스텀 getter, setter

  • 코틀린에서는 필드를 만들면 getter와 setter가 자동으로 생긴다.
    • 이를 프로퍼티 라고 부른다.
fun isAdult(): Boolean {
	return this.age >= 20
}

val isAdult: Boolean
	get() = this.age >= 20

  • 코틀린에서는 위의 함수를 Custom getter 형식으로 표현할 수 있다.
    • 사실상 바이트 코드로 보면 같다.
  • 객체의 속성이라면 custom getter 그렇지 않다면 함수

backing field

  • custom getter, custom setter에서 무한루프를 막기 위해 field라는 키워드를 사용한다.
    • 이를 backing field라 한다.
  • name의 getter를 customizing 해보자.
class Person(
	name: String,
	var age: Int){
	val name: String = name
		get() = field.uppercase()

  • 위에서 처럼 getter를 커스터마이징 하기 위해 name을 필드로 빼고 getter를 재정의한다.
  • name을 접근할 때 field로 접근한다.

코틀린에서 상속을 다루는 방법

추상 클래스

//kotlin
abstract class Animal(
	protected val species: String,
	protected val legCount: Int,
){
	abstract fun move()
}

class Cat(
	species: String
) : Animal(spcies, 4) {

	override fun move() {
		println("고양이가 움직인다.")
	}
}

  • legCount를 override 하려면 부모 클래스에서 open을 붙여주어야 한다.
class Penguin(
	species: String
) : Aniamal(species, 2) {
	private val wingCount: Int = 2
override fun move() {
	println("펭귄이 움직인다.")
}
override val legCount: Int
	get() = super.legCount + this.wingCount // legCount를 open으로 설정해야한다.
}

인터페이스

  • 코틀린의 메서드는 디폴트 메서드를 구현할 때 default 키워드가 필요없다.
  • 추상메서드는 똑같이 작성하면 된다.
  • 인터페이스 구현도 : 을 사용한다.
  • 중복되는 인터페이스를 특정할때 super\<type\>.함수 형태로 호출한다.
  • 프로퍼티도 인터페이스에 만들 수 있다.
    • getter 구현

클래스 상속할 때 주의할 점

  • 상위 클래스를 설계할 때 생성자 또는 초기화 블록에 사용되는 프로퍼티에는 open을 피해야 한다.
    • 이상한 값이 들어가 있을 수 있다.

상속 관련 지시어

  1. final : override를 할 수 없게 한다. default로 보이지 않게 존재한다.
  2. open : override를 열어준다.
  3. abstract : 반드시 override 해야 한다.
  4. override : 상위 타입을 오버라이드 하고 있다. (annotation이 아닌 키워드로 사용해야한다.)

코틀린에서 접근 제어를 다루는 방법

자바와 코틀린의 가시성 제어

  • Java
    • public : 모든 곳에서 접근 가능
    • protected : 같은 패키지 또는 하위 클래스에서만 접근 가능
    • default : 같은 패키지에서만 접근 가능
    • private : 선언된 클래스 내에서만 접근 가능
  • Kotlin
    • 코틀린에서는 패키지를 namesapce를 관리하기 위한 용도로만 사용
    • 모듈 : 한 번에 컴파일 되는 Kotlin 코드
    • public : 모든 곳에서 접근 가능
    • protected : 선언된 클래스 또는 하위 클래스에서만 접근 가능 (패키지 x)
    • internal : 같은 모듈에서만 접근 가능
    • private : 선언된 클래스 내에서만 접근 가능
  • Java의 기본 접근 지시어 default
  • 코틀린의 기본 접근 지시어 public

코틀린 파일의 접근 제어

  • 코틀린은 .kt 파일에 변수, 함수, 클래스 여러개를 바로 만들 수 있다.
  • public 기본값, 어디서든 접근 할 수 있다.
  • protected : 파일에는 사용 불가능
  • internal : 같은 모듈에서만 접근 가능
  • private : 같은 파일 내

다양한 구성요소의 접근 제어

  • 클래스
    • public
    • protected
    • internal
    • private
  • 생성자
    • 가시성 범위는 동일하다.
    • 단, 생성자에 접근 지시어를 추가하려면 constructor를 명시적으로 적어주어야 한다.
  • 파일 최상단에 바로 유틸 함수를 작성하면 편하다
  • 프로퍼티
    • 가시성 범위는 동일하다.
    • setter에만 가시성 부여 가능(아래 코드 참조)
class Car(
	internal val name: String,
	private var name: String,
	_price: Int
){
	var price = _price
		private set
}

Java와 Kotlin을 함께 사용할 경우 주의할 점

  • Internal은 바이트 코드 상 public이 된다.
    • 때문에 Java 코드에서는 Kotlin 모듈의 internal 코드를 가져올 수 있다.
  • protected 도 주의

코틀린에서 object 키워드를 다루는 방법

static 함수와 변수

// java
public class JavaPerson{
	private static final int MIN_AGE = 1;

	public static JavaPerson newBaby(String name) {
		return new JavaPerson(name, MIN_AGE);
	}

	private String name;
	private int age;

	private JavaPerson(String name, int age){
		this.name = name;
		this.age = age;
	}
}

//kotlin
class Person private constructor(
	var name: String,
	var age: Int,
){
	companion object{
		private const val MIN_AGE = 1
		fun newBaby(name: String){
			return Person(name, MIN_AGE)
		}
	}
}

  • static 대신 companion object를 사용한다.
    • static: 클래스가 인스턴스화 될 때 새로운 값이 복제되는게 아니라 정적으로 인스턴스끼리의 값을 공유
    • companion object: 클래스와 동행하는 객체
    • const : 컴파일 시에 변수가 할당된다. 진짜 상수에 붙이기 위한 용도. 기본 타입과 String에 붙일 수 있다.
  • companion object, 즉 동반 객체도 하나의 객체로 간주된다. 때문에 이름을 붙일 수도 있고, interface를 구현할 수도 있다.
  • Java에서 Kotlin companion object를 사용하려면 @JvmStatic을 붙여주어야 한다.
    • companion object의 이름이 있다면 이름을 사용하면된다.

싱글톤

object Singleton{
	var a: Int = 0
}

익명 클래스

  • 특정 인터페이스나 클래스를 상속받은 구현체를 일회성으로 사용할 때 쓰는 객체
  • object : Interface{ override function{}}

코틀린에서 중첩 클래스를 다루는 방법

중첩 클래스의 종류

  • static을 사용하는 중첩클래스
  • static을 사용하지 않는 클래스 - 내부클래스

코틀린의 중첩 클래스와 내부 클래스

  • 코틀린에서는 클래스 안에 기본 클래스를 사용하면 바깥 클래스에 대한 참조가 없다.
  • 바깥 클래스를 참조하고 싶다면, inner 키워드를 붙여야 한다.
    • 코틀린에서 inner class에서 바깥 클래스를 참조하려면 this@바깥클래스를 사용해야 한다.
//java
public class JavaHouse{
	private String address;
	private LivingRoom livingRoom;

	public JavaHouse(String address){
		this.address = address;
		this.livingRoom = new LivingRoom(10);
	}

	public static class LivingRoom{
		private double area;

		public LivingRoom(double area){
			this.area = area;
		}
	}
}

//kotlin
class House(
	private val address: String,
	private val livingRoom: LivingRoom
){
	class LivingRoom(
		private val area: Double
	)
}

//kotlin - 내부 클래스
	private val address: String,
	private val livingRoom: LivingRoom
){
	inner class LivingRoom(
		private val area: Double
	){
		val address: String
			get() = this@House.address
	}
}

코틀린에서 다양한 클래스를 다루는 방법

Data Class

  • 계층간의 데이터를 전달하기 위한 DTO(Data Transfer Object)
    • 데이터(필드)
    • 생성자와 getter
    • equals, hashCode
    • toString
// java
@Data
class JavaPersonDto{
	private final String name;
	private final int age;

	public JavaPersonDto(String name, int age){
		this.name = name;
		this.age = age;
	}
}

//kotlin
data class PersonDto(
	val name: String,
	val age: Int,
)

  • kotlin 에서는 data 키워드를 붙이면 equals, hashcode, toString을 만들어 준다.
    • java jdk 16에서 record class도 dataclass와 비슷한 클래스를 만들 수 있다.

Enum Class

public enum JavaCountry{
	KOREA("KO"),
	AMERICA("US");

	private final String code;

	JavaCountry(String code){
		this.code =code;
	}

	public String getCode(){
		return code;
	}
}

//kotlin
enum class Country(
	val code: String
){
	KOREA("KO"),
	AMERICA("US");
}

  • when은 enum class, sealed class 와 함께 사용할 때 진가를 발휘한다.
fun handleCountry(country: Country){
	when(country){
		Country.KOREA -> TODO()
		Country.AMERICA -> TODO()
		// else 는 필요 없다.
	}
}

  • 컴파일러가 enum의 모든 타입을 알고 있기 때문에 else를 작성하지 않아도 된다.

Sealed Class, Sealed interface

  • 상속이 가능하도록 추상클래스를 만들까 하는데, 외부에서는 이 클래스를 상속받지 않았으면 좋겠다.
    • 하위 클래스를 봉인하자!!
  • 컴파일 타임 때 하위 클래스의 타입을 모두 기억한다.
    • 즉, 런타임때 클래스 타입이 추가될 수 없다.
  • 하위 클래스는 같은 패키지에 있어야 한다.
  • Enum과 다른 점
    • 클래스를 상속받을 수 있다.
    • 하위 클래스는 멀티 인스턴스가 가능하다.
sealed class HyundaiCar(
	val name: String,
	val price: Long
)

class Avante : HyundaiCar()

class Sonata : HyundaiCar()

class Grandeur : HyundaiCar()

// 사용
when(car){
	is Avante -> TODO()
	is Grandeur -> TODO()
	is Sonata -> TODO()
	// else 가 필요하지 않다.
}

  • jdk 17에서도 sealed class가 추가되었다.

4. 코틀린에서의 FP

코틀린에서 배열과 컬렉션을 다루는 방법

배열

  • 배열을 잘 사용하지 않는다.
val array = arrayOf(100, 200)
for ((idx, value) in array.withIndex()){
	println("$idx $value")
}

array.plus(300) // element 추가

코틀린에서 Collection - List, Set , Map

  • 컬렉션을 만들어줄 때, 불변인지, 가변인지를 설정해야 한다.
    • List, Set, Map
    • MutableList, MutableSet, MutableMap
  • 가변 컬렉션: 컬렉션에 element를 추가, 삭제할 수 있다.
  • 불변 컬렉션: 컬렉션에 element를 추가, 삭제할 수 없다.
    • Collection을 만들자 마자 Collections.unmodifiableList 등을 붙여주는 것과 같다
    • 불변이라 하더라도 컬렉션 안의 ReferenceType의 값을 바꿀수는 있다.
  • List
val numbers = listOf(100, 200)
val emptyList = emptyList<Int>()

  • empty\<Int\>()
    • 추론할 수 있다면 타입 생략 가능
val numbers = listOf(100, 200)
println(numbers[0])
for(number in numbers){}
	println(number)
}

for((index, number) in numbers.withIndex()){
	println("$index $number")
}

  • 가변 리스트 생성 = mutableListOf(100, 200)
    • 기본 구현체는 ArrayList이고 기타 사용법은 Java와 동일하다.
  • 우선 불변 리스트를 만들고, 꼭 필요한 경우 가변 리스트를 만들자.
  • Set 집합은 List와 다르게 순서가 없고 같은 element는 하나만 존재할 수 있다.
    • 자료구조적인 의미를 제외하곤 사용법은 List와 동일하다.
    • 기본적인 구현체는 LinkedhashSet이다.
  • Map
val oldMap = mutableMapOf<Int, String>()
oldMap.put(1, "Monday") // oldMap[1] = "Monday"
oldMap.put(2, "Tuesday") // oldMap[2] = "Tuesday"

mapOf(1 to "Monday", 2 to "Tuesday")

for (key in oldMap.keys){
	println(key)
	println(oldmap[key])
}

for ((key, value) in oldMap.entries){
	println(key)
	println(value)
}

컬렉션의 null 가능성, Java와 함께 사용하기

  • ? 위치에 따라 null 가능성 의미가 달라지므로 차이를 잘 이해해야 한다.
  • List\<Int?\> : List에 null 이 들어갈 수 있지만, 리스트는 절대 null이 아님
  • List\<Int\>? : List에 null 이 들어갈 수 없지만, 리스트는 null일 수 있음
  • List\<Int?\>? : List에 null 이 들어갈 수 있고, 리스트가 null일 수도 있음
  • Java는 읽기 전용 컬렉션과 변경 가능 컬렉션을 구분하지 않는다.
    • 코틀린의 불변 리스트를 자바에서 사용할 때에는 불변인지 모르고 Element를 추가할 수 있다.
    • Java는 nullable 타입과 non-nullable 타입을 구분하지 않으므로 null을 non-nullable 리스트에 추가할 수 있다.
  • Kotlin에서 Java 컬렉션을 가져다 사용할때 플랫폼 타입을 신경써야 한다.
    • List\<Integer\> 을 전달했을 때 null 여부를 알 수 없으므로 Java 코드를 가져올 때 주의해야 한다.

코틀린에서 다양한 함수를 다루는

확장함수

  • 어떤 클래스안에 있는 메소드처럼 호출할 수 있지만, 함수는 밖에 만들 수 있게 하자
//kotlin
fun String.lastChar(): Char {
	return this[this.length - 1]
}

  • fun 확장하려는클래스.함수이름(파라미터): 리턴타입{//this를 이용해 실제 클래스 안의 값에 접근}
    • this를 수신객체라 한다.
    • 확장하려는 클래스는 수신객체 타입이라 한다.
  • 원래 클래스의 멤버함수처럼 사용할 수 있다.
  • 확장함수는 클래스에 있는 private, protected 멤버를 가져올 수 없다.
  • 멤버함수와 확장함수의 시그니처가 같다면?
    • 멤버함수가 우선적으로 호출된다.
  • 확장함수가 override 된다면?
    • 해당 변수의 현재 타입 즉, 정적인 타입에 의해 확장함수가 결정된다.
  • 코틀린의 확장함수는 Java 언어에서는 정적 함수를 호출하는 것처럼 사용할 수 있다.
  • 확장 프로퍼티의 getter를 정의해서 사용할 수도 있다.

infix 함수

  • downTo, step도 함수이다.
  • 변수.함수이름(argument) 대신 변수 함수이름 argument 로 표현할 수 있다.
infix fun Int.add2(other: Int): Int{
	return this + other
}

3.add2(4)
3 add2 4

inline 함수

  • 함수가 호출되는 대신, 함수를 호출한 지점에 함수 본문을 그대로 복붙하고 싶은 경우!
inline fun Int.add(other: Int): Int{
	return this + other
}

fun main(){
	3.add(4)
}

  • 함수를 파라미터로 전달할 때에 오버헤드를 줄일 수 있다.
    • 하지만 inline 함수의 사용은 성능 측정과 함께 신중하게 사용되어야 한다.

지역함수

  • 함수 안에 함수를 선언할 수 있다.
fun createPerson(firstName: String, lastName: String): Person{
		fun validateName(name: String, fieldName: String){
			if (name.isEmpty()){
				throw IllegalArgumentException()
			}
		}
		validateName(firstName)
		validateName(lastName)
		return Person(firstName, lastName)
}

  • 하지만 depth가 깊어지기도 하고, 코드가 그렇게 깔끔하지 않다.

코틀린에서 람다를 다루는 방법

Java에서 람다를 다루기 위한 노력

  • Java에서는 함수는 변수에 할당되거나 파라미터로 전달할 수 없다.(2급 시민)
    • 함수를 넘길수 있는 것처럼 보인다.
    • 살제로는 인터페이스로 받아야 한다!

코틀린에서의 람다

  • 코틀린에서는 함수가 그 자체로 값이 될 수 있다.
    • 변수에 할당될수도, 파라미터로 넘길 수도 있다.
// 람다를 만드는 방법 1
val isApple = fun(fruit: Fruit ): Boolean {
	return fruit.name = "사과"
}

// 람다를 만드는 방법 2
val isApple2 = { fruit: Fruit -> fruit.name == "사과"}

// 람다를 호출하는 방법
isApple(Fruit())
isApple.invoke(Fruit())

  • 함수의 타입 : 파라미터타입 -> 반환타입
  • 람다를 여러줄 작성할 수 있고 리턴을 쓰지않아도 마지막줄이 람다의 반환이다.

Closure

  • 코틀린에서는 Closure를 사용하여 non-final 변수도 람다에서 사용할 수 있다.
// java
String targetFruitName = "바나나";
targetFruitName = "사과";
filterFruit(fruits, (fruit) -> targetFruitName.equals(fruit.getName())); // 에러발생 Variable used in lambda exrpession should be final or effectively final

// kotlin
var targetFruitName = "바나나"
targetFruitName = "사과"
filterfruits(fruits) { it.name == targetFruitName } // 에러 x

  • 코틀린에서는 람다가 시작하는 지점에 참조하고 있는 변수들을 모두 포획하여 그 정보를 가지고 있다.
    • 이렇게 해야만, 람다를 진정한 일급 시민으로 간주할 수 있다.
    • 이 데이터 구조를 Closure라고 부른다.

다시 try with resources

public inline fun <T: Closeable?, R> T.use(block: (T) -> R): R {}

코틀린에서 컬렉션을 함수형으로 다루는 방법

필터와 맵

fruits.filter { fruit -> fruit.name == "사과"}
 .map { fruint -> fruit.currentPrice }

  • filter
  • map
  • filterIndexed
  • mapIndexed
  • mapNotNull

다양한 컬렉션 처리 기능

fruits.all{ fruit -> fruit.name == "사과"}

  • all : 조건을 모두 만족하면 true
  • none : 조건을 모두 만족하지 않으면 true
  • any : 조건을 하나라도 만족하면 true 그렇지 않으면 false
  • count : 개수를 센다
  • sortedBy : 정렬을 한다
    • sortedByDescending
  • distinctBy : 변형된 값을 기준으로 중복을 제거한다.
  • first : 첫번째 값을 가져온다 (무조건 null이 아니어야함)
    • firstOrNull: 첫번째 값 또는 null을 가져온다
  • last : 마지막 값을 가져온다
    • lastOrNull : 마지막 값 또는 Null을 가져온다.

List를 Map으로

val map: Map<String, List<Fruit>> = fruits.groupBy{ fruit -> fruit.name }

val map: Map<String, List<Long>> = fruits
 .groupBy({fruit -> fruit.name}, {fruit -> fruit.factoryPrice })

val map: Map<Long, Fruit> = fruits.associateBy { fruit -> fruit.id }

val map: Map<Long, Long> = fruits
	.associateBy({ fruit -> fruit.id}, {fruit -> fruit.factoryPrice })

  • groupBy
  • associateBy
  • Map에 대해서도 컬렉션에서 사용할 수 있는 기능을 사용할 수 있다!!

중첩된 컬렉션 처리

  • flatMap \/ flatten

5. 추가적으로 알아두어야 할 코틀린 특성

코틀린의 이모저모

Type Alias와 as import

Type Alias

  • 긴 이름의 클래스 혹은 함수 타입이 있을때 축약하거나 더 좋은 이름을 사용하고 싶을 때
typealias FruitFilter = (Fruit) -> Boolean

fun filterFruits(fruits: List<Fruit>, filter: FruitFilter){}

typealias USGTMap = Map<String, UltraSuperGuardianTribe>

as import

  • 다른 패키지의 같은 이름 함수를 동시에 가져오고 싶다면?
  • as import: 어떤 클래스나 함수를 임포트할 때 이름을 바꾸는 기능
import com.harris.a.helloWorld as printHelloWorldA
import com.harris.b.helloWorld as printHelloWorldB

구조분해와 componentN 함수

구조분해

  • 구조분해: 복합적인 값을 분해하여 여러 변수를 한 번에 초기화하는 것
data class Person(
val name: String,
val age: Int
)
val person = Person("최태현", 100)
val (name, age) = person //이름과 나이가 한번에 초기화
/*
val name = person.component1()
val age = person.component2()
*/

componentN 함수

  • data class 는 각 프로퍼티에 대해 componentN 함수를 만들어준다.
  • 이를 활용하여 구조분해 문법을 사용할 수 있다.
  • data class가 아닌데 구조분해 문법을 활용하고 싶다면 직접 componentN 함수를 만들어줄 수 있다.
    • (in 절에서도 구조분해가 사용됨)

Jump와 Label

  • return : 기본적으로 가장 가까운 enclosing function 또는 익명함수로 값이 반환된다
  • break : 가장 가까운 루프가 제거된다.
  • continue : 가장 가까운 루프를 다음 step으로 보낸다.

Reference

  • 자바 개발자를 위한 코틀린 입문(Java to Kotlin Starter Guide) by 인프런

'Kotlin' 카테고리의 다른 글

함수형 언어의 타입시스템  (0) 2022.01.22