본문 바로가기
Kotlin

Kotiln Fundamentals

by 이상한나라의개발자 2024. 11. 24.

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

  • 모든 변수는 var / val 을 붙여 주어야 한다.
    • var : 변경 가능하다.
    • val : 변경 불가능하다 (read-only)
  • 타입을 명시적으로 작성하지 않아도 타입이 추론된다.
  • Primitive TYpe과 Reference Type을 구분하지 않아도 된다.
  • Null이 들어갈 수 있는 변수 타입 뒤에 ?를 붙여 주어야 한다.
    • 아예 다른 타입으로 간주된다.
  • 객체를 인스턴스화 할 때 new를 붙이지 않아야 한다.
  • 모든 변수는 우선 val(불변)으로 만들고 꼭 필요한 경우 var로 변경한다.
-- Java
long number1 = 10L;
final long number2 = 10L;

Long number3 = 1_000L;
License license = new License("productName");

-- Kotlin
var number1 = 10L; // 가변 
var number1: Long = 10L; 
val number2 = 10L; // 불변
val number2: Long = 10L;

초기값을 지정해주지 않는 경우는 
var number: Int
println(number) // 컴파일 에러 발생 Variable 'number' must be initialize

 

코틀린에서 Primitive Type and Reference Type

코틀린에서는 숫자, 문자, 불리언과 같은 몇몇 타입은 내부적으로 특별한 표현을 갖는다. 이 타입들은 실행시에 Primitive Value로 표현되지만 코드에서는 평번한 클래스 처럼 보인다. 즉, 프로그래머가 boxing / unboxing을 고려하지 않아도 되도록 Kotlin이 알아서 처리해준다. Java의 경우 primitive의 경우 int, long..., reference type의 경우 Integer, Long 로 표현하지만 코틀린에서는 var or val 로 통합되면 자동으로 치환된다. 결론은 개발자가 신경쓰지 않아도 되는 영역이다.

-- java
long number1 = 10L; // primitive type
Long number3 = 1_000L; // reference type

-- kotiln
var number1 = 10L;
var number3 = 10L;
var number1: Long = 10L;

 

코틀린에서 null 변수 

코틀린에서는 기본적으로 모든 변수에 null을 가질 수 없도록 되어있다. 만약 null을 사용해야 한다면 아래와 같다.

var number: Long? = 1_000L
number = null

 

코틀린에서 객체 인스턴스화 

코틀린에서는 java에서 사용하는 new 키워드를 사용하지 않는다.

-- java
Person person = new Person("java")

-- kotlin
var person = Person("kotlin")

 

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

코틀린에서 null 체크

-- Java

public class Lec02Main {

  public boolean startsWithA1(String str) {
    if (str == null) {
      throw new IllegalArgumentException("null이 들어왔습니다");
    }
    return str.startsWith("A");
  }


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


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

  public static void main(String[] args) {

  }
}


-- Kotlin

fun main() {
    println(startWithA1("Addd"))
    println(startWithA2(null))

}

fun startWithA1(str: String?) : Boolean {
    if (str == null) {
        throw IllegalArgumentException("str is null")
    }
    return str.startsWith("A")
}

fun startWithA2(str: String?) : Boolean? {
    if ( str == null ) {
        return null
    }
    return str.startsWith("A")
}

fun startWithA3(str: String?) : Boolean {
    if ( str == null ) {
        return false
    }
    return str.startsWith("A")
}

 

Safe Call과 Elvis 연산자

코틀린에서는 null이 가능한 타입을 완전히 다르게 취급한다. null 이 가능한 타입만을 위한 기능은 아래와 같다.

-- safe call
var str: String? = "ABC"
str.length // 불가능, 사용할 수 없다.
str?.length // 가능 , null이면 실행하지 않고(그대로 null), null이 아니면 실행

-- elvis 연산자
var str: String? = "ABC"
str?.length ?: 0 // str 값이 null 이면 0으로 사용한다.

// 위 코틀린 코드를 safe call과 elvis 적용한 예시
// 널을 허용하지만 절대 널일 수 없다!!
fun startWithA4(str: String?) : Boolean {
    return str!!.startsWith("A")
}

fun startWithA1Refector(str: String?) : Boolean {
    return str?.startsWith("A") ?: throw IllegalArgumentException("str is null")
}

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

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

 

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

기본 타입

  • Byte
  • Short
  • Int
  • Long
  • Float
  • Double
  • 부호 없는 정수들

코틀린에서는 선언된 기본 값을 보고 타입을 추론한다

val number1 = 3    // Int
val number2 = 3L   // Long
val number3 = 3.0f // Float
val number4 = 3.0  // Double

// 타입을 명시하고 싶다면 아래와 같이 사용한다.
val number1: Int    = 3
val number2: Long   = 3L
val number3: Float  = 3.0f
val number4: Double = 3.0

 

 

Java : 기본 타입간의 변환은 암시적으로 이루어질 수 있다.

Kotlin : 기본 타입간의 변환은 명시적으로 이루어져야 한다.

// java

// int 타입의 값이 long 타입으로 암시적으로 변경되었습니다. Java에서 더 큰 타입으로 암시적 변경
int number1 = 4;
long number2 = number1;


// kotlin

// 코틀린에서는 암시적 타입 변경이 불가능합니다.
// 아래 처럼 toLong 또는 Long -> Int로 변경해야 합니다.
fun typeCheck()  {
    val number1 = 4
    val number2: Long = number1.toLong()
    println(number1 + number2)
}

val number1: Int = 4
val number2: Long = number1.toLong()

val number1 = 3
val number2 = 5
val result = number1 / number2.toDouble()

 

타입 캐스팅

코틀린에서는 is, !is, as, as? 를 이용해 타입을 확인하고 캐스팅 합니다.

기본 타입이 아닌 일반 객체 타입의 처리는 아래와 같습니다.

// java

public static void pringAgeIfPerson(Object obj) {
	if (obj instanceof Person) {
    	Person person = (Person) obj;
        System.out.println(person.getAge());
    }
}

// kotlin

fun pringAgeIfPerson(obj: Any) {
	if ( obj is Person ) {
    	val person = obj as Person // as Person 은 자동으로 만들어짐
        println(person.age)
    }
}

fun pringAgeIfPerson(obj: Any) {
	if ( obj is Person ) {
    	println(obj.age) // 스마트 캐스트
    }
}

// type 에서의 null 체크
fun printAgeIfPersonNull(obj: Any?) {
    val person = obj as? Person
    println(person?.age)
}

 

Kotlin의 3가지 특이한 타입

Any <-> Object 

  • Java의 Object 역할 ( 모든 객체의 최상위 타입 )
  • 모든 Primitive Type의 최상의 타입도 Any이다.
  • Any 자체로는 null을 포함할 수 없어 null을 포함하고 싶다면 Any?로 표현
  • Any에 equals / hasCode / toString 존재

Unit <-> Void

  • Unit는 Java의 Void와 동일한 역할
  • void와는 다르게 Unit는 그 자체로 타입 인자로 사용 가능하다.
  • 함수형 프로그래밍에서 Unit는 단 하나의 인스턴스만 갖는 타입을 의미 즉, 코틀린의 Unit은 실제 존재하는 타입이라는 것을 표현

Nothing

  • 함수가 정상적으로 끝나지 않았다는 사실을 표현하는 역할
  • 무조건 예외를 반환하는 함수 / 무한 루프 함수 등
fun fail(message: String): Nothing {
    throw IllegaArgumentException(message)
}

 

String Interpolation, String indexing

자바에서는 문자열을 연결할 때 StringBuilder 이나 %s 등으로 사용한다. 하지만, 코틀린에서는 사용 방법이 아래와 같다.

var person = Person("name", 10)
var log = "사람의 이름은 ${person.name}이고 나이는 ${person.age}세 입니다"

 

특정 문자열의 index 로 가져오기 

// java

String str = "ABC";
char ch = str.charAt(1);
"B"

// kotlin

var str = "ABC"
var ch = str[1]
"B"

 

 

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

단항 연산자 / 산술 연산자

코틀린에서의 단항/산술 연산자는 자바와 완전 동일합니다.

 

  • 단항연산자 : ++, --
  • 산술연산자 : +, -, *, /, %
  • 산술대입연산자 : +=, -=. *=, /=, %=

 

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

비교연산자 : >, <, >=, <= 

 

자바, 코틀린 사용법은 동일합니다. 단, 객체를 비교할 때 Java와 다르게 비교 연산자를 사용하면 자동으로 compareTo를 호출해줍니다.

 

동등성(Equality) : 두 객체의 값이 같은가

동일성(Identity) : 완전히 동일한 객체인가? 즉 주소가 같은가?

 

자바에서 두 인스턴스의 정체성이 동일하다 (동일성, 객체의 주소가 같은가) : == 사용

자바에서 두 인스턴스의 값이 동등하다 (동등성, 값이 같은가) : equals 사용

 

코틀린에서 동일성 : ===

코틀린에서 동등성 : == 을 사용하면 간접적으로 equals를 호출

 

 

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

논리연산자 : &&, ||, !

 

자바와 완전히 동일합니다. Java 처럼 Lazy 연산을 수행

 

특이한 연산자 

 

in / !in : 컬렉션이나 범위에 포함되어 있다, 포함되어 있지 않다를 표현합니다.

println(1 in numbers)
println(1 !in numbers)

 

a..b : a부터 b 까지의 범위 객체를 생성한다

 

연산자 오버로딩

 

코틀린에서는 객체마다 연산자를 직접 정의할 수 있다.

fun calculateMoney() {
    val money1 = JavaMoney(1_000L)
    val money2 = JavaMoney(3_000L)
    println(money1 + money2) // JavaMoney{amount=4000}
}

 

코틀린에서 조건문을 다루는 방법

if 문 & Expression과 Statement

자바에서 if-else는 Statement 이지만 Kotlin에서는 Express 입니다.

  • Satement : 프로그램의 문장, 하나의 값으로 도출되지 않는다.
  • Expression : 하나의 값으로 도출되는 문장
  • int score = 30 + 40 -> 70이라는 하나의 값이 나옵니다. Expression 이면서 Statement

예를 들어 아래 코드처럼 java에서 하나의 값으로 취급하게 되면 에러가 발생합니다.

if 문을 하나의 값을 취급하지 않으니 에러가 발생합니다. 하지만 코틀린에서는 if 문 또한 하나의 값으로 취급할 수 있습니다.

코틀린에서는 if-else를 expression으로 사용할 수 있기 때문에 3항 연산자가 없습니다.

String grade = if ( score >= 50 ) {
		"P" 
	} else {
    	"F"
    }
    
    
// 자바에서 하나의 값으로 취급 하기
// 3항 연산자 사용
String grade = score >= 50 ? "P" : "F";
fun validateScoreIsNotNegative(score: Int) : Unit {
    if ( score < 0 ) {
        throw IllegalArgumentException("${score}는 0보다 작을 수 없습니다.")
    }
}

fun getPassOrFail(score: Int) : String {
    return if ( score >= 50 ) {
        "P";
    } else {
         "F";
    }
}

fun getGrade(score: Int) : String {
    // 하나의 값으로 취급 가능 Expression 하고 Statement 하다.
    return if ( score >= 90 ){
        "A"
    } else if ( score >= 80 ) {
        "B"
    } else if ( score >= 70 ) {
        "C"
    } else if ( score >= 60 ) {
        "D"
    } else {
        "F"
    }
}
// if ( score >=0 && score <= 100) {
fun getRange(score: Int) : Boolean {
    return if ( score in 0..100) {
        true
    } else {
        false
    }
}

 

switch와 when

코틀린에서의 switch when 문이 없습니다 대신 아래와 같이 사용할 수 있습니다. 또한, 자바의 switch case 문 보다 휠씬 유연하며, enum class, sealed class와 함께 사용할 경우, 더욱더 진가를 발휘한다.

when (값) { --> 값이 없을경우는 early retrun 처럼 동작
	조건부 -> 어떠한 구문
    조건부 -> 어떠한 구믄
    else -> 어떠한 구문
}

fun getGradeWithSwitch(score: Int): String {
return when (score) {
        in 90..100 -> "A"
        in 80..89 -> "B"
        in 70..79 -> "C"
        in 60..69 -> "D"
        else -> "F"
    }
}

fun getGradeWithSwitch(score: Int): String {
	return when ( score / 10 ) {
    	9 -> "A"
        8 -> "B"
        7 -> "C"
        else -> "D"
    }
}

fun startWithA(obj: Any) : Boolean {
    return if ( obj is String ) {
        obj.startsWith("A")
    } else {
        false
    }
}

fun startWithB(obj: Any) : Boolean {
    return when (obj) {
        is String -> obj.startsWith("A")
        else -> false
    }
}

// when에 값이 있다면 값에 따라서 조건을 살펴보고
fun judgeScore(number: Int): Unit {
    when ( number ) {
        1,0,-1 -> println("same")
        else -> println("not same")
    }
}

// when에 값이 없다면 들어온 값에 따라서 조건을 수행한다.
fun judgeNumber2(number: Int) {
    when {
        number == 0 -> println("주어진 숫자는 0 입니다.")
        number % 2 == 0 -> println("주어진 숫자는 짝수 입니다.")
        else -> println("주어진 숫자는 홀수 입니다.")
    }
}

 

조건문 정리 

  • if / if - else / if - else if - else 모두 자바 문법이 동일하다.
  • 단 코틀린에서는 Expression으로 취급된다.
    • 때문에 코틀린에서는 삼항 연산자가 없다.
  • 자바의 switch는 코틀린에서 when으로 대체되었고, when은 더 유연하며 더 강력한 기능을 갖습니다.

 

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

코틀린에서의 반복문은 java와 굉장히 유사하다.

 

for-each 문 & 전통적인 for 문 & Progression 과 Range

// for each 문
fun getListOf() {
    val list = listOf(1, 2, 3, 4, 5) // 컬럭션 만드는 방법
    for (i in list) {
        println(i)
    }
}

// 전통적인 for 문
// for 1~3 까지 출력
// for 3~1 까지 출력
// for 2칸씩 올라가는 경우
fun getForLoop() {
    for (i in 1..3) {
        println(i)
    }

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

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

 

 

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

try catch finally 구문

자바 구문과 동일하다. 단 Expression으로 사용할 수 있다.

// try catch finally
fun getParseIntOrThrow(str: String) : Int {
    return try {
        str.toInt()
    } catch (e: NumberFormatException) {
        throw IllegalArgumentException("주어진 ${str} 숫자가 아닙니다.")
    } finally {
    	println("M")
    }
}

// 실패하면 null 반환
fun parseIntOrThrowV2(str: String) : Int? {
    return try {
        str.toInt()
    } catch (e: NumberFormatException) {
        null
    }
}

 

 

Chekced Exception & Unchecked Exception 

코틀린에서는 checked exception이 없다. 모두 unchecked exception 이다.

아래 코드를 보면 자바에서는 파일을 읽는 인스턴스를 사용하게 되면 checked exception을 명시해야만 한다. 하지만 코틀린은 모두 unchecked exception으로 생략이 가능하다.

-- java
public void readFile(String path) throws IOException {
   File currentFile = new File(".");
   FIle file = new File(currentFile.getAbsolutePath() + "/a.txt");
   BufferedReader reader = new BufferedReader(new FileReader(file));
   System.out.println(reader.readLine());
   reader.close();
}

-- kotlin
fun readFile(path: String) : Unit {
    val currentFile = File(".")
    val file = File(currentFile.absolutePath + "a.txt")
    val reader = BufferedReader(FileReader(file))
    println(reader.readLine())
    reader.close()
}

 

예외 정리 

  • try catch finally 구문은 문법적으로 완전히 동일하다.
    • 코틀린에서 try catch가 expression 이다.
  • 코틀린에서 모든 예외는 unchecked exception이다.
  • 코틀린에서는 try with resources 구문이 없다. 대신 코틀린의 언어적 특징을 활용해 close를 호출해준다.

 

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

함수 선언 문법

두 정수를 받아 더 큰 정수를 반환하는 예제 

// 접근 지시어 public 는 default 로 생략이 가능하다.
// fun 함수를 의미하는 키워드
// max 함수 이름
// a: Int, b: Int ( 매개변수 명 : 타입 )
// : Int ( 리턴 타입 )
public fun max(a: Int, b:Int) : Int {
    return if ( a > b ) {
        a
    } else {
        b
    }
}

// 함수가 하나의 결과값이면 block 대신 = 사용 가능하다.
public fun maxV2(a: Int, b:Int) : Int =
    if ( a > b ) {
        a
    } else {
        b
    }

// 함수가 하나의 결과값이면 block 대신 = 사용 가능하며 한줄로 변경 가능
// = 을 사용하는 경우는 반환 타입 생략이 가능하다.
fun maxV3(a: Int, b: Int) = if ( a > b ) a else b

 

  • block {} 을 사용하는 경우에는 반환 타입이 Unit이 아니면 명시적으로 반환 타입을 작성해 주어야 합니다.
  • 함수는 클래스 안에 있을 수도, 파일 최상단에 있을 수도 있습니다.
  • 또한, 한 파일 안에 여러 함수들이 있을 수도 있습니다.

 

default parameter

public void repeat(String str, int num, boolean useNewLine) {
    for ( int i=0; i<=num; i++ ) {
        if (useNewLine) {
            System.out.println(str);
        }
        else {
            System.out.println(str);
        }
    }
}

// 많은 코드에서 useNewLine 를 사용한다면 ? 
// 메소드 오버로딩을 사용하여 해결 
public void repeat(String str, int num) {
    repeat(str, num, true);
}

// 많은 코드에서 출력을 3회씩 사용한다면?
// 다시 한번 오버로딩을 사용 
public void repeat (String str) {
    repeat(str, 3, true);
}

 

위 코드는 자바로 작성된 코드입니다. 각각의 사용에 맞게 메소드 오버로딩을 이용하여 함수를 호출하고 있습니다. 각각의 상황에 맞게 하기 위해 메소드가 3개가 작성되었습니다. 코틀린에서는 이와 같은 경우 간단히 제어할 수 있는 문법이 제공됩니다.

fun main() {
    repeat("test")
}

// 위 메인 함수에서 repeat 인자 값으로 주어지지 않으면 기본 값을 사용하게된다.(default parameter)
fun repeat(str: String, num: Int = 3, useNewLine: Boolean = true) : Unit {
    for ( i in 1..num) {
        if ( useNewLine) {
            println(str)
        }
        else {
            println(str)
        }
    }
}

 

물론 코틀린에서 Java와 동이랗게 오버로딩 기능이 있습니다.

매개변수 이름을 통해 직접 지정, 지정되지 않은 매개 변수는 기본 값을 사용

 

named argument ( parameter )

builder를 직접 만들지 않고 builder의 장점을 가지게 된다. 이로인해 자바의 빌더로 인자값을 넣는 것과 동일한 효과를 볼 수 있다.

코틀린에서 java 함수를 가져다 사용할 때는 named argument를 사용할 수 없다. 

repeat("hello world", useNewLine = false)

 

 

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

문자열 N개를 받아 출력하는 예제

// java
// List<String> data = List.of("a1", "b1", "c1");
// printAll( "a", "b", "c");
// printAll(String.valueOf(data));
public static void printAll(String... strings) {
    for (String string : strings) {
        System.out.println("string = " + string);
    }
}

// kotlin
// printAll("hello", "world")
// val array = arrayOf("hello", "world");
// printAll(*array) 배열을 바로 넣는 대신 연산자 (*)를 붙여주어야 한다.
// 자바에서의 ... 대신 vararg 사용
fun printAll(vararg strings: String) {
    for (string in strings) {
        println(string)
    }
}

 

 

함수 다루는 방법 정리

  • 함수의 문법은 자바와 다르다
    • 접근 지시어 fun 함수이름(파라미터): 반환타입 {}
  • body가 하나의 값으로 간주되는 경우 block를 없앨 수도 있고, block가 없다면 반환 타입을 없앨 수도 있다.
  • 함수 인자에 기본값을 설정할 수 있다.
  • 가변 인자에는 vararg 키워드를 사용하며, 가변인자 함수를 벼열과 호출할 때는 *를 붙여주어야 한다.

 

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

아래는 자바 코이이며, 이를 코틀린과 비교하여 작성하겠습니다.

public class JavaPerson {

  private final String name;
  private int age;

  public JavaPerson(String name, int age) {
    if (age <= 0) {
      throw new IllegalArgumentException(String.format("나이는 %s일 수 없습니다", age));
    }
    this.name = name;
    this.age = age;
  }

  public JavaPerson(String name) {
    this(name, 1);
  }

  public String getName() {
    return name;
  }

  public int getAge() {
    return age;
  }

  public void setAge(int age) {
    this.age = age;
  }

  public boolean isAdult() {
    return this.age >= 20;
  }

}

 

클래스와 프러퍼티

package com.lannstrak.lec09

fun main() {
    val person = Person("홍길동", 20)
    // .필드를 통해 gettter 와 setter 을 바로 호출할 수 있다.
    println(person.name);
    println(person.age);
    person.age = 30; //
}

// 코틀린에서 주 생성자는 클래스 선언과 동시에 작성
// getter, setter 를 자동으로 생성
public class Person constructor(
    val name: String,
    var age: Int
) {
}

 

코틀린에서는 getter, setter이 자동으로 생성되기 때문에 자바처럼 get,set 메소드를 별도로 만들거나 lombok를 사용하여 어너테이션을 작성할 필요가 없습니다.  .필드 를 통해 바로 접근 가능합니다. 

 

생성자와 init

코틀린에서의 주 생성자는 클래스 선언과 동시에 사용하는게 일반적입니다.  

위 자바 코드에서 인스턴스 생성과 동시에 나이를 검증하는 로직이 있는데요. 코틀린에서는 인스턴스 생성과 동시에 작업을 하기 위해서는 int 블럭을 사용하여 검증할 수 있습니다. 

public class Person constructor(
    val name: String,
    var age: Int
) {
    
    init {
        if ( age <= 0 ) {
            throw IllegalArgumentException("나이는 ${age}보다 커야 합니다.")
        }
    }
}

 

추가적으로 생성자를 만들고 싶다면 아래와 같이 하면 됩니다.

// 코틀린에서 주 생성자는 클래스 선언과 동시에 작성
// getter, setter 를 자동으로 생성
public class Person constructor(
    val name: String,
    var age: Int
) {

    init {
        if ( age <= 0 ) {
            throw IllegalArgumentException("나이는 ${age}보다 커야 합니다.")
        }
    }
    
    constructor(name: String) : this(name, 0) {
        println("보조 생성자 호출")
    }
    
    constructor() : this("이름 없음") {
        println("보조 생성자 호출")
    }

}

 

하지만 부 생성자의 경우 거의 사용할 일이 없으며 이럴 경우 default parameter or 정적팩토리를 만들어 사용하는걸 추천드립니다.

// 코틀린에서 주 생성자는 클래스 선언과 동시에 작성
// getter, setter 를 자동으로 생성
public class Person constructor(
    val name: String = "",
    var age: Int = 0
) {

    init {
        if ( age <= 0 ) {
            throw IllegalArgumentException("나이는 ${age}보다 커야 합니다.")
        }
    }
}

 

// 코틀린에서 주 생성자는 클래스 선언과 동시에 작성
// getter, setter 를 자동으로 생성
public class Person constructor(
    val name: String,
    var age: Int
) {
    // 클래스가 생성되는 시점에 호출 한다.
    // 이는 자바 생성자에서 로직을 처리하는 것과 동일하다.
    init {
        if ( age <= 0) {
            throw IllegalArgumentException("나이는 ${age}로 설정할 수 없습니다.")
        }
    }

    companion object {
        // 정적 팩토리 메서드 - 이름과 나이를 사용하는 방식
        fun fromNameAndAge(name: String, age: Int): Person {
            return Person(name, age)
        }

        // 정적 팩토리 메서드 - 이름만 사용하는 방식 (나이는 기본값 1로 설정)
        fun withNameOnly(name: String): Person {
            return Person(name, 1)
        }

        // 정적 팩토리 메서드 - 기본값 사용 (이름 없음, 나이 1)
        fun defaultPerson(): Person {
            return Person("이름 없음", 1)
        }
    }
}

 

커스텀 getter, setter

예를 들어 성인 여부를 확인하는 함수를 추가 하겠습니다.

public class Person constructor(
    val name: String = "",
    var age: Int = 0
) {

    // 함수로 getter 를 대체할 수 있다.
    fun isAdult(): Boolean {
        return this.age >= 20
    }
}

 

만약 하나의 결과값을 도출하는 것 이면 아래와 같이 custom gettter 형식으로 만들 수 있습니다.

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

 

위 두개의 방법중 어떤 방법을 사용하는 것이 좋을까요? 

객체의 속성이라면 ( 객체가 포함하는 멤버변수 ) custom getter를 그렇지 않다면 함수 방식을 선호합니다. 

 

또 하나의 예로 name 을 가져올 때 무조건 대문자로만 가져오고 싶다면? 이는 객체의 속성이므로 custom getter 방식을 사용합니다.

val nameUpperCase: String 
    get() = name.uppercase()
    
---

val name: String = this.name
    get() = field.uppercase() // field or this 사용

 

name에 대한 custom getter를 만들 때 field를 사용합니다. 왜 field를 사용할까요?

name은 name에 대한 getter를 호출 하니깐 다시 get을 부릅니다. getter 안에는 다시 name이 있다. -> 무한 루프 발생

무한루프를 발생을 막기위해 field 라는 자신을 가르키는 예약어를 사용합니다. 이를 backing field라고 합니다.

하지만 custom getter 에서 backing field를 쓰는 경우는 거의 없습니다.

 

아래 코드는 name를 set할 때 무조건 대문자로 바꾸는 코드입니다.

var name: String = name
        set(value) {
            field = value.uppercase()
        }

 

사실은 setter 자체를 지양하기 때문에 custom setter도 잘 안쓴다!

 

클래스 정리

  • 코틀린에서는 필드를 만들면 getter와 (필요에 따라) setter가 자동으로 생긴다.
  • 때문에 이를 프로퍼티라고 부른다.
  • 코틀린에서는 주생성자가 필수다.
  • 코틀린에서는 consturcotr 키워드를 사용해 부생성자를 추가로 만들 수 있다.
    • 단 default parameter나 정적 팩토리 메소드를 추천한다.
  • 실제 메모리에 존재하는 것과 무관하게 custom getter와 custom setter를 만들 수 있다.
  • custom getter, setter 에서 무한루프를 막기 위해 field라는 키워드를 사용한다.
    • 이를 backing field라고 부른다.

 

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

 

public abstract class JavaAnimal {

  protected final String species;
  protected final int legCount;

  public JavaAnimal(String species, int legCount) {
    this.species = species;
    this.legCount = legCount;
  }

  abstract public void move();

  public String getSpecies() {
    return species;
  }

  public int getLegCount() {
    return legCount;
  }

}

추상 클래스

 

인터페이스

 

 

클래스를 상속할 때 주의할 점

 

 

상속 관련 지시어 정리