Reputation: 1439
I wrote the folowing code:
class MyRoamable
{
var r: Roamable = Wolf()
fun myFunction()
{
var x = r as? Wolf
if (r as? Wolf != null)
{
r.wolfNoise()
}
}
}
open class Roamable
{
fun roam():String
{
return "awhooooooo"
}
}
class Wolf:Roamable()
{
fun wolfNoise():String
{
return "wolf Noise"
}
}
I can't understand why the following doesn't work:
if (r as? Wolf != null) { r.wolfNoise() }
I'm sure that i made such a cast in the past. The error is: Unresolved reference: wolfNoise
Why it's continue to treat r as a Roamable and not as a Wolf
The way it worked for me in the past:
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder as? ViewHolder != null) {
holder.homeTeamTextView.text = homeTeam
holder.awayTeamTextView.text = awayTeam
holder.homeTeamTextView.setTypeface(null,Typeface.NORMAL)
holder.awayTeamTextView.setTypeface(null,Typeface.NORMAL)
if (holder.winDescription != null)
{
holder.winDescription.text = gameWindowDescription
}
else
{
holder.winDescription.text = ""
}
class ViewHolder (view : View, onRecyclerItemClickListener: OnRecyclerItemClickListener?) : RecyclerView.ViewHolder(view)
{
val homeTeamTextView : TextView = view.findViewById(R.id.home_club_tv)
val awayTeamTextView : TextView = view.findViewById(R.id.away_club_tv)
val homeTeamImageView : ImageView = view.findViewById(R.id.home_club_iv)
val awayTeamImageView : ImageView = view.findViewById(R.id.away_club_iv)
val timeTextView : TextView = view.findViewById(R.id.games_status_tv)
val scoreTextView : TextView = view.findViewById(R.id.score_tv)
val winDescription: TextView = view.findViewById(R.id.game_window_description)
i could get all the data from the views since if (holder as? ViewHolder != null) succeeded
Upvotes: 0
Views: 223
Reputation: 170723
Smart casts don't just happen, every pattern has to be explicitly supported in the compiler. If you make r
a local variable
fun myFunction() {
var r: Roamable = Wolf()
if (r as? Wolf != null) {
r.wolfNoise()
}
}
or a val
val r: Roamable = Wolf()
fun myFunction() {
if (r as? Wolf != null) {
r.wolfNoise()
}
}
it works.
Parameters are treated like val
local variables in this respect, so the override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int)
example is actually both.
In your case, if an instance of Roamable
was visible to two threads, and one of them executed myFunction
, the other one could change r
after r as? Wolf
was evaluated and before r.wolfNoise()
was.
You could say: but this program doesn't have any threads! But the compiler has to compile Roamable
so it can be used in other programs as well.
EDIT: I somehow misread your example and thought you had
var r: Roamable = Wolf()
fun myFunction() {
var x = r as? Wolf
if (r as? Wolf != null) {
r.wolfNoise()
}
}
inside a function, not a class. The below explanation would apply in that case.
If you use a local variable (not a property!) var
from outer scope in a local function, as in
fun outer() {
var r: Roamable = Wolf()
fun myFunction() {
var x = r as? Wolf
if (r as? Wolf != null) {
r.wolfNoise()
}
}
}
the compiler has to turn it into something like
private class RoamableHolder(var value: Roamable)
private fun outer_myFunction(holder: RoamableHolder) {
if (holder.value as? Wolf != null) {
holder.value.wolfNoise()
}
}
fun outer() {
val r_holder = RoamableHolder(Wolf())
outer_myFunction(r_holder)
}
behind the scenes, because JVM doesn't have local methods. You can see how it would allow myFunction
to change the value of r
and make this visible afterwards in outer
.
The smart cast wouldn't work with that code, because you can't smart-cast var
properties, so it doesn't work with the original code either.
But if r
in outer
was a val
instead of var
, there's no need for RoamableHolder
and the translation is simpler:
private fun outer_myFunction(r: Roamable) {
if (r as? Wolf != null) {
r.wolfNoise()
}
}
fun outer() {
val r: Roamable = Wolf()
outer_myFunction(r)
}
Upvotes: 4
Reputation: 93581
In your Roamable example, r
is a property. A var
property can never be smart-cast because the compiler can't guarantee the property will return the same value in between when you type-check it and when you try to do something with it (call its functions, pass it as a parameter, assign it to a variable, etc.). This is also true of val
properties with custom getters.
Now maybe you aren't using this class in a multi-threaded way so you know the cast would be safe, but the compiler isn't smart enough to check for that, so you have to manually cast. I think the reason for this compiler rule is that it is too complicated to check. Maybe it's feasible, but some compiler limitations were put in place so compiling will be fast, not because of logical impossibility.
In the ViewHolder example, holder
is a function parameter, so it is not possible for some other thread to modify it. In Kotlin, unlike Java, function parameters are constants so it's not possible to modify them at all. So smart-casts are always possible with function parameters.
You can also smart-cast with local var
variables, since the compiler can see in the local code that the variable hasn't been reassigned in between your check and your use of the value. If you use the local variable in a closure, then it is no longer smart-castable, because now it's kind of like a property: it can be modified from somewhere else, so the compiler can no longer make guarantees about its type.
Upvotes: 1
Reputation: 8422
Too complex for the Kotlin's compiler :) I think it will be more idiomatic to write like this:
fun myFunction() {
val x = r as? Wolf
x?.wolfNoise()
}
In your example with view holder you don't cast variable. You just check it for null
Upvotes: 0