1. Общ преглед
Като разработчици на Java може да сме се сблъскали с типа Void по някакъв повод и да се чудим каква е целта му.
В този бърз урок ще научим за този странен клас и ще видим кога и как да го използваме, както и как да избегнем използването му, когато е възможно.
2. Какво представлява Void Type
От JDK 1.1, Java ни предоставя типа Void . Целта му е просто да представи типа void return като клас и да съдържа публична стойност на Class . Той не е инстанцируем, тъй като единственият му конструктор е private.
Следователно единствената стойност, която можем да присвоим на променлива Void, е null . Може да изглежда малко безполезно, но сега ще видим кога и как да използваме този тип.
3. Употреби
Има някои ситуации, когато използването на типа Void може да бъде интересно.
3.1. Отражение
Първо, бихме могли да го използваме, когато правим размисъл. Всъщност типът на връщане на всеки метод void ще съответства на променливата Void.TYPE, която съдържа стойността на Class, спомената по-рано .
Нека си представим прост клас на калкулатор :
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.TYPE :
@Test void givenCalculator_whenGettingVoidMethodsByReflection_thenOnlyClearAndPrint() { Method[] calculatorMethods = Calculator.class.getDeclaredMethods(); List calculatorVoidMethods = 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 static V defer(Callable callable) throws Exception { return callable.call(); } }
Но Callable, който искаме да преминем, не трябва да връща нищо. Следователно можем да преминем Callable :
@Test void givenVoidCallable_whenDiffer_thenReturnNull() throws Exception { Callable callable = new Callable() { @Override public Void call() { System.out.println("Hello!"); return null; } }; assertThat(Defer.defer(callable)).isNull(); }
Може да използваме произволен тип (напр. Callable ) и да върнем null или изобщо да няма тип ( Callable) , но използването на Void ясно заявява намеренията ни.
Можем да приложим този метод и към ламбда. Всъщност нашият Callable можеше да бъде написан като ламбда. Нека си представим метод, изискващ функция , но искаме да използваме функция, която не връща нищо. Тогава просто трябва да го накараме да върне Void :
public static R defer(Function function, T arg) { return function.apply(arg); }
@Test void givenVoidFunction_whenDiffer_thenReturnNull() { Function function = s -> { System.out.println("Hello " + s + "!"); return null; }; assertThat(Defer.defer(function, "World")).isNull(); }
4. Как да избегнем използването му?
Сега видяхме някои употреби от типа Void . Въпреки това, дори ако първата употреба е напълно добра, може да искаме да избягваме използването на Void в генерични продукти, ако е възможно . Всъщност, срещането на тип връщане, който представлява липсата на резултат и може да съдържа само null, може да бъде тромаво.
Сега ще видим как да избегнем тези ситуации. Първо, нека разгледаме нашия метод с параметъра Callable . За да избегнем използването на 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 static void 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.