Автор оригинала: François Dupire.
1. Обзор
Как разработчики Java, мы могли бы в какой-то момент столкнуться с типом Void и задаться вопросом, какова его цель.
В этом кратком уроке мы узнаем об этом своеобразном классе и увидим, когда и как его использовать, а также как избежать его использования, когда это возможно.
2. Что такое тип пустоты
Начиная с JDK 1.1, Java предоставляет нам тип Void . Его цель-просто представить возвращаемый тип void в виде класса и содержать Class public value. Он не является инстанцируемым, так как его единственный конструктор является частным.
Поэтому единственное значение, которое мы можем присвоить переменной Void , – это null . Это может показаться немного бесполезным, но теперь мы увидим, когда и как использовать этот тип.
3. Обычаи
Есть некоторые ситуации, когда использование типа Void может быть интересным.
3.1. Рефлексия
Во-первых, мы могли бы использовать его при отражении. Действительно, возвращаемый тип любого void метода будет соответствовать Void.ТИП переменная, содержащая класс значение, упомянутое ранее .
Давайте представим себе простой Калькулятор класс:
public class Calculator { private int result = 0; public int add(int number) { return result += number; } public int sub(int number) { return result -= number; } public void clear() { result = 0; } public void print() { System.out.println(result); } }
Некоторые методы возвращают целое число, некоторые ничего не возвращают. Теперь предположим, что мы должны получить путем рефлексии все методы, которые не возвращают никакого результата . Мы достигнем этого, используя Void.ТИП переменная:
@Test void givenCalculator_whenGettingVoidMethodsByReflection_thenOnlyClearAndPrint() { Method[] calculatorMethods = Calculator.class.getDeclaredMethods(); ListcalculatorVoidMethods = Arrays.stream(calculatorMethods) .filter(method -> method.getReturnType().equals(Void.TYPE)) .collect(Collectors.toList()); assertThat(calculatorVoidMethods) .allMatch(method -> Arrays.asList("clear", "print").contains(method.getName())); }
Как мы видим, были извлечены только методы clear() и print () .
3.2. Дженерики
Другое использование типа Void – с универсальными классами. Предположим, что мы вызываем метод, для которого требуется параметр Callable :
public class Defer { public staticV defer(Callable callable) throws Exception { return callable.call(); } }
Но … Подлежащий выкупу мы хотим, чтобы передача не должна ничего возвращать. Таким образом, мы можем пройти Вызываемый :
@Test void givenVoidCallable_whenDiffer_thenReturnNull() throws Exception { Callablecallable = new Callable () { @Override public Void call() { System.out.println("Hello!"); return null; } }; assertThat(Defer.defer(callable)).isNull(); }
Мы могли бы либо использовать случайный тип (например, Callable ) и возвращать null , либо вообще не использовать тип ( Callable) , но использование Void ясно указывает на наши намерения.
Мы также можем применить этот метод к лямбдам. На самом деле наш Callable мог бы быть записан как лямбда. Давайте представим себе метод , требующий Функции , но мы хотим использовать функцию , которая ничего не возвращает. Тогда нам просто нужно заставить его вернуться Void :
public staticR defer(Function function, T arg) { return function.apply(arg); }
@Test void givenVoidFunction_whenDiffer_thenReturnNull() { Functionfunction = s -> { System.out.println("Hello " + s + "!"); return null; }; assertThat(Defer.defer(function, "World")).isNull(); }
4. Как избежать Его использования?
Теперь мы видели некоторые обычаи типа Void . Однако, даже если первое использование полностью нормально, мы могли бы избежать использования Void в дженериках, если это возможно . Действительно, встреча с типом возвращаемого значения, который представляет собой отсутствие результата и может содержать только null , может быть громоздкой.
Теперь посмотрим, как избежать подобных ситуаций. Сначала рассмотрим наш метод с параметром Callable . Чтобы избежать использования Вызываемого , мы могли бы предложить другой метод, принимающий вместо него параметр Runnable :
public static void defer(Runnable runnable) { runnable.run(); }
Таким образом, мы можем передать ему Runnable , который не возвращает никакого значения, и таким образом избавиться от бесполезного return null :
Runnable runnable = new Runnable() { @Override public void run() { System.out.println("Hello!"); } }; Defer.defer(runnable);
Но тогда что делать, если класс Defer не наш для модификации? Тогда мы можем либо придерживаться опции Callable , либо создать другой класс, взяв Runnable и отложив вызов на Defer класс :
public class MyOwnDefer { public static void defer(Runnable runnable) throws Exception { Defer.defer(new Callable() { @Override public Void call() { runnable.run(); return null; } }); } }
Делая это, мы инкапсулируем громоздкую часть раз и навсегда в наш собственный метод, позволяя будущим разработчикам использовать более простой API.
Конечно, то же самое может быть достигнуто для Функции . В нашем примере функция | ничего не возвращает, поэтому мы можем предоставить другой метод, принимающий вместо него Потребителя :
public staticvoid defer(Consumer consumer, T arg) { consumer.accept(arg); }
Тогда что, если наша функция не принимает никаких параметров? Мы можем либо использовать Runnable , либо создать свой собственный функциональный интерфейс (если это кажется более понятным):
public interface Action { void execute(); }
Затем мы снова перегружаем метод defer() :
public static void defer(Action action) { action.execute(); }
Action action = () -> System.out.println("Hello!"); Defer.defer(action);
5. Заключение
В этой короткой статье мы рассмотрели класс Java Void . Мы видели, в чем его предназначение и как им пользоваться. Мы также узнали некоторые альтернативы его использованию.
Как обычно, полный код этой статьи можно найти на нашем GitHub .