Home > OS >  How can generics in functions be used to avoid duplicate code?
How can generics in functions be used to avoid duplicate code?

Time:11-01

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
  • Related