Когда используется пример правильного решения?

Как я всегда это понимал, основными случаями, когда подходит instanceof, являются:

  • Реализация Object.equals(Object). Поэтому, если бы я писал класс List и не расширял AbstractList по какой-либо причине, я бы выполнил equals(o), сначала проверив o instanceof List, а затем сравнив элементы.
  • Значительная (алгоритмическая?) оптимизация для особого случая, который не меняет семантики, а только производительность. Например, Collections.binarySearch выполняет тест instanceof RandomAccess и использует немного другой бинарный поиск для списков RandomAccess и non RandomAccess.

Я не думаю, что instanceof представляет запах кода в этих двух случаях. Но есть ли другие случаи, когда разумно использовать instanceof?

Ответ 1

Устаревший код или API за пределами вашего контроля являются законным прецедентом для instanceof. (Даже тогда я предпочел бы написать над ним OO-слой, но время иногда исключает такой редизайн.)

В частности, фабрики, основанные на внешних иерархиях классов, кажутся обычным использованием.

Ответ 2

Один из способов ответить на ваш вопрос - ответить "когда используется твердая библиотека Java instanceof?" Если мы предположим, что Guava является примером хорошо разработанной библиотеки Java, мы можем посмотреть, где она использует instanceof, чтобы решить, когда это приемлемо делать.

Если мы извлекаем java файл java и grep его, мы видим, что instanceof упоминается 439 раз, в 122 файлах:

$ pwd
/tmp/guava-13.0.1-sources
$ grep -R 'instanceof' | wc -l
439
$ grep -Rl 'instanceof' | wc -l
122

И, глядя на некоторые из этих случаев, мы видим, как появляются несколько шаблонов:

  • Чтобы проверить равенство

    Это наиболее распространенное использование. Это несколько конкретная реализация, но если вы действительно хотите измерить равенство на основе класса/интерфейса, который он расширяет/реализует, вы можете использовать instanceof, чтобы обеспечить объект, с которым вы работаете. Однако это может потенциально вызвать нечетные проблемы, если дочерний класс переопределяет equals() и не соблюдает те же требования instanceof, что и родительский. Примеры везде, но простой - Lists.equalsImpl(), который используется ImmutableList.

  • Для короткого замыкания ненужной конструкции объекта

    Вы можете использовать instanceof, чтобы проверить, может ли переданный аргумент быть безопасно использован или возвращен без дальнейшего его преобразования, например, если он уже является экземпляром желаемого класса или если мы знаем его неизменяемым. См. Примеры в CharMatcher.forPredicate(), Suppliers.memoize(), ImmutableList.copyOf() и т.д.

  • Чтобы получить доступ к деталям реализации, не подвергая себя другому поведению

    Это можно увидеть повсюду в Guava, но особенно в статических классах утилиты в пакете com.google.common.collect, например, в Iterables.size(), где он вызывает Collection.size(), если это возможно, и в противном случае учитывает количество элементов в итераторе в O(n) времени.

  • Чтобы избежать вызова toString()

    Я скептически отношусь к тому, что это делается в более чем нескольких случаях, но если вы уверены, что делаете правильные вещи, вы можете избежать ненужного преобразования объектов CharSequence в String с помощью instanceof, как это сделано в Joiner.toString(Object).

  • Выполнение сложной обработки исключений

    Очевидно, что "правильная" вещь заключается в использовании блока try/catch (хотя действительно, что делает проверку instanceof уже), но иногда у вас есть более сложная логика обработки, которая заслуживает использования условных блоков или передачи обработки на отдельный метод, например, вытаскивание причин или обработка, специфичная для реализации. Пример можно найти в SimpleTimeLimiter.throwCause().

Одна вещь, которая выделяется, глядя на это поведение, почти все они решают проблемы, которые я не должен решать. Они полезны в библиотечном коде, например. в Iterables, но если я реализую это поведение, я, вероятно, должен спрашивать себя, нет ли библиотек или утилит, которые решают это для меня.

Во всех случаях я бы сказал, что проверки instanceof должны использоваться только когда-либо в качестве детали реализации, т.е. вызывающий объект любого метода, который полагается на проверку instanceof, не может (легко ) скажите, что вы сделали. Например, ImmutableList.copyOf() всегда возвращает ImmutableList. В качестве детали реализации он использует instanceof, чтобы избежать создания новых ImmutableList s, но это не обязательно для приемлемого обеспечения ожидаемого поведения.

В стороне, это было забавно, встречая ваше имя, Луис, когда я копался в исходном коде Guava. Клянусь, я понятия не имел!

Ответ 3

Ваш первый случай - пример, когда я бы не использовал оператор instanceof, но посмотрим, равны ли классы:

o != null && o.getClass() == this.getClass()

Это позволит избежать того, что экземпляр A extends B и B считается равным

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

  • factory, где у вас есть, например, метод canCreate и create, который получает общий интерфейс в качестве параметра. Каждая из фабрик может обрабатывать конкретную реализацию интерфейса, поэтому для этого потребуется instanceof. Определение только интерфейса в абстрактном классе/интерфейсе factory позволяет, например, написать композитный factory
  • составные реализации (как показано в моем первом примере)

Ответ 4

Как вы уже упоминали, "правильное" использование instanceof довольно ограничено. Насколько я знаю, вы в основном суммировали два основных вида использования.

Однако вы можете немного обобщить свои утверждения, как показано ниже:

  • Проверка типов перед необходимыми отбрасываниями.
  • Реализация сценариев особых случаев, зависящих от очень конкретных экземпляров класса