Spring
Basic
Kotlin 문법
코틀린 클래스

Kotlin Class

Basic

class Contact(val id: Int, var email: String = "홍길동@gmail.com") {
    fun printId() {
        println(id)
    }
}
 
fun main() {
    val contact = Contact(1, "mary@gmail.com")
    // Calls member function printId()
    contact.printId()           
    // 1
}

Data Class

클래스 내의 값을 쉽게 비교하기 위해서 제공되는 클래스, 앞에 data를 붙여서 명시하며, toString(), equals(), hashCode(), copy() 메소드를 자동으로 생성해준다.
class끼리 ==을 사용해서 비교를 하면 값이 같은 경우 true를 반환하는 것으로보아 Javascript의 ===과 같은 의미는 아닌것 같다.

data class User(val name: String, val id: Int)
 
val user = User("Alex", 1)
val secondUser = User("Alex", 1)
println("user == secondUser: ${user == secondUser}") // true

Data Class와 Jpa

data class는 자동으로 hashcode, equals, toString을 만들어준다.
연관관계가 있는 경우는 OneToMany일 경우 Lazy Loading이 default인데 (연관관계가 참조될 때 initialize됨) 이 경우에 equals를 사용하게 되면 에러가 발생할 수 있다.
그래서 Kotlin의 data class는 Jpa와 그다지 어울리지 않는다.

Interface

fun main() {
	val apple = Apple(10)
	apple.eat()
}
 
class Apple (var count: Int): Fruit {
	override fun eat() {
		count--
		println("Eat apple, Remaining: $count")
	}
}
 
interface Fruit {
	fun eat()
}

Abstract Class

  1. 상속은 constructor의 뒤에 : 을 통해서 상속받을 수 있다.
  2. open 해놓지 않은 메서드는 override 할 수 없다.
  3. 부모 클래스에서 private으로 선언된 메서드는 상속받은 클래스에서 사용할 수 없다.
fun main() {
	val userEntity: UserEntity = UserEntity(1, "홍길동@gmail.com", "1234")
	val loginRequest: LoginRequest = GoogleLoginRequest("홍길동", "홍길동@gmail.com", "1234")
	println(loginRequest.whoami(userEntity))
}
 
abstract class LoginRequest(val email: String, val password: String) {
	/// 여기서 private 해버리면 상속받은 클래스에서 사용할 수 없다.
	fun validate(userEntity: UserEntity): Boolean {
		if (userEntity.email == email && userEntity.password == password) {
			return true
		}
		return false
	}
	
	open fun whoami(userEntity: UserEntity): String {
		return "LoginRequest"
	}
}
 
class GoogleLoginRequest(private val name: String, email: String, password: String) : LoginRequest(email, password) {
	override fun whoami(userEntity: UserEntity): String {
		if (super.validate(userEntity)) {
			return "Google Login Success : ${this.name}"
		}
		return "Google Login Fail"
	}
}

Sealed Class

sealed class는 컴파일러한테 자신의 자식이 어떤게 있는지 알려준다.
그래서 when 키워드를 사용할 때 컴파일러가 어떤 자식에 대해서 처리가 되지 않았는지를 찾아내서 에러를 출력해준다.

fun main() {
	val color: Colors = Blue
	/// 여기에서 에러 발생 Blue를 처리해주고 있지 않기 때문
	when (color) { 
		is Red -> println("Color is Red")
		is Green -> println("Color is Green")
	}
}
 
sealed class Colors
 
data object Red : Colors()
data object Green : Colors()
data object Blue : Colors()

Companion Object

Kotlin애서 사용하는 static method들은 class를 선언할 때, companion object 블록안에 선언하면 된다.

ℹ️

코틀린 공식문서 📜
클래스 내부의 객체 선언은 companion object와 같이 표시될 수 있다.
companion object의 멤버는 클래스 이름을 한정자로 사용하여 간단하게 호출할 수 있다.

data class Api<T>(
	val result: Result? = null,
	@field:Valid
	val body: T? = null,
) {
	companion object {
		fun <T> OK(body: T?): Api<T> {
			return Api(
				result = Result.ok(),
				body = body
			)
		}
 
		... other codes
	}
}

이렇게 선언한 코드를 다음과 같이 Intance.Companion.method() 형태로 사용할 수 있다.

    return Api.Companion.OK(res);

이름지정

companion object는 이름을 지정해줄 수도 있고 생략(생략할 경우 클래스.Companion이 기본 이름이 된다.)할 수도 있다.

class Fruit {
    companion object Apple {
			fun amount() = 10
    }
}
 
fun main() {
	Fruit.Apple.amount()
}

JvmStatic

Instance.Companion.method()와 같은 형태로 사용하게 되면 Java에서 Kotlin으로 Refactoring을 할 때 어려움을 겪게 된다.
이를 방지하기 위해서 @JvmStatic을 사용하면 Instance.method() 형태로 사용할 수 있다.

ℹ️

JvmStatic : "Java에서 static하게 동작할거야"라는 표현과 같은 Annotation이다.

data class Api<T>(
	val result: Result? = null,
	@field:Valid
	val body: T? = null,
) {
	companion object {
		@JvmStatic
		fun <T> OK(body: T?): Api<T> {
			return Api(
				result = Result.ok(),
				body = body
			)
		}
	}
}

Companion object 상속

kotlin에서 Slf4j를 사용할 때 interface로 선언해주고 companion object에서 상속받아서 많이 사용한다고 한다.

예를 들어서 아래와 같이 slf4j Logger interface를 만들어주고

import org.slf4j.Logger
import org.slf4j.LoggerFactory
 
interface Log {
	val log: Logger get() = LoggerFactory.getLogger(this.javaClass)
}

logger를 사용해야 하는 클래스의 companion obejct에서 Log를 상속받으면 companion object의 log 라는 변수에 접근할 수 있다.

⭐️

코틀린은 interface에서 변수를 선언할 수 있다.

import org.delivery.api.common.Log
 
@Business
class UserOrderBusiness {
	companion object: Log
	
	fun main() {
		log.info("Hello World!")
	}
}

Bytecode

kotlin byte code는 shift 두 번 누르고
show kotlin bytecode
를 통해서 나오는 창에 Decompile을 눌러서 확인해볼 수 있다.
이 때, Companion이 public static final로 선언되어 있는 것을 확인할 수 있다.