Přehled
Jako vývojáři v Javě jsme se možná někdy setkali s typem Void a zajímalo nás, jaký je jeho účel.
V tomto stručném návodu se seznámíme s touto zvláštní třídou a zjistíme, kdy a jak ji použít a jak se jí pokud možno vyhnout.
Co je to typ Void
Od verze JDK 1.1 nám Java poskytuje typ Void. Jeho účelem je jednoduše reprezentovat návratový typ void jako třídu a obsahovat veřejnou hodnotu Class<Void>. Není instantovatelný, protože jeho jediný konstruktor je soukromý.
Proměnné Void tedy můžeme přiřadit pouze hodnotu null. Může se to zdát trochu zbytečné, ale my si nyní ukážeme, kdy a jak tento typ použít.
Použití
Existují situace, kdy může být použití typu Void zajímavé.
3.1. Reflexe
Poprvé bychom jej mohli použít při provádění reflexe. Návratový typ libovolné metody void totiž bude odpovídat proměnné Void.TYPE, která obsahuje již zmíněnou hodnotu Class<Void>.
Představme si jednoduchou třídu Calculator:
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); }}
Některé metody vracejí celé číslo, některé nevracejí nic. Řekněme, že nyní máme pomocí reflexe získat všechny metody, které nevracejí žádný výsledek. Dosáhneme toho pomocí proměnné Void.TYPE:
@Testvoid givenCalculator_whenGettingVoidMethodsByReflection_thenOnlyClearAndPrint() { Method calculatorMethods = Calculator.class.getDeclaredMethods(); List<Method> 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()));}
Jak vidíme, načteny byly pouze metody clear() a print().
3.2. V případě, že se nám nepodařilo zjistit, jaká je situace, můžeme použít proměnnou Void.TYPE. Generika
Další využití typu Void je u generických tříd. Předpokládejme, že voláme metodu, která vyžaduje parametr Callable:
public class Defer { public static <V> V defer(Callable<V> callable) throws Exception { return callable.call(); }}
Ale Callable, který chceme předat, nemusí nic vracet. Proto můžeme předat parametr Callable<Void>:
@Testvoid givenVoidCallable_whenDiffer_thenReturnNull() throws Exception { Callable<Void> callable = new Callable<Void>() { @Override public Void call() { System.out.println("Hello!"); return null; } }; assertThat(Defer.defer(callable)).isNull();}
Mohli jsme buď použít náhodný typ (např. Callable<Integer>) a vrátit null, nebo žádný typ (Callable), ale použití Void jasně vyjadřuje naše záměry.
Tuto metodu můžeme použít i na lambdy. Ve skutečnosti by náš Callable mohl být zapsán jako lambda. Představme si metodu vyžadující funkci, ale my chceme použít funkci, která nic nevrací. Pak nám stačí, aby vracela Void:
public static <T, R> R defer(Function<T, R> function, T arg) { return function.apply(arg);}
@Testvoid givenVoidFunction_whenDiffer_thenReturnNull() { Function<String, Void> function = s -> { System.out.println("Hello " + s + "!"); return null; }; assertThat(Defer.defer(function, "World")).isNull();}
Jak se jejímu použití vyhnout?“
Nyní jsme viděli některá použití typu Void. Nicméně i když je první použití naprosto v pořádku, možná bychom se chtěli použití Void v generikách pokud možno vyhnout. Setkání s návratovým typem, který představuje nepřítomnost výsledku a může obsahovat pouze null, totiž může být těžkopádné.
Nyní se podíváme, jak se těmto situacím vyhnout. Nejprve uvažujme naši metodu s parametrem Callable. Abychom se vyhnuli použití parametru Callable<Void>, mohli bychom místo toho nabídnout jinou metodu přijímající parametr Runnable:
public static void defer(Runnable runnable) { runnable.run();}
Můžeme jí tedy předat parametr Runnable, který nevrací žádnou hodnotu, a zbavit se tak zbytečné návratové nuly:
Runnable runnable = new Runnable() { @Override public void run() { System.out.println("Hello!"); }};Defer.defer(runnable);
Ale co potom, když třída Defer není naše, abychom ji upravovali? Pak můžeme buď zůstat u možnosti Callable<Void>, nebo vytvořit další třídu přebírající Runnable a odkládající volání na třídu Defer:
public class MyOwnDefer { public static void defer(Runnable runnable) throws Exception { Defer.defer(new Callable<Void>() { @Override public Void call() { runnable.run(); return null; } }); }}
Tímto způsobem zapouzdříme těžkopádnou část jednou provždy do naší vlastní metody, což umožní budoucím vývojářům používat jednodušší API.
Toho samého lze samozřejmě dosáhnout i pro Function. V našem příkladu funkce nic nevrací, můžeme tedy místo ní poskytnout jinou metodu, která přebírá parametr Consumer:
public static <T> void defer(Consumer<T> consumer, T arg) { consumer.accept(arg);}
A co když naše funkce nepřijímá žádný parametr? Můžeme buď použít Runnable, nebo vytvořit vlastní funkční rozhraní (pokud se nám to zdá přehlednější):
public interface Action { void execute();}
Pak opět přetížíme metodu defer():
public static void defer(Action action) { action.execute();}
Action action = () -> System.out.println("Hello!");Defer.defer(action);
Závěr
V tomto krátkém článku jsme se zabývali třídou Java Void. Viděli jsme, jaký je její účel a jak ji používat. Naučili jsme se také některé alternativy jejího použití.
Jako obvykle, celý kód tohoto článku najdete na našem GitHubu.
Začněte se Spring 5 a Spring Boot 2 prostřednictvím kurzu Naučte se Spring:
>> VYHLEDEJTE SI KURZ
.