I was wondering if there is any way to refer to a previously defined property in a constructor in Kotlin. Something like:
data class Order(
val id: String,
val transformedId: String
}
and then when initiating the class, do:
val orderId = getOrderId()
Order(
id = orderId,
transformedId = transform(id)
}
You can do this with a secondary constructor.
data class Order(
val id: String,
val transformedId: String
) {
constructor(id: String): this(id, transform(id))
}
If you want this to be the only way to create the class, you can make the primary constructor private:
data class Order private constructor(
val id: String,
val transformedId: String
) {
constructor(id: String): this(id, transform(id))
}
Related
I went through this code example but i can't get it to run neither do understand what it does exactly.
data class Order(
val id: String,
val name: String,
val data:String
)
data class OrderResponse(
val id: String,
val name: String,
val data: String
) {
companion object {
fun Order.toOrderResponse() = OrderResponse(
id = id,
name = name,
data = data ?: "",
)
}
}
The function in the companion object extends Order with a help function to turn Order instances into OrderResponse instances. So for example like
val order = Order("a", "b", "c")
val orderResponse = order.toOrderResponse()
I have this kind of implementation but it's not allowed after bumping Kotlin to 1.6.10 and received the following error which was warning until previous version.
Error: JvmField can only be applied to final property
What could be the solution here? Requirement is:
I need #JvmField for java consumers as I don't want to refactor the code to use the setter/getter method and want to use as a field.
sealed class PowerTool(
#JvmField open val name: String,
#JvmField open val price: Double
) {
data class CircularSaw(
val diameter: Int,
val cordless: Boolean,
override val name: String,
override val price: Double
) : PowerTool(name, price)
data class DrillPress(
val rpm: Int,
override val name: String,
override val price: Double
) : PowerTool(name, price)
}
First, the reason why a property isn't be open and JvmField at the same time is because you can't override a field. What normally happens when you "override" a property is a new field gets declared, and its getters and setters gets overridden.
It is simple enough to mark the properties as JvmField - just don't redeclare and override the properties in the subclasses. Instead, just declare them as regular parameters.
sealed class PowerTool(
#JvmField val name: String,
#JvmField val price: Double
) {
class CircularSaw(
val diameter: Int,
val cordless: Boolean,
name: String,
price: Double
) : PowerTool(name, price)
class DrillPress(
val rpm: Int,
name: String,
price: Double
) : PowerTool(name, price)
}
This also means that you can't use data classes and has to implement toString/equals/hashCode explicitly, which is not shown above.
I would instead suggest that you just expose a different property to JVM:
// hides all the getters by marking them as JvmSynthetic.
sealed class PowerTool(
#get: JvmSynthetic open val name: String,
#get: JvmSynthetic open val price: Double
) {
// this is the property you expose. lateinits are automatically fields
// need a better name for this...
lateinit var jvmName: String
lateinit var jvmPrice: Double
data class CircularSaw(
val diameter: Int,
val cordless: Boolean,
#get: JvmSynthetic override val name: String,
#get: JvmSynthetic override val price: Double
) : PowerTool(name, price) {
init {
jvmName = name
jvmPrice = price
}
}
data class DrillPress(
val rpm: Int,
#get: JvmSynthetic override val name: String,
#get: JvmSynthetic override val price: Double
) : PowerTool(name, price) {
init {
jvmName = name
jvmPrice = price
}
}
}
I tried hiding the getter and setter of the lateinit var from the Java code, but adding JvmSynthetic makes the field inaccessible for some reason :(
I have the following data classes:
sealed class ExampleDto
object Type1ExampleDto : ExampleDto()
object Type2ExampleDto : ExampleDto()
data class Type3ExampleDto(val name: Int, val age: Int) : ExampleDto()
data class Type4ExampleDto(val name: Int, val age: Int) : ExampleDto()
data class Type5ExampleDto(val email: String) : ExampleDto()
data class Type6ExampleDto(val name: Int, val age: Int, val email: String) : ExampleDto()
In particular, Type3ExampleDto, Type4ExampleDto and Type6ExampleDto share some common fields but it's important for my business logic to distinguish between types (i.e. even if Type3ExampleDto and Type4ExampleDto are identical, I have to know if I'm in the type3 or type4 case).
In one of my method I have the following call:
when (type) {
is Type3ExampleDto -> myMethod(type.vote, type.text)
is Type4ExampleDto -> myMethod(type.vote, type.text)
is Type6ExampleDto -> myMethod(type.vote, type.text)
else -> null
}
I find very ugly that I'm doing the same operation in all 3 cases and repeating the same line...
It makes sense to made Type3ExampleDto, Type4ExampleDto and Type6ExampleDto an implementation of some kind of interface just because only in this point I'm doing this kind of ugly repetition?
If all three dtos implement the following interface
interface MyInterface{
fun getVote() : Int
fun getText() : String
}
I can write:
if (type is MyInterface) {
myMethod(type.getVote(), type.getText())
}
So, it's acceptable to create this interface just to solve this isolated repetition?
Thanks
Note you can do it much more cleanly like this:
interface NameAndAgeDto {
val name: Int
val age: Int
}
data class Type3ExampleDto(override val name: Int, override val age: Int) : ExampleDto(), NameAndAgeDto
if (type is NameAndAgeDto) {
myMethod(type.name, type.age)
}
Whether it's "acceptable" is opinion. Looks fine to me.
You may change your model to have your logic based on behaviour instead of inheritance.
This way of modelling is based on principles of (but ain't exactly) Strategy Design Pattern.
interface HasName {
val name: String
}
interface HasAge {
val age: Int
}
interface HasEmail {
val email: String
}
object Type1
object Type2
data class Type3(
override val name: String,
override val age: Int
) : HasName, HasAge
data class Type4(
override val name: String,
override val age: Int
) : HasName, HasAge
data class Type5(
override val email: String
) : HasEmail
data class Type6(
override val name: String,
override val age: Int,
override val email: String
) : HasName, HasAge, HasEmail
// Then you can pass any object to it.
fun main(obj: Any) {
// Koltin type-casts it nicely to both interfaces.
if (obj is HasName && obj is HasAge) {
myMethod(text = obj.name, vote = obj.age)
}
}
fun myMethod(vote: Int, text: String) {
}
If you still want all the types to belong to some parent type, you can use marker interface (without any methods).
interface DTO
interface HasName {
val name: String
}
interface HasAge {
val age: Int
}
interface HasEmail {
val email: String
}
object Type1 : DTO
object Type2 : DTO
data class Type3(
override val name: String,
override val age: Int
) : HasName, HasAge, DTO
data class Type4(
override val name: String,
override val age: Int
) : HasName, HasAge, DTO
data class Type5(
override val email: String
) : HasEmail, DTO
data class Type6(
override val name: String,
override val age: Int,
override val email: String
) : HasName, HasAge, HasEmail, DTO
// Here, it is DTO instead of Any
fun main(obj: DTO) {
if (obj is HasName && obj is HasAge) {
myMethod(text = obj.name, vote = obj.age)
}
}
And use sealed class instead of marker interface if you need classes as enum.
In that case, when over sealed class is exhaustive with all options without null ->.
I have a redis entity using a kotlin data class and its Id should be a combination of few other fields from the same class.
Can this be achieved by defining setter for Id field instead of computing it outside of data class?
#RedisHash("Game")
data class Game(
#Id
val generatedId: String = "Default_ID",
val name: String,
val location: String,
val homeTeam: String,
val awayTeam: String
)
// want something like this
var generatedId : String = "DEFAULT_ID"
get() = "${name}${location}"
// or even better
var generated_Id : String = "${name}${location}"
Did you try to do something like this?
#RedisHash("Game")
data class Game(
val name: String,
val location: String,
val homeTeam: String,
val awayTeam: String,
#Id
val generatedId: String = "${name}${location}"
)
I have the following abstract class:
abstract class AbstractBook {
abstract val type: String
abstract val privateData: Any
abstract val publicData: Any
}
and the following class which inherits the AbstactBook class:
data class FantasyBook (
override val type: String = "FANTASY",
override val privateData: FantasyBookPrivateData,
override val publicData: FantasyBookPublicData
) : AbstractBook()
And then there is this class which should include data from any type of AbstractBook:
data class BookState(
val owner: String,
val bookData: AbstractBook,
val status: String
)
If I have an instance of BookState, how do I check which type of Book it is and then access the according FantasyBookPrivateData, and FantasyBookPublicData variables?
I hope I described my issue well & thanks in advance for any help!
What you describe is a sealed class:
sealed class Book<T, K> {
abstract val type: String
abstract val privateData: T
abstract val publicData: K
data class FantasyBook(
override val type: String = "FANTASY",
override val privateData: String,
override val publicData: Int) : Book<String, Int>()
}
and in your data class you can do pattern matching like this:
data class BookState(
val owner: String,
val bookData: Book<out Any, out Any>,
val status: String) {
init {
when(bookData) {
is Book.FantasyBook -> {
val privateData: String = bookData.privateData
}
}
}
}
to access your data in a type-safe manner. This solution also makes type redundant since you have that information in the class itself.
I agree with #Marko Topolnik that this seems like a code smell, so you might want to rethink your design.
interface AbstractBook<T , U> {
val privateData: T
val publicData: U
}
data class FantasyBook (
override val privateData: FantasyBookPrivateData,
override val publicData: FantasyBookPublicData
) : AbstractBook<FantasyBookPrivateData , FantasyBookPublicData>
data class BookState(
val owner: String,
val bookData: AbstractBook<*, *>,
val status: String
)
if(bookState.bookData is FantasyBook) {
// Do stuff
}
Creating a type variable is a weak type language writing style. You should use generic class.