References: Do it! 코틀린 프로그래밍

코틀린에서의 내부 클래스에 대해 알아봅니다.

 

 

1. 내부 클래스

 

내부 클래스는 클래스 내부에 클래스를 다시 정의하는 것을 말합니다.

내부 클래스는 독립적인 클래스로 정의하기 모호한 경우나 한 클래스의 내부에서만 사용하고 외부에선 접근할 필요가 없는 클래스를 정의하기 위해 사용합니다.

단, 이러한 내부 클래스는 남용하면 클래스의 의존성이 커지고 가독성이 저하되므로 주의가 필요합니다.

 

 

2. 중첩(Nested) 클래스

 

코틀린에서 내부 클래스를 정의하기 위한 방법 중 하나입니다.

코틀린에서 중첩 클래스는 기본적으로 적적 클래스처럼 다뤄집니다. 즉 객체 생성 없이 접근이 가능합니다.

 

다음 예제를 통해 확인해 보도록 하겠습니다.

 

class Outer{
    val ov = 5
    class NestedNested {
        val nv = 10
        fun greeting() = "[Nested] Hello! $nv"
    }
    fun outside() {
        val msg = Nested().greeting()
        println("[Outer] $msg, ${Nested().nv}")
    }
}
fun main() {
    val output = Outer.Nested().greeting()
    println(output)
    val outer = Outer()
    outer.outside();
}

 

  • class Nested: 중첩 클래스를 정의합니다.
  • $nv: 내부의 nv는 접근 가능하나 외부의 ov는 접근할 수 없습니다.
  • Nested().greetring: 정적 클래스 취급으로 객체 생성 없이 사용 가능합니다.
  • val outer = Outer(): 외부 클래스는 객체를 생성해야 접근 가능합니다.

위 코드를 실행하면 다음과 같은 결과를 확인할 수 있습니다.

 

만약 외부 내부 클래스에서 외부 클래스에 접근하고 싶으면 외부 클래스에 컴패니언 객체를 정의하면 됩니다.

컴패니언 객체는 static처럼 접근이 가능하므로 중첩 클래스에서 접근이 가능합니다.

 

예를 들어 다음과 같은 경우엔 중첩 클래스에서 외부 클래스의 프로퍼티에 접근할 수 있습니다.

 

class Outer{
    val ov = 5
    class Nested{
        val nv = 10
        fun gretting() = "[Nested] Hello! $nv"
        fun accessOuter() = println("Country: $country")
    }
    fun outside() {
        val msg = Nested().gretting()
        println("[Outer] $msg, ${Nested().nv}")
    }
    companion object{
        const val country = "Korea"
    }
}

 

  • const val country: 외부에 존재하는 클래스에 컴패니언 객체를 선언하였습니다.

위와 같이 컴패니언 객체 내부의 프로퍼티는 중첩 클래스에서 접근할 수 있습니다.

   

 

3. 이너(Inner) 클래스

 

이너 클래스는 inner라는 선언자를 통해 정의할 수 있습니다.

inner를 통해 선언된 이너 클래스는 외부 클래스의 멤버에 접근할 수 있습니다.

심지어 private멤버까지도 사용할 수 있습니다.

 

다음 예시를 통해 확인해 보도록 하겠습니다.

 

class SmartPhone(val model: String){
    private val cpu = "Snapdragon"
    inner class ExternalStorage(val size: Int){
        fun getInfo() = "${model}: Installed on $cpu with ${size}${size} GB"
    }
}
fun main() {
    val mySdcard = SmartPhone("Note9").ExternalStorage(256)
    println(mySdcard.getInfo())
}

 

  • inner class ExternalStorage: 이너 클래스에서는 외부 클래스의 멤버에 접근 가능합니다.

위의 코드를 실행시키면 다음과 같은 결과를 확인할 수 있습니다.

이너 클래스에선 외부 클래스의 private멤버를 사용할 수 있는 것을 확인할 수 있습니다.

 

 

4. 익명 객체

 

자바에서는 익명 이너 클래스를 통해 일회용 객체를 생성하였습니다.

코틀린에서는 object 키워드를 사용하는 익명 객체로 같은 기능을 수행할 수 있습니다.

자바와의 차이점은 다중 인터페이스를 통한 구현이 가능하는 것입니다.

 

다음 예제를 통해 확인해 보도록 하겠습니다.

 

interface SwitcherSwitcher { fun on(): String }
class SmartPhone(val model: String){
    private val cpu = "Snapdragon"
    inner class ExternalStorage(val size: Int){ fun getInfo() = "${model}: Installed on $cpu with ${size}GB" }
    fun powerOn(): String {
        class Led(val color: String){ fun blink(): String = "Blinking $color on $model" }
        val powerStatus = Led("Red")
        val powerSwitch = object: Switcher {
            override fun on(): String { return powerStatus.blink() }
        }
        return powerSwitch.on()
    }
}
fun main() {
    val myPhone = SmartPhone("Note9")
    myPhone.ExternalStorage(256)
    println(myPhone.powerOn())
}

 

  • interface switcher: 인터페이스 선언
  • object: Switcher: 익명 객체 사용
  • override fun on: 인터페이스의 메서드 구현
  • powerSwitch.on(): 익명 객체의 메서드 사용

위의 코드를 실행하면 다음과 같은 결과를 확인할 수 있습니다.

 

 

 

 

 

반응형

References: Do it! 코틀린 프로그래밍

코틀린에서의 데이터 클래스에 대해 알아봅니다.

 

 

1. 데이터 클래스

 

데이터 클래스는 데이터 전달을 위한 클래스라고 말합니다. 

보통 이러한 객체를 DTO(Date Transfer Object)라고 부릅니다.

DTO는 구현 로직을 갖지 않고 순수한 데이터를 표현하기 때문에 보통 속성과 게터/세터를 갖습니다.

 

코틀린에서는 DTO를 위해 데이터 클래스를 정의할 때 게터/세터/toString/equal 등을 직접 구현할 필요 없이 자동으로 구현해 줍니다. 코틀린의 데이터 클래스 내부에선 다음과 같은 메서드를 자동으로 생성해 줍니다.

 

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

DTO를 통해 일종의 표준과 같은 약속을 정하면 송신측과 수신 측 모두 데이터를 쉽게 다룰 수 있어 DTO를 사용합니다.

 

 

2. 데이터 클래스 선언 방법

 

데이터 클래스는 data라는 선언자를 이용해서 선언합니다.

데이터 클래스는 다음과 같은 조건을 만족해야 합니다.

  • 주 생성자는 하나 이상의 매개변수를 가져야 합니다.
  • 주 생성자의 모든 매개변수는 var 혹은 val로 지정된 프로퍼티여야 한다.
  • 데이터 클래스는 abstract, open, sealed, inner 키워드를 사용할 수 없다.

단, 필요하다면 부 생성자나 init을 사용해 간단한 로직을 포함할 수 있습니다.

 

다음 예시를 통해 DTO에 대해 알아보겠습니다.

 

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 { /** Simple logic here. */ }
}
fun main() {
    val cus1 = Customer("smoh", "smoh@mail.com")
    val cus2 = Customer("smoh", "smoh@mail.com")
    println(cus1 == cus2)
    println(cus1.equals(cus2))
    println("${cus1.hashCode()}, ${cus2.hashCode()}")
    println(cus1.toString())
}

 

  • var name: Stirng, var email: String: 주 생성자를 통해 두 프로퍼티를 갖습니다.
  • constructor: 부 생성자를 통해 job 프로퍼티를 하나 더 갖습니다.
  • init: init에 간단한 로직을 포함하게 할 수 있습니다.
  • 이 외에도 코틀린은 자동으로 여러 메서드를 생성해 둡니다.

위의 코드를 수행하면 다음과 같은 결과를 확인할 수 있습니다.

위 처럼 우리가 직접 정의하지 않은 여러 메서드를 코틀린에서 자동으로 추가해주며 바로 사용할 수 있습니다

 

 

 

3. 객체 디스트럭처링.

 

디스트럭처링(Destructuring)은 객체가 갖고 있는 프로퍼티를 개별 변수로 분해하여 할당하는 것을 말합니다.

js의...(Spread syntax)의 구조 분해 할당과 같은 개념으로 보입니다.

 

다음 예시를 통해 디스트럭처링에 대해 알아보겠습니다.

 

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 { /** Simple logic here. */ }
}
fun myFunc(): Customer { return Customer("smoh92", "smoh92@mail.com") }
fun main() {
    val cus1 = Customer("smoh1", "smoh1@mail.com")
    val (name, email) = cus1
    println("name = $name, email = $email")

    val (_, email2) = cus1

    val name3 = cus1.component1()
    val email3 = cus1.component2()
    println("name = $name3, email = $email3")

    val cus2 = Customer("smoh2", "smoh2@mail.com")
    val customers = listOf(cus1, cus2)
    for((name, email) in customers){ println("name = $name, email = $email") }

    val(myName, myEmail) = myFunc()

    val myLamda = {
        (nameLa, emailLa): Customer ->
        println(nameLa)
        println(emailLa)
    }
    myLamda(cus1)
}

 

  • val (name, email) = cus1: 기본적인 디스트럭처링
  • val (_, email2) = cus1: 디스트럭처링 하지 않을 프로퍼티는 언더바로 표시합니다.
  • val name3 = cus1.component1(): 컴포넌트 순서대로 골라서 디스트럭처링 하는 방법입니다.
  • for((name, email) in customers): 디스트럭처링 해야할 대상이 많은 경우 루프 문을 이용할 수 있습니다.
  • val(myName, myEmail) = myFunc(): 리턴 값이 객체인 함수에 대해서도 디스트럭처링을 적용할 수 있습니다.
  • val myLamda: 람다식을 사용해 디스트럭처링 하는 방법입니다.

해당 예시코드를 수행하면 다음과 같은 결과를 확인할 수 있습니다.

 

 

 

 

 

 

반응형

'Programming' 카테고리의 다른 글

[Kotlin] 34. 봉인 클래스와 열거형 클래스  (0) 2019.09.16
[Kotlin] 33. 내부 클래스  (0) 2019.09.16
[Kotlin] 31. 인터페이스  (0) 2019.09.12
[Kotlin] 30. 추상 클래스  (0) 2019.09.05
[Kotlin] 29. 컴패니언 객체  (0) 2019.09.01

References: Do it! 코틀린 프로그래밍

코틀린에서의 인터페이스에 대해 알아봅니다.

 

 

1. 인터페이스(Interface)

 

코틀린의 인터페이스에는 추상 메서드나 일반 메서드가 포함될 수 있습니다. 

다른 객체지향 언어와는 달리 메서드에 구현 내용이 포함될 수 있습니다. 단, 추상 클래스처럼 프로퍼티를 통해 상태를 지정할 수는 없습니다.

 

추상 클래스는 기본적으로 클래스이기 때문에 상속을 통해 하위로 확장이 가능합니다. 

그런데 하위 클래스는 상속을 하나만 허용합니다. 또한 상위 클래스와 강한 관계가 생기며 상위 클래스의 영향을 받게 됩니다.

 

반면에 인터페이스는 기본적으로 클래스가 아닙니다. 따라서 상속으로 하위 클래스에 프로퍼티와 메서드를 전달하지 않습니다. 상속받은 하위 클래스라고 하기보단 구현 클래스라고 합니다.

구현 클래스는 인터페이스와 강한 관계를 갖지 않습니다. 

또한 원하는 인터페이스 만큼 구현 클래스에 붙여서 필요한 메서드를 구현할 수 있습니다.

 

 

2. 인터페이스 선언 및 구현 방법.

 

인터페이스는 interface라는 선언자를 통해 선언합니다.

구현 클래스에서는 override 라는 선언자를 통해 메서드들을 구현합니다.

 

다음 예시를 통해 확인하겠습니다.

 

interface Pet{
    var category: String
    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()
}

 

  • var category / fun feeding: 인터페이스에서는 별도의 선언자 없어도 기본적으로 abstract로 선언됩니다.
  • fun patting: 단, 이와 같이 메서드를 구현하면 일반 메서드가 됩니다.
  • override var category / override fun feeding: 추상 프로퍼티와 클래스를 구현합니다.
  • : Pet: Pet 인터페이스를 구현하는 클래스임을 알려줍니다.

 

위의 코드를 실행하면 다음과 같은 결과를 확인할 수 있습니다.

 

 

 

3. 인터페이스의 필요성.

 

다음 예시를 통해 인터페이스의 필요성에 대해 알아보겠습니다.

우선 애완동물과 주인의 관계를 표현하는 코드를 작성해 보겠습니다.

 

open class Animal(val name: String)
interface Pet{
    var category: String
    fun feeding()
    fun patting(){ println("Keep patting") }
}
class Dog(name: String, override var category: String): Animal(name), Pet{
    override fun feeding() { println("Feed the dog a bone.") }
}
class Cat(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: Cat){ println("Enjoy with my cat") }
}
fun main(){
    val master = Master()
    val dog = Dog("Toto", "small")
    val cat = Cat("Coco", "BigFat")
    master.playWithPet(dog)
    master.playWithPet(cat)
}

 

  • Dog / Cat: Animal클래스를 상속받고 Pet 인터페이스를 구현하는 클래스
  • playWithPet: 함수 인자에 따른 함수 오버로딩

위의 코드를 실행하면 다음과 같은 결과를 확인할 수 있습니다.

여기서 만약 애완동물 종류가 늘어나면 어떻게 될까요? 애완동물마다 playWithPet함수를 새로 오버로딩해야 하는 문제가 생깁니다. 

 

이러한 문제를 피하기 위해 코드를 다음과 같이 수정해 봅시다.

 

open class Animal(val name: String)
interface Pet{
    var category: String
    var species: String
    fun feeding()
    fun patting(){ println("Keep patting") }
}
class Dog(name: String, override var category: String): Animal(name), Pet{
    override var species: String = "dog"
    override fun feeding() { println("Feed the dog a bone.") }
}
class Cat(name: String, override var category: String): Animal(name), Pet{
    override var species: String = "cat"
    override fun feeding() { println("Feed the cat a tuna can.") }
}
class Master {
    fun playWithPet(pet: Pet){ println("Enjoy with my ${pet.species}.") }
}
fun main(){
    val master = Master()
    val dog = Dog("Toto", "small")
    val cat = Cat("Coco", "BigFat")
    master.playWithPet(dog)
    master.playWithPet(cat)
}

 

위와 같이 수정하면 더 이상 애완동물을 추가 할 때 마다 새로운 함수를 오버로딩할 필요가 없어집니다.

기존의 Master 클래스는 각각의 애완동물 클래스에 의존적이었으나 이젠 더이상 의존적인 클래스가 아니게 됩니다.

이런 식으로 클래스의 독립성을 유지하는데 인터페이스가 큰 역할을 합니다.

 

 

 

4. 여러 인터페이스를 구현하는 방법

 

클래스는 하나의 클래스만 상속이 가능합니다.

하지만 인터페이스를 이용하면 다중 상속과 같은 형태로 여러 인터페이스를 구현할 수 있습니다.

 

다음 예시를 통해 확인해 보겠습니다.

 

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("Pegasus jump!")
    }
}
fun main(){
    val pegasus = Pegasus()
    pegasus.fly()
    pegasus.run()
    pegasus.jump()
}

 

  • class Pegasus: Bird, Horse: 페가수스 클래스를 만들고 Bird, Horse 인터페이스를 구현하였습니다.
  • super<Horse>.jump(): 부모 인터페이스 중 Horse 인터페이스의 jump 메서드를 호출합니다.

 

해당 코드를 실행하면 다음과 같은 결과를 확인할 수 있습니다.

위와 같은 방법을 통해 한 클래스에 여러 인터페이스를 구현하게 할 수 있습니다.

 

 

5.  인터페이스 위임

 

인터페이스에서 by위임을 사용하는 방법을 예시를 통해 알아보겠습니다.

 

interface Nameable { var name: String }
class StaffName: Nameable { override var name: String = "Sean" }
class Work: Runnable { override fun run() { println("Work...") } }
class Person(name: Nameable, work: Runnable): Nameable by name, Runnable by work
fun main() {
    val person = Person(StaffName(), Work())
    println(person.name)
    person.run()
}

 

  • class Person: 각 매개변수에 인터페이스들을 위임합니다.
  • Person(StaffName(), Work()): 클래스의 생성자를 통해 객체를 전달합니다.

위의 코드를 실행한 결과는 다음과 같습니다.

이처럼 각 클래스의 위임된 멤버에 접근해 사용할 수 있습니다.

 

 

 

반응형

'Programming' 카테고리의 다른 글

[Kotlin] 33. 내부 클래스  (0) 2019.09.16
[Kotlin] 32. 데이터 클래스  (0) 2019.09.12
[Kotlin] 30. 추상 클래스  (0) 2019.09.05
[Kotlin] 29. 컴패니언 객체  (0) 2019.09.01
[Kotlin] 28. 위임(Delegation)  (0) 2019.09.01

구매

 

8월 19일 예약 열리자마자 결제했습니다.

 

주문 시 판매가는 정가이며 실제 결제 가격은 저기에 쿠폰 등 할인가를 적용해서 결제합니다.

 

 

LTE 모델은 글 쓰는 지금까지 품절이네요

 

 

개봉

 

배송은 공식 출품일인 29일 당일에 왔습니다.

 

본 제품과 사은품인 블루투스 마우스, 라미펜 입니다.

 

라미펜은 안 쓸 거 같습니다. 생각보다 그립갑도 별로고 심지어 이번 탭 S6에선 후방에 S펜을 부착할 수 있어 굳이 펜을 따로 들고 다닐 이유는 없습니다.

 

블루투스 마우스는 안드로이드와 연동하면 마우스 커서가 생겨서 엄청 편합니다.

키보드 커버와 함께 쓰면 굉장히 유용할 듯합니다.

 

실제 박스 모습입니다.

비닐 포장은 되어있지 않으며 보이드 스티커가 붙어있습니다.

 

박스 후방엔 스펙과 함께 보이드 스티커를 확인할 수 있습니다.

이전 S3는 검은색 박스였는데 이번 모델 박스는 아이패드 프로 박스랑 매우 유사해 보입니다.

 

 

박스를 열면 아름다운 본체가 포장된 모습을 보여줍니다.

우측은 후방 사진입니다. 포장재 겉면에 펜을 부착하는 방식을 알려주고 있습니다.

 

먼저 언급하지만 탭 S65는 S펜을 부착하는 방향이 정해져 있습니다.

뒷면을 보지 않은 채 붙이려면 좀 익숙해져야 합니다.

 

본체를 들어내면 기본 구성품이 보입니다. 

  1. 제품 설명서 및 유심/SD카드 트레이 탈착 기구
  2. USB-C 타입 커널형 이어폰
  3. S펜 펜촉 교체용 기구
  4. S펜
  5. USB-C 타입 충전 케이블
  6. 충전기

사진에는 안 보이는데 여분 펜촉 들어있습니다.

 

기본 이어폰은 C타입 단자입니다.

이번 제품부터 이어폰 단자가 제거되었습니다. 다행히 기본 이어폰은 C타입으로 제공해 줍니다만...

이어폰이 개인적으론 별롭니다 ㅠㅠ 꺼내서 사진 찍고 도로 상자에 봉인해놨습니다.

 

이번 S펜입니다. 좌측이 전방, 우측이 후방 샷입니다.

앞서 언급한 것처럼 후방에 저 색이 다른 타원이 본체 후면에 딱 맞아야 합니다.

 

제품 후방 샷입니다.

녹색 네모칸 안에 색이 살짝 다른 게 보이시나요? 

 

펜 부착 샷입니다.

사진처럼 딱 맞게 붙여야지 방향이 다르면 부착력도 엄청 약해집니다.

 

켜봤습니다. 베젤이 얇은 게 맘에 드네요.

 

심카드 및 SD카드 트레이입니다. 한 트레이에 앞뒤로 달려있어요

앞에 심카드를 끼고 뒤에 SD카드를 꽂는 형식입니다.

 

미리 구매해둔 SD카드와 심카드를 장착했습니다. 우측처럼 장착하시면 됩니다. 

 

 

후기

 

생각보다 맘에 드는 제품입니다.

아이패드 프로 3세대와 비교해서 별로 밀리는 느낌이 들지 않습니다.

 

실제로 전 아이패드 프로 3세대를 구매 후 2주 사용 뒤에 반납했습니다.

퍼포먼스를 비교해보자면 별 차이를 못 느끼겠습니다. 물론 이 부분은 제가 헤비 한 작업을 하지 않아 못 느끼는 거라 생각됩니다. 작업용으론 확실히 아이패드가 낫다고들 합니다.

 

영상 관람 측면에선 갤럭시가 낫습니다.

둘 다 NAS와 유튜브를 통해 영상을 시청해 봤습니다만 갤럭시가 좀 더 보기 좋습니다.

사운드는 갤럭시가 낫습니다. 아이패드로 영상 볼 땐 그냥 그랬는데 갤럭시로 볼 땐 소리 좋은 게 느껴지더라고요

 

가격 면에선 말할 필요가 없습니다.

아이패드 프로 3세대 256G LTE 버전이 140입니다. 펜까지 하면 공홈 기준 156만 원입니다.

 

문제는 갤럭시엔 굿 노트처럼 킬러앱이 없습니다. 

갤럭시탭 쓰면서 가장 크게 느껴지는 부분입니다. 필기용 앱 중에 빼어난 게 없어요.

삼성 노트와 원노트를 쓸 때마다 굿 노트랑 자꾸 비교하게 됩니다.

 

펜 사용경험은 애플이 압승입니다.

일단 S펜은 디자인부터 별로예요. 실제 애플 펜슬과 비교해보면 디자인이 확 차이 납니다. S펜은 펜같이 안 생겼잖아요...

애초에 펜 동작 방식 자체가 달라서 어쩔 순 없지만 S펜은 EMR방식이고 애플 펜슬은 AES 방식입니다.

제가 민감한걸 수도 있지만 딜레이 느껴집니다.

 

 

지금 S5e나 S4를 갖고 계신 분들이 라면 고민 많이 되실 거로 생각됩니다.

S5e를 가신분들은 펜이 별로 필요 없으셔서 고르셨겠죠? 그럼 굳이 S6을 살 필욘 없다고 봅니다.

이제 나온 지 반년쯤 지났나요? 스펙이 엄청 밀리는 것도 아니고 펜도 잘 안 쓰는데 살 필욘없을것 같습니다.

S4를 가신분들은 특히 고민이 많이 되실 거 같은데 저 같으면 안 갈아탑니다. 탭 S시리즈는 나름 프리미엄 타이틀을 달고 나온 제품들입니다. 나온 시점에서 스펙이 많이 밀리지 않아요.

딱하나 아쉬운 건 전작들의 AP지만 AP 하나로 이제 1년 된 제품을 버리고 수십만 원 내고 구매하진 않을 거 같습니다.

 

S3 쓰고 계시는 분들은 적극적으로 교체를 권장합니다. 홍보는 아녜요 오해하지 마시고요.

저도 S3에서 갈아탔습니다. 네 체감 많이 돼요

 

아이패드 쓰시던 분들은 UX가 너무 다르기 때문에 제품에 문제가 생긴 경우가 아니라면 추천은 안 드립니다.

애초에 생태계가 너무 달라서 갤럭시로 갈아타기도 힘들고 적응하기도 불편합니다. 명확한 단점도 보일 테고요.

 

 

 

키보드 커버

 

배송받자마자 주문했습니다.

 

주문일 보이세요? 근데 아직도 배송 지시랍니다.

 

네 망했어요... 아는 분은 벌써 배송받고 쓰고 계시던데 전 늦었나 봅니다.

추석 이후 배송받은 후 사용해본 뒤 후기 올리겠습니다.

 

키보드 커버 후기는 여기서 확인해 주시기 바랍니다.

 

반응형

References: Do it! 코틀린 프로그래밍

코틀린에서의 추상 클래스에 대해 알아봅니다.

 

 

1. 추상 클래스

 

추상 클래스는 abstract 키워드와 함께 전업됩니다.

추상 클래스는 일반적인 방법으로 인스턴스화 될 수 없습니다.

추상 클래스를 상속하는 하위 클래스가 어떻게 만들어져야 하는지 나타내는 용도로 사용됩니다.

 

추상 클래스의 멤버인 프로퍼티나 메서드 또한 abstract로 선언될 수 있으며 이를 추상 프로퍼티 혹은 추상 메서드라고 합니다. 추상 프로퍼티나 추상 메서드가 하나라도 존재한다면 그 클래스는 추상 클래스가 되어야 합니다.

추상 프로퍼티와 추상 메서드는 abstract로 선언 함으로써 미완성의 의미를 줄 수 있습니다.

 

다음 예시를 통해 추상 클래스에 대해 알아보겠습니다.

 

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

 

  • abstract class Vehicle : 추상 클래스 정의
  • abstract var maxSpeed : 추상 프로퍼티 정의
  • var year : 일반 프로퍼티 정의
  • abstract fun start / stop : 추상 메서드 정의
  • fun displaySpecs : 일반 메서드 정의

해당 클래스는 객체를 생성할 수 없고 설계의 역할을 합니다.

abstract선언자가 있는 모든 프로퍼티와 메서드는 반드시 자식 클래스에서 정의되어야 합니다.

이 추상 클래스를 상속하는 자식 클래스를 만들어 보겠습니다.

 

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("Motorcycle started.")
    override fun stop() = println("Motorcycle stopped.")
}

 

  • override : 추상 클래스에서 선언된 모든 추상 프로퍼티와 추상 메서드를 구현.

모든 추상 프로퍼티와 추상 메서드를 구현하지 않으면 컴파일러에서 오류를 반환하는 것을 확인할 수 있습니다.

모든 추상 클래스와 프로퍼티를 구현 후 다음 메인 함수를 실행시켜 봅시다.

 

fun main() {
    val car = Car("SuperMatiz", "Yellow", 1100.0, 270.0)
    var motor = Motorcycle("DreamBike", "Red", 173.0, 100.0)
    car.year = "2013"
    car.displaySpecs()
    car.start()
    motor.displaySpecs()
    motor.start()
}

 

실행결과는 다음과 같습니다.

추상 클래스는 이처럼 하위 클래스에서 반드시 가져야 하는 프로퍼티나 메서드를 제시할 수 있습니다. 또한 일반 프로퍼티나 메서드를 만들어 공통적으로 필요한 기능을 정의할 수도 있습니다.

 

만약 하위 클래스를 생성하지 않고 단일 인스턴스로 객체를 생성하려면 다음과 같이 수행하면 됩니다.

 

abstract class Printer{
    abstract fun print()
}
var myPrinter = object: Printer(){
    override fun print() = println("Print!")
}
fun main() {
    myPrinter.print()
}

 

  • abstract class Printer : 추상 클래스를 정의합니다.
  • abstract fun print() : 추상 메서드를 정의합니다.
  • var myPrinter = object: Printer() : 익명 객체를 생성하고 Printer() 추상 클래스를 상속받습니다.
  • override fun print() : 추상 메서드를 구현합니다.

실행 결과는 다음과 같습니다.

익명 객체를 지정하는 object 키워드를 사용해 추상 메서드를 인스턴스화 시킬 수 있습니다.

 

 

 

 

 

반응형

'Programming' 카테고리의 다른 글

[Kotlin] 32. 데이터 클래스  (0) 2019.09.12
[Kotlin] 31. 인터페이스  (0) 2019.09.12
[Kotlin] 29. 컴패니언 객체  (0) 2019.09.01
[Kotlin] 28. 위임(Delegation)  (0) 2019.09.01
[Kotlin] 27. 지연 초기화: lazy  (0) 2019.08.27

References: Do it! 코틀린 프로그래밍

코틀린에서의 컴패니언 객체에 대해 알아봅니다.

 

 

1. 컴패니언(companion)

 

코틀린은 static 키워드가 없는 대신 companion객체를 제공합니다.

 

컴패니언 객체는 프로그램 실행 시 독립적인 메모리 공간을 할당받습니다.

따로 인스턴스화 할 필요가 없으며 어떤 객체라도 동일한 참조 값을 갖고 있어 해당 클래스의 상태에 관계없이 접근할 수 있습니다.

따라서 모든 객체에 의해 공유되는 효과를 갖습니다.

 

다음 예시를 통해 컴패니언 객체를 사용하는 방법에 대해 알아봅시다

 

class Person{
    var id: Int = 0
    var name: String = "smoh"
    companion object {
        var language: String = "Korean"
        fun work(){ println("working ...") }
    }
}
fun main() {
    println(Person.language)
    Person.language = "English"
    println(Person.language)
    Person.work()
    //println(Person.name)
}

 

  • companion object : 컴패니언 객체를 선언합니다. 해당 객체의 내부에 선언된 내용은 프로그램 시작 시 초기화되어 바로 사용할 수 있습니다.
  • //println(Person.name) : 컴패니언 객체 외부의 프로퍼티를 사용하기 위해선 초기화가 먼저 필요합니다.

위 코드를 실행하면 다음과 같은 결과를 확인할 수 있습니다

 

컴패니언 객체는 주로 실제 객체의 싱글톤(Singleton)으로 정의됩니다.

이는 전역 변수를 사용하지 않고 객체를 하나만 생성하도록 하며 생성된 객체를 어디서든 참조하도록 하기 위함입니다.

이와 같이 하면 객체가 동일한 정보를 가질 때 하나의 메모리만 유지해 메모리를 절약할 수 있는 장점이 있습니다.

 

 

 

 

 

 

반응형

'Programming' 카테고리의 다른 글

[Kotlin] 31. 인터페이스  (0) 2019.09.12
[Kotlin] 30. 추상 클래스  (0) 2019.09.05
[Kotlin] 28. 위임(Delegation)  (0) 2019.09.01
[Kotlin] 27. 지연 초기화: lazy  (0) 2019.08.27
[Kotlin] 26. 지연초기화: lateinit  (0) 2019.08.27

References: Do it! 코틀린 프로그래밍

코틀린에서 by를 이용한 위임에 대해 알아봅니다.

 

1. 위임(Delegation)

 

현실에서의 위임(Delegation)이란 특정한 일을 대신하는 중간자의 역할을 말합니다.

코틀린에서도 by를 이용한 위임을 사용해 특정 클래스를 확장하거나 이용할 수 있습니다.

 

기본적으로 코틀린의 라이브러리는 open 되지 않은 채 제공됩니다. 따라서 상속이나 직접 클래스에 기능 확장이 어렵습니다.

따라서 필요한 경우에만 상속과 비슷하게 위임을 사용하여 해당 클래스의 모든 기능을 사용하면서 기능을 추가 확장 구현을 가능하게 합니다.

 

 

2. by에 의한 클래스 위임

 

by를 사용하면 하나의 클래스가 다른 클래스에 위임 하도록 선언할 수 있습니다.

위임이 선언되면 위임된 클래스가 가지는 멤버를 참조없이 호출할 수 있게 됩니다.

 

다음 예시를 통해 by를 이용한 클래스 위임에 대해 알아봅시다.

 

interface Car { fun go(): String }
class VanImpl(val power: String): Car{
    override fun go() = "은 짐을 적재하며 $power 을 가집니다."
}
class SportImpl(var power: String): Car{
    override fun go() = "은 경주용에 사용되며 $power 을 가집니다."
}
class CarModel(var model: String, impl: Car): Car by impl{
    fun carInfo(){ println("$model ${go()}") }
}
fun main(){
    var myDamas = CarModel("Damas", VanImpl("100마력"))
    var my350z = CarModel("350z", SportImpl("350마력"))
    myDamas.carInfo()    
    my350z.carInfo()
}

 

  • VanImpl / SportImpl : Car 인터페이스를 상속받아 구현합니다.
  • Car by impl인자 impl은 CarModel에 의해 위임됩니다. 타입이 Car이므로 특정 참조 없이 Car의 멤버를 사용할 수 있게 됩니다.

위 예시의 실행결과는 다음과 같습니다.

 

 

 

3. observable() 함수에 의한 위임

 

observable() 함수는 특정 프로퍼티를 감시하고 있다가 변화가 감지되면 호출되어 처리됩니다.

특정 변경 이벤트에 따라 호출되므로 콜백이라고 부르기도 합니다.

 

다음 예시를 통해 observable() 함수의 사용법에 대해 알아보겠습니다.

 

import kotlin.properties.Delegates
class User{
    var name: String by Delegates.observable("NONAME"){
        prop, old, new -> println("$old -> $new")
    }
}
fun main(){
    var user = User()
    user.name="smoh"
    user.name="Anonymous"
}

 

  • by Delegates.observable("NONAME"해당 프로퍼티에 obervable 함수로 위임합니다. 초깃값은 "NONAME"으로 설정합니다.
  • new -> println("$old -> $new") : 프로퍼티의 변경이 감지되면 new에 할당되어 있는 람다식이 실행됩니다.

 

위의 함수를 실행하면 다음과 같은 결과를 확인할 수 있습니다.

 

 

User클래스의 name이 변경될 때마다 지정해둔 람다식이 실행된 것을 알 수 있습니다.

 

 

4. vetoable() 함수에 의한 위임

 

obervable() 함수와 유사한 함수입니다. 프로퍼티 값이 변경되면 호출되며 콜백이 true값을 반환할 때 만 프로퍼티를 업데이트합니다.

 

다음 예시를 통해 알아보겠습니다.

 

import kotlin.properties.Delegates
fun main() {
    var max: Int by Delegates.vetoable(0){
        prop, old, new -> new > old
    }
    println(max)
    max = 10
    println(max)
    max = 5
    println(max)
}

 

  • by Delegates.vetoable(0) : 해당 프로퍼티에 vetoable() 함수로 위임합니다. 초깃값은 0으로 설정합니다.
  • new -> new > old : 콜백 함수입니다. 해당 조건이 true로 리턴되면 new값을 prop에 새로 저장합니다.

 

위 함수를 실행한 결과는 다음과 같습니다.

 

 

 

초깃값은 0으로 초기화됩니다. 이후 10은 0보다 크니까 프로퍼티 값에 저장합니다. 그다음 5는 10보다 작으므로 프로퍼티 값을 경신하지 않습니다.

 

 

 

반응형

'Programming' 카테고리의 다른 글

[Kotlin] 30. 추상 클래스  (0) 2019.09.05
[Kotlin] 29. 컴패니언 객체  (0) 2019.09.01
[Kotlin] 27. 지연 초기화: lazy  (0) 2019.08.27
[Kotlin] 26. 지연초기화: lateinit  (0) 2019.08.27
[Kotlin] 25. Custom Getter & Setter  (0) 2019.08.27

References: Do it! 코틀린 프로그래밍

코틀린에서 lazy을 이용한 지연 초기화에 대해 알아봅니다.

 

 

1. lazy

 

앞서 알아본 lateinit는 val을 허용하지 않습니다.

var은 언제든 값이 변경될 수 있다는 문제를 갖고 있습니다.

lazy를 사용하면 val로 선언해도 지연 초기화를 사용할 수 있습니다.

 

lazy는 다음과 같은 특징을 갖습니다.

  • val에서만 사용 가능하다.
  • 호출 시점에 by laze {...} 정의에 의해 초기화를 진행한다.
  • 람다식으로 구성되어 lazy인스턴스 반환 값을 갖는 함수다.

 

 

2. lazy를 통한 프로퍼티 지연 초기화

 

다음 예시를 통해 lazy를 통해 프로파티를 지연 초기화시키는 방법에 대해 알아봅시다.

 

class LazyTest{
    init{ println("Init block") }
    val subject by lazy {
        println("Initialized by lazy.")
        "Kotlin programming"
    }
    fun flow(){
        println("Not initialized")
        println("Subject one: $subject")
        println("Subject two: $subject")
    }
}
fun main() {
    val lazyTest = LazyTest()
    lazyTest.flow()
}

 

  • val subject by lazy { : by lazy를 통한 지연 초기화. 프로퍼티에 최초 접근될 때 블록 내부의 내용이 실행된다.
  • println("Subject one: $subject") : 프로퍼티에 최초 접근하는 코드. 이때 by lazy 블록의 코드가 실행된다.
  • println("Subject two: $subject") : 프로퍼티가 이미 초기화되어 by lazy 블록의 코드가 실행되지 않는다.

위의 코드를 실행하면 다음과 같은 결과를 확인할 수 있습니다.

 

 

 

3. lazy를 통한 객체 지연 초기화

 

다음 예시를 통해 lazy를 통해 객체를 지연 초기화시키는 방법에 대해 알아봅시다.

 

class Person(val name: String, val aget: Int)
fun main() {
    var isPersonInitialized: Boolean = false
    val person: Person by lazy{
        isPersonInitialized = true
        Person("smoh", 28)
    }
    val personDelegate = lazy{ Person("smoh", 28) }
    println("person Init: $isPersonInitialized")
    println("personDelegate Init: ${personDelegate.isInitialized()}")
    println("person.name: ${person.name}")
    println("personDelegate.value.name = ${personDelegate.value.name}")
    println("person Init: $isPersonInitialized")
    println("personDelegate Init: ${personDelegate.isInitialized()}")
}

 

  • by lazy: by lazy를 사용한 객체의 지연 초기화. 
  • lazy{ Person("smoh", 28) : 위임 변수를 사용한 객체의 초기화
  • ${person.name} : 객체 내 프로퍼티에 접근할 때 지연 초기화가 이루어집니다.
  • ${personDelegate.value.name} : 객체 내 프로퍼티에 접근할 때 지연 초기화가 이루어집니다.

 

 

4. lazy의 모드들

 

lazy에는 SYNCRONIZED, PUBLICATION, NONE의 세 가지 모드가 있습니다.

 

  • SYNCRONIZED : lock을 사용해 단일 스레드만 사용하는 걸 보장합니다. 기본값입니다.
  • PUBLICATION : 여러 군데에서 호출될 수 있으나 처음 초기화된 후 반환 값을 사용합니다.
  • NON : lock을 사용하지 않아 빠르지만 여러 스레드가 접근할 수 있어 값의 일관성을 보장할 수 없습니다.

다른 모드를 사용하고 싶으면 by lazy(모드 이름){ ... }과 같이 사용하면 됩니다.

 

 

 

 

반응형

References: Do it! 코틀린 프로그래밍

코틀린에서 lateinit을 이용한 지연 초기화에 대해 알아봅니다.

 

 

1. 지연 초기화

 

프로퍼티를 선언하면 기본적으로 모두 초기화를 해야 합니다.

만약 객체 정보가 나중에 나타나는 경우 객체 생성과 동시에 초기화가 힘들 수 있습니다.

이럴 경우에 지연 초기화를 사용합니다.

 

 

2. lateinit

 

lateinit키워드를 사용하면 프로퍼티에 값이 바로 할당되지 않아도 컴파일러에서 이를 허용합니다.

단 lateinit은 다음과 같은 제약사항이 있습니다.

 

  • var로 선언된 프로퍼티에만 사용할 수 있다.
  • 프로퍼티에 대한 게터와 세터를 사용할 수 없다.

다음 예시를 통해 확인해 보도록 하겠습니다.

 

class Person{
    lateinit var name: String
    fun test(){
        if(!::name.isInitialized){ println("Not initialized.") }
        else {{ println("Initialized.") }
    }
}
fun main() {
    var person = Person()
    person.test()
    person.name = "smoh"
    person.test()
}

 

  • lateinit var name: String: 지연된 초기화를 허용합니다.
  • ::name: 프로퍼티 참조를 위해 콜론을 두 개 사용함.
  • .isInitialized: 프로퍼티가 초기화되었는지 확인하는 코틀린 표준 함수 API

위의 코드를 실행하면 결과는 다음과 같습니다.

만약 초기화 없이 사용하면 어떻게 될까요?

코드를 다음과 같이 수정한 후 실행시켜 봅시다.

 

...
fun main() {
    var person = Person()
    println("Name: ${person.name})
}

 

위와 같이 lateinit 프로퍼티인 name이 초기화되지 않았다는 에러가 발생합니다.

 

 

이번에는 생성자를 통해 객체를 지연된 초기화하는 법을 알아보겠습니다.

 

data class Person(var name: String, var age: Int)
lateinit var person: Person
fun main() {
    person = Person("smoh", 28)
    println(person.name + " is "+ person.age.toString())
}

 

  • lateinit : 객체를 지연 초기화하도록 함.
  • person = Person("smoh", 28) : 생성자 호출 시점에서 객체가 초기화됨.

main함수 밖의 person객체는 생성자를 통해 선언되지 않았지만 lateinit에 의해 지연 초기화가 허용됩니다.

 

 

 

반응형

References: Do it! 코틀린 프로그래밍

코틀린에서 Getter와 Setter를 커스텀하는 방법에 대해 알아봅니다.

 

 

1. 사용자 정의 게터 및 세터

 

코틀린에서 게터와 세터는 별도의 작업없이 자동으로 생성되어 "." 를 통해 사용할 수 있습니다.

만약 게터와 세터에 추가적인 작업이 필요하다면 사용자가 직접 정의하여 사용할 수 도 있습니다.

다음 예시에서 확인해 보도록 하겠습니다.

 

class User(_id: Int, _name: String, _age: Int){
    val id: Int = _id
    var name: String = _name
        set(value){
            println("The name was changed.")
            field = value.toUpperCase()
        }
    var age: Int = _age
}

 

  • User(1, "test", 28): 주 생성자를 이용해 객체를 생성합니다. 세터는 사용되지 않았습니다.
  • user.name = "smoh": 세터를 사용해 name의 값을 변경합니다. 대문자로 저장됩니다.

 

위 예시에서 세터는 입력받은 값을 대문자로 변경하고 저장합니다.

결괏값은 다음과 같습니다.

 

 

2. 보조 프로퍼티의 사용

 

임시적으로 사용할 프로퍼티를 선언한 후에 게터나 세터에서 사용할 수 도 있습니다.

다음 예시로 확인해 보겠습니다.

 

class User(_id: Int, _name: String, _age: Int){
    val id: Int = _id
    private var tempName: String? = null
    var name: String = _name
    get(){
        if(tempName == null) { tempName = "NONAME" }
        return tempName ?: throw AssertionError("Asserted by others")
    }
    var age: Int = _age
}
fun main() {
    val user = User(1, "smoh", 28)
    user.name = ""
    println("user.name = ${user.name}")
}

 

  • private var tempName: String? = null: 이름이 null이 되는 경우를 처리하기 위한 보조 프로퍼티

위 예시의 실행 결과는 다음과 같습니다.

 

 

3. 프로퍼티 오버 라이딩

 

프로퍼티는 기본적으로 final로 선언되어 오버 라이딩이 불가합니다. 만약 오버 라이딩을 하고 싶다면 open키워드를 통해 프로퍼티를 선언해야 합니다.

다음 코드를 통해 확인해 봅시다.

 

open class First{
    open val a: Int = 0
        get(){
            println("First a")
            return field
        }
    val b: Int = 0
}
class Second: First() {
    override val a: Int = 0
        get(){
            println("Second a")
            return field +1
        }
}
fun main() {
    val sec = Second()
    println(sec.a)
    println(sec.b)
}

 

  • open val a: Int = 0: 프로퍼티 a를 오버라이드 가능한 상태로 선언합니다.
  • override val a: Int = 0: 프로퍼티 a를 오버라이드 합니다.

위 코드의 실행 결과는 다음과 같습니다.

 

b에는 open 키워드가 없어 오버 라이딩이 불가능합니다. 만약 val로 프로퍼티를 선언했다면 오버라이딩이 불가능합니다.

 

 

 

 

반응형

+ Recent posts