I have this super class:
abstract class Node(rawId: String) {
open val id: String
init {
id = Base64.toBase64(this.javaClass.simpleName + "_" + rawId)
}
}
And this subclass that extends Node:
data class Vendor (
override var id: String,
val name: String,
val description: String,
val products: List<Product>?
): Node(id)
When I initialize the Vendor class like this:
new Vendor(vendor.getId(), vendor.getGroup().getName(), description, products);
I can see the init block in Node get fired as expected. However, when I get the id from the Vendor object, it is the rawId and not the encoded Id.
So I am a bit confused about the initialization order/logic in Kotlin classes. I want the encoding code to be common across all subclasses. Is there a better way to do it?
The problem is because you are overriding the id field in the subclass and hence it would always remain the rawId value.
Since the base class has already an id field which has to be an encoded value, you don't need to override it in the subclass. You need to provide the rawId to the Node class in your Vendor class and let the base class take care of the id value to be instantiated with. You can have your abstract class as
abstract class Node(rawId: String) {
val id: String = Base64.toBase64(this.javaClass.simpleName + "_" + rawId)
}
and then define your subclass as
data class Vendor (
val rawId: String,
val name: String,
val description: String,
val products: List<Product>?
): Node(rawId)
Then with
Vendor newVendor = new Vendor(vendor.getId(), vendor.getGroup().getName(), description, products);
newVendor.getId() // would be the encoded id as you expect
since Vendor is a subclass of Node, the id field is also available to the Vendor object with the encoded value.
Related
In Kotlin, how can I derive fields from a base definition (abstract, interface, inheritance, something else) without explicitly overriding them?
The closest I can get is:
abstract class Person {
open val name: String = "Stranger"
}
data class Doctor(
override val name: String,
val yearsOfExperience: Int
): Person()
val doc = Doctor(yearsOfExperience = 20, name = "Eric")
But ideally, since I have a use case of an unchangeable model with hundreds of fields, I would like to have:
abstract class Person {
open val name: String = "Stranger"
}
data class Doctor(
val yearsOfExperience: Int
): Person()
val doc = Doctor(yearsOfExperience = 20, name = "Eric")
You can't. If you want Doctor to be able to change Person.name to anything other than what's defined in Person, you are by definition overriding the behavior in Person - Kotlin is just forcing you to make that contract explicit.
If it didn't do that, it would be possible to do something like this:
data class Doctor(
val yearsOfExperience: Int
) : Person()
Then later decide to add a name field:
data class Doctor(
val yearsOfExperience: Int,
val name: String = "Doctor"
) : Person
Now Doctor.name has a default value of "Doctor" which is different to the expected behavior defined in Person. Previous code that did Doctor(yearsOfExperience = 20) will now behave differently - it will get the name "Doctor" instead of "Stranger". Kotlin is making sure that you realise that, and explicitly ask for it by adding the override modifier.
So you can omit the fields you want to inherit, but not the ones you want to override.
Example:
data class Car (
val type: TypeEnum,
val brand: BrandEnum,
val modelNumber: Int)
{
constructor(val type: TypeEnum,
val brand: BrandEnum,
val input: String) : this (
type,
brand,
Valdidator.validateModelNumber(input)
)
}
In the code above, the method validateModelNumber() validates a raw input and throws an exception if the model number has an invalid format. I want to force the user to use this constructor every time he/she wants to make a Car object.
Essentially: I want to make sure that no invalid Car object can exist, while still making the code as immutable as possible.
You could use the init block instead. Something like this
data class Car (
val type: TypeEnum,
val brand: BrandEnum,
val modelNumber: Int)
{
init {
Valdidator.validateModelNumber(input)
}
}
Using an init block for validation (as per another answer) can work well if it only needs the parameters/properties specified in the primary constructor. However, there are other approaches.
If you don't want the primary constructor to be used by other code, you can make it private, by changing:
data class Car(
to:
data class Car private constructor(
You could then leave a public secondary constructor for other classes to use, as in the question. However, that's still a bit limiting, as you can't do any serious processing before calling the primary constructor.
So the usual pattern is to have a private constructor and factory methods in the companion object. This is much more flexible: you can do any amount of processing before and after calling the actual constructor; you can even return cached instances, subclass instances, etc.
You can make those look like constructors by implementing them as operator fun invoke() with suitable parameters. In this case, that could look like:
data class Car private constructor(
val type: TypeEnum,
val brand: BrandEnum,
val modelNumber: Int)
{
companion object {
operator fun invoke(type: TypeEnum, brand: BrandEnum, input: String)
= Car(type, brand, Validator.validateModelNumber(input))
}
}
You could then create instances with e.g.:
Car(TypeEnum.SPORTS, BrandEnum.ASTON_MARTIN, "DB5")
looking just like an ordinary constructor.
I have a Java class that holds generic information on databse entities (i.e. their id).
#Data
public class DbEntity {
protected final String id;
public DbEntity(String id) {
this.id = id;
}
}
We use Lombok #Data to generate getters, toString, equals...
In Java I would simply extend this class and add #Data once again.
#Data
class JavaSubClass extends DbEntity {
public JavaSubClass(String id) {
super(id);
}
}
In a newer service we use Kotlin but would like to reuse standard classes such as DbEntity.
My first approach was to simply declare a data class such as
data class SubClass1(val id: String, val name: String) : DbEntity(id)
Accidental override: The following declarations have the same JVM signature (getId()Ljava/lang/String;):
fun <get-id>(): String defined in com.demo.SubClass1
fun getId(): String! defined in com.demo.SubClass1
After some reading I found several solutions, all of which I'm not super happy with.
Don't use data classes. This works but leaves me with the task of implementing equals etc.
class SubClass4(id: String, val name: String) : DbEntity(id)
Duplicate the field. This works but we end up with two fields that could go out of sync.
data class SubClass3(val subId: String, val name: String) : DbEntity(subId)
Assign a different name to the getter. This fundamentally also duplicates the field, but hides the getter.
data class SubClass2(#get:JvmName("getId_") val id: String, val name: String) : DbEntity(id)
As I said, I'm not happy with any of the solution presented above. Having an abstract super class or an interface instead would certainly be more appropriate. However the Entity class resides in a library that primarily Java projects depend on. I'm hesitant to change it just because of a new Kotlin dependnecy.
Did anyone encounter similar issues and has advice on how to solve them?
As a workaround, until KT-6653 - Kotlin properties do not override Java-style getters and setters is fixed, I would go for a variant of your point 3, i.e.:
data class SubClass(#get:JvmName("bogusId") private val id: String, val name: String) : DbEntity(id)
The benefit of this variant is, that you always access the "original" getId-function. You will not use the bogusId()-function as it is not visible/accessible (accessing it via reflection makes no sense... you are only interested in the actual id-field). This works and looks similar for both sides: from Java as also from Kotlin. Still, under the hood this variant uses 2 fields, but in the best case you can just replace it in future with something like:
data class SubClass(override val id: String, val name : String) : DbEntity(id)
I have a simple inheritance tree in my Kotlin project, where a base class is extended by a data class. I cannot declare construction of my data class without overriding the parameters from the base class
I've noticed that this would work if I wasn't extending in a data class:
open class Base(
val first: String,
val second: String
)
class Child(
first: String,
second: String,
val third: List<String>
) : Base(first, second)
This is what I ended up with currently:
open class Base(
open val first: String,
open val second: String
)
data class Child(
override val first: String,
override val second: String,
val third: List<String>
) : Base(first, second)
But I would like to be able not to override the constructor parameters, because I'm not really overriding them. I just need to take them in my Child constructor to be able to pass them to Base.
Having a base class like this and a derived data class, you have to override its properties or separate them, because all primary constructor parameters of a data class must also be declared as properties:
— All primary constructor parameters need to be marked as val or var;
However, based on what your goal really is, you can transform your code in one of the following ways:
Declare the properties in Child as separate, unrelated properties:
open class Base(
open val first: String,
open val second: String
)
data class Child(
val childFirst: String,
val childSecond: String,
val third: List<String>
) : Base(childFirst, childSecond)
This will allow you to have separate implementations for the properties if you need it, storing the values passed as childFirst and childSecond in the Child and probably altering them in some way in the implementation of Base.
Make Base an interface:
interface Base {
val first: String,
val second: String
}
data class Child(
override val first: String,
override val second: String,
val third: List<String>
) : Base
This ensures that Base doesn't have an implementation that stores property values in addition to the Child's properties with backing fields (those will consume additional memory, but, as the propeties are overridden, Base will consistently see the values of the Child's backing fields as first and second).
Make Base an abstract class with abstract properties:
abstract class Base {
abstract val first: String,
abstract val second: String
}
data class Child(
override val first: String,
override val second: String,
val third: List<String>
) : Base()
This follows a similar purpose: Base won't store the property values in its implementation needlessly duplicating the properties of Child.
Make Child a normal class, manually implementing those of the functions that are generated for data classes which you actually need.
I have two library classes, Item and ItemFood : Item (that is derived from Item), and a library function registerItem(item: Item, name: String). I cannot modify them.
I have two of my own classes (ItemKey : Item and ItemBerry : ItemFood) that are derived from the library classes.
What I want is to store the name: String property in my classes ItemKey and ItemBerry and make them "count" as a NamedItem, so I can write a function like so:
fun registerNamedItem(namedItem: NamedItem) {
registerItem(namedItem, namedItem.name)
}
I cannot just make a class like so: class NamedItem(val name: String) : Item and derive my classes from it, because sometimes I need to derive my classes from ItemFood, not from Item.
I don't want to make a class wrapper like class NamedItem(val item: Item, val name: String), because then every time I want to get the "underlying" Item I will need to manually get the item property: registerItem(namedItem.item, namedItem.name), and this is ugly.
I cannot use an interface INamedItem { val name: String } and implement this interface in ItemKey and ItemBerry, because then I will need to write a function in this way:
fun registerNamedItem(item: Item, namedItem: INamedItem) {
registerItem(item, namedItem.name)
}
, and it is not an improvement at all.
Is there some kind of advanced technique - using an interface, delegation, generic, whatever - so I can implement the registerNamedItem function like I want it - passing to the registerItem(item: Item, name: String) an instance of the NamedItem as the first parameter and the namedItem.name as the second parameter?
Actually, you could use interface just for that:
fun main() {
registerNamedItem(ItemKey("item_key"))
registerNamedItem(ItemBerry("item_berry"))
}
// Cannot change this
open class Item
// Cannot change this
open class ItemFood : Item()
// This is your class
class ItemKey(override val name: String) : NamedItem()
// This is also your class
class ItemBerry(override val name: String) : NamedFoodItem()
// This is the property you would like to enforce
interface INamedItem {
val name: String
}
// Since Item and ItemFood are concrete classes, you don't have much choice there
abstract class NamedItem : Item(), INamedItem
abstract class NamedFoodItem : ItemFood(), INamedItem
// Adapter pattern
fun registerNamedItem(namedItem: NamedFoodItem) {
registerItem(namedItem, namedItem.name)
}
// Adapter pattern
fun registerNamedItem(namedItem: NamedItem) {
registerItem(namedItem, namedItem.name)
}
fun registerItem(namedItem: Item, name: String) {
println("Item $namedItem registered with $name")
}
Delegation won't work in your case, since from your example, Item is a class, and you can only delegate to interfaces.