In C# there is a kind of class that is called "record" which is more or less the same as a "data" class in Kotlin.
When using a record in C# you can use the keyword "with" to create a new instance of the your record with some properties set to specific values (see https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/with-expression )
I was wondering if there is a similar way to do it in kotlin ? I cannot find anything regarding that, and the way I do it for now is by defining function that do the job, but it can be kind of boilerplate sometimes, and using data class is supposed to avoid me that boilerplate work.
Also I'd prefer to avoid using "var" properties (to have immutable instances), hence my question.
With a data class, you can use the copy method:
val someData = SomeClass(a = 1, b = 2)
val modifiedData = someData.copy(b = 0) // modifiedData = SomeClass(a = 1, b = 0)
See the official data class documentation.
Kotlin has Data classes, that are close to C# records.
As you can see from the Kotlin documentation:
It is not unusual to create classes whose main purpose is to hold
data. In such classes, some standard functionality and some utility
functions are often mechanically derivable from the data. In Kotlin,
these are called data classes and are marked with data:
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 (see below).
The last line shows that the compiler automatically generates a copy function for a Data class, and this is what you are looking for
Again from the documentation, the usage would be:
Use the copy() function to copy an object, allowing you to alter some
of its properties while keeping the rest unchanged.
[...]
You can then write the following:
val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)
Do you mean something like copy() function that is auto-generated for data classes?
fun main() {
val foo1 = Foo("foo", 5)
val foo2 = foo1.copy(value1 = "bar")
val foo3 = foo2.copy(value2 = 10)
}
data class Foo(
val value1: String,
val value2: Int,
)
Related
I have a data class like this
data class Task(
var id: Int,
var description: String,
var priority: Int
)
I implement it the following
val foo = Task(1, "whatever", 10)
I read about accessing whatever like this
foo.description
or
foo.component2()
What is the difference?
There is no difference in behaviour, but use foo.description.
It's extremely rare to use a componentN() function directly. If you know which component you're accessing, it's just way more readable to use the property directly.
The componentN() functions are mostly a tool to implement actual destructuring declarations like:
val (id, desc, prio) = task
Which is a shortcut that is equivalent to:
val id = task.component1()
val desc = task.component2()
val prio = task.component3()
..which you should probably never write in source code.
I'm trying to reduce boilerplate on something I'm working on and wondering if something is possible - I suspect it's not but was looking for confirmation
class Something<T> {
private val list = mutableListOf<T>()
fun addToList(value: T) = list.add(value) }
So if I wanted to use this with a class like:
class Data(number: Int, letter: Char)
I'd have to use addToList like:
addToList(Data(1,"a"))
Is there some way to use the supplied type T to construct the method addToList dynamically? So that the class would be instantiated like:
val thing = Something<Data>()
but then addToList were called like
addToList(1,"a")
Like I said, don't think this is possible but was looking for confirmation.
What I was really trying to do was come up with something that would allow me to do this without declaring Data at all, but instead just define the structure and the subsequent addToList method when Something() was instantiated - not sure if I have described this all that well but if anyone has any suggestions in general around that I'd be grateful!
Thanks!
There are Pair and Triple tuple classes provided in the standard library which allows you to avoid declaring a class for simple combinations of values. If you need more than 3 parameters of different types, you'd need to create your own class or use a library that provides larger tuple classes. If all types are the same, you can use List instead of a tuple.
In my opinion even Triple is pushing it and anything with more than two distinct properties should just have its own data class defined.
class Something<A, B> {
private val list = mutableListOf<Pair<A, B>>()
fun addToList(valueA: A, valueB: B) = list.add(Pair(valueA, valueB))
}
val something = Something<Int, String>()
something.addToList(1, "a")
An alternate approach if you want to keep the flexibility of your Something class to hold anything would be to use an extension function.
class Something<T> {
private val list = mutableListOf<T>()
fun addToList(value: T) = list.add(value)
}
fun <A, B> Something<Pair<A, B>>.addToList(valueA: A, valueB: B) =
addToList(Pair(valueA, valueB))
val something = Something<Pair<Int, String>>()
something.addToList(1, "a")
This feature is inspired by TypeScript which allows us to create arrays based on the property of another class, whatever that property's type is.
For example assume you have this class in Kotlin:
class Person(
val name: String,
val age: Int
)
And later, somewhere else in the code I want to have a list of names, so I would do something like this:
val namesList = List<Person::name>()
And Kotlin will know that this will be equivalent to List<String>() at compile time.
This avoids me to manually propagate the type of a field I already declared in one place. Plus, if one day the name type changes from String to something else, all the collections would get updated automatically.
Can this be done in Kotlin?
No, Kotlin is very explicit about types. It is a strongly-typed language.
Maybe the closest you could do is define a type alias next to your class and use that:
typealias PersonName = String
data class Person(val name: PersonName, val age: Int)
and then:
val namesList = mutableListOf<PersonName>()
However, in most cases you don't have to explicitly write the types anyway because they can be inferred.
// Is a List<String> and would automatically update if name type changed
val nameList = personList.map(Person::name)
// Or to get an empty mutable list:
val nameList = emptyList<Person>().map(Person::name).toMutableList()
The standard thing to do is to use map to extract the type you need:
val people = listOf(
Person("a", 1),
Person("b", 2),
Person("c", 3),
)
val names = people.map { it.name } // statically inferred to List<String>
If you changed the type of name to something else, you wouldn't need to change the val names = people.map { it.name } line - the new type will be inferred automatically.
I have an immutable object:
class Foo(
val name: String,
val things: List<Thing>
)
A third party lib creates the Foo object with some 'null' Thing objects.
I am creating a new object:
val foo = thirdPartyGetFoo()
val filteredFoo = Foo(foo.name, foo.things.filterNotNull())
That works, however AndroidStudio greys out the filterNotNull function call and presents a warning:
Useless call on collection type: The inspection reports filter-like
calls on already filtered collections.
Is this the right way to filter that list? Should I ignore the warning or is there a better way?
You do not specify what library creates the object with nulls. Some deserialization libraries can use static factory methods which you could configure, and then have the factory method strip the null. For example, if this were Jackson you would simply:
class Foo(val name: String, val things: List<Thing>) {
companion object {
#JsonCreator
#JvmName("createFromNullable")
fun create(name: String, things: List<Thing?>) = Foo(name, things.filterNotNull())
fun create(name: String, things: List<Thing>) = Foo(name, things)
}
}
Then...
val goodFoo = jacksonObjectMapper().readValue<Foo>(someJsonWithNulls)
Maybe your library has options that are similar?
If not, and you don't have 100 of these things with this problem, I would probably create a temporary class to hold the results and convert that to the final class:
open class FooNullable(val name: String, open val things: List<Thing?>) {
open fun withoutNulls(): Foo = Foo(name, things.filterNotNull())
}
class Foo(name: String, override val things: List<Thing>) : FooNullable(name, things) {
override fun withoutNulls(): Foo = this
}
Then you can deserialize into FooNullable and just call withoutNulls() to get the other flavor that is clean. And if you accidentally call it on one without nulls already, it just does nothing.
val goodFoo = Foo("", emptyList<Thing>())
val alsoGoodFoo = goodFoo.withoutNulls() // NOOP does nothing
val badFoo = thirdPartyGetFoo()
val betterFoo = badFoo.withoutNulls() // clean up the instance
val safeFoo = thirdPartyGetFoo().withoutNulls() // all at once!
Not the cleanest, but does work. The downsides is this second step, although it looks like you were already planning on doing that anyway. But this model is safer than what you proposed since you KNOW which type of object you have and therefore you continue to be typesafe and have the compiler helping you avoid a mistake.
You don't have to use inheritance as in the above example, I was just trying to unify the API in case there was a reason to have either version in hand and know which is which, and also act upon them in a similar way.
I have a Kotlin data class that I am constructing with many immutable properties, which are being fetched from separate SQL queries. If I want to construct the data class using the builder pattern, how do I do this without making those properties mutable?
For example, instead of constructing via
var data = MyData(val1, val2, val3)
I want to use
builder.someVal(val1)
// compute val2
builder.someOtherVal(val2)
// ...
var data = builder.build()
while still using Kotlin's data class feature and immutable properties.
I agree with the data copy block in Grzegorz answer, but it's essentially the same syntax as creating data classes with constructors. If you want to use that method and keep everything legible, you'll likely be computing everything beforehand and passing the values all together in the end.
To have something more like a builder, you may consider the following:
Let's say your data class is
data class Data(val text: String, val number: Int, val time: Long)
You can create a mutable builder version like so, with a build method to create the data class:
class Builder {
var text = "hello"
var number = 2
var time = System.currentTimeMillis()
internal fun build()
= Data(text, number, time)
}
Along with a builder method like so:
fun createData(action: Builder.() -> Unit): Data {
val builder = Builder()
builder.action()
return builder.build()
}
Action is a function from which you can modify the values directly, and createData will build it into a data class for you directly afterwards.
This way, you can create a data class with:
val data: Data = createData {
//execute stuff here
text = "new text"
//calculate number
number = -1
//calculate time
time = 222L
}
There are no setter methods per say, but you can directly assign the mutable variables with your new values and call other methods within the builder.
You can also make use of kotlin's get and set by specifying your own functions for each variable so it can do more than set the field.
There's also no need for returning the current builder class, as you always have access to its variables.
Addition note: If you care, createData can be shortened to this:
fun createData(action: Builder.() -> Unit): Data = with(Builder()) { action(); build() }.
"With a new builder, apply our action and build"
I don't think Kotlin has native builders. You can always compute all values and create the object at the end.
If you still want to use a builder you will have to implement it by yourself. Check this question
There is no need for creating custom builders in Kotlin - in order to achieve builder-like semantics, you can leverage copy method - it's perfect for situations where you want to get object's copy with a small alteration.
data class MyData(val val1: String? = null, val val2: String? = null, val val3: String? = null)
val temp = MyData()
.copy(val1 = "1")
.copy(val2 = "2")
.copy(val3 = "3")
Or:
val empty = MyData()
val with1 = empty.copy(val1 = "1")
val with2 = with1.copy(val2 = "2")
val with3 = with2.copy(val3 = "3")
Since you want everything to be immutable, copying must happen at every stage.
Also, it's fine to have mutable properties in the builder as long as the result produced by it is immutable.
It's possible to mechanize the creation of the builder classes with annotation processors.
I just created ephemient/builder-generator to demonstrate this.
Note that currently, kapt works fine for generated Java code, but there are some issues with generated Kotlin code (see KT-14070). For these purposes this isn't an issue, as long as the nullability annotations are copied through from the original Kotlin classes to the generated Java builders (so that Kotlin code using the generated Java code sees nullable/non-nullable types instead of just platform types).