Chapter4. 클래스, 객체, 인터페이스

김신영·2022년 10월 12일
0

kotlin-in-action

목록 보기
4/11
post-thumbnail

Interface

  • kotlin interface
interface Clickable {
	fun click()
}

class Button : Clickable {
	override fun click() = println("I was clicked")
}
  • java interface
public interface Clickable {
	void click();
}

public class Button implements Clickable {
	@Override
	public void click() {
		System.out.println("I was clicked");
	}
}
  • Default 구현
class Button : Clickable, Focusable {  
    override fun click() = println("I was clicked")  
  
    override fun showOff() {  
        super<Clickable>.showOff()  // java: Clickable.super.showOff()
        super<Focusable>.showOff()  // java: Focusable.super.showOff()
    }  
}  
  
interface Clickable {  
    fun click()  
    fun showOff() = println("I'm clickable!")  
}  
  
interface Focusable {  
    fun setFocus(b: Boolean) =  
        println("I ${if (b) "got" else "lost"} focus.")  
  
    fun showOff() = println("I'm focusable!")  
}  
  
fun main(args: Array<String>) {  
    val button = Button()  
    button.showOff()  
    button.setFocus(true)  
    button.click()  
}

// I'm clickable!
// I'm focusable!
// I got focus.
// I was clicked

기본적으로 public, final

  • 코틀린의 클래스와 메소드는 기본적으로 final이다.
  • 클래스 상속을 기본적으로 금지한다.

NOTE - Effective Java
상속을 위한 설계와 문서를 갖추거나, 그럴 수 없다면 상속을 금지하라.

상속 제어 변경자

변경자이 변경자가 붙은 멤버는...설명
final오버라이드할 수 없음클래스 멤버의 기본 변경자다.
open오버라이드할 수 있음반드시 open을 명시해야 오버라이드할 수 있다.
abstract반드시 구현해야함추상 클래스의 멤버에만 이 변경자를 붙일 수 있다.
override오버라이드하는 중오버라이드하는 멤버는 기본적으로 open.
하위 클래스의 오버라이드를 금지하려면 final을 명시해야 한다.
  • final 이 기본 설정
  • if override , then open

가시성 변경자 (Access Modifier / Visibility Modifier)

변경자클래스 멤버최상위 선언
public모든 곳에서 볼 수 있다.모든 곳에서 볼 수 있다.
internal같은 모듈 안에서만 볼 수 있다.같은 모듈 안에서만 볼 수 있다.
protected하위 클래스 안에서만 볼 수 있다.(최상위 선언에 적용할 수 없음)
private같은 클래스 안에서만 볼 수 있다.같은 파일 안에서만 볼 수 있다.
  • public 이 기본 설정

중첩 클래스 vs 내부 클래스

구분바깥쪽 클래스에 대한 참조JavaKotlin
중첩 클래스 (Nested Class)바깥쪽 클래스에 대한 참조 불가능static class NestedClassclass NestedClass
내부 클래스 (Inner Class)바깥쪽 클래스에 대한 참조 가능.class InnerClassinner class InnerClass

Nested Class

  • static 멤버 선언 가능
  • instance 멤버 선언 가능
  • OuterClass의 instance 멤버 참조 불가능
  • OuterClass의 static 멤버 참조 가능
  • Java에서는 static class NestedClass
  • Kotlin에서는 class NestedClass

Java Code

public class OuterClass {  
	private static String staticProperty = "staticProperty";
    private String property = "property";  
  
    static class NestedClass {  
	    private static String nestedClassStaticProperty = "nestedClassStaticProperty";
        private String nestedClassProperty = "nestedClassProperty";  
  
        public void test() {  
            System.out.println(nestedClassStaticProperty);
            System.out.println(this.nestedClassProperty); 
            System.out.println(Solution.staticProperty); 
            // System.out.println(Solution.this.property);  // Compile Error
        }  
    }

	public static void main(String[] args) {  
	    new Solution.NestedClass().test();  
	}
}

Kotlin Code

class OuterClass {  
    val property: String = "property"  
  
    class InnerClass {  
        private val innerClassProperty: String = "innerClassProperty"  
  
        fun createOuterClassInstance() : OuterClass{  
            println(innerClassProperty)  
            return OuterClass()  
        }  
    }  
}  
  
fun main(args: Array<String>) {  
    println(OuterClass.InnerClass().createOuterClassInstance().property)  
}

Inner Class inner class

  • static 멤버 선언 불가능 (Java 16부터 가능)
  • instance 멤버 선언 가능
  • OuterClass에 대한 참조 가능
    - Kotlin
    - this@OuterClass
    - Java
    - OuterClass.this
  • OuterClass의 instance 멤버 참조 가능
  • OuterClass의 static 멤버 참조 가능
  • Java에서는 class InnerClass
  • Kotlin에서는 inner class InnerClass

Java Code

public class OuterClass {  
    private static String staticProperty = "staticProperty";  
    private String property = "property";  
  
    class InnerClass {  
        // private static String innerClassStaticProperty = "innerClassStaticProperty"; // Supported since Java 16  
        private String innerClassProperty = "innerClassProperty";  
  
        public void test() {  
            System.out.println(this.innerClassProperty);  
            System.out.println(Solution.this.property);  
            System.out.println(Solution.staticProperty);  
        }  
    }  
  
    public static void main(String[] args) {  
        new OuterClass().new InnerClass().test();  
    }  
}

Kotlin Code

class OuterClass {  
    val property: String = "property"  
  
    inner class InnerClass {  
        private val innerClassProperty: String = "innerClassProperty"  
  
        fun getOuterReference() : OuterClass{  
            println(innerClassProperty)  
            println(this@OuterClass.property)  
            return this@OuterClass  
        }  
    }  
}  
  
fun main(args: Array<String>) {  
    println(OuterClass().InnerClass().getOuterReference().property)  
}

Sealed Class (봉인된 클래스) sealed class

봉인된 클래스를 상속하는 하위 클래스 정의를 제한할 수 있다.

sealed 클래스의 하위 클래스를 정의할 때는 반드시 봉인된 클래스 안에서 정의되어야 한다.

  • sealed class 를 상속하려면, 그 클래스 안에 정의되어 있어야 한다.
  • sealed class 는 기본적으로 open 이다.
  • sealed class 는 private 생성자를 가진다. 클래스 내부에서만 호출 가능.
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 가 없어도 됨.
		//else -> throw IllegalArgumentException("Illegal Expression")
	}

fun main(args: Array<String>) {  
    println(eval(Expr.Sum(Expr.Sum(Expr.Num(1), Expr.Num(2)), Expr.Num(4))))  // 7
}

Interface에 선언된 프로퍼티 구현

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 FacebookUser(val accountId: Int) : User {  
    override val nickname = getFacebookName(accountId)  
}

Getter, Setter에서 뒷받침하는 필드에 접근 field

  • field를 사용하지 않고 커스텀 Getter, Setter를 구현했을 경우, 뒷받침 필드는 존재하지 않는다.
class User(val name: String) {  
    var address: String = "unspecified"  
        set(value: String) {  
            println("""  
                Address was changed for $name:  
                "$field" -> "$value".""".trimIndent())  
            field = value  
        }  
}

Getter, Setter의 가시성 변경

class LengthCounter {  
    var counter: Int = 0  
        private set  
  
    fun addWord(word: String) {  
        counter += word.length  
    }  
}  
  
fun main(args: Array<String>) {  
    val lengthCounter = LengthCounter()  
    lengthCounter.addWord("Hi!")  
    println(lengthCounter.counter)  // 3
}

Data Class data class

  • toString(), hashCode(), eqauls() 메서드 구현 제공
  • copy() 메서드
data class Client(val name: String, val postalCode: Int)  
  
fun main(args: Array<String>) {  
    val bob = Client("Bob", 973293)  
    println(bob.toString())  // Client(name=Bob, postalCode=973293)
    println(bob == Client("Bob", 973293))  // true
    println(bob.hashCode())  // 3049208
    println(bob.copy(postalCode = 382555))  // Client(name=Bob, postalCode=382555)
    println(bob === bob.copy())  // false
}

Class Delegation by

import java.util.HashSet  
  
class CountingSet<T>(  
        val innerSet: MutableCollection<T> = HashSet<T>()  
) : MutableCollection<T> by innerSet {  
  
    var objectsAdded = 0  
  
    override fun add(element: T): Boolean {  
        objectsAdded++  
        return innerSet.add(element)  
    }  
  
    override fun addAll(c: Collection<T>): Boolean {  
        objectsAdded += c.size  
        return innerSet.addAll(c)  
    }  
}  
  
fun main(args: Array<String>) {  
    val cset = CountingSet<Int>()  
    cset.addAll(listOf(1, 1, 2))  
    println("${cset.objectsAdded} objects were added, ${cset.size} remain")  
}

클래스 선언과 인스턴스 생성 object

import java.util.Comparator  
  
data class Person(val name: String) {  
    object NameComparator : Comparator<Person> {  
        override fun compare(p1: Person, p2: Person): Int =  
            p1.name.compareTo(p2.name)  
    }  
}  
  
fun main(args: Array<String>) {  
    val persons = listOf(Person("Bob"), Person("Alice"))  
    println(persons.sortedWith(Person.NameComparator))  
}

동반 객체 companion object

  • 확장함수의 경우, 클래스의 public 메서드와 멤버만 접근 가능
  • 동반객체는 자신을 둘러싼 클래스의 모든 private 멤버에 접근할 수 있다.
  • 동반객체 이름을 지정하지 않으면, 기본적으로 Companion 이라는 이름을 부여한다.
interface JSONFactory<T> {  
    fun fromJSON(jsonString: String): T  
}  

class Person(val name: String) {  
    companion object JSONParser: JSONFactory<Person> {  
        override fun fromJSON(jsonString: String) : Person {  
            // ...  
            return Person("name")  
        }  
    }  
}  
  
fun main(args: Array<String>) {  
    val jsonString = """{"name": "John"}"""  
    Person.fromJSON(jsonString)  
    Person.JSONParser.fromJSON(jsonString)  
}

동반객체에서 인터페이스 구현

fun getFacebookName(accountId: Int) = "fb:$accountId"  
  
class User private constructor(val nickname: String) {  
    companion object {  
        fun newSubscribingUser(email: String) =  
            User(email.substringBefore('@'))  
  
        fun newFacebookUser(accountId: Int) =  
            User(getFacebookName(accountId))  
    }  
}  
  
fun main(args: Array<String>) {  
    val subscribingUser = User.newSubscribingUser("bob@gmail.com")  
    val facebookUser = User.newFacebookUser(4)  
    println(subscribingUser.nickname)  
}

동반객체의 확장함수

class Person(val name: String) {
	companion object {}  // 비어있는 동반 객체
}

// 동반객체의 확장함수 정의
fun Person.Companion.fromJSON(jsonString: String): Person {
	// ...
}

fun main() {
	val p = Person.fromJSON("....")
}
profile
Hello velog!

0개의 댓글