Home > Software design >  How to correctly cast list in Kotlin?
How to correctly cast list in Kotlin?

Time:11-29

I have a list for example of type People. My list can contain only elements of type Student or only elements of type Worker:

 interface People {
        val name: String
        val age: Int
    }

    data class Student(
        override val name: String,
        override val age: Int,
        val course: Int
    ) : People

    data class Worker(
        override val name: String,
        override val age: Int,
        val position: String
    ) : People

At some point I need to know the exact type of the list (student or worker). Can I safely find out the exact type? So far I've written this code, but it doesn't look very good:

fun someLogic(items: List<People>): List<People> {
    return (items as? List<Student>) ?: (items as? List<Worker>)
?.filter {}
....
}

Also, I get a warning:

Unchecked cast

Can you please tell me how to perform such transformations correctly?

CodePudding user response:

If you want to check that List<People> is List<Student> you can use this extension function:

fun List<People>.isStudentList(): Boolean {
    // returns true if no element is not Student, so all elements are Student
    return none { it !is Student } 
}

And if you want to cast List<People> to List<Student>, you can use map, and this cast is safe so let's say that there is some People that the are not Student so the cast is going to return null instead of Student because of as? and the mapNotNull is going to exclude null elements so in worst cases where you pass a list that doesn't contain any Student this function is going to return an empty list:

fun List<People>.toStudentList(): List<Student> {
    // This is going to loop through the list and cast each People to Student
    return mapNotNull { it as? Student }
}

And the same approach can be used for Worker

CodePudding user response:

I would solve the problem with more specific classes.

You can define:

interface PeopleList<P : People> : List<P>

class StudentList : PeopleList<Student> {
   // add implementation
}

class WorkerList : PeopleList<Worker> {
   // add implementation
}

You can then easily check the types of these lists. Each of those classes can then provide guarantees that you are not mixing Student and Worker objects in the same List, something you can't do with plain List<People> objects.

Note also you are better off writing your code avoiding checking types if at all possible. Much better to add methods to the PeopleList interface and force the subclasses to implement them, for example:

interface PeopleList<P : People> : List<P> {
    fun doSomethingGood()
}

Then you can call these methods at the appropriate time, instead of checking the type. This approach keeps the functionality associated with the subtypes alongside those subtypes and not scattered through the code at the various points where you have to check the type of PeopleList.

  • Related