backend/kotlin
[Kotlin] 클래스, 객체, 인터페이스 (코틀린 인 액션 4장)
규턴이
2023. 9. 3. 22:02
현재 들어간 회사에서는 코프링이 주 핵심 기술스택이기에 해당 기술스택을 배우고자 코틀린 인 액션 스터디를 진행한다.
- 아래의 내용은 코틀린 인 액션을 저의 방식으로 정리한 글입니다.
인터페이스
- 코틀린에서의 override는 자바와 달리 @Override annotaion을 사용하지 않음
- 인터페이스에 프로퍼티 선언이 가능하다.
- 자바와 동일하게 하나의 클래스에 대해서만 extends가 가능하며, 반대로 여러개의 인터페이스 가능
- 코틀린에서는 ":" 으로 상속, 인터페이스화 둘다 가능하다
- kotlin에서의 default 메서드 구현은 자바와 좀 다르다
- 추가로 아래의 코드와 같이 두개의 같은 default method를 상속한다면, 반드시 override가 필수적이다
interface Clickable {
fun click()
fun showOff() = println("default method")
}
interface Clickable {
fun click()
fun showOff() = println("default method")
}
interface Focusable {
fun showOff() = println("default method2")
}
class Button : Clickable, Focusable{
override fun click() = println(" i was clicked")
override fun showOff() { //동일한 메서드를 가진 interface를 2개 인터페이스화 하는경우 override 필수
super<Clickable>.showOff() //super<~> 를 통해 상위 클래스 지정 가능
super<Focusable>.showOff()
}
}
클래스의 상속
- 기본적으로 코틀린에서 클래스는 final이 붙는다
- 즉, 일반적으로 상속을 하게되면 에러가 날 수 있다. (반대로 자바는 상속에 기본적으로 열려 있음)
- 해당 컴파일에러를 고치기 위해선 상위 클래스에서 'open' 변경자를 붙어야 한다.
- 하지만 추상클래스에서는 'open' 변경자가 필요없다. 추상클래스 자체로는 인스턴스화가 불가능하기 때문
abstract class Animated {
abstract fun animate() // 추상함수
open fun stopAnimating(){} // 비추상함수이지만 open으로 override 가능
fun animateTwice(){} // override 불가능(비추상함수)
}
변경자 표
변경자 | 설명 |
---|---|
final | 오버라이드 불가능, 기본변경자 |
open | 오버라이드 가능, open이 있어야 override가능 |
abstract | 반드시 오버라이드 해야함 |
override | 오바리이드는 기본적으로 열려 있음, 원한다면 final로 금지를 시켜야함 |
내부 클래스, 중첩클래스 => 기본적으로 중첩 클래스
- 자바에서는 중첩 클래스를 작성할때 static class A 와 같이 static이 붙게 된다, 반대로 코틀린은 class A로 가능하다.
- 코틀린에서 일반적으로 중첩클래스는 Class A, 외부 클래스에 참조를 하고자 하는경우에는 inner class A로 지정한다.
- 코틀린에서 outer class에 접근하려면 this@를 붙어야 한다.
클래스 B안에 정의된 A클래스 | 자바 | 코틀린 |
중첩클래스(바깥쪽 클래스에 대한 참조를 저장하지 않음) | static class A | class A |
내부클래스(바깥쪽 클래스에 대한 참조를 저장) | class A | inner Class A |
//kotlin
class Outer{
inner class Inner{
fun test():Outer= this@Outer
}
}
//java
public class OuterJava {
class Inner{
public OuterJava getOuterReference(){
return OuterJava.this;
}
}
}
Sealed Class
sealed class Expr {
class Num(val value:Int) : Expr()
class Sum(val left:Expr, val right:Expr): Expr()
}
fun eval(e: Expr): Int =
when (e) {
is Expr.Num -> e.value
is Expr.Sum -> eval(e.left) + eval(e.right)
// else-> throw Exception() 필요없음
}
뻔하지 않은 생성자와 프로퍼티를 갖는 클래스 선언
- 클래스 생성자는 아래의 코드와 같이 생성된다.
Class User(val nickname:String , val tf : Boolean = true //디폴트 파라미터도 가능)
or
Class User(\_nickname:String){
val nickname: String
init{
nickname = \_nickname
}
}
- init 블록은 객체가 만들어질때(인스턴스화 될떄) init블럭내의 코드가 실행됨.
- 첫번째 방법과 같이 작성한다면 컴파일러가 해당 파라미터에 맞게 생성자를 만들어준다. ( 내가 듣기론 이러한 이유 때문에 kotlin에서는 빌더 패턴이 필요없다고 들었다.)
- Class User(val nickname:String)에서 val은 해당 파라미터에 상응하는 프로퍼티가 생성된다는 의미이다.
- 참고로 ' class SecretClass prviate constructor(){}' 와 같은 클래스를 지정하면 해당 생성자만 비공개(private)지정할 수 있다. 이는 추후 유틸함수나 companion object에 해당 기능을 사용하면 좋을 수 있다.(이유는 이후 장에 나옴)
인터페이스에 선언된 프로퍼티 구현
interface User {
val nickname:String
}
class PrivateUser(override val nickname: String) :User {
}
class SubscribingUser(val email: String):User {
override val nickname: String
get() = email.substringBefore("@")
}
class FackebookUser(val accountId:Int):User{
override val nickname=getFacebookName(accountId)
}
- 인터페이스에도 프로퍼트 선언이 가능하다
- 클래스에서 User인터페이스를 상속하려면 인터페이스의 프로퍼티 때문에 override가 필요하다
- 커스텀 getter를 사용해서도 가능하다.(PrivateUser 참고)
- 커스텀getter같은 경우는 nickname을 호출할때 마다 customgetter의 로직을 수행하여 Nickname을 가져온다.
- 반대로 FacebookUser의 nickname같은 경우는 처음 객체가 지정될때 nickname을 저장하고 이후 다음에 nickname을 호출한다면, 이미 저장된 nickname을 호출한다.
모든 클래스가 정의해야 하는 메서드
class Client(val name: String, val postalCode: Int) {
override fun toString(): String {
return "Cleint(name=$name, postalCode=$postalCode)"
}
override fun equals(other: Any?): Boolean {
if(other == null || other !is Client)
return false
return name==other.name && postalCode==other.postalCode
}
override fun hashCode(): Int {
return name.hashCode()*31+postalCode
}
}
- 코틀린에서 equals메서드는 자동으로 '=='비교를 할 시에 객체간의 비교인경우 equals가 호출된다
- 코틀린에서의 is는 자바에서의 instanceof와 같은 역할을 한다. ( a is User == a instanceof User)
- 원래는 JVMd에서 equals만 정의해서는 의도하지 않는 false값이 나올 수 있다. 실제로는 hashCode도 구현을 해줘야 한다. 하지만 코틀린 컴파일러(자바도 되는지는 정확하지 않음) 해당 메서드를 자동으로 생성해줄수 있다.
Data Class
- 코틀린의 data클래스는 아래의 3가지 기능을 모두 포함한다.
- 인스턴스 간 비교를 위한 equals
- Hashmap과 같이 해시 기반 컨테이너에서 키로 사용할 수 있는 hashCode
- 클래스의 각 필드를 순서대로 나열해 줄 수 있는 toString
BY
interface IWindow {
fun getWidth() : Int
fun getHeight() : Int
}
open class TransparentWindow : IWindow {
override fun getWidth(): Int {
return 100
}
override fun getHeight() : Int{
return 150
}
}
class UI(window: IWindow) : IWindow by window{}
val window: IWindow = TransparentWindow()
val ui = UI(window)
System.out.println("Width : ${ui.getWidth()}, height: ${ui.getHeight()}") // by 키워드를 통해 사용가능
object keyword
- 싱글턴 생성
object Payrool { val allEmployees = arrayListOf<Person>() .... }
- 팩터리 메서드와 정적멤버
- 아래의 코드는 정적팩터리 메서드(A클래스의 동반객체를 companion 이라는 keyword로 생성가능)
- 위와 같은 이점은 동반객체는 자신을 둘러싼 클래스의 모든 Private 멤버에 접근할 수 있다. 즉 동반객체는 바깥쪽 클래스의 private 생성자를 호출 할 수 있음
class A{
companion object {
fun bar(){
print("foo hi")
}
}
}
- 동반 객체 object를 사용할때도 interface를 구현할 수 있다.
interface JsonFactory<T>{
fun fromJson(jsonText:String):T
}
class Persoon (val name:String) {
companion object : JsonFactory<Persoon> {
override fun fromJson(jsonText: String): Persoon {
TODO("Not yet implemented")
}
}
}