Home Kotlin 기초 1
Post
Cancel

Kotlin 기초 1

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


  1. 변수 타입 종류
    • var : 가변 변수
    • val : 불변 변수 (= final)
  2. 변수 특성
    • var, val 모두 최초 한번은 초기값 할당이 가능하다.
    • val의 경우, element 추가는 가능하다.(final List 객체에서 add()가 가능한 것처럼!)
  3. ? 키워드
    • null 값 가능 여부를 지정
    • 코틀린에서 변수는 기본적으로 null 값이 불가능 하도록 구현
  4. 구현 클래스 객체 생성시
    • new 예약어를 사용하지 않고 선언

위 개념을 토대로 자바 코드를 변경한 코틀린 코드는 아래와 같다.

자바 코드

1
2
3
4
5
6
7
8
9
10
11
public class Lec01 {

  public static void main(String[] args) {
    long number1 = 10L;
    final long number2 = 10L;

    Long number3 = 1_000L;
    Person person = new Person("김선호");
  }

}

코틀린 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Lec01 {
    fun main() {
        // 기본 변수 선언 방법
        var number1 = 5L // var : 가변 변수
        val number2 = 5L // val : 불변 변수

        // 초기값 미지정 시
        var number3 : Long // 변수 지정을 안하면 에러가 발생
        number3 = 5
      
        val number3 : Long
        number3 = 5 // val의 경우도 최초 한번은 초기값 할당 가능
      
        val number3 : Long = 5 // 위보단 해당 코드로 적는 것이 적절
        
        // ? 예약어 사용 시
        var numberNull : Long? = null // ?가 타입 뒤에 있어야만 null 선언 가능

        // 구현 클래스 객체 생성시
        var person = Person("김선호")
        var person2 : Person = Person("김선호")
    }
}

Tip
최대한 변수는 val 타입으로 선언하고 필요에 따라 var 타입으로 선언해 사용하자.(디버깅시 쉬운 구조를 생성하기 위해서)

코틀린에서는 Primitive / Reference Type에 대한 구분이 없고 내부적으로 적절히 사용하도록 코틀린이 처리해준다.

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


코틀린에선 null을 별도의 타입으로 간주하고 처리한다. 그래서 null값을 다루는 여러 방법에 대해 이해가 필요하다.

아래 코드는 함수의 매개변수로 null이 들어온 경우 다른 처리 방법을 표현하고 있다.

자바 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Lec02 {

  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; // null 그대로 리턴
    }
    return str.startsWith("A");
  }


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

}

위 세가지 경우를 코틀린 코드로 변경하기 위해선 아래 개념에 대한 이해가 필요하다.

  • Safe Call(?.) : 특정 객체가 null인 경우, 함수를 호출해도 null로 리턴
  • Elvis 연산자(?:) : 특정 함수의 리턴값이 null일 경우 대체 리턴값을 지정

코틀린 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
fun startsWithA1(str : String?) : Boolean {
    return str?.startsWith("A")
        ?: throw IllegalArgumentException("null이 들어왔습니다")
}

fun startsWithA2(str : String?) : Boolean? { // null값으로 리턴이 가능해야 하기 때문에 Boolean에 ? 예약어 사용
    return str?.startsWith("A")
}

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

추가적으로 null 처리와 관련된 개념은 아래와 같다.

  • !! 단언(!!) : nullable한 객체임에도 불구하고 null이 없다고 단언하는 개념
    1
    2
    3
    
    fun startsWithA4(str : String?) : Boolean {
        return str!!.startsWith("A") // nullable한 변수이지만 절대 null이 들어올 수 없는 경우 사용
    }
    

    하지만 매개변수에서 null 이 들어올 수 있기 때문에 null 상태에서 !! 를 만나면 NPE를 터뜨리므로 확실한 경우 아니면 사용하지 않는 것을 추천

  • 플랫폼 타입 : 코틀린이 null 관련 정보를 알 수 없는 타입을 의미

    자바로 구현된 타입을 코틀린에서 호출하는 경우, @Nullable이나 @NotNull과 같은 어노테이션이 적용되어 있지 않은 경우는 코틀린에서 null 가능 여부를 판단할 수가 없다. 하지만 코틀린은 이러한 플랫폼 타입을 컴파일 에러가 아닌 런타임 에러로 잡아주기 때문에 플랫폼 타입에 대한 고려를 하지 않으면 NPE를 발생시키는 지점이 될 위험이 있다.

3. 코틀린에서 타입을 다루는 방법

기본 타입

  • 자바와 다르게 코틀린에서는 기본값을 통해 타입을 추론한다.
  • 자바는 기본 타입 변환이 암묵적으로 이뤄지지만 코틀린은 명시적으로 타입 변환이 이뤄진다.

자바 코드

1
2
3
4
5
6
class Lec03 {
    public static void primitiveType() {
        int n1 = 3;
        long n2 = n1; // long 타입으로 자동 타입 변환
    }
}

코틀린 코드

1
2
3
4
5
6
7
8
9
fun primitiveType() {
  var n1 = 3
  var n2 : Long = n1.toLong() // to타입() 함수를 사용해서 명시적으로 형변환을 진행해줘야 한다.
  
  // [추가] null 이 가능한 경우
  var n3 : Int? = null
  var n4 : Long = n3?.toLong() ?: 0L
  
}

타입 캐스팅

코틀린에서 타입 캐스팅에 사용되는 예약어는 다음과 같다.

  • is
    • 자바의 instanceof와 동일
    • 비교하고자 하는 타입이 맞다면 true, 아니면 false 리턴
    • !를 사용해 반대 결과값 설정도 가능
  • as
    • 자바의 ({타입})과 동일
    • ?를 사용해 nullable 설정 가능
    • 자바 코드

      1
      2
      3
      4
      5
      6
      7
      8
      
      class Lec03 {
        public static void printAgeIfPerson(Object obj) {
        if (obj instanceof Person) {
          Person person = (Person) obj;
          System.out.println(person.getAge());
        }
        }
      }
      

      코틀린 코드

      1
      2
      3
      4
      5
      6
      
      fun printAgeIfPerson(obj : Any?) {
        if (obj is Person) {
        val person = obj as Person // 생략 가능
        println(person.age)
        }
      }
      

      생략 가능한 이유 : 스마트 캐스팅 개념, 조건문 안에 캐스팅이 이뤄지면 코틀린 컴파일러에서 미리 타입에 대한 인지하기 때문

코틀린의 특이한 타입 3가지

  • Any
    • 자바의 Object와 동일한 최상위 타입
    • 모든 Primitive Type의 최상위 타입도 Any
    • Any? 로 null 표현 가능
    • equals, hashcode, toString 사용 가능
  • Unit
    • 자바의 void와 동일한 역할
    • 제네릭에서 그대로 사용 가능
    • 함수형 프로그래밍에서 Unit은 단 하나의 인스턴스만 갖는 타입을 의미. 특 코틀린에서 Unit은 실제 존재하는 타입
  • Nothing
    • 함수가 정상적으로 끝나지 않았다는 사실을 표현하는 역할
    • 무조건 예외를 던지거나 무한루프의 경우
    • 실전에서 거의 사용되지 않음

String interpolation / String indexing

String 관련 함수 및 처리 방식의 차이점을 설명하는 내용이며 코드 상 차이를 통해 알아보자.

자바 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Lec03 {
  public static void printLog() {
    Person person = new Person("홍길동", 40);

    // (1)
    String log = String.format("제 이름은 %s이고 %s세 입니다.", person.getName(), person.getAge());

    // (2)
    StringBuilder sb = new StringBuilder();
    String log2 = sb.append("이름 : ")
      .append(person.getName())
      .append("\n")
      .append("나이 : ")
      .append(person.getAge())
      .toString();

    // (3)
    String log3 = "ABC";
    System.out.println(log3.charAt(0));
    System.out.println(log3.charAt(1));
    System.out.println(log3.charAt(2));
  }

}

코틀린 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fun printLog() {
  val person = Person("홍길동", 40)

  // (1) ${} 사용 가능
  val log =  "제 이름은 ${person.name}이고 ${person.age}세 입니다."

  // (2) """"""를 사용해 줄바꿈 가능한 문자열 생성
  var log2 = """
        이름 : ${person.name}
        나이 : ${person.age}
    """.trimIndent() // 인덴트만 삭제. 엔터는 유지

  // (3) []를 사용해 바로 인덱스값 조회 가능
  var log3 = "ABC"
  println(log3[0])
  println(log3[1])
  println(log3[2])
}

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

  1. 자바에서 객체 타입의 값을 비교할 때 compareTo()를 사용해야 했지만 코틀린은 비교 연산자 사용하면 자동으로 compareTo()를 호출해준다.

  2. 동등성, 동일성 차이
    • 동등성 : 두 객체의 값이 같은가?
      • 자바 : equals()
      • 코틀린 : ==
    • 동일성 : 완전히 동일한 객체인가? 즉, 같은 주소값을 가지고 있는가?
      • 자바 : ==
      • 코틀린 : ===
  3. 코틀린은 자바와 다르게 연산자를 객체의 함수로 선언해 사용할 수 있다.

자바코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public class Lec04Main {

  public static void main(String[] args) {
    JavaMoney money1 = new JavaMoney(1_000L);
    JavaMoney money2 = new JavaMoney(1_000L);
    JavaMoney money3 = money2;
    
    // 1번
    if (money1.compareTo(money2) > 0) System.out.println("money1이 money2보다 큽니다.");

    // 2번
    System.out.println(money1.equals(money2));  // true, 같은 값을 가지고 있음
    System.out.println(money1 == money2);  // false, 같은 주소는 아님
    System.out.println(money2.equals(money3));  // true, 같은 값을 가지고 있음
    System.out.println(money2 == money3);  // true, 같은 주소임
    
    // 3번
    JavaMoney money4 = new JavaMoney(1_000L);
    JavaMoney money5 = new JavaMoney(1_000L);
    System.out.println(money4.plus(money5)); // JavaMoney{amount=2000}
  }
}

// 자바로 구현한 Money 객체
public class JavaMoney implements Comparable<JavaMoney> {

  private final long amount;

  public JavaMoney(long amount) {
    this.amount = amount;
  }

  public JavaMoney plus(JavaMoney other) {
    return new JavaMoney(this.amount + other.amount);
  }

  @Override
  public int compareTo(@NotNull JavaMoney o) {
    return Long.compare(this.amount, o.amount);
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    JavaMoney javaMoney = (JavaMoney) o;
    return amount == javaMoney.amount;
  }

  @Override
  public int hashCode() {
    return Objects.hash(amount);
  }

  @Override
  public String toString() {
    return "JavaMoney{" +
      "amount=" + amount +
      '}';
  }

}

코틀린 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
fun main() {
    val money1 = JavaMoney(1_000L)
    val money2 = JavaMoney(1_000L)
    val money3 = money2

    // 1번
    if (money1 > money2) println("money1이 money2보다 큽니다.") // 더 직관적으로 이해가 가능
  
    // 2번
    println(money1 == money2) // true, 같은 값을 가지고 있음
    println(money1 === money2) // false, 같은 주소는 아님
    println(money2 == money3) // true, 같은 값을 가지고 있음
    println(money2 === money3) // true, 같은 주소임


    // 3번
    val money4 = Money(1_000L)
    val money5 = Money(1_000L)
    println(money4 + money5) // Money{amount=2000} , 연산자로도 사용 가능
}

// 코틀린에서 생성한 Money 객체 
data class Money(
  val amount: Long
) {
  operator fun plus(other: Money) : Money { // 해당 함수를 선언하면 실 사용시 +로 동작이 가능
    return Money(this.amount + other.amount)
  }

  override fun toString(): String {
    return "Money{amount=${amount}}"
  }
}
This post is licensed under CC BY 4.0 by the author.