Приводит ли использование `is` в выражении when к запечатанному классу к отражению во время выполнения? - PullRequest
1 голос
/ 25 февраля 2020

В Java, если у меня есть иерархия классов, например, так:

private interface Foo { }

private class FooBar implements Foo { }

private class FooZoo implements Foo { }

И тогда у меня есть функция, подобная этой:

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

Во время выполнения отражение будет использоваться для определения типа foo. В идеальном мире нам бы хотелось, чтобы в нашем коде не использовалось отражение. Кроме того, насколько я знаю, в Java нет способа создать иерархию закрытых типов, а это значит, что вам всегда нужно будет предоставить ветку else для компиляции кода.

Однако в Kotlin вы можете создать иерархию закрытых типов, например, так:

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

И затем вы можете написать выражение when для включения типа и возврата значения следующим образом:

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

В этом есть интересное свойство; а именно, else не требуется, потому что выражение when исчерпывающе проверяет иерархию запечатанных типов. Итак, из этого свойства может ли компилятор получить достаточно информации для компиляции байт-кода, который каким-то образом не будет использовать отражение во время выполнения, чтобы определить, имеет ли переданный экземпляр заданный тип?

Или другими словами, есть ли какая-либо разница (в отношении отражения) во время выполнения между кодом Kotlin выше и кодом Kotlin ниже:

interface Foo

class FooBar : Foo { }

class FooZoo : Foo { } 

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

Я спрашиваю об этом, потому что, как правило, мы, программисты, будем нравится избегать размышлений (где это возможно), но официальные документы Kotlin для закрытых классов показывают пример включения экземпляра с использованием is (https://kotlinlang.org/docs/reference/sealed-classes.html). Я также делаю это справедливо в коде, который я делаю на работе, и хотя я не вижу никаких проблем с этим, некоторые коллеги высказывают опасения, поскольку это похоже на запах кода.

1 Ответ

3 голосов
/ 26 февраля 2020

То, что компилятор делает за сценой, не имеет значения, является ли это хорошей практикой. Отражение - это запах кода, когда вы используете его для микроуправления поведением объектов, которые иначе могли бы позаботиться о своем поведении через полиморфизм.

В случае запечатанных классов вы работаете с конечным набором классы, поэтому микроуправление их поведением менее трудоемко, но, на мой взгляд, все еще пахнет, если вы делаете это вне файла, который их определяет.

Вот интересная статья о том, когда это имеет смысл полагаться на рефлексию, а не на полиморфизм. TLDR:

Полиморфизм имеет смысл только тогда, когда поведение polymorphi c действительно является поведением цели. Когда это поведение наблюдателя, вам нужно набирать во время выполнения.

Так что, если ваш оператор when просит, чтобы каждый объект что-то сделал или вычислил, то, скорее всего, предпочтительнее полиморфизм , Если ваш оператор when что-то делает с объектами или ему нужно каким-то конкретным образом c сортировать их, отражение может быть более подходящим.

Одна область, где, на мой взгляд, имеет смысл использовать закрытые классы когда они возвращают значение чего-то, что анализирует файл или что-то из Интернета. В отличие от примера Expr, где все может оцениваться как Double, при чтении из непредсказуемого файла возвращаемое значение может иметь непредсказуемый тип. Возможно, вы захотите отсортировать возвращаемое значение в конкретный обработчик c. Или возвращаемое значение может быть ошибкой, которую вы обрабатываете иначе, чем допустимое значение.

Вы также можете использовать запечатанные классы в качестве лучшей альтернативы проверенным исключениям. Оборачивая результат в класс, который может быть допустимым держателем результата или ошибкой, вы заставляете вызывающего обработчик обрабатывать ошибки, если это необходимо, или он может всплывать, без подписи функции, которая должна знать что-либо о типах ошибок. может столкнуться. Затем на любом уровне, необходимом для обработки ошибки, ее можно распаковать с помощью оператора when.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...