What is difference between object and data class in Kotlin? - kotlin

What is difference between data and object class in Kotlin, and what is the purpose of each?
data class User(val name: String, val age: Int)
and
object user {
val name = ""
fun printName(name: String) = "Hello, $name!"
}

object
object is Kotlin's way to create a singleton (one instance class) which is instantiated by the compiler.
data class
A data class is like a usual class but with a few advantages/resctrictions (Source).
Advantages
equals()/hashCode()
toString()
componentN()
copy()
Those are created from the properties specified in the primary constructor.
Restrictions
The primary constructor needs to have at least one parameter;
All primary constructor parameters need to be marked as val or var;
cannot be abstract, open, sealed or inner;
(before 1.1) may only implement interfaces.

Kotlin's object is similar to a class in Java, where all methods and variables are static.
object User {
val name = ""
fun printName(name: String) = "Hello, $name!"
}
in Kotlin is similar to the following in Java:
class User {
public static String name = "";
public static String printName(name: String) {
return "Hello " + name + "!";
}
}
Usage example:
//Kotlin
User.printName(User.name)
//Java
User.printName(User.name);
An object isn't exactly the same as the Java comparison I gave, though. It can inherit interfaces and classes, and the object itself is instantiated as a singleton instance. If you annotate methods inside an object with #JvmStatic, they will become true static members.
Kotlin's object
The data class in Kotlin is just a simpler syntax for a class that has no (or minimal) logic, and contains certain values. Kotlin generates the equals(), hashCode() and toString() functions for you in a data class, along with some other helper functions.
data class User(val name: String, val age: String)
in Kotlin will look something like this in Java:
class User {
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) {
//Kotlin-generated equality check
}
#Override
public long hashCode() {
//Kotlin's hashcode
}
#Override
public String toString() {
return "User(name=" + name + ",age=" + age + ")";
}
//other generated methods
}
Kotlin's data class documentation

First, there is no object class, the feature you are referring to is called object declaration.
Object declaration
This is a feature in Kotlin that allows you implement a singleton. The object declaration combines a class declaration and a declaration of a single instance of the class in a single statement.
// Let's assume that class Person is defined somewhere else
object Payroll {
val employees = arrayListOf<Person>()
fun calculateSalary() {
for (person in employees) {
// ...
}
}
}
// calling methods and properties
>>> Payroll.employees.add(Person("John", 23)) // calling a property
>>> Payroll.calculateSalary() // calling a method
Just like a class, an object declaration can contain declarations of properties, methods, initializer blocks, and so on. The only thing they are not allowed are constructors (either primary or secondary).
Object declarations are created immediately at the point of the definition, not through constructor calls from other places in the code.
Note: the object keyword can also be used for companion objects and object expressions.
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
However, by adding the modifier data to your class, the necessary methods are automatically added for you. In addition, the following methods are also generated:
componentN() functions corresponding to the properties in their order of declaration
copy() function
class PersonClass(val name: String, val age: Int) // regular class
data class PersonDataClass(val name: String, val age: Int) // data class
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. On the other hand, if you need to create a singleton, you use the object declaration feature.

In short, object is used, if you want to create singleton, unique object for the class and data class is a class that has equals, hashCode, toString automatically generated.

Related

How to change return type based on a function input which is a class name?

I have multiple data classes and each class has a corresponding class containing more info. I want to write a function in which I should be able to pass an identifier (table name corresponding to the data class). Based on this identifier, object of the corresponding class should be made, the value changed and this object should be returned as output of the function. I have written a simplified version of it on playground but I am unable to get it to work. Any help is appreciated.
class someClass(
)
class objectForSomeClass(
var value: String
)
class someOtherClass(
)
class objectForSomeOtherClass(
var value: String
)
class doSomething() {
companion object {
val classMap = mapOf(
"someClass" to objectForSomeClass::class,
"someOtherClass" to objectForSomeOtherClass::class,
)
}
// Create a map of class name to a new object based on the class name input
fun dummyFun(className: String, valueInput: String): Map<String, kotlin.Any> {
var returnObject = mutableListOf<Pair<String, kotlin.Any>>()
when(className) {
"SOME_CLASS" -> {
returnObject = mutableListOf<Pair<String, justDoIt.classMap["someClass"]()>>()
}
"SOME_OTHER_CLASS" -> {
returnObject = Map<String, justDoIt.classMap["someOtherClass"]()>
}
}
returnObject[className].value = valueInput
return returnObject
}
}
fun main() {
var obj = doSomething()
var t = obj.dummyFun("SOME_CLASS", "Value to be inserted")
// do something with t
}
Not knowing more about your classes (the ones in your code are not data classes – a data class in Kotlin is a specific type of class) I still think a lot could be simplified down to maybe even this:
fun createObject(className: String, value: String): Any? {
return when (className) {
"SomeClass" -> ObjectForSomeClass(value)
"SomeOtherClass" -> ObjectForSomeOtherClass(value)
// ...
else -> null
}
}
Additionally:
The classMap is not necessary, you can hard-code the cases in the when clause as in my example. There is also no need for reflection, which you would need to create instances from SomeType::class.
With getting rid of classMap you also do not need the companion object holding it anymore, and then you are left with one function for creating instances of your classes, and this function does not have to be in a class. You might put it into a singleton class called object in Kotlin (https://kotlinlang.org/docs/object-declarations.html#object-expressions)
Data classes in Kotlin: https://kotlinlang.org/docs/data-classes.html
You could maybe also replace each class someClass & class objectForSomeClass pair with a class someClass with a companion object.

Kotlin lazy initialization in subclass

I'm trying to build a string with properties that are initialized in a subclass.
I read about lazy initialization but somehow this doesn't work as I expected.
abstract class SubProcessFullNameBuilder(technicalDomain: TechnicalDomainEnumeration) {
protected val moduleName = "td.${technicalDomain.value().toLowerCase()}.shared"
private val packageName by lazy { packageName() }
private val processName by lazy { processName() }
val processFullName: String = "$moduleName/$packageName.$processName"
protected abstract fun packageName(): String
protected abstract fun processName(): String
}
class WorkerFullNameBuilder(
private val jmsDirection: JmsDirectionEnumeration,
technicalDomain: TechnicalDomainEnumeration,
private val cdmCode: String) : SubProcessFullNameBuilder(technicalDomain) {
override fun packageName() = "$moduleName.workers.${jmsDirection.value().toLowerCase()}.${cdmCode.toLowerCase()}"
override fun processName() = "Worker"
}
Since I have overridden the packageName() and processName() properties, I would expect that on calling the packageName property it would use the implementation from the subclass.
But when I call the processFullName property, it throws a java.lang.NullPointerException.
val builder = WorkerFullNameBuilder(JmsDirectionEnumeration.ESB_IN, TechnicalDomainEnumeration.INFOR, "ccmd")
val name = builder.processFullName
How can I initialize the packageName and processName properties in a proper way?
This is a case of calling a non-final method in a constructor and thus accessing uninitialized variables.
This line is still evaluated eagerly, at the time when the base class is constructed:
val processFullName: String = "$moduleName/$packageName.$processName"
To get the values of the two lazy properties, this will make calls to the abstract methods, of which packageName() refers to jmsDirection and cdmCode to return its value - these properties are not initialized yet, because their values are set after the superclass constructor runs. Here's a simplified version of the subclass' constructor, decompiled back to Java:
public WorkerFullNameBuilder(#NotNull JmsDirectionEnumeration jmsDirection, #NotNull TechnicalDomainEnumeration technicalDomain, #NotNull String cdmCode) {
super(technicalDomain);
this.jmsDirection = jmsDirection;
this.cdmCode = cdmCode;
}
As a demonstration, if you don't refer to these, for example, if you return constants in both of the subclass methods, your code will actually run fine:
override fun packageName() = "foo"
override fun processName() = "Worker"
However, the solution you need here is most likely to make the processFullName property itself lazy instead of the two values it uses (which you're evaluating at constructor time right now anyway, so you're not making use of them being lazy). This means you don't even need those two as separate properties:
abstract class SubProcessFullNameBuilder(technicalDomain: TechnicalDomainEnumeration) {
protected val moduleName = "td.${technicalDomain.value().toLowerCase()}.shared"
val processFullName by lazy { "$moduleName/${packageName()}.${processName()}" }
protected abstract fun packageName(): String
protected abstract fun processName(): String
}

Is there any way to transform the value of a property at data class construction time?

When creating a data class I frequently find that I want to transform one of the properties, usually to normalize it or to make a defensive copy. For example, here I want productCode to always be lowercase:
data class Product(val productCode: String)
I've tried adding an init block, in the hopes that Kotlin would be smart enough to let me manually deal with the assignment of the constructor parameter to the property:
data class Product(val productCode: String) {
init {
this.productCode = productCode.toLowerCase()
}
}
but it treats this as a reassignment.
I'd rather not have to write equals/hashCode/toString/copy by hand and IDE generated methods aren't really much better.
Is there any way to transform constructor parameters in a data class?
No. For equality and toString to work, the properties need to be in the primary constructor.
What you can do however, is create a factory method:
data class Product private constructor(val productCode: String) {
companion object Factory {
fun create(productCode: String) : Product {
return Product(productCode.toLowerCase())
}
}
}
By making the constructor private you force usage of this create method.
If you want to get 'hacky', you can pretend you're still calling the constructor, by renaming create to invoke and making it an operator function:
data class Product private constructor(val productCode: String) {
companion object {
operator fun invoke(productCode: String): Product {
return Product(productCode.toLowerCase())
}
}
}
Calling Product("foo") will call the invoke method.
Note: the constructor is still exposed through the copy method, see https://youtrack.jetbrains.com/issue/KT-11914
What about
sealed class Product {
abstract val productCode: String
private data class Product(override val productCode: String) : your.package.Product()
companion object {
operator fun invoke(productCode: String): your.package.Product =
Product(productCode.toLowerCase())
}
}
All the advantages of data class without exposing copy. A negative is having to repeat property names an extra time.

Kotlin data class with different backing field type

I have a simple class used for JSON serialization. For this purpose, the external interface uses Strings, but the internal representation is different.
public class TheClass {
private final ComplexInfo info;
public TheClass(String info) {
this.info = new ComplexInfo(info);
}
public String getInfo() {
return this.info.getAsString();
}
// ...more stuff which uses the ComplexInfo...
}
I have this working in Kotlin (not sure if there's a better way). But the non-val/var constructor prevents me from using data.
/*data*/ class TheClass(info: String) {
private val _info = ComplexInfo(info)
val info: String
get() = _info.getAsString()
// ...more stuff which uses the ComplexInfo...
}
How do I get this working as a data class?
You can use a combination of a private ComplexInfo property declared in the primary constructor and a secondary constructor that accepts a String.
Optionally, make the primary constructor private.
Example:
data class TheClass private constructor(private val complexInfo: ComplexInfo) {
constructor(infoString: String) : this(ComplexInfo(infoString))
val info: String get() = complexInfo.getAsString()
}
Note that it's the complexInfo property that is used in the data class generated members implementations.

When creating an interface in Kotlin, does it matter if properties have get/set?

In a Kotlin interface, does it matter if properties are declared with empty get/set statements?
For instance...
interface ExampleInterface {
// These...
val a: String
get
var b: String
get
set
// ...compared to these...
val c: String
var d: String
}
I'm having a hard time noticing a difference.
When implementing the interface, it doesn't seem to matter if I use getters/setters for the properties, or if I set the value directly.
When accessing these through java, the val's both have getters, and the var's both have getters and setters.
public void javaMethod(ExampleInterface e) {
e.getA();
e.getB();
e.setB();
e.getC();
e.getD();
e.setD();
}
The property declarations in your example are identical, get and set can be safely removed from there, because, as you correctly noted, the accessors are generated anyway. The syntax with get and set can, however, be used to provide an accessor implementation or to restrict its visibility.
Providing implementation:
interface ExampleInterface {
var b: String
get() = ""
set(value) { }
}
This example shows a default implementation of a property declared in an interface. This property can still be overriden inside the interface implementations.
class Example {
var b: String = ""
get() = "$field$field"
}
Here, get() = ... overrides the default getter behavior of a property with a backing field, whereas set is not mentioned, thus it behaves normally.
Visibility restriction:
class Example {
var s: String = "s"
private set
}
In this example, the setter visibility is private. The visibility of get is always the same to the visibility of the property, so there's no need to specify it separately. Interfaces cannot declare private members.
abstract class Example {
abstract var b: String
protected set // Restrict visibility
}
The setter of this property is restricted to this class and its subclasses. Interfaces cannot declare protected members.
Of course, an accessor implementation can be combined with visibility restriction:
class Example {
var s: String = "abc"
private set(value) { if (value.isNotEmpty()) field = value }
}
See also:
The Kotlin reference article about properties
Properties visibility explanation in another answer