코 틀린에서 데이터 클래스 확장


176

데이터 클래스는 Java의 구식 POJO를 대체하는 것으로 보입니다. 이러한 클래스가 상속을 허용 할 것으로 예상되지만 데이터 클래스를 확장하는 편리한 방법은 없습니다. 내가 필요한 것은 다음과 같습니다.

open data class Resource (var id: Long = 0, var location: String = "")
data class Book (var isbn: String) : Resource()

component1()메소드 의 충돌로 인해 위의 코드가 실패 합니다. data클래스 중 하나에 만 주석을 남겨두면 작동하지 않습니다.

아마도 데이터 클래스를 확장하는 또 다른 관용구가 있습니까?

UPD : 자식 자식 클래스에만 주석을 달 수 있지만 data주석은 생성자에 선언 된 속성 만 처리합니다. 즉, 모든 부모의 속성을 선언 open하고 무시해야합니다.

open class Resource (open var id: Long = 0, open var location: String = "")
data class Book (
    override var id: Long = 0,
    override var location: String = "",
    var isbn: String
) : Resource()

3
Kotlin은 암시 적 componentN()으로 N 번째 속성 값을 반환 하는 메서드 를 만듭니다 . 다중 선언
Dmitry

속성을 열기 위해 Resource abstract를 만들거나 컴파일러 플러그인을 사용할 수도 있습니다. 코 틀린은 개방 / 폐쇄 원칙에 대해 엄격합니다.
Željko Trogrlić

@Dmitry 데이터 클래스를 확장 할 수 없기 때문에 부모 클래스 변수를 열어두고 단순히 자식 클래스에서 변수를 재정의하는 "솔루션"이 "ok"해결 방법입니까?
Archie G. Quiñones

답변:


163

진실은 데이터 클래스가 상속과 잘 어울리지 않는다는 것입니다. 데이터 클래스의 상속을 금지하거나 심각하게 제한하는 것을 고려하고 있습니다. 예를 들어, equals()비추 상 클래스의 계층 구조에서 올바르게 구현할 수있는 방법이없는 것으로 알려져 있습니다.

따라서 내가 제공 할 수있는 모든 것 : 데이터 클래스에 상속을 사용하지 마십시오.


Andrey, 데이터 클래스에서 생성되는 equals ()는 어떻게 작동합니까? 유형이 정확하고 모든 공통 필드가 동일한 경우에만 또는 필드가 동일한 경우에만 일치합니까? 대수 데이터 유형을 근사화하는 클래스 상속의 가치 때문에이 문제에 대한 해결책을 제시하는 것이 좋습니다. 흥미롭게도, 한마디로 검색하면 Martin Odersky의 주제에 대한이 토론이 밝혀졌습니다 : artima.com/lejava/articles/equality.html
orospakr

3
이 문제에 대한 해결책이 많이 있다고 생각하지 않습니다. 지금까지 데이터 클래스에는 데이터 서브 클래스가 없어야한다고 생각합니다.
Andrey Breslav

3
ORM과 같은 라이브러리 코드가 있고 영구 데이터 모델을 갖도록 모델을 확장하려면 어떻게해야합니까?
Krupal Shah

3
데이터 클래스에 대한 @AndreyBreslav Docs는 Kotlin 1.1 이후의 상태를 반영하지 않습니다. 1.1 이후로 데이터 클래스와 상속은 어떻게 함께 작동합니까?
Eugen Pechanec

2
@EugenPechanec 다음 예 참조 : kotlinlang.org/docs/reference/…
Andrey Breslav

114

생성자 외부의 수퍼 클래스에서 속성을 추상으로 선언하고 하위 클래스에서 재정의합니다.

abstract class Resource {
    abstract var id: Long
    abstract var location: String
}

data class Book (
    override var id: Long = 0,
    override var location: String = "",
    var isbn: String
) : Resource()

15
이것은 가장 유연한 것으로 보입니다. 데이터 클래스가 서로 상속받을 수 있기를 간절히 바란다 ...
Adam

안녕하세요, Data Class Inheritance를 깔끔하게 처리해 주셔서 감사합니다. 추상 클래스를 제네릭 형식으로 사용할 때 문제가 발생합니다. 내가 얻을 Type Mismatch"필수 T는 찾았 : 리소스"오류가 발생했습니다. 제네릭에서 어떻게 사용할 수 있는지 알려주시겠습니까?
ashwin mahajan

또한 추상 클래스에서 제네릭이 가능한지 알고 싶습니다. 예를 들어 위치가 상속 된 데이터 클래스와 사용자 정의 클래스의 문자열 인 경우 어떻게해야 Location(long: Double, lat: Double))합니까?
Robbie Cronin

2
나는 거의 희망을 잃었다. 감사!
Michał Powłoka

매개 변수를 복제하는 것은 상속을 구현하는 나쁜 방법으로 보입니다. 기술적으로 Book은 Resource에서 상속 받으므로 id와 위치가 존재한다는 것을 알아야합니다. 실제로 지정할 필요는 없습니다.
AndroidDev

23

위의 추상 클래스를 사용하는 솔루션은 실제로 해당 클래스를 생성하고 데이터 클래스가 해당 클래스에서 확장되도록합니다.

추상 클래스를 원하지 않으면 인터페이스 를 사용하는 것이 어떻습니까?

Kotlin의 인터페이스는 이 기사에 표시된대로 속성 을 가질 수 있습니다 .

interface History {
    val date: LocalDateTime
    val name: String
    val value: Int
}

data class FixedHistory(override val date: LocalDateTime,
                        override val name: String,
                        override val value: Int,
                        val fixedEvent: String) : History

Kotlin이 이것을 어떻게 컴파일하는지 궁금했습니다. Intellij [Kotlin bytecode] 기능을 사용하여 생성 된 동등한 Java 코드는 다음과 같습니다.

public interface History {
   @NotNull
   LocalDateTime getDate();

   @NotNull
   String getName();

   int getValue();
}

public final class FixedHistory implements History {
   @NotNull
   private final LocalDateTime date;
   @NotNull
   private final String name;
   private int value;
   @NotNull
   private final String fixedEvent;

   // Boring getters/setters as usual..
   // copy(), toString(), equals(), hashCode(), ...
}

보시다시피, 일반 데이터 클래스와 똑같이 작동합니다!


3
불행히도 데이터 클래스의 인터페이스 패턴을 구현하는 것은 Room 아키텍처에서 작동하지 않습니다.
Adam Hurwitz

@AdamHurwitz 너무 나쁘다. 나는 그것을 알아 차리지 못했다!
Tura

4

@ Željko Trogrlić의 답변이 정확합니다. 그러나 추상 클래스에서와 동일한 필드를 반복해야합니다.

또한 추상 클래스 내에 추상 서브 클래스가있는 경우 데이터 클래스에서 이러한 추상 서브 클래스에서 필드를 확장 할 수 없습니다. 먼저 데이터 서브 클래스 를 만든 다음 필드를 정의해야합니다.

abstract class AbstractClass {
    abstract val code: Int
    abstract val url: String?
    abstract val errors: Errors?

    abstract class Errors {
        abstract val messages: List<String>?
    }
}



data class History(
    val data: String?,

    override val code: Int,
    override val url: String?,
    // Do not extend from AbstractClass.Errors here, but Kotlin allows it.
    override val errors: Errors?
) : AbstractClass() {

    // Extend a data class here, then you can use it for 'errors' field.
    data class Errors(
        override val messages: List<String>?
    ) : AbstractClass.Errors()
}

History.Errors를 AbstractClass.Errors.Companion.SimpleErrors 또는 외부로 이동하여 상속되는 각 데이터 클래스에서 복제하지 않고 데이터 클래스에서 사용할 수 있습니까?
TWiStErRob 2016 년

트윗 담아 가기 History.Errors는 모든 클래스에서 변경 될 수 있으므로 재정의해야합니다 (예 : 필드 추가).
CoolMind 2016 년

4

코 틀린 특성이 도움이 될 수 있습니다.

interface IBase {
    val prop:String
}

interface IDerived : IBase {
    val derived_prop:String
}

데이터 클래스

data class Base(override val prop:String) : IBase

data class Derived(override val derived_prop:String,
                   private val base:IBase) :  IDerived, IBase by base

샘플 사용법

val b = Base("base")
val d = Derived("derived", b)

print(d.prop) //prints "base", accessing base class property
print(d.derived_prop) //prints "derived"

이 접근법은 @Parcelize의 상속 문제에 대한 해결 방법이 될 수도 있습니다.

@Parcelize 
data class Base(override val prop:Any) : IBase, Parcelable

@Parcelize // works fine
data class Derived(override val derived_prop:Any,
                   private val base:IBase) : IBase by base, IDerived, Parcelable

2

비 데이터 클래스에서 데이터 클래스를 상속 할 수 있습니다. 상속시 컴파일러 생성 데이터 클래스 메서드를 일관되고 직관적으로 작동시킬 수있는 방법이 없기 때문에 다른 데이터 클래스에서 데이터 클래스를 상속 할 수 없습니다.


1

equals()계층 구조에서 올바르게 구현 하는 것은 실제로 피클이지만 여전히 다른 방법 (예 :) 상속을 지원하는 것이 좋습니다 toString().

좀 더 구체적으로 설명하자면, 다음과 같은 구조를 가지고 있다고 가정 해 봅시다 toString().

abstract class ResourceId(open val basePath: BasePath, open val id: Id) {

    // non of the subtypes inherit this... unfortunately...
    override fun toString(): String = "/${basePath.value}/${id.value}"
}
data class UserResourceId(override val id: UserId) : ResourceId(UserBasePath, id)
data class LocationResourceId(override val id: LocationId) : ResourceId(LocationBasePath, id)

우리의 가정 UserLocation단체가 (자신의 적절한 리소스 ID를 반환 UserResourceIdLocationResourceId호출, 각각을) toString()하나에서 ResourceId: 모든 하위 유형에 대한 일반적으로 유효 아주 좋은 작은 표현 될 수 있습니다 /users/4587, /locations/23하위 유형의 비는 오버라이드 (override) 상속 때문에, 등 불행히도 toString()으로부터 방법 추상 기본은 ResourceId, 호출 toString()사실은 덜 꽤 표현 결과 : <UserResourceId(id=UserId(value=4587))>,<LocationResourceId(id=LocationId(value=23))>

위의 모델을 모델링하는 다른 방법이 있지만 이러한 방법을 사용하면 비 데이터 클래스를 사용하도록 강요하거나 (데이터 클래스의 많은 이점을 생략 함) toString()모든 데이터 클래스에서 구현을 복사 / 반복하게 됩니다. (상속하지 않음).


0

비 데이터 클래스에서 데이터 클래스를 상속 할 수 있습니다.

기본 수업

open class BaseEntity (

@ColumnInfo(name = "name") var name: String? = null,
@ColumnInfo(name = "description") var description: String? = null,
// ...
)

어린이 수업

@Entity(tableName = "items", indices = [Index(value = ["item_id"])])
data class CustomEntity(

    @PrimaryKey
    @ColumnInfo(name = "id") var id: Long? = null,
    @ColumnInfo(name = "item_id") var itemId: Long = 0,
    @ColumnInfo(name = "item_color") var color: Int? = null

) : BaseEntity()

효과가있었습니다.


이제는 이름 및 설명 속성을 설정할 수 없으며 생성자에 추가하면 데이터 클래스에 기본 클래스 속성을 재정의하는 val / var이 필요합니다.
브릴 파핀
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.