I'm using kotlin sealed class. And I need to retrieve specific subclass. My sealed class:
sealed class Course(
val type: Type
) {
data class ProgrammingCourse(val name: String, val detail: String) : Course(Type.PROGRAMMING)
object LanguageCourse: Course(Type.LANGUAGE)
.....
}
For example I have function which can return Course
:
fun getCourse(): Course {
if(...)
return Course.ProgrammingCourse("test", "test")
else
return Course.LanguageCourse
}
In addition, I have a method that can only work with a specific subclass of the Course
class. Fox example:
fun workWithCourse(course: Course.ProgrammingCourse) {
// here some logic
}
And now I'm trying to get the course using the method getCourse()
, and then pass it to the method workWithCourse()
fun main() {
val course = getCourse()
workWithCourse(course)
}
Error:
Type mismatch.
Required:
Course.ProgrammingCourse
Found:
Course
But I know the course type - Type
, parameter that each course has. Can I, knowing this Type
, cast the course (which I retrieve from getCourse()
method) to a specific subclass ? Is there such a way ?
Please help me
P.S.
I don't need type checks like:
if(course is Course.ProgrammingCourse) {
workWithCourse(course)
}
I need the subclass to be automatically inferred by the Type
parameter, if possible.
P.S.2
The need for such a solution is that I have a class that takes a Course
, it doesn't know anything about a particular course, at the same time the class takes the Type
that I want to use for identification. This class also receives an interface (by DI) for working with courses, a specific implementation of the interface is provided by the dagger(multibinding) by key, where I have the Type
as the key. In the same way I want to pass by the same parameter Type
specific subclass of my Course
to my interface which working with specific courses.
CodePudding user response:
No, there is no way for automatic inference to the best of my knowledge.
You returned a Course
, and that's what you have. Being sealed
here does not matter at all. Generally what you do here is use the when
expression if you want to statically do different things depending on the type, but if it's just one type (ProgrammingCourse
) that can be passed to workWithCourse
, then an if
is probably right, with dispatch using as
.
That said, this looks like counter-productive design. If you can only work with one course, why do they even share a top level interface? The way the code is written implies working is a function that can take any course, or should be a method member. Anything else is very confusing. Perhaps workWithCourse
should take a Course
and use the when
expression to dispatch it appropriately?
CodePudding user response:
In kotlin you can specify the class explicitly with as
.
val course = getCourse()
if (type == Type.PROGRAMMING) {
workWithCourse(course as Course.ProgrammingCourse)
}
*thanks Joffrey for his comment
CodePudding user response:
What you seem to be asking for is a compile-time guarantee for something that will only be known at runtime. You didn't share the condition used in getCourse()
, but in general it could return both types.
Therefore, you need to decide what will happen in both cases - that's not something the compiler can decide for you via any "inference".
- If you want the program to throw an exception when
getCourse()
returns something else than aCourse.ProgrammingCourse
, you can cast the returned value usingas
:
val course = getCourse() as Course.ProgrammingCourse
workWithCourse(course)
- If you don't want to crash, but you only want to call
workWithCourse
in some cases, then you need anif
orwhen
statement to express that choice. For instance, to call it only when the value is of typeCourse.ProgrammingCourse
, then you would write the code you already know:
if (course is Course.ProgrammingCourse) {
workWithCourse(course)
}
Or with a when
statement:
val course = getCourse()
when (course) {
is Course.ProgrammingCourse -> workWithCourse(course)
is Course.LanguageCourse -> TODO("do something with the other value")
}
The when
is better IMO because it forces you (or other devs in the team) to take a look at this when
whenever you (or they) add a new subclass of the sealed class. It's easy to forget with an if
.
You can also decide to not test the actual type, and focus on the type
property like in @grigory-panov's answer, but that is brittle because it relies on an implicit relationship between the type
property and the actual type of the value:
val course = getCourse()
if (type == Type.PROGRAMMING) {
workWithCourse(course as Course.ProgrammingCourse)
}
The main point of using sealed classes is so you can use their actual type instead of a manually managed type
property casts. So I'd say use only is X
and don't set a type
property at all. Using a sealed class allows Kotlin to type-check a bunch of things, it's more powerful than using such a property.