Difference between object and companion object and how to test them - kotlin

I have a data class PersonRecord. But the data I receive from an API has different form, I need to process it in order to extract A.
The first solution consist of creating a data class PersonForm to represent the API-data and then create an independent function that take into parameters an instance of class PersonForm and returns an instance of class PersonRecord.
Looking at some stackoverflow posts, I have also found the following solutions :
2.
data class PersonRecord(val name: String, val age: Int, val tel: String){
object ModelMapper {
fun from(form: PersonForm) =
PersonRecord(form.firstName + form.lastName, form.age, form.tel)
}
}
Same as two but with companion object instead of object.
Is there a way that is more idiomatic/efficient/natural etc ? In which context, each one is preferred ?
Thanks.

The most idiomatic/natural way is creating secondary constructor:
data class PersonRecord(val name: String, val age: Int, val tel: String) {
constructor(form: PersonForm) : this(form.firstName + form.lastName, form.age, form.tel)
}

Related

Comapring data class in Kotlin using == operator

I am trying to write an assertion by comparing two Kotlin data classes.I am just simplifying the question by using a minimal class.
data class X(val isThatSo: Boolean) {
val name:String = "xyz"
}
In my test
val s = """
{
"isThatSo": "true",
"name": "Ankit"
}
"""
assert(Gson().fromJson(s, X::class.java) == X(true))
Looks to me that the name field is not compared at all because the value in both the objects is different. Is my understanding correct?
From the documentation:
data class User(val name: String, val age: Int)
The compiler automatically derives the following members from all properties declared in the primary constructor:
equals()/hashCode() pair
toString() of the form "User(name=John, age=42)"
componentN() functions corresponding to the properties in their order of declaration.
copy() function.
To exclude a property from the generated implementations, declare it inside the class body:
data class Person(val name: String) {
var age: Int = 0
}
You're comparing two instances of a class, which are not identical. You can compare the name variable within X for equality.

Override variable in subclass in Kotlin

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.

Is there a way to "disable" the primary constructor or force the user to always use a secondary constructor?

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.

Kotlin data classes with Java super class

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)

Kotlin data class with additional properties not in constructor

Starting out with Kotlin and wanting to make a data class
data class Person(val Email: String, val firstName: String, val lastName: String)
But let's say I want to add additional properties that I don't know at the time when I am using the constructor but I want to store this data at a later point when I am aware of it for example a person's mood (Represented as a String)
In Java I would make a data class like this. I would be able to not include it in the Constructor and make a getter where I could set it at a later time.
public class Person{
private String email;
private String firstName;
private String lastName;
private String mood;
public person (String email, String firstName, String lastName){
this.email = email;
this.firstName = firstName;
this.lastName = lastName;
}
public setMood(String mood){
this.mood = mood;
}
}
Kotlin doesn't appear to have an answer on this or if it does I do not know how to phrase correctly. Hence why this question could already be answered and I am unable to find it.
I do understand that by not including mood in the data class line Kotlin may not be able to identify mood as part of the data class but aside from including it in the constructor and setting it to null I'm not sure what else to do or is that what I am supposed to do?
You should be able to just add it as a property to Person. In Kotlin, a data class is still a class, it just comes with some extras (toString, copy constructors, hashCode/equals, etc). You can still define any properties that you want.
data class Person(val Email: String, val firstName: String, val lastName: String) {
var mood: String? = null
}
In this case it is nullable, because as you stated, you might not know the mood until later.
Kotlin's data class must have first constructor, you can avoid it by not using the data keyword.
If you still want to add another property to the data class you can do the following:
data class Person(val email: String, val firstName: String, val lastName: String){
var mood: String = ""
}
This way you can do person.mood = "happy" without including it in the constructor.
Kotlin only considers the values passed to the primary constructor in terms of giving you the "for free" features that a Data class provides. Beyond that, you can add whatever additional properties you desire, but they aren't accounted for in the special code that Kotlin writes by way of you marking a class as data.
Per the Kotlin docs:
Note that the compiler only uses the properties defined inside the
primary constructor for the automatically generated functions. To
exclude a property from the generated implementations, declare it
inside the class body:
Per this, declaring properties outside of the primary constructor actually has benefits. You might be able to declare a property via the primary constructor, but choose not to.
Not only do you have to provide a primary constructor, but it has to include at least one property declaration. If you didn't do this, there would be no benefit to making the class a data class. But marking a class so does not limit what else you can do with that class.
Have you tried:
data class Person(val Email: String, val firstName: String, val lastName: String) {
var mood: String? = null
}
An alternative to #Todd's and #jingx's answers is
data class Person(val Email: String, val firstName: String, val lastName: String, var mood: String? = null)
The difference is that this way mood participates in toString/equals/hashCode/copy and that you can set mood in the constructor call. Even if that's probably not desirable for this specific case, it can be useful in others.