Comapring data class in Kotlin using == operator - kotlin

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.

Related

Kotlin inc() operator overloading

I have a little problem to overload inc() operator, precisely to make a postfix and a prefix one.
Here my data class
data class Person(val firstName: String, val name: String, var age: Int) {
operator fun inc(): Person {
val tmp = this
this.age++;
return tmp
}
}
With this, age change before returning so it's only working for prefix version.
How can I do a postfix version of inc() operator ?
inc is expected to return a new, incremented instance of the class. Since you've got a dataclass, we can use Kotlin's convenience functions that work on dataclasses to get a new instance for you relatively effortlessly.
data class Person(val firstName: String, val name: String, var age: Int) {
operator fun inc(): Person =
this.copy(age = this.age + 1)
}
Person.copy is one of several dataclass methods generated for you. It takes the same arguments as your primary constructor, with each argument defaulting to the current value on this (i.e. any arguments not passed will be the same as the corresponding values on this). So by passing only the age parameter by name, we modify only the one we want to and leave the others untouched.
There is no way to do what you’re trying to do. You are breaking the contract that the increment operator must not mutate the class. It must return a new instance of the class.

Difference between object and companion object and how to test them

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)
}

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 - Transform value at initialisation

i'm new in kotlin and i want to know if we can transform a content value at initialisation : with this example :
#Document
data class Category(
#Id val id: Id? = null,
val label: String
)
Category is a document (entity for mongodb) and when i'm instanciating this object, i want to transform label property in uppercase. How can i do that to stay idiomatic with the language ? The point is to keep the immutable properties of the val keyword.
val categ = Category(label = "Test")
println(categ.label) // --> TEST
Thanks.
You can encapsulate the "upperCasing" into a factory:
data class Category constructor(val label: String) {
init {
if (label != label.toUpperCase()) {
throw IllegalStateException("Label must be uppercase")
}
}
companion object {
fun createInstance(str: String) = Category(str.toUpperCase())
}
}
The init block ensures, that clients don't create unwanted instances with non-upper labels (which should be documented).
Create an instance like this:
val instance = Category.createInstance("xy")
You might want to make explicit that you do transformations if the parameter is not upper case already by naming the factory accordingly, e.g. withTransformedLabel or simply add some documentation ;-)

What is the difference between a normal class and a data class in Kotlin?

I tried to resolve task #6 (DataClass) at Kotlin Koans. When I used the normal class in code, the test case failed.
Here's my code of the data class:
data class Person(val name: String, val age: Int)
fun task6(): List<Person> {
return listOf(Person("Alice", 29), Person("Bob", 31))
}
Here's result of the data class:
[Person(name=Alice, age=29), Person(name=Bob, age=31)]
Here's my code of the normal class:
class Person(val name: String, val age: Int)
fun task6(): List<Person> {
return listOf(Person("Alice", 29), Person("Bob", 31))
}
Here's result of the normal class:
[i_introduction._6_Data_Classes.Person#4f47d241, i_introduction._6_Data_Classes.Person#4c3e4790]
Does that mean there is difference between a normal class and a data class in Kotlin. If yes, what is that?
Updated:
Thank #Mallow, you are right. That works:
class Person(val name: String, val age: Int) {
override fun toString(): String {
return "Person(name=$name, age=$age)"
}
}
fun task6(): List<Person> {
return listOf(Person("Alice", 29), Person("Bob", 31))
}
Most of the time we developers use class to keep only data in classes. Classes have some methods which needs to be overridden wrt the data it holds. ex: hashCode(), equals().
Data classes automatically take care of such utilities.
From the official documentation:
We frequently create a class to do nothing but hold data. In such a class some standard functionality is often mechanically derivable from the data. In Kotlin, this is called a data class and is marked as data.
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 (see below).
If any of these functions is explicitly defined in the class body or inherited from the base types, it will not be generated.
To read more, check data-classes
About the result, Technically, you are getting is different because of implementation of toString() method. data class' toString() method uses data class properties and values to form returning string. General class' toString() method uses hash code to form returning string.
for a data class.
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 (see below).
see https://kotlinlang.org/docs/reference/data-classes.html
A class represents some data "type" and its behaviour(s) so from that point of view data class isn't any different than a class. But there are certain behaviours and rules about a data class that makes it a bit different:
Calling toString() on a data class dumps a string with all its member properties.
It has componentN method that get member properties by their order n.
It has a copy method which takes the member properties as parameters for making a diff copy of the object.
A data class can not be open. Cant be inherited.
It can not be abstract.
It can not be nested, inner or sealed.
Although it can inherit, define abstract methods and implement interfaces.
data class properties can be destructed into individual variables e.g val (name, address) = Person("name", "address")
Pair(a, b) internally uses data class.
It is very common to create classes whose main goal is to hold data. If you want your class to be a convenient holder for your data you need to override the universal object methods:
toString() - string representation
equals() - object equality
hashCode() - hash containers
Note: equals() is used for structural equality and it is often implemented among with hashCode().
Usually, the implementation of these methods is straightforward, and your IDE can help you to generate them automatically. However, in Kotlin, you don't have to general all of these boilerplate code. If you add the modifier data to your class, the necessary methods are automatically added for you.
The return value of toString() will have the format ClassName(parm1=value1, param2=value2, ...). equals() and hashCode() methods take into account all the properties declared in the primary constructor.
The copy() method
When you mark a class as a data class, the method copy() is also automatically generated which allows you to make copies of an existing instance. This feature is very handy when you are using your instances as keys for a HashMap or if you are dealing with multithreaded code.
Even though the properties of a data class are not required to be val, i.e., you can use var, it is strongly recommended that you use read-only properties, so that you make the instances immutable.
Finally, componentN() functions corresponding to the properties in their order of declaration are also generated by the compiler when you mark a class as a data class.
Sample Code
class PersonClass(val name: String, val age: Int)
data class PersonDataClass(val name: String, val age: Int)
>>> val ron = PersonClass("Ron", 18)
>>> val harry = PersonDataClass("Harry", 17)
>>> println(ron) // notice the string representation of a regular class
PersonClass#3b6eb2ec
>>> println(harry) // notice the string representation of a data class
PersonDataClass(name=Harry, age=17)
>>> val harryClone = harry.copy() // this creates a copy of the object referenced by harry
>>> val hermione = PersonDataClass("Hermine", 16)
>>> harry == harryClone
true
>>> harry == hermione
false
In summary, if you need a holder for data, you should use a data class which means adding the modifier data to your class. This will generate the following methods for you: toString(), equals(), hashCode(), componentN(), and copy(), so you avoid writing boilerplate code. If you use a regular class, you won't have all these "batteries included".
Data Class contains internal code which we have to override in Java-like Kotlin generates the equals(), hashCode(), and toString()
Kotlin:
data class User(val name: String, val age: String)
Java:
class Student {
public final String name;
public final String age;
public User(String name, String age) {
this.name = name;
this.age = age;
}
#Override
public boolean equals(Object other) {
}
#Override
public long hashCode() {
}
#Override
public String toString() {
return "User(name=" + name + ",age=" + age + ")";
}
}
Normal Class:
Can be abstract, open, sealed, or inner but not for Data Class
Constructor parameter can be declared without var and val