I know that data class are like simple models in kotlin with getters and setter by default and are as simple this:
data class User(val name: String, val age: Int)
Is it possible to declare a second constructor for that data class?
A Kotlin data class must have a primary constructor that defines at least one member. Other than that, you can add secondary constructors as explained in Classes and Inheritance - Secondary Constructors.
For your class, and example secondary constructor:
data class User(val name: String, val age: Int) {
constructor(name: String): this(name, -1) { ... }
}
Notice that the secondary constructor must delegate to the primary constructor in its definition.
Although many things common to secondary constructors can be solved by having default values for the parameters. In the case above, you could simplify to:
data class User(val name: String, val age: Int = -1)
If calling these from Java, you should read the Java interop - Java calling Kotlin documentation on how to generate overloads, and maybe sometimes the NoArg Compiler Plugin for other special cases.
Updated answer for data classes:
Yes you can, but you will need to delegate everything to the primary constructor
data class User(val name: String, val age: Int)
{
constructor(name: String): this(name, -1) {
}
constructor(age: Int): this("Anon", age) {
}
}
// Anon name: Anon
println("Anon name: " + User(30).name)
// No age: -1
println("No age: " + User("Name").age)
// Name: Name age: 20
val u = User("Name", 20)
println("Name: " + u.name + " age: " + u.age)
You can also set default values in your primary constructor as Alexey did.
I wanted to have a class similar to below (with a constructor that parses an input)
data class Data(val a: String, val b: String) {
constructor(spaceSeparated: String) { // error because we don't call this()
val split = spaceSeparated.split(" ")
this(split.first(), split.last()) // error again because it's not valid there
}
}
The solution is to do this:
data class Data(val a: String, val b: String) {
companion object {
operator fun invoke(spaceSeparated: String): Data {
val split = spaceSeparated.split(" ")
return Data(split.first(), split.last())
}
}
}
And it can be called just as if it were a constructor
Default values in the primary constructor eliminates many needs for secondary constructors, but if the needed instance depends on logic based on data that must be analyzed the better answer may be to use a companion object.
data class KeyTag(val a: String, val b: Int, val c: Double) {
companion object Factory {
val empty = KeyTag("", 0, 0.0)
fun create(bigString: String): KeyTag {
// Logic to extract appropriate values for arguments a, b, c
return KeyTag(a, b, c)
}
fun bake(i: Int): KeyTag = KeyTag("$i", i, i.toDouble())
}
}
Usage is then:
val ks = KeyTag.create("abc:1:10.0")
val ke = KeyTag.empty
val kb = KeyTag.bake(2)
Yes, but each variable should be initialized, so you may set default arguments in your data class constructor, like this:
data class Person(val age: Int, val name: String = "Person without name")
Now you can create instance of this data class in two ways
Person(30)
Person(20, "Bob")
you can set the data class like this
data class User(val name: String? = null, val id: String? = null, val email: String? = null)
and you can instance the object with multiple constructors like this
val userId = User(id = "123456")
val userMail = User(email= "email#email.com")
val userName = User("Name")
Data class will ensure consistency and meaningful behavior also we need to have val for immutability.
data class SampleData(val name: String, val age: Int, val email: String ?= null) {
constructor(name: String, age: Int) : this(name, age, null) {
}
}
secondary constructor must delegate to the primary constructor in its definition, so to maintain the immutability, having "null" will work.
Instructs the Kotlin compiler to generate overloads for this function that substitute default parameter values.
If a method has N parameters and M of which have default values, M overloads are generated: the first one takes N-1 parameters (all but the last one that takes a default value), the second takes N-2 parameters, and so on.
data class User #JvmOverloads constructor(
var email: String="",
var password: String="")
Yes, we can use like below code, and in primary constructor for data class should have min one parameter.
data class SampleData(val name: String, val age: Int) {
constructor(name: String, age: Int, email: String) : this(name, age) {
}
}
Yes you can have multiple contractors on data classes. But there is something which makes the primary constructor special. The compiler will auto generate methods like equals, hashCode, copy, toStrings based on the primary constructor for the data class.
Below is an example of two instances of a data class which looks different (first.gender = male, second.gender = female) but equals method would evaluate to true because gender is not defined in the primary constructor and therefore not considered in the auto generated methods. Likewise, gender would not be included in the string representation.
data class A(val name: String, val age: Int) {
var gender: String = "Female"
constructor(name: String, age: Int, gender: String) : this(name, age) {
this.gender = gender
}
}
fun main(args: Array<String>) {
val first = A("foo", 10)
val second = A("foo", 10, "Male")
println(first == second) //prints true
println(second) //prints A(name=foo,age=10)
}
Related
I'm writing a client for a third-party REST API that returns JSON with a variety of alternative values instead of proper null or omitting the property entirely if null. Depending on the entity or even property in question, null could be represented by either null, "", "0" or 0.
It's easy enough to make a custom serializer, e.g. something like this works fine:
#Serializable
data class Task(
val id: String,
#Serializable(with = EmptyStringAsNullSerializer::class)
val parentID: String?
)
object EmptyStringAsNullSerializer : KSerializer<String?> {
private val delegate = String.serializer().nullable
override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor("EmptyStringAsNull", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: String?) {
when (value) {
null -> encoder.encodeString("")
else -> encoder.encodeString(value)
}
}
override fun deserialize(decoder: Decoder): String {
return delegate.deserialize(decoder) ?: ""
}
}
fun main() {
val json = """
{
"id": "37883993",
"parentID": ""
}
""".trimIndent()
val task = Json.decodeFromString(json)
println(task)
}
But annotating many properties like this is a bit ugly/noisy. And I'd also like to use inline/value classes for strong typing, like this:
#Serializable
data class Task(
val id: ID,
val parentID: ID?
/* .... */
) {
#JvmInline
#Serializable
value class ID(val value: String)
}
This means that in addition to annotating these properties I also need a custom serializer for each of them. I tried some generic/parameters-based solution that can work for all cases like this:
open class BoxedNullAsAlternativeValue<T, V>(
private val delegate: KSerializer<T>,
private val boxedNullValue: T,
private val unboxer: (T) -> V
) : KSerializer<T> {
private val unboxedNullValue by lazy { unboxer.invoke(boxedNullValue) }
override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor(this::class.simpleName!!, PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: T) {
when (value) {
null -> delegate.serialize(encoder, boxedNullValue)
else -> delegate.serialize(encoder, value)
}
}
override fun deserialize(decoder: Decoder): T {
#Suppress("UNCHECKED_CAST")
return when (val boxedValue = delegate.deserialize(decoder)) {
boxedNullValue -> null as T
else -> boxedValue
}
}
}
But that doesn't work because #Serializable(with = ...) expects a static class reference as argument, so it can't have parameters or generics. Which means I'd still need a concrete object for each inline/value type:
#Serializable
data class Task(
val id: ID, // <-- missing serializer because custom serializer is of type ID? for parentID
val parentID: ID?
) {
#JvmInline
#Serializable(with = IDSerializer::class)
value class ID(val value: String)
}
internal object IDSerializer : BoxedNullAsAlternativeValue<Task.ID?, String>(
delegate = Task.ID.serializer().nullable, // <--- circular reference
boxedNullValue = Task.ID(""),
unboxer = { it.value }
)
That doesn't work because there is no longer a generic delegate like StringSerializer and using Task.ID.serializer() would mean the delegate would be the custom serializer itself, so a circular reference. It also fails to compile because one usage of the ID value class is nullable and the other not, so I would need nullable + non-nullable variants of the custom serializer and I would need to annotate each property individually again, which is noisy.
I tried writing a JsonTransformingSerializer but those need to be passed at the use site where encoding/decoding happens, which means I'd need to write one for the entire Task class, e.g. Json.decodeFromString(TaskJsonTransformingSerializer, json) and then also for all other entities of the api.
I found this feature request for handling empty strings as null, but it doesn't appear to be implemented and I need it for other values like 0 and "0" too.
Question
Using kotlinx.serialization and if necessary ktor 2, how to deserialize values like "", "0" and 0 as null for inline/values classes, considering that:
Properties of the same (value) type can be nullable and non-nullable in the same class, but I'd like to avoid having to annotate each property individually
I'd like a solution that is as generic as possible, i.e. not needing a concrete serializer for each value class
It needs to work both ways, i.e. deserializing and serializing
I read in the documentation that serializing is done in 2 distinct phases: breaking down a complex object to it's constituent primitives (serializing) --> writing the primitives as JSON or any other format (encoding). Or in reverse: decoding -> deserializing;
Ideally I'd let the compiler generate serializers for each value class, but annotate each of them with a reference to one of three value transformers (one each for "", "0" and 0) that sit in between the two phases, inspects the primitive value and replaces it when necessary.
I've been at this for quite some time, so any suggestions would be much appreciated.
I'm getting a list of ints (which are really enums) from the API. When I try to parse it, I get: Unable to create converter for java.util.List<MyEnum>
My adapter is currently looking like this:
#Retention(AnnotationRetention.RUNTIME)
#JsonQualifier
annotation class MyEnumListAnnotation
class MyEnumListAdapter {
#ToJson
fun toJson(#MyEnumListAnnotation myEnumList: List<MyEnum>): List<Int> {
return myEnumList.map { it.type }
}
#FromJson
#MyEnumListAnnotation
fun fromJson(typeList: List<Int>): List<MyEnum> {
return typeList.map { MyEnum.from(it) }
}
}
I'm adding this to the network client like this:
Moshi.Builder()
.add([A lot of other adapters])
.add(MyEnumListAdapter())
And I'm using the annotation like this (in the object I want to parse to):
data class InfoObject(
val id: String,
val name: String,
val email: String,
val phone: String,
#MyEnumListAnnotation
val myEnums: List<MyEnum>
)
How can I write my adapter so that this is working? Thanks for all help. :)
If you use Moshi's codegen (which you should), you only need to write adapter for your MyEnum itself.
class MyEnumAdapter {
#ToJson
fun toJson(enum: MyEnum): Int {
return enum.type
}
#FromJson
fun fromJson(type: Int): MyEnum {
return MyEnum.from(it)
}
}
Attach the adapter to your Moshi builder the way you did it in your question. Then, update your InfoObject:
#JsonClass(generateAdapter = true)
data class InfoObject(
#Json(name = "id") val id: String,
#Json(name = "name") val name: String,
#Json(name = "email") val email: String,
#Json(name = "phone") val phone: String,
#Json(name = "myEnums") val myEnums: List<MyEnum>
)
#JsonClass(generateAdapter = true) will ensure that the library will auto-create an adapter for your InfoObject, including an adapter for List<MyEnum> (the one you tried to create yourself), so you don't have to create those adapters yourself. #Json(name="...") is just a convention, you can omit it.
To integrate codegen, just add to dependencies:
kapt("com.squareup.moshi:moshi-kotlin-codegen:1.9.3")
See https://github.com/square/moshi for more details.
I am new to Kotlin and working through the tutorials that are available.
However now I seem to have a problem with a secondary constructor:
Parameters declared in the primary constructor can be accessed in a function,
but when I try to do this with a parameter from the secondary constructor I get an error: Unresolved reference:nbr
The code:
class Test(_name: String) {
val name: String = _name
constructor(_name: String, _nbr: Int) : this(_name) {
val nbr: Int = _nbr
}
fun printNameAndNumber() {
println("Name: $name")
println("Number: $nbr")
}
}
It is clear to me that I am doing something basically wrong but who can tell me what?
nbr should be a variable, because in this specific case it is optional:
class Test(_name: String) {
val name: String = _name
var nbr: Int? = null
constructor(_name: String, _nbr: Int) : this(_name) {
this.nbr = _nbr
}
fun printNameAndNumber() {
println("Name: $name")
println("Number: $nbr")
}
}
Parameters of the primary constructor are not available in member functions. Fields are. Fortunately Kotlin has a short syntax to make primary constructor parameters member properties right away.
What do you expect nbr to be when constructed using the primary constructor? I suggest you to swap your constructors, so it's clear what are properties and what are just parameters:
class Test(val name: String, val nbr: Int) {
constructor(name: String) : this(name, 0)
fun printNameAndNumber() {
println("Name: $name")
println("Number: $nbr")
}
}
fun main(args : Array<String>) {
Test("Péter").printNameAndNumber()
}
name is accessible because it is a member.
nbr is not accessible because it is a local (immutable) variable inside the secondary constructor.
If you declare nbr as member: putting val nbr: Int for example below the val name line, it will be accessible, however it will not compile if nbr is defined as immutable (val).
A simpler structure would be:
class Test(_name: String, _nbr: Int = 0) {
val name: String = _name
val nbr: Int = _nbr
fun printNameAndNumber() {
println("Name: $name")
println("Number: $nbr")
}
}
or even simpler
class Test(val name: String, val nbr: Int = 0) {
fun printNameAndNumber() {
println("Name: $name")
println("Number: $nbr")
}
}
If you want your nbr member as nullable, you couldgo with the suggestion of #gil.fernandes.
I have following class:
class Person(val name: String) {
private var surname: String = "Unknown"
constructor(name: String, surname: String) : this(name) {
this.surname = surname
}
}
But when I want to have the name parameter immutable in second constructor:
constructor(val name: String, surname: String) : this(name) {
this.surname = surname
}
I have the following compile-time error:
Kotlin: 'val' on secondary constructor parameter is not allowed
Can someone explain why is Kotlin compiler not allowing to do this?
Parameters in Kotlin are always immutable. Marking a constructor parameter as a val turns it into a property of a class, and this can only be done in the primary constructor, because the set of properties of a class cannot vary depending on the constructor used to create an instance of the class.
In addition to the great answer of yole, the documentation is pretty clear as well:
Note that parameters of the primary constructor can be used in the initializer blocks. They can also be used in property initializers declared in the class body.
[...] In fact, for declaring properties and initializing them from the primary constructor, Kotlin has a concise syntax:
class Person(val firstName: String, val lastName: String, var age: Int) {
// ...
}
Much the same way as regular properties, the properties declared in the primary constructor can be mutable (var) or read-only (val).
This all does not apply to secondary constructors.
You can define the variable as val or var in the class you inherit from
open class Human(val name: String) constructor(name: String) {
open fun showInfo()
{
println("Show Info")
}
}
class Person:Human {
constructor(name: String) : super(name)
private var surname: String = "Unknown"
override fun showInfo() {
println("$name And surname is $surname")
}
}
The currently accepted answer is correct in explaining why your initial attempt did not work. As such, given your particular scenario, I would inverse the solution and make your secondary constructor the primary, and make that second parameter have a default value.
class Person(val name: String, private var surname: String = "Unknown")
Also, if the class's purpose is to simply hold data, I would make it a data class to improve its handling.
I had been using the traditional Java TableCellRenderer approach for providing the renderers in a scala.swing.Table where I declare my renderers on the table's TableColumnModel. The code for this looked like:
val myTable = new Table {
lazy val tcm = initColumnModel
peer.setColumnModel(tcm)
override
protected def rendererComponent(sel: Boolean, foc: Boolean, row: Int, col: Int) = {
//GET THE VALUE FROM THE TableModel
val value = model.getValueAt(
peer.convertRowIndexToModel(row),
peer.convertColumnIndexToModel(col))
//GET THE RENDERER FROM THE ColumnModel
val renderer = tcm.getColumn(col).getCellRenderer
//WRAP IN A COMPONENT
Component.wrap(renderer.getTableCellRendererComponent(
peer,
value,
sel,
foc,
row,
col).asInstanceOf[JComponent])
}
}
Unfortunately this appears to have a memory leak - presumably because I am creating a new Component instance for every cell in the table (for ~30k rows). Certainly when I replace my scala table with a JTable (using exactly the same column and data models) my memory leak goes away.
My question is therefore, what sort of code do people use when overriding the rendererComponent method assuming one has ones own cell renderers?
The idiomatic way of using Scala table cell renderers is to use Table.AbstractRenderer (if implementing your own) or one of its subclasses:
val tcr = new Table.AbstractRenderer[MyObj, MyRenderer](new MyRenderer) {
def configure(t: Table, sel: Boolean, foc: Boolean, o: MyObj, row: Int, col: Int) = {
//component variable is bound to your renderer
component.prepare(o)
}
}
In this case prepare is a method you would define on your own renderer class:
class MyRenderer extends Label {
def prepare(o: MyObj) {
text = o.toString //or whatever
}
}
Then this is used by overriding the rendererComponent method on Table:
val t = new Table {
override def rendererComponent(sel: Boolean, foc: Boolean, row: Int, col: Int) = {
//FIND VALUE
val v = model.getValueAt(
peer.convertRowIndexToModel(row),
peer.convertColumnIndexToModel(row))
col match {
case 0 => tcr.componentFor(this, sel, foc, v, row, col)
}
}
}
Scala comes with its own implementations of AbstractRenderer, namely LabelRenderer which takes a function as an argument, converting an instance of MyObj to a Tuple2 consisting of a String and an Icon, for that label to display:
val ltcr = new LabelRenderer[MyObj] ( (o: MyObj) => (null, o.toString) )
Thanks a ton for your example oxbow_lakes!
IMHO this scala-thing has become as ugly as table-rendering can possibly get.
Trying to hide it as much as possible...
class TableRenderer[A](comp: TableRendererComp[A]) extends Table.AbstractRenderer[A,TableRendererComp[A]](comp) {
def configure(t: Table, sel: Boolean, foc: Boolean, a: A, row: Int, col: Int): Unit =
component.render(a, sel, foc)
}
trait TableRendererComp[A] extends Component {
def render(a: A, sel: Boolean, foc: Boolean): Unit
}
Using like (at least the "configure" is gone...)
val tcr = new TableRenderer[MyObj](new MyRenderer)
class MyRenderer extends Label with TableRendererComp[MyObj] {
def render(o: MyObj, sel: Boolean, foc: Boolean) {
text = o.toString //or whatever
}
}