인프런/자바 개발자를 위한 코틀린 입

2) 코틀린에서 Type, 연산자를 다루는 방법

backend dev 2025. 1. 6.

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

 

1.1 기본타입

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

 

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

 

 

int number1 = 4;
long number2 = number1;

System.out.println(number1 + number2);

자바의 경우 number1이 long 타입으로 암시적 변환이 이루어진다.

암시적이라는것은 (long)이라던지 .toLong()이라던지와 같은 명시적 설정 없이 알아서 변환 된다는것 

 

 

 

하지만 코틀린에서는

val number1 = 4
val number2: Long = number1 

TypeMismatch 컴파일 에러가 발생한다. 

 

코틀린에서는 메모리가 더 큰 타입이라도 암시적 변환이 되지않는다.

 

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

.toLong()

.to변환타입()를 사용해서 

명시적으로 변환해줘야한다.

 

예시2 [ nuallble 타입 변환처리 ] 

var number1: Int? = 4
val number2: Long = number1?.toLong() ?: 0L

 

 

 

1.2 타입 캐스팅

예시)

 

자바코드

public class Person {

  private final String name;
  private final int age;

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

  public String getName() {
    return name;
  }

  public int getAge() {
    return age;
  }

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

Obejct 객체를 받아서 Person 타입이라면 형변환해서 age를 출력하는 코드이다.

[ instanceof : 변수가 주어진 타입이면 true, 그렇지 않으면 false ]

 

형변환해주는 저 메소드를 코틀린으로 작성해보면 다음과 같다.

fun printAgeIfPerson(obj: Any) {
    if (obj is Person) {
        val person = obj as Person
        println(person.age)
    }
}

 

 

코틀린에서 is 는 Java의 instanceof의 기능을 한다.  [ 맞으면 true, 다르면 false ] 

 

as Person은 Java의 (Person)과 같은 명시적 형변환 기능을 한다. 

 

그리고 as 문법은 생략이 가능하다.

 

즉 다음과 같이 작성 가능하다.

fun printAgeIfPerson(obj: Any) {
    if (obj is Person) {
        println(obj.age)
    }
}

 

이렇게 코틀린이 컨텍스트를 분석해서 자동 형변환 해주는것을 스마트 캐스트라고 한다.

[ Any에 대해서는 밑에서 설명한다. ]

 

instanceof의 반대를 진행하려면

 

자바의 경우

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

!를 붙여서 진행하고

 

코틀린 또한 !를 붙여서 가능하지만

fun printAgeIfPerson(obj: Any) {
    if (!(obj is Person)) {
        println(obj.age)
    }
}

 

is 앞에 !를 붙여서 not is를 만들 수 있다.

fun printAgeIfPerson(obj: Any) {
    if (obj !is Person) {
        println(obj.age)
    }
}

 

 

위의 예시에서 obj에 null이 들어올 수 있다면?

fun main() {
    printAgeIfPerson(null)
}

fun printAgeIfPerson(obj: Any?) {
    val person = obj as Person
    println(person.age)
}

이 상태에서 코드 실행시 NPE가 발생한다.

fun main() {
    printAgeIfPerson(null)
}

fun printAgeIfPerson(obj: Any?) {
    val person = obj as? Person // person 변수의 타입은 Person?
    println(person?.age)
}

 

as 뒤에 ? 를 붙여주면 Safe Call 동작 방식과 비슷하게

obj가 null이 아닐때는 Person으로 타입캐스팅해주고, null일 경우는 null을 반환한다.

 

저렇게 코드를 작성하면 person 변수는 null이 들어갈 수 도있고 아닐수도 있으므로 타입은 Person?가 되고 

person의 프로피터를 가져오거나, 함수를 실행하려면 Safe Call을 해야하므로 person?.age로 수정해줘야한다.

is, !is

as

 

 

as?

 

 

1.3. Kotlin의 3가지 특이한 타입

 

1.3.1. Any

- Java의 Object 역할 (모든 객체의 최상위 타입)

 

- 모든 Primitive Type의 최상위 타입도 Any이다.

-> 자바에서 Primitive Type의 최상위 타입은 Object가 아닌데 코틀린에서는 모든 Primitive Type의 최상위 타입도 Any이다.

-> 코틀린에서는 Primitive 타입 구분없이 Int, Long ,Double 을 사용하기 때문이다.

 

- Any 자체로는 null을 포함할 수 없어 null을 포함하고 싶다면, Any?로 표현한다.

 

- Any 에 equals / hashCode / toString 존재한다.

-> 자바의 Obejct와 비슷 

 

 

1.3.2. Unit

- Unit은 Java의 void와 동일한 역할이다.

-> 반환할것이 없다면 void를 쓰듯이 코틀린에서는 Unit을 사용하면된다. 하지만 타입추론이 가능해서 생략도 가능하다.

 

- (살짝 어려운 내용) void와 다르게 Unit은 그 자체로 타입 인자로 사용 가능하다.

-> 자바에서는 Void로 사용해야하는데 Unit은 그냥 사용하면된다.

 

- 함수형 프로그래밍에서 Unit은 단 하나의 인스턴스만 갖는 타입을 의미. 즉, 코틀린의 Unit은 실제 존재하는 타입이라는 것을 표현

 

차이점 정리

 

  • Java의 void:
    • 반환값이 없음을 나타내는 키워드입니다.
    • 실제로 타입(type)은 아닙니다.
    • 메서드에서 값을 반환하지 않는 경우 사용됩니다.
  • Kotlin의 Unit:
    • 반환값이 없음을 나타내는 타입(type)입니다.
    • Singleton 객체로 구현되어 있으며, 기본적으로 반환값으로 Unit 객체를 반환합니다.
    • 타입 추론이 가능하므로 생략할 수 있습니다.

 

 

 

  • Unit은 Kotlin에서 반환값이 없는 함수의 반환 타입을 명시할 때 사용됩니다.
  • 반환 타입을 생략하면 컴파일러가 자동으로 Unit을 추론합니다.
  • Unit은 Singleton 객체로, 내부적으로 단일 인스턴스를 반환합니다.

 

 

fun example1(): Unit {  // 명시적으로 Unit을 반환
    println("This function returns Unit")
}

fun example2() {  // Unit은 생략 가능
    println("This function also returns Unit")
}

 

함수형 프로그래밍에서 Unit의 의미

  1. Unit은 단일 값만 가질 수 있는 타입:
    • 함수형 프로그래밍에서 Unit은 값이 하나뿐인 타입을 뜻합니다.
    • 이 값은 "아무 의미 없는 값" 또는 "작동이 성공적으로 완료되었다"는 것을 나타냅니다.
    • Kotlin의 Unit은 이러한 개념을 따르며, 딱 하나의 인스턴스만 존재합니다.
  2. Unit은 void와 달리 값으로 존재:
    • Java의 void는 반환값이 없는 것으로 간주되며, 실제 값은 없습니다.
    • 반면, Kotlin의 Unit은 값으로 존재하는 객체입니다.
    • Kotlin의 Unit은 object 키워드로 정의된 Singleton 객체로, 단 하나의 인스턴스만 가질 수 있습니다.

 

 

fun printMessage(message: String): Unit {
    println(message)
}

val result: Unit = printMessage("Hello")
// result는 `Unit` 객체를 참조합니다.


println(result) // 출력: kotlin.Unit

여기서 result는 Kotlin의 Unit 타입 객체를 가지며, 이 값은 단 하나의 인스턴스(Unit)입니다.

왜 Unit이 중요한가?

함수형 프로그래밍에서는 모든 함수가 반환값을 가진다는 개념이 중요합니다.

  • 반환값이 없는 함수(void를 반환하는 함수)조차 "어떤 값"을 반환해야 하는데, 이 역할을 수행하는 것이 Unit입니다.
  • Unit을 반환하는 함수는 "나는 값을 반환하지 않지만, 호출이 성공적으로 완료되었음을 의미한다"는 의사를 전달합니다.

Unit은 "진짜 타입"이다

 

Unit이 "실제 타입"이라는 것은:

  1. 타입 시스템 내에서 사용 가능:
    • 다른 타입처럼 변수에 할당하거나 반환값으로 사용할 수 있음.
    • Java의 void와 달리 타입으로서의 역할을 명확히 합니다.
  2. 함수형 프로그래밍 관점:
    • 모든 함수는 반드시 반환값을 가지므로, 반환값이 없는 경우 Unit을 반환하도록 설계.
    • 이를 통해 일관된 타입 체계를 유지.
  3. 단 하나의 값만 가짐:
    • Unit은 항상 같은 객체를 반환하므로 추가 메모리 할당 없이 재사용됩니다.

결론

  • Unit은 단 하나의 인스턴스만 가지는 타입으로, 함수형 프로그래밍에서 "반환값이 없음을 나타내는 값"의 역할을 합니다.
  • Kotlin에서는 이를 Singleton 객체로 구현해 실제로 존재하는 타입으로 표현했으며, Java의 void와는 달리 타입 시스템에 포함될 수 있는 객체입니다.

 

 

1.3.2. Nothing

 

- Nothing은 함수가 정상적으로 끝나지 않았다는 사실을 표현하는 역할이다.

 

- 무조건 예외를 반환하는 함수 / 무한 루프 함수 등

 

 

실행되면 무조건 예외가 반환되는 로직이 들어있는 메소드의 반환타입에 적어주면 된다.

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

많이 사용되지않는 타입이다.

 

 

 

1.4. String Interpolation, String indexing

 

1.4.1 코틀린 문자열 가공

자바의 경우 위와 같이 문자열을 가공하여 사용했다.

 

 

하지만 코틀린에서는 위와 같이 문자열을 가공할 수 있다. 

 

사용예시)

var person = Person("g", 10)

var str = "이름 : ${person.name} 이름2 : $person.name"

println(str)

 

 

$person.name의 잘못된 구문

  • 문자열 템플릿에서 $ 기호를 사용하여 변수를 참조할 때, 복합적인 속성이나 메서드에 접근하려면 **중괄호 {}**를 사용해야 합니다.
  • 코드에서 $person.name처럼 작성하면 $person 변수를 참조하려고 시도합니다. 하지만 person은 객체이고, 문자열로 변환될 때 기본적으로 객체의 toString() 메서드가 호출됩니다.
  • toString() 메서드가 재정의되지 않은 경우, 객체의 기본 toString() 출력 형식은 클래스명@해시코드 형태입니다.

출력 결과의 구성

  • 이름 : ${person.name}: 올바르게 작성된 부분으로, person.name의 값 g가 출력됩니다.
  • 이름2 : $person.name: $person은 객체를 문자열로 변환한 결과(com.lannstark.lec03.Person@36d64342)가 출력되고, .name은 그대로 문자열로 취급됩니다

 

 

사용예시2)

fun main() {
    var num = 10

    var str = "숫자 : $num"

    println(str)

 

 

1.4.2 여러줄의 문자열 생성

val name = "ABC"

val str =
""" 
    hi
    my $name
""".trimIndent()

println(str)

 

코틀린에서는 """ """ 를 이용하여 여러줄의 문자열을 생성할 수 있다.

 

.trimIndent()로 인해 들여쓰기가 된 부분이나, 공백줄을 없애서 문자열을 깔끔하게 정리 할 수 있다.

 

1.4.3 문자열의 특정 문자 가져오기

 

 

 

val str = "ABC"
println(str[0])
println(str[1])
println(str[2])

 

 


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

 

2.1 단항 연산자 / 산술 연산자

 

 

 

 

 

 

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

 

2.2.1 비교 연산자

자바와 코틀린 사용법은 동일하다

 

하지만

 

Java와 다르게 객체를 비교할때 비교 연산자를 사용하면 자동으로 compareTo를 호출해준다.

 

public class JavaMoney implements Comparable<JavaMoney> {

  private final long amount;

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

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

이런 자바클래스가 존재한다고 했을때

 

자바에서 두 JavaMoney 객체의 amount 비교할때는 구현한 compareTo 메서드를 이용하여

 

public static void main(String[] args) {
    JavaMoney money1 = new JavaMoney(1_000L);
    JavaMoney money2 = new JavaMoney(2_000L);

    if (money1.compareTo(money2) > 0) {
      System.out.println(" Money1 돈이 더크다.");
    }
  }

이런식으로 구현했다면

 

코틀린에서는

val money1 = JavaMoney(2_000L)
val money2 = JavaMoney(1_000L)

if (money1 > money2) {
    println(" Money1 돈이 더크다.")
}

다음과 같이 구현 가능하다. [ > 과 같은 부등호에 자동으로 compareTo를 적용해준다. ] 

 

 

2.2.2 동등성과 동일성

 

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

 

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

 

 

3개의 객체가 존재한다고 해보자

 

주소가 동일한 객체는 같은 객체로 판단된다. [ 동일성 ] 

==를 이용해서 주소값 비교 [ 동일성 체크] 

 

 

 

 

주소값이 다르지만 내부 값이 같다. [ 동등성 ]

equals 메소드를 이용하여 동등성 체크를 한다.

 

자바에서는 위와 같이 동일성,동등성 체크를 했지만

코틀린은 다르게 체크한다.

 

자바 예시)

public class JavaMoney {

  private final long amount;

  public JavaMoney(long amount) {
    this.amount = 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;
  }
}
JavaMoney money1 = new JavaMoney(1_000L);
JavaMoney money2 = money1;
JavaMoney money3 = new JavaMoney(1_000L);

System.out.println(money1 == money3);
System.out.println(money1.equals(money3)); // Object의 기본 equals 구현은 주소값 비교이다.

equals를 구현해서 어떤식으로 동등성을 확인할지 구현해줘야한다.

 

[String 같은경우는 기본적으로 equals가 구현되어있다. ]

 

 

 

코틀린은 === 을 이용하여 주소값 비교 [ 동일성 ]

==를 이용하여 값을 비교한다. [ 동등성 ] 

 

val money1 = JavaMoney(1_000L)
val money2 = money1
val money3 = JavaMoney(1_000L)

if (money1 === money2) {
    println("동일성 체크 성공")
}
if (money1 == money3) {
    println("동등성 체크 성공")
}

== 를 눌러보면 구현되어있는 equals 함수로 이동된다. 즉 ==가 알아서 해당 클래스에 구현되어있는 equals를 호출해준다.

 

 

 

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

 

2.3.1 논리 연산자

 

 

Lazy 연산 예시)

fun main() {
    if (fun1() || fun2()) {
        println("good")
    }
}

fun fun1(): Boolean {
    println("fun 1")
    return true
}

fun fun2(): Boolean {
    println("fun 2")
    return true
}

 

if (fun1() || fun2())

fun1()이 true이므로 뒤에 fun2() 함수를 실행하지않고 

바로 

println("good")

를 실행하는것을 말한다. 

자바에서도 똑같이 동작한다. 

 

2.3.2 코틀린에 있는 특이한 연산자

 

in / !in

// 예시
if(1 in numbers){

}

 

컬렉션이나 범위에 포함되어 있다, 포함되어 있지 않다

 

a..b

//예시
val range = 1..5  // 1부터 5까지의 범위를 생성
println(range.toList()) // [1, 2, 3, 4, 5]

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

 

a[i]

//예시
val str = "ABC"
println(str[2]) // C

a에서 특정 Index i로 값을 가져온다

 

 

 

2.4 연산자 오버로딩

 

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

 

말 그대로 연산자 +,-같은 연산자의 동작을 직접 정의할 수 있다.

 

예시코드)

data class Vector(val x: Int, val y: Int) {
    operator fun plus(other: Vector): Vector {
        return Vector(this.x + other.x, this.y + other.y)
    }
}

fun main() {
    val v1 = Vector(1, 2)
    val v2 = Vector(3, 4)
    val result = v1 + v2 // plus 연산자 호출
    println(result) // 출력: Vector(x=4, y=6)
}

 

 

data 키워드

  • data class는 Kotlin에서 데이터를 저장하기 위한 클래스
  • 자동으로 equals, hashCode, toString 등의 메서드를 생성해 주기 때문에 데이터를 다루기 편리합니다.

 

operator 키워드

  • Kotlin에서는 연산자(+, -, *, /, 등)를 특정 함수에 연결할 수 있습니다.
  • operator 키워드는 이 함수가 연산자로 동작하게끔 만듭니다.

연산자 오버로딩은 Kotlin의 공식 문서에서도 다뤄지고 있으며, 다양한 연산자(-, *, /, %, == 등)를 커스터마이징할 수 있습니다.

주의: 남용하면 코드가 읽기 어려워질 수 있으니, 적절히 사용하는 것이 중요

 

 

 

 

댓글