I'm new to kotlin and I know that nullable are something I should'nt use as much as I would want too. So I was wondering if something like that would be possible.
class Header(var next: Trailer)
class Trailer(var prev: Header)
fun main() {
lateinit var trailer: Trailer
val header = Header(trailer)
trailer = Trailer(header)
}
Thank you for the time used to answer my question!
First of all, I suggest to re-consider if there is a better way. Such cyclic relations are generally problematic.
Secondly, I think the only non-hacky way to create this kind of object relationship is inside the constructor, for example:
class Header {
var next: Trailer = Trailer(this)
}
fun main() {
val header = Header()
val trailer = Trailer(header)
header.next = trailer
}
Still, initialization for these objects is very tricky, so I suggest to hide their constructors and encapsulate initialization inside a function. This way we can also turn both prev and next properties into val, for example:
class Header internal constructor() {
val next: Trailer = Trailer(this)
}
class Trailer internal constructor(val pref: Header)
fun createHeaderTrailerPair(): Header = Header()
fun main() {
val header = createHeaderTrailerPair()
val trailer = header.next
}
If you need to pass any parameters to Header or Footer, you have to do this through both createHeaderTrailerPair() function and Header constructor:
class Header internal constructor(
val name: String,
trailerName: String
) {
var next: Trailer = Trailer(this, trailerName)
}
class Trailer internal constructor(
val pref: Header,
val name: String
)
fun createHeaderTrailerPair(headerName: String, trailerName: String): Header = Header(headerName, trailerName)
I don't think this is possible, and even if you manage to do that, I'd recommend against it
You can (and should) use nullable when you have a reason to use it and this seems a good enough reason
But if you really want to avoid null (and all you needing is the order between objects), you could use a Pair<Header, Trailer>, a List<>, or even a new class as a wrapper:
class HeaderTrailerWrapper(val header: Header, val trailer: Trailer)
class Header
class Trailer
Related
Let's say I have the following class constructor:
class Car(val brand: Brand,val modelName: String, val version: Int){}
If for example, I want the version number to always start with 1. Is there a way to manipulate it in the class body to achieve this ?
Meaning:
val firstdigit:Int = abs(version).ToString().Substring(0,1)
And then parse it to Int. But how to replace the original first digit after that?
I'm just learning Kotlin and I got a bit stuck with this
Is this what you had in mind?
class Car(val brand: Brand, val modelName: String) {
val version = getNextVersion()
companion object {
private var nextVersion = 0
private fun getNextVersion(): Int {
nextVersion++
if (nextVersion.toString()[0] != '1') {
nextVersion = (10.0.pow(ceil(log10(nextVersion.toDouble())))).toInt()
}
return nextVersion
}
}
}
You already said in the comments that you want the number to increment per instance, so the caller shouldn't be providing that number in the first place really! But just generally, here's two approaches to sanitising your input parameters:
1) Make it the caller's responsibility to provide valid data
init {
require(version.toString().first() == '1') { "Needs to start with 1 thanks" }
}
require throws an IllegalArgumentException if it fails, which is the standard exception for "the value of this argument is invalid". Should the class be responsible for taking bad data and trying to "fix" it, or should the caller be handling that - and maybe not constructing an instance at all if it doesn't have valid data?
2. create a newInstance function that uses valid data, and keep the constructor private
class Thing private constructor(val number: Int){
companion object {
fun newInstance(num: Int): Thing {
return Thing(abs(num))
}
}
}
fun main() {
Thing.newInstance(-2).let { println(it.number)}
}
If it makes sense for the class itself to sanitise the input parameters, you can delegate construction to a function that takes care of that, and prevent things from calling the constructor directly with potentially bad data.
This can cause issues with e.g. serialisation libraries (which want to call the constructor directly) but in that case you could leave the constructor public, and just advise callers to call newInstance instead. Not ideal, but it's an option!
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.
Let's say I have an object which helps me to deserialize other objects from storage:
val books: MutableList<Book> = deserializer.getBookList()
val persons: MutableList<Person> = deserializer.getPersonList()
The methods getBookList and getPersonList are extension functions I have written. Their logic is allmost the same so I thought I may can combine them into one method. My problem is the generic return type. The methods look like this:
fun DataInput.getBookList(): MutableList<Book> {
val list = mutableListOf<Book>()
val size = this.readInt()
for(i in 0 .. size) {
val item = Book()
item.readExternal(this)
list.add(item)
}
return list
}
Is there some Kotlin magic (maybe with inline functions) which I can use to detect the List type and generify this methods? I think the problem would be val item = T() which will not work for generic types, right? Or is this possible with inline functions?
You cannot call the constructor of a generic type, because the compiler can't guarantee that it has a constructor (the type could be from an interface). What you can do to get around this though, is to pass a "creator"-function as a parameter to your function. Like this:
fun <T> DataInput.getList(createT: () -> T): MutableList<T> {
val list = mutableListOf<T>()
val size = this.readInt()
for(i in 0 .. size) {
val item = createT()
/* Unless readExternal is an extension on Any, this function
* either needs to be passed as a parameter as well,
* or you need add an upper bound to your type parameter
* with <T : SomeInterfaceWithReadExternal>
*/
item.readExternal(this)
list.add(item)
}
return list
}
Now you can call the function like this:
val books: MutableList<Book> = deserializer.getList(::Book)
val persons: MutableList<Person> = deserializer.getList(::Person)
Note:
As marstran mentioned in a comment, this requires the class to have a zero-arg constructor to work, or it will throw an exception at runtime. The compiler will not warn you if the constructor doesn't exist, so if you pick this way, make sure you actually pass a class with a zero-arg constructor.
You can't initialize generic types, in Kotlin or Java. At least not in the "traditional" way. You can't do this:
val item = T()
In Java, you'd pass a Class<T> and get the constructor. Very basic example of that:
public <T> void x(Class<T> cls){
cls.getConstructor().newInstance(); // Obviously you'd do something with the return value, but this is just a dummy example
}
You could do the same in Kotlin, but Kotlin has a reified keyword that makes it slightly easier. This requires an inline function, which means you'd change your function to:
inline fun <reified T> DataInput.getBookList(): MutableList<T> { // Notice the `<reified T>`
val list = mutableListOf<T>() // Use T here
val size = this.readInt()
for(i in 0 .. size) {
// This is where the initialization happens; you get the constructor, and create a new instance.
// Also works with arguments, if you have any, but you used an empty one so I assume yours is empty
val item = T::class.java.getConstructor().newInstance()!!
item.readExternal(this) // However, this is tricky. See my notes below this code block
list.add(item)
}
return list
}
However, readExternal isn't present in Any, which will present problems. The only exception is if you have an extension function for either Any or a generic type with that name and input.
If it's specific to some classes, then you can't do it like this, unless you have a shared parent. For an instance:
class Book(){
fun readExternal(input: DataInput) { /*Foo bar */}
}
class Person(){
fun readExternal(input: DataInput) { /*Foo bar */}
}
Would not work. There's no shared parent except Any, and Any doesn't have readExternal. The method is manually defined in each of them.
You could create a shared parent, as an interface or abstract class (assuming there isn't one already), and use <reified T : TheSharedParent>, and you would have access to it.
You could of course use reflection, but it's slightly harder, and adds some exceptions you need to handle. I don't recommend doing this; I'd personally use a superclass.
inline fun <reified T> DataInput.getBookList(): MutableList<T> {
val list = mutableListOf<T>()
val size = this.readInt()
val method = try {
T::class.java.getMethod("readExternal", DataInput::class.java)
}catch(e: NoSuchMethodException){
throw RuntimeException()
}catch(e: SecurityException){
throw RuntimeException()// This could be done better; but error handling is up to you, so I'm just making a basic example
// The catch clauses are pretty self-explanatory; if something happens when trying to get the method itself,
// These two catch them
}
for(i in 0 .. size) {
val item: T = T::class.java.getConstructor().newInstance()!!
method.invoke(item, this)
list.add(item)
}
return list
}
The Kotlin documentation describes cloning only in accessing Java and in enum class. In latter case clone is just throwing an exception.
So, how would I / should I clone arbitrary Kotlin object?
Should I just use clone() as in Java?
For a data class, you can use the compiler-generated copy() method. Note that it will perform a shallow copy.
To create a copy of a collection, use the toList() or toSet() methods, depending on the collection type you need. These methods always create a new copy of a collection; they also perform a shallow copy.
For other classes, there is no Kotlin-specific cloning solution. You can use .clone() if it suits your requirements, or build a different solution if it doesn't.
You can use Gson library to convert the original object to a String and then convert back that String to an actual Object type, and you'll have a clone. Although this is not the intended usage of the Gson library which is actually used to convert between JSON and other object types, but I have devised this method to solve the cloning problem in many of my Kotlin based Android applications.
See my example. Put this function in the class/model of which you want to create a clone. In my example I'm cloning an Animal type object so I'll put it in the Animal class
class Animal{
fun clone(): Animal
{
val stringAnimal = Gson().toJson(this, Animal::class.java)
return Gson().fromJson<Animal>(stringAnimal, Animal::class.java)
}
}
Then use it like this:
val originalAnimal = Animal()
val clonedAnimal = originalAnimal.clone()
A Kotlin data class is easy to clone using .copy()
All values will be shallow copied, be sure to handle any list/array contents carefully.
A useful feature of .copy() is the ability to change any of the values at copy time. With this class:
data class MyData(
val count: Int,
val peanuts: Int?,
val name: String
)
val data = MyData(1, null, "Monkey")
You could set values for any of the properties
val copy = data.copy(peanuts = 100, name = "Elephant")
The result in copy would have values (1, 100, "Elephant")
If the class you are trying to clone does not implement Cloneable or is not a data class and is a part of an outside library, you can create an extension method that returns a new instance. For example:
class Person {
var id: String? = null
var name: String? = null
}
fun Person.clone(): Person {
val person = Person()
person.id = id
person.name = name
return person
}
It requires to implement Cloneable for your class then override clone() as a public like:
public override fun clone(): Any {<your_clone_code>}
https://discuss.kotlinlang.org/t/how-to-use-cloneable/2364/3
fun <T : Any> clone (obj: T): T {
if (!obj::class.isData) {
println(obj)
throw Error("clone is only supported for data classes")
}
val copy = obj::class.memberFunctions.first { it.name == "copy" }
val instanceParam = copy.instanceParameter!!
return copy.callBy(mapOf(
instanceParam to obj
)) as T
}
I've voted for #yole for nice answer, but other ways if you don't (or can't) use data class. You can write helper method like this:
object ModelHelper {
inline fun <reified T : Serializable> mergeFields(from: T, to: T) {
from::class.java.declaredFields.forEach { field ->
val isLocked = field.isAccessible
field.isAccessible = true
field.set(to, field.get(from))
field.isAccessible = isLocked
}
}
}
So you can "copy" instance A into B by:
val bInstance = AClassType()
ModelHelper.mergeFields(aInstance, bInstance)
Sometimes, I use this way to merge data from many instances into one object which value available (not null).
Here is a consistent solution that works for any object type:
Kotlin's Array data structure provides a clone() method that can be used to clone the contents of the array:
val a = arrayOf(1)
//Prints one object reference
println(a)
//Prints a different object reference
println(a.clone())
As of Kotlin 1.3, the clone method has been supported on all major targets, so it should be usable across platforms.
It's also possible to clone an object using kotlinx.serialization
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
#Serializable
class A
{
val name: String = "Cloneable class A"
fun clone(): A {
val json = Json(JsonConfiguration.Stable)
val jsonStr = json.stringify(serializer(), this)
return json.parse(serializer(), jsonStr)
}
}
Collection copying functions, such as toList(), toMutableList(), toSet() and others, create a snapshot of a collection at a specific moment. Their result is a new collection of the same elements. If you add or remove elements from the original collection, this won't affect the copies. Copies may be changed independently of the source as well.
val alice = Person("Alice")
val sourceList = mutableListOf(alice, Person("Bob"))
val copyList = sourceList.toList()
sourceList.add(Person("Charles"))
alice.name = "Alicia"
println("First item's name is: ${sourceList[0].name} in source and ${copyList[0].name} in copy")
println("List size is: ${sourceList.size} in source and ${copyList.size} in copy")
First item's name is: Alicia in source and Alicia in copy
List size is: 3 in source and 2 in copy
Kotlin Official Document
Sample Screenshot
for example , I want to change all setters this way:
this.a = StringUtils.trim(a);
If it's a java bean, I can do this by modifying the code generating template of the ide. But Intellij seems not support to atomically add getter/setter for kotlin data class.
Is there a way to do this?
There is not a way to do this as of Kotlin 1.1.
A Kotlin data class, for the most part, is a class "to do nothing but hold data".
I think the closest you can get is to validate your data upon class initialization and make your data class properties read-only values. e.g.:
data class Data(val a: String) {
init {
require(a == a.trim())
}
}
The following won't throw an exception:
val a = Data("ab")
val b = a.copy(a = "abc")
While the following will:
val c = a.copy(a = "abc ")
It looks like if you declare the property as private, you can create your own getter/setters for accessing it. This example works for me.
fun main(args: Array<String>) {
var t = test("foo")
t.setHello("bar")
println(t)
}
data class test(private var hello: String) {
fun setHello(blah: String) {
this.hello = blah
}
}
But you will still have an issue when the property is passed in to the constructor. You will probably need to rethink how you are doing this, either declaring the field private and trimming it in the getter, or not using a data class for this instance.