본문 바로가기
Kotlin

Kotlin #9 : 객체, 데이터 클래스, Companion 객체, object, singleton, 추상 클래스, 인터페이스 230222

by haheehee 2023. 2. 23.

자바에서 코틀린 Companion 객체 사용

@JvmStatic

- 자바에서는 코틀린의 컴패니언 객체를 접근하기 위해 @JvmStatic 애노테이션(annotation) 표기법을 사용

 


1.

package com.example.pp192

class KCustomer {
    companion object {
        const val LEVEL = "INTERMEDIATE"
        @JvmStatic fun login() = println("Login...")    // 어노테이션 표기 사용
            fun hahee() = println("hahee..")
    }
}

2.

package com.example.pp192;

public class KCustomerAccess {
    public static void main(String[] args)  {
        // 코틀린 클래스의 컴패니언 객체를 접근
        System.out.println(KCustomer.LEVEL);
        KCustomer.login();  // 어노테이션을 사용할 때 접근 방법
        KCustomer.Companion.hahee();    // 위와 동일한 결과로 어노테이션을 사용하지 않을 때 접근 방법
    }
}

 자바에서 코틀린 컴패니언 객체 사용

 

 


Object와 Singleton

상속할 수 없는 클래스에서 내용이 변경된 객체를 생성할 때

- 자바의 경우 익명 내부 클래스를 사용해 새로운 클래스 선언

- 코틀린에서는 object 표현식이나 object 선언으로 이 경우를 좀 더 쉽게 처리


package com.example.pp194

// (1) object 키워드를 사용한 방식
object OCustomer {
    var name = "Kildong"
    fun greeting() = println("Hello World!")
    val HOBBY = Hobby("Basketball")
    init {
        println("Init!")
    }
}

class Hobby(val name: String)

fun main() {
    OCustomer.greeting()    // 객체의 접근 시점
    OCustomer.name = "Dooly"
    println("name = ${OCustomer.name}")
    println(OCustomer.HOBBY.name)
}

Object 선언과 컴패니언 객체 비교 예제

object 선언 방식은 접근 시점에 객체가 생성된다. 때문에 생성자 호출을 하지 않으므로 object 선언에는 주/부 생성자를 사용할 수 없다. 자바에서는 OCustomer.INSTANCE.getName(); 와 같이 접근해야 한다.

 


Object 표현식

- object 선언과 달리 이름이 없음.

- 싱글톤이 아님

- object 표현식이 사용될 때마다 새로운 인스턴스가 생성

- 이름이 없는 익명 내부 클래스로 불리는 형태를 object 표현식으로 생성 가능


package com.example.pp196

open class Superman() {
    fun work() = println("Taking photos")
    fun talk() = println("Talking with people.")
    open fun fly()  = println("Flying in the air")
}

fun main() {
    val pretendedMan = object: Superman() { // object 표현식으로 fly() 구현의 재정의
        override  fun fly() = println("I'm not a real supernam. I can't fly!")
    }
    pretendedMan.work()
    pretendedMan.talk()
    pretendedMan.fly()
}

object 표현식 사용 예제

- 하위 클래스를 만들지 않고도 새로운 구현인 fly()를 포함 가능

- object 표현식으로 fly()구션의 재정의

 


++ 추상 클래스(abstract class) ++

- 구현 클래스에서 가져야할 명세를 정의한 클래스 (프로퍼티 및 메서드 템플릿)

- abstract라는 키워드와 함께 선언하며 추상클래스는 객체 생성 안됨

- ‘구체적이지 않은 것’을 나타내기 때문에 하위 파생 클래스에서 구체적으로 구현

- open 키워드를 사용하지 않고도 파생 클래스 작성 가능


package com.example.pp198

abstract class Vehicle(val name: String, val color: String, val weight: Double) {
    abstract var maxSpeed: Double
    var year = "2023"
    abstract fun start()
    abstract fun stop()
    fun displaySpecs() {
        println("Name: $name, Color: $color, Weight: $weight, Year: $year, Max Speed: $maxSpeed")
    }
}

class Car(name: String, color: String, weight: Double, override var maxSpeed: Double)
    : Vehicle(name, color, weight) {
        override fun start() {
            println("Car Started")
        }
        override fun stop() {
            println("Car Stopped")
        }
    }

class Motorcycle(name: String, color: String, weight: Double, override var maxSpeed: Double)
    : Vehicle(name, color, weight) {
        override fun start() {
            println("Bike Started")
        }
        override fun stop() {
            println("Bike Stopped")
        }
    }

fun main() {
    val car = Car("SuperMatiz", "yellow", 1110.0, 270.0)
    val motor = Motorcycle("DreamBike", "red", 173.0, 100.0)

    car.year = "2022"
    car.start()
    motor.displaySpecs()
    motor.start()
}

추상클래스 예제

 


단일 인스턴스로 객체 생성

- object를 사용한 생성

    추상 클래스로부터 하위 클래스를 생성하지 않고 단일 인스턴스로 객체 생성 가능

package com.example.pp201

// 추상 클래스의 선언
abstract class Printer {
    abstract fun print() // 추상 메서드
}

val myPrinter = object: Printer() { // 객체 인스턴스
    override fun print() {  // 추상 메서드의 구현
        println("출력합니다.")
    }
}

fun main() {
    myPrinter.print()
}

단일 인스턴스로 객체 생성 예제

 


인터페이스

- 생성자가 없다.

- 클래스가 아니므로 다양한 인터페이스로부터 클래스 구현 가능(다중 상속)

- 추상 클래스와는 다르게 강한 연관을 가지지 않는다.

 

- 다른 언어와는 다르게 기본적인 구현 내용이 포함할 수 있음

- 선언하려면 interface 키워드

- 상속한 하위 클래스에서는 override를 사용해 해당 메서드를 구현

interface 인터페이스명 [: 인터페이스명...] {
	추상 프로퍼티 선언
	추상 메서드 선언
	[일반 메서드 선언 { ... }]
}

 

** 인터페이스는 상수일때만 게터를 통한 구현이 가능하다.

** 프로퍼티 상수 - 추상

** 인터페이스의 생성자는 없다.

** 인터페이스의 메소드는 총 세가지 지원 1. 추상 메소드 2. 정적 메소드 3. 디폴트 메소드

** 하지만 실무에서는 특별한 경우가 아니면 거의 추상 메소드만 사용


package com.example.pp204

interface Pet {
    var category: String // abstract 키워드가 없어도 기본은 추상 프로퍼티
    fun feeding()   // 마찬가지로 추상 메서드
    fun patting()   { // 일반 메서드 : 구현부를 포함하면 일반적인 메서드로 기본이 됨
        println("Keep patting!")    // 구현부
    }
}
class Cat(override var category: String) : Pet {
    override fun feeding() {
        println("Feed the cat a tuna can!")
    }
}

fun main() {
    val obj = Cat("small")
    println("Pet Category: ${obj.category}")
    obj.feeding()   // 구현된 메서드
    obj.patting()   // 기본 메서드
}

Pet 인터페이스 예제


package com.example.pp206

interface Pet {
    var category: String
    val msgTags: String     // val 선언 시 게터의 구현이 가능 
        get() = "I'm your lovely pet!"
    
    fun feeding()
    fun patting() {
        println("Kepp pattin!")
    }
}

class Cat(override var category: String) : Pet {
    override fun feeding() {
        println("Feed the cat a tuna can!")
    }
}

fun main() {
    val obj = Cat("small")
    println("Pet Message Tags: ${obj.msgTags}")
    obj.feeding()       // 구현된 메서드
    obj.patting()       // 기본 메소드
}

Getter를 구현한 프로퍼티.

- 상수일 때만

- 인터페이스에서는 프로퍼티에 값을 저장할 수 없음. val로 선언된 프로퍼티는 게터를 통해 필요한 내용 구현 가능


package com.example.pp207

interface Pet {
    var category: String
    val msgTags: String     // val 선언 시 게터의 구현이 가능
        get() = "I'm your lovely pet!"
    fun feeding()
    fun patting() {
        println("Keep patting!")
    }
}
open class Animal(val name: String)

// feeding의 구현을 위해 인터페이스 Pet 지정
class Dog(name: String, override var category: String) : Animal(name), Pet {
    override fun feeding() {
        println("Feed the dog a bone")
    }
}
class Cat7(name: String, override var category: String) : Animal(name), Pet {
    override fun feeding() {
        println("Feed the cat a tuna can!")
    }
}
class Master {
    fun playWithPet(dog: Dog) { // 각 애완동물 종류에 따라 오버로딩됨
        println("Enjoy with my dog.")
    }
    fun playWithPet(cat: Cat7) { // 고양이를 위한 메서드
        println("Enjoy with my cat.")
    }
}

fun main() {
    val master = Master()
    val dog = Dog("Toto", "Small")
    val cat = Cat7("Coco", "BigFat")
    master.playWithPet(dog)
    master.playWithPet(cat)
}

오버로딩 예제

- Master클래스 안의 palyWithPet(...)


package com.example.pp209

interface Pet {
    var category: String
    val msgTags: String     // val 선언 시 게터의 구현이 가능
        get() = "I'm your lovely pet!"
    fun feeding()
    fun patting() {
        println("Keep patting!")
    }
}
open class Animal(val name: String)

// feeding의 구현을 위해 인터페이스 Pet 지정
class Dog(name: String, override var category: String) : Animal(name), Pet {
    override fun feeding() {
        println("Feed the dog a bone")
    }
}
class Cat7(name: String, override var category: String) : Animal(name), Pet {
    override fun feeding() {
        println("Feed the cat a tuna can!")
    }
}
class Master {
    fun playWithPet(dog: Dog) { // 각 애완동물 종류에 따라 오버로딩됨
        println("Enjoy with my ${dog.name}.")
    }
    fun playWithPet(cat: Cat7) { // 고양이를 위한 메서드
        println("Enjoy with my ${cat.name}.")
    }
}

fun main() {
    val master = Master()
    val dog = Dog("Toto", "Small")
    val cat = Cat7("Coco", "BigFat")
    master.playWithPet(dog)
    master.playWithPet(cat)
}

 


여러 인터페이스의 구현

다중 상속

- 클래스는 기본적으로 다중 상속 지원하지 않음

- 인터페이스 여러 개를 하나의 클래스에서 구현하는 것이 가능 -> 다중 상속과 같은 효과

 

자바는 다중상속 불가

코틀린도 다중상속 불가(자바 기반임)


package com.example.pp211

interface Bird {
    val wings: Int
    fun fly()
    fun jump() = println("bird jump!")
}

interface Horse {
    val maxSpeed: Int
    fun run()
    fun jump() = println("jump!, max speed: $maxSpeed")
}

class Pegasus: Bird, Horse {
    override val wings: Int = 2
    override val maxSpeed: Int = 100
    override fun fly() = println("Fly!")
    override fun run() = println("Run!")
    override fun jump() {
        super<Horse>.jump()
        println("and Jump!")
    }
}

fun main() {
    val pegasus = Pegasus()
    var jump = pegasus.jump()
}

다중 상속 예제

 


데이터 전달을 위한 데이터 클래스

데이터 전달을 위한 객체 DTO(Data Transfer Object)

- 자바에서는 POJO(Plain Old Java Object)

- 구현 로직을 가지고 있지 않고 순수한 데이터 객체를 표현

- 데이터를 접근하는 게터/세터를 포함

- toString(), equals() 등과 같은 데이터 표현 및 비교 메서드

 

자바로 DTO를 표현하면 데이터 필드들, 게터/세터들, 데이터 표현 및 비교 메서드들을 모두 작성해야 함

코틀린으로 DTO를 표현하면 프로퍼티만 신경 써서 작성하면 나머지는 내부적으로 자동 생성됨

 

코틀린의 데이터 클래스

- DTO를 표현하기 적합한 클래스 표현으로 data class 키워드를 사용해 정의

      코틀린의 프로퍼티 = 필드(변수) + 게터와 세터

- 자동 생성되는 메서드들

  • 프로퍼티를 위한 게터/세터
  • 비교를 위한 equals()과 키 사용을 위한 hashCode()
  • 프로퍼티를 문자열로 변환해 순서대로 보여주는 toString()
  • 객체 복사를 위한 copy()
  • 프로퍼티에 상응하는 component1(), component2() 등

데이터 클래스 선언

data class Customer(var name: String, var email: String)

- 주 생성자는 최소한 하나의 매개변수를 가짐

- 주 생성자의 모든 매개변수는 val, var로 지정된 프로퍼티여야 함

- 데이터 클래스는 abstract, open, sealed, inner 키워드를 사용할 수 없음

- 간단한 로직을 포함 하려면 부 생성자나 init 블록을 넣어 데이터를 위한 간단한 로직을 포함할 수 있다.

data class Customer(var name: String, var email: String) {
	var job: String = "Unknown"
	constructor(name: String, email: String, _job: String): this(name, email) {
		job = _job
	}
	init {
		// 간단한 로직은 여기에
	}
}

 

method function
equals() 두 객체의 내용이 같은지 비교하는 연산자로 ==와 동일 (고유값은 다르지만 의미값이 같을 때)
hashCode() 객체를 구별하기 위한 고유한 정수값 생성, 데이터 셋이나 해시테이블을 사용하기 위한 하나의 생성된 인덱스
copy() 빌더 없이 특정 프로퍼티만 변경해서 객체 복사하기
toString() 데이터 객체를 읽기 편한 문자열로 반환하기
componentN() 객체의 선언부 구조를 분해하기 위해 프로퍼티에 상응하는 메서드

 


package com.example.pp217

data class Customer(var name: String, var email: String) {
    var job: String = "Unknown"
    constructor(name: String, email: String, _job: String): this(name, email) {
        job = _job
    }
    init {
        //
    }
}

fun main() {
    val cus1 = Customer("Skyler", "skyler@email.com")
    val cus2 = Customer("Skyler", "skyler@email.com")

    println(cus1 == cus2)   // 동등성 비교 true
    println(cus1.equals(cus2))  // 위와 동일 true
    println("${cus1.hashCode()}, ${cus2.hashCode()}")    // 고유값도 동일
}

데이터 클래스의 제공 메서드 - equals(), hashCode() 예제

 

package com.example.pp218

data class Customer(var name: String, var email: String) {
    var job: String = "Unknown"
    constructor(name: String, email: String, _job: String): this(name, email) {
        job = _job
    }
    init {
        //
    }
}

fun main() {
    val cus1 = Customer("Skyler", "skyler@email.com")

    val cus3 = cus1.copy(name = "Jeniffer") // name만 변경하고자 할 때
    println(cus1.toString())
    println(cus3.toString())
}

데이터 클래스의 제공 메서드 - copy() 예제

 

댓글