Does the use of `is` in a when expression over a sealed class result in reflection at runtime?

Thomas Cook :

In Java, if I have a class hierarchy like so:

private interface Foo { }

private class FooBar implements Foo { }

private class FooZoo implements Foo { }

And then I have a function like so:

public int Return0IfFooBarElseIfFooZooReturn1ElseReturn2(Foo foo) {
    if (foo instanceof FooBar) {
        return 0;
    } else if (foo instanceof FooZoo) {
        return 1;
    } else {
        return 2;
    }
}

At run time, reflection will be used to determine the type of foo. In an ideal world, we would like to avoid having our code use reflection. In addition, as far as I know, there is no way to create a sealed type hierarchy in Java, meaning you will always need to provide an else branch to have the code compile.

However in Kotlin you can create a sealed type hierarchy, like so:

sealed class Foo {
  object FooBar : Foo()
  object FooZoo : Foo()
}

And you can then write a when expression to switch on the type and return a value like so:

fun return0IfFooBarElseReturn1(foo: Foo) = when (foo) {
  is Foo.FooBar -> 0
  is Foo.FooZoo -> 1 
}

There is an interesting property about this; namely, there is no need for an else, because the when expression is exhaustively checking the sealed type hierarchy. So, from this property, can the compiler derive enough information in order to compile bytecode which somehow is not going to use reflection at runtime to determine if the passed instance is of a given type?

Or in other words, is there any difference (with regards to reflection) at run time between the Kotlin code above, and the Kotlin code below:

interface Foo

class FooBar : Foo { }

class FooZoo : Foo { } 

fun return0IfFooBarElseReturn1(foo: Foo) = when (foo) {
    is FooBar -> 0
    else -> 1
}

I am asking this, because generally as programmers, we would like to avoid reflection (where possible), but the official Kotlin docs for sealed classes show an example of switching on an instance using is (https://kotlinlang.org/docs/reference/sealed-classes.html). I also do this a fair bit in the code I do at work, and whilst I don't really see any issue with doing it, some co-workers have voiced concerns as it looks like a code smell.

Tenfour04 :

Whatever the compiler does behind the scenes is irrelevant to whether this is good practice. Reflection is code smell when you're using it to micromanage the behavior of objects which could otherwise be taking care of their own behavior through polymorphism.

In the case of sealed classes, you are working with a finite set of classes, so micromanaging their behavior is less arduous, but in my opinion is still a smell if you're doing it outside the file that defines them.

Here's an interesting article about when it makes sense to rely on reflection rather than polymorphism. The TLDR:

Polymorphism only makes sense when the polymorphic behavior is really a behavior of the target. When it's the behavior of the observer, you need runtime typing.

So if your when statement is asking each object to do something or calculate something, it is likely a situation where polymorphism should be preferred. If your when statement is doing something to the objects or needs to sort them in some specific way, the reflection may be more appropriate.

One area where I think it makes sense to use sealed classes in when they are the return value of something that parses a file or something from the Web. Unlike the Expr example where everything can evaluate to a Double, when you read from some unpredictable file, the return value may have an unpredictable type. You might want to sort the return value into a specific handler. Or the return value might be an error that you handle differently than a valid value.

You can also use sealed classes as a better alternative to checked exceptions. By wrapping a result in a class that can be a valid result holder or an error, you force the caller to handle errors if it needs to, or it can bubble them up without the function signature having to know anything about the types of errors it might encounter. Then at whatever level needs to handle the error, it can be unpacked with a when statement.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=12542&siteId=1