Kotlin function for data class - kotlin

What is the best way in Kotlin to write a function that can be used by a data class?
For example, say I have a data class and I need a function where the result is based on the value of a field from that data class:
data class Person(
val dateOfBirth: String
)
How would I go about writing an 'age' function for the Person object?

The same way you would write it for a non-data class!
You could add a method within the class:
data class Person(val dateOfBirth: String) {
fun age() = // …
}
Or you could add an extension method outside it:
data class Person(val dateOfBirth: String)
fun Person.age() = //…
(A method within the class is usually a better option if you can modify the class, and it belongs conceptually to the class. An extension method is useful if you don't have access to the class, or if it's specific to some particular usage or has a dependency on something unrelated to the class.)
Of course, you can always write a simple, old-style function:
fun calculateAge(person: Person) = // …
…but an extension method is clearer, reads better, and your IDE will suggest it.
In this case (where the age is quick to calculate, doesn't change the object's visible state, and won't throw an exception), a property or extension property might be more natural:
data class Person(val dateOfBirth: String) {
val age get() = // …
}
Or:
data class Person(val dateOfBirth: String)
val Person.age get() = //…
Then you can access it simply as myPerson.age.

The same way you would for any other class:
data class Person(val dateOfBirth: String) {
fun age(): Int {
// Use dateOfBirth here to compute the age.
}
}

Related

Share common properties between Kotlin classes

I have 2 (data) classes that almost share the same properties:
data class Foo(
val id: FooId,
val name: String,
... 10+ properties
}
data class NewFoo(
val name: String,
... 10+ properties
}
I just want some syntax sugar magic here: not to repeat 10+ properties. I can make a base sealed class, but you would end up writing even more text (for passing arguments to base class ctor), although you are safer from making a mistake.
Yes, I know I could use composition for this, but here I don't want to, as there might be different 'variants' of the same data.
Am I missing something or this is not possible in Kotlin?
You can use an abstract (or sealed) class with abstract params instead and override them in the constructor of your data class (i.e. without additional passing them into the constructor of the base class).
abstract class Base {
// put commons parameter here
// abstract param needs to be initialized in the constructor of data class
abstract val name: String
// you can define some not-abstract params as well
open lateinit var someOtherParam: String
}
data class Foo1(
override val name: String,
val id: Int,
val someAdditionalParam1: String
) : Base()
data class Foo2(
override val name: String,
val someAdditionalParam2: String,
override var someOtherParam: String
) : Base()

How to have a data class (Kotlin) extend from a superclass and inherit one of the superclass fields?

I try to create a custom toString() method for some of my data classes. But I would like to define the override of fun toString only once.
This would be my super class:
abstract class Id(val value: String) {
override fun toString(): String {
return value
}
}
Now I want to use the customised toString() method in some of my data classes by extending the superclass Id:
data class MyId(val v: String): Id(v)
however, this would introduce a secondary field and getter for "v" in MyId, which is not what I want. Using "value" instead of "v" gives me the issue: 'value' hided a member of supertype 'Id'. I would like to reuse the "value" field and getter, defined in Id. I do not want to introduce a new one.
How can I correct this?
I'm not quite sure what you are trying to do, but you can do this
abstract class Id(open val value: String) {
override fun toString(): String {
return value
}
}
data class MyId(override val value: String): Id(value)
Note that for non-data classes, you could just remove val instead of overriding it:
class MyId(value: String): Id(value)
This also doesn't require value in Id to be open.
This isn't legal when MyId is a data class because all of its constructor parameters must be val or var.
What I wanted to do in my question was overriding the toString() method of a data class, by exsting a superclass.
This is not possible. Francesc his solution would not override the toString() method of the dataclass. The generated toString method of the dataclass would still be used.
consider:
val myId = MyId("a string value")
assertThat(myId.toString()).isEqualTo("MyId\"a string value\")")
assertThat(myId.toString()).isNotEqualTo("a string value")
The only way of altering the .toString() method of a data class, is in the data class itself:
data class MyId(val value: String) {
override fun toString() = value
}

What is difference between object and data class in 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.

Override getters in Kotlin?

So I have an abstract class Composition, which has two children: one is a Track, and one is an Album (which is a group of Tracks).
class Composition(val name: String, ...)
class Track(name: String): Composition(name)
class Album(name: String, val tracks: List<Track>): Composition(name)
So far, so good. Now, I have the duration that is added. It is abstract in Composition, so I can override it in the children:
abstract class Composition(...){
abstract fun getDuration(): Int
}
Now, I can add override the method in the Track, which takes it as a parameter:
class Track(..., private val duration: Int): Composition(...){
override fun getDuration() = duration
}
And finally, I make the Album, whose duration is the sum of the Tracks:
class Album(..., val tracks: List<Track>): Composition(...){
override fun getDuration() = tracks.sumBy { it.getDuration() }
}
It works as intended, but I do not understand why I cannot simply use tracks.sumBy { it.duration }, since in Kotlin properties are nothing more than getters and setters (I'm thinking about the getDuration in Composition).
I feel like I'm missing something, because if the same code was written in Java, I would be able to call composition.duration as a property -- so that makes me think that Kotlin allows it from Java code, but not from Kotlin code, which is sad.
An other example:
Let's say I have a class named Artist, who wrote multiple Compositions:
class Artist(
val nom: String,
private val _compositions: MutableList<Composition> = ArrayList()
) {
// HERE (I wrote the extension method List<E>.toImmutableList)
fun getCompositions() : List<Composition> = _compositions.toImmutableList()
}
This is standard in Java (exposing immutable versions of Collections via getters, so they are not modified) ; Kotlin doesn't recognize it though:
val artist = Artist("Mozart")
artist.getCompositions() // Legal
artist.compositions // Illegal
I thought about making this a property, but:
- If I choose the type List<E>, I can override the getter to return the immutable list, but I cannot use regular methods (add...) as the List is immutable
- If I choose the type MutableList<E>, I cannot override the getter to return ImmutableList (which is a subclass of List that I wrote, and is obviously not a subclass of MutableList).
There's a chance I'm doing something ridiculous while there is an easy solution, but right now I cannot find it.
In the end, my question is: Why aren't manually-written getters considered properties when written from Kotlin?
And, if I'm mistaking, What is the expected way of solving both of these patterns?
If you want to use it as property, you should use Kotlin-way to override getter.
For example:
abstract class Composition(...){
abstract val duration: Int
}
// You can use "override" in constructor
// val - is immutable property that has only getter so you can just
// remove private modifier to make possible get it.
class Track(..., override val duration: Int): Composition(...){
...
}
class Album(..., val tracks: List<Track>): Composition(...) {
override val duration: Int
get() = tracks.sumBy { it.duration }
}
Also there are may be case when you need mutable property that can be changed only inside of object. For this case you can declare mutable property with private setter:
class SomeClass(value: Int) {
var value: Int = value
private set
}
Read more in docs: https://kotlinlang.org/docs/reference/properties.html#getters-and-setters
You have to define duration as an abstract property and not as an abtract function (https://kotlinlang.org/docs/reference/properties.html#getters-and-setters):
abstract class Composition(val name: String) {
abstract val duration: Int
}
class Track(name: String, override val duration: Int): Composition(name)
class Album(name: String, val tracks: List<Track>): Composition(name) {
override val duration: Int
get() = tracks.sumBy { it.duration }
}
The getter/setter conversion as properties does only work for Java classes (https://kotlinlang.org/docs/reference/java-interop.html#getters-and-setters).

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.