Kotlin'de veri sınıfını genişletme


176

Veri sınıfları, Java'daki eski moda POJO'ların yerini alıyor gibi görünüyor. Bu sınıfların kalıtıma izin vermesi oldukça beklenebilir, ancak bir veri sınıfını genişletmek için uygun bir yol göremiyorum. İhtiyacım olan böyle bir şey:

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

Yukarıdaki kod, component1()yöntemlerin çakışması nedeniyle başarısız oluyor . dataSınıflardan yalnızca birinde not bırakma işi de yapmaz.

Belki de veri sınıflarını genişletmek için başka bir deyim vardır?

UPD: Yalnızca alt çocuk sınıfına ek açıklama ekleyebilirim, ancak dataek açıklama yalnızca yapıcıda bildirilen özellikleri işler. Yani, tüm ebeveynin özelliklerini ilan etmeli openve çirkin olanları geçersiz kılmalıyım:

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 dolaylı componentN()olarak N-th özelliğinin değerini döndüren yöntemler oluşturur . Üzerinde doküman Bkz Çok Bildirgeleri'nin
Dmitry

Özellikleri açmak için Kaynak özeti yapabilir veya derleyici eklentisini kullanabilirsiniz. Kotlin açık / kapalı prensibi konusunda katıdır.
jeljko Trogrlić

@Dmitry Bir veri sınıfını genişletemediğimiz için, üst sınıf değişkenini açık tutma ve alt sınıfta bunları geçersiz kılma "çözümünüz" geçici bir çözüm olabilir mi?
Archie G. Quiñones

Yanıtlar:


164

Gerçek şu ki: veri sınıfları kalıtımla çok iyi oynamıyor. Veri sınıflarının mirasını yasaklamayı veya ciddi şekilde kısıtlamayı düşünüyoruz. Örneğin, equals()soyut olmayan sınıflar üzerinde bir hiyerarşide doğru bir şekilde uygulamanın bir yolu olmadığı bilinmektedir .

Yani, tüm sunabileceğim: veri sınıflarıyla kalıtım kullanma.


Hey Andrey, veri sınıflarında oluşturulduğu haliyle equals () nasıl çalışıyor? Yalnızca türün tam olması ve tüm ortak alanların eşit olması veya yalnızca alanların eşit olması durumunda eşleşir mi? Cebirsel veri türlerini tahmin etmek için sınıf mirasının değeri nedeniyle, bu soruna bir çözüm bulmaya değer olabilir. İlginç bir şekilde, bir cursory araması
orospakr

3
Bu soruna çok fazla çözüm olduğuna inanmıyorum. Benim düşüncem, veri sınıflarının veri alt sınıflarına sahip olmaması gerektiğidir.
Andrey Breslav

3
bazı ORM gibi bir kütüphane kodumuz varsa ve modelini kalıcı veri modelimize sahip olacak şekilde genişletmek istiyorsak ne olur?
Krupal Shah

3
@AndreyBreslav Veri Sınıfları Dokümanları Kotlin 1.1'den sonraki durumu yansıtmaz. Veri sınıfları ve miras 1.1'den beri nasıl birlikte oynuyor?
Eugen Pechanec

2
@EugenPechanec Bu örneğe bakın: kotlinlang.org/docs/reference/…
Andrey

114

Yapıcı dışındaki süper sınıftaki özellikleri soyut olarak bildirin ve alt sınıfta geçersiz kılın.

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
bu en esnek gibi görünüyor. Ben de olsa veri sınıflarının birbirinden miras almasını isterdim ...
Adam

Merhaba Sir, Data Class Mirasını temizlemenin düzgün yolu için teşekkürler. Soyut sınıfı Genel Tür olarak kullandığımda bir sorunla karşılaşıyorum. Bir Type Mismatchhata alıyorum: "Gerekli T, Bulundu: Kaynak". Bana Generics'te nasıl kullanılabileceğini söyleyebilir misiniz?
ashwin mahajan

Soyut sınıflarda jeneriklerin mümkün olup olmadığını bilmek istiyorum. Örneğin, konum bir miras alınan veri sınıfında ve özel bir sınıfta bir Dize ise ( Location(long: Double, lat: Double))başka bir diyelim mi?)
Robbie Cronin

2
Neredeyse umudumu kaybettim. Teşekkürler!
Michał Powłoka

Parametrelerin çoğaltılması, kalıtım uygulamasının zayıf bir yolu gibi görünmektedir. Teknik olarak, Kitap Kaynaktan devraldığından, kimlik ve konumun var olduğunu bilmelidir. Bunları belirtmek için gerçekten bir ihtiyaç olmamalı.
AndroidDev

23

Soyut sınıfı kullanan yukarıdaki çözüm aslında karşılık gelen sınıfı üretir ve veri sınıfının bu sınıftan genişlemesine izin verir.

Soyut sınıfı tercih etmiyorsanız, arayüz kullanmaya ne dersiniz ?

Kotlin arabirimi bu makalede gösterildiği gibi özelliklere sahip olabilir ..

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'in bunu nasıl derlediğini merak ettim. Eşdeğer Java kodu (Intellij [Kotlin bayt kodu] özelliği kullanılarak oluşturulmuştur):

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(), ...
}

Gördüğünüz gibi, normal bir veri sınıfı gibi çalışır!


3
Ne yazık ki bir veri sınıfı için arayüz kalıbını uygulamak Room mimarisiyle çalışmaz.
Adam Hurwitz

@AdamHurwitz Bu çok kötü .. Fark etmedim!
Tura

4

@ Željko Trogrlić cevap doğrudur. Ancak soyut bir sınıftaki alanları tekrarlamalıyız.

Ayrıca, soyut sınıfın içinde soyut alt sınıflarımız varsa , veri sınıfında bu soyut alt sınıflardan alanları genişletemeyiz. Önce veri alt sınıfını oluşturmalı ve sonra alanları tanımlamalıyız.

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'u AbstractClass.Errors.Companion.SimpleErrors veya dışına taşıyabilir ve her miras veri sınıfında çoğaltmak yerine veri sınıflarında kullanabilir miyiz?
TWiStErRob

@TWiStErRob, böyle ünlü birini duyduğuma sevindim! History.Errors her sınıfta değişebilir, böylece onu geçersiz kılmalıyız (örneğin, alanlar ekleyin).
CoolMind

4

Kotlin Özellikleri size yardımcı olabilir.

interface IBase {
    val prop:String
}

interface IDerived : IBase {
    val derived_prop:String
}

veri sınıfları

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

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

örnek kullanım

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

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

Bu yaklaşım, @Parcelize ile kalıtım sorunları için de bir geçici çözüm olabilir

@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

Bir veri sınıfını veri olmayan bir sınıftan devralabilirsiniz. Derleyicinin oluşturduğu veri sınıfı yöntemlerinin kalıtım durumunda tutarlı ve sezgisel çalışmasını sağlamanın bir yolu olmadığından, başka bir veri sınıfından bir veri sınıfının miras alınmasına izin verilmez.


1

Uygulanırken equals()bir hiyerarşi içinde doğru gerçekten oldukça zor bir durum, yine de güzel örneğin başka yöntemler miras desteklemek olacaktır: toString().

Biraz daha somut olmak için, aşağıdaki yapıya sahip olduğumuzu varsayalım (açıkçası, toString()miras alınmadığı için işe yaramaz , ancak eğer iyi olmaz mı?):

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)

Bizim varsayarsak Userve Locationkuruluşlar (onların uygun kaynak kimlikleri iade UserResourceIdve LocationResourceIdçağıran sırasıyla) toString()herhangi ResourceId: tüm alt tipleri için genellikle geçerlidir oldukça güzel küçük temsili neden olabilir /users/4587, /locations/23alt tiplerinin olmayan geçersiz kılınan için kalıtsal çünkü vb Maalesef toString()dan yöntemle soyut temel ResourceId, çağıran toString()aslında az güzel temsil sonuçlanır: <UserResourceId(id=UserId(value=4587))>,<LocationResourceId(id=LocationId(value=23))>

Yukarıdakileri modellemenin başka yolları da vardır, ancak bu yollar ya veri olmayan sınıfları kullanmaya zorlar (veri sınıflarının birçok faydasını kaçırırız) ya da toString()uygulamayı tüm veri sınıflarımızda kopyalar / tekrarlar (kalıtım yok).


0

Bir veri sınıfını veri olmayan bir sınıftan devralabilirsiniz.

Temel sınıf

open class BaseEntity (

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

çocuk sınıfı

@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()

İşe yaradı.


Artık ad ve açıklama özelliklerini ayarlayamamanız ve bunları yapıcıya eklerseniz, veri sınıfı, temel sınıf özelliklerini geçersiz kılacak val / var değerine ihtiyaç duyar.
Brill Pappin
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.