Эффективный обзор Java (Серия из 79 частей)
Глава этой недели продолжается с того места, на котором закончилась наша предыдущая глава. Если на прошлой неделе мы говорили об универсальных типах, то на этой неделе мы обсуждаем универсальные методы. Как и в случае с созданием универсальных типов, одной из основных целей использования универсальных методов является повышение читабельности и безопасности кода, что часто можно реализовать, заметив, что во время компиляции нет приведений и непроверенных предупреждений. Итак, давайте рассмотрим несколько примеров.
Давайте сначала рассмотрим метод, который не использует дженерики:
public static Set union(Set s1, Set s2) { Set result = new HashSet(s1); result.addAll(s2); return result; }
Хотя приведенный выше код работает, он выдает предупреждения во время компиляции, поскольку не может обеспечить безопасность типов во время компиляции. Исправление немного простое. Мы добавляем список параметров типа, в котором объявляются параметры типа, которые будут использоваться между модификаторами метода и типом возвращаемого значения. Оттуда мы можем использовать параметр типа во всей функции. Давайте рассмотрим нашу вышеприведенную функцию в общем виде.
public staticSet union(Set s1, Set s2) { Set result = new HashSet<>(s1); result.addAll(s2); return result; }
Это так просто, теперь мы избавились от наших предупреждений, а также обеспечили лучшую безопасность типов. Текущее ограничение функции объединение
заключается в том, что тип трех наборов должен быть точно таким же, мы можем ослабить это требование, используя ограниченные типы подстановочных знаков , которые мы рассмотрим в следующей главе.
Еще одна возможность, которую мы имеем с универсальными методами, – это создание функций, которые предоставляют типизированные универсальные неизменяемые объекты. Поскольку дженерики реализуются с помощью удаления типов, мы можем иметь неизменяемый класс, который обслуживает все типы. Это одно из преимуществ удаления типа. Мы можем видеть примеры этого в JRE с помощью таких методов, как Collections.ReverseOrder
и Коллекции.Пустой набор
. Давайте рассмотрим пример этого. Давайте представим, что мы хотим реализовать нашу собственную функцию идентификации
. Мы, конечно, не должны этого делать, потому что это уже существует, но это полезно учитывать.
private static UnaryOperator
Нам действительно нужно сделать слепок но мы знаем, что это безопасно из-за того, что на рассматриваемом объекте на самом деле не происходит никаких действий, поэтому мы подавляем предупреждения.
Последнее, что нам следует рассмотреть, – это то, что называется привязкой к рекурсивному типу . Это происходит, когда параметр типа ограничен некоторым выражением, включающим сам тип. Это звучит более запутанно, чем есть на самом деле. Общее место, где это можно увидеть, используется в связи с интерфейсом Comparable
. Тип T
of Сопоставимый
обозначает, с каким типом объекта можно сравнивать. На практике большинство типов просто сопоставимы сами с собой, таким образом Строка
реализует Сопоставимый<Строка>
и Целое число
реализует Сопоставимый<Целое число>
и так далее. Итак, в качестве примера привязки к рекурсивному типу давайте рассмотрим функцию, которая находит максимальное значение в коллекции.
public static> Optional max(Collection c) { E result = null; for (E e : c) { if (result == null || e.compareTo(result) > 0) { result = Objects.requireNonNull(e); } } return Optional.ofNullable(result); }
Параметр типа можно прочитать как “любой тип E, который можно сравнить с самим собой”, что в конечном итоге является именно тем, что мы ищем. Хотя границы рекурсивного типа могут быть довольно сложными, надеюсь, вы сможете увидеть, как они могут быть полезны.
В целом, предпочтение универсальным методам и типам приводит к более безопасному коду, а также к более простому в использовании коду. Мы должны сделать все возможное, чтобы сделать наш код предупреждающим и свободным.
Эффективный обзор Java (Серия из 79 частей)
Оригинал: “https://dev.to/kylec32/effective-java-favor-generic-methods-344i”