I have a specific question about the usage of generics in Kotlin.
I want to create a function which takes a generic T
as an argument.
It uses that to assign name
from one of the classes: Class1
or Class2
to the local variable testString
.
Unfortunately this is only possible when I check the type of the argument with the if conditions.
This leads to duplicate code. If I try to avoid that and use Line 12 I get this error during compile time: Unresolved reference: name
Is it possible in Kotlin to avoid the if conditions and use the testString assignment only once when the classes you are going to use have the same property with the same name?
Code:
fun main() {
val class1 = Class1("Foo1")
val class2 = Class2("Foo2")
}
class Class1(val name: String)
class Class2(val name: String)
fun <T> doStuff(classOneOrTwo: T) {
var testString: String
testString = classOneOrTwo.name //not working: Unresolved reference: name
if (classOneOrTwo is Class1) {
testString = classOneOrTwo.name
}
if (classOneOrTwo is Class2) {
testString = classOneOrTwo.name
}
}
CodePudding user response:
You don't need generics here.
You can just write an interface that requires its implementers to have a name
property.
interface HasName {
val name: String
}
Class1
and Class2
should implement the interface:
class Class1(override val name: String): HasName
class Class2(override val name: String): HasName
Then doStuff
can be written as:
fun doStuff(classOneOrTwo: HasName) {
var testString = classOneOrTwo.name
// ...
}
You can make doStuff
generic:
fun <T: HasName> doStuff(classOneOrTwo: T) {
var testString = classOneOrTwo.name
// ...
}
But you don't gain anything in particular by doing so.
Non-reified* generics are the most helpful when you want to establish some kind of "link", whether it be between parameters, or between parameters and the return type. For example, if your method is supposed to return the same type of thing as it takes:
fun <T> doStuff(foo: T): T { ... }
Or your method takes two parameters, and the second parameter must be the element type of the first parameter, which is a mutable list:
fun <T> doStuff(list: MutableList<T>, t: T) { ... }
* This paragraph doesn't quite apply to reified generics, which could be useful on their own.
CodePudding user response:
Class1
and Class2
have nothing in common for the doStuff function to resolve the property name
even though they were written exactly the same way, if you expect that just because you have a generic parameter T
everything will be automatically be resolved.
This compiles fine because everything in Kotlin is a direct or indirect child of Any?
fun main() {
val class1 = Class1("Foo1")
val class2 = Class2("Foo2")
doStuff(class1)
doStuff(class2)
}
if you try to invoke any function using classOneOrTwo
param and pressed cltr click
on it, youll see its a function of the type Any?
fun <T> doStuff(classOneOrTwo: T) {
...
...
classOneOrTwo.toString() // <-- ctrl click this you'll see its a function of Any?,
If you're getting the gist here, you should create a hierarchy (Inheritance
) where Class1
and Class2
can inherit something from, in your case name
open class ParentClass(open val name: String)
class Class1(override val name: String) : ParentClass(name)
class Class2(override val name: String) : ParentClass(name)
fun <T: ParentClass> doStuff(classOneOrTwo: T) {
Log.e("DoStuff", classOneOrTwo.name)
}
back to your main function
doStuff(class1)
doStuff(class2)
prints,
Foo1
Foo2