Java Optional efektivně

Klíčem k efektivnímu využití Optional je používání metod, které dokáží s Optional pracovat. Tyto metody buď zpracují hodnotu zabalenou v Optional, nebo, v případě, že není přítomna, vytvoří náhradní hodnotu (objekt).

Metoda Optional.ifPresent() jako parametr bere funkci java.util.function.Function a v případě, že Optional obsahuje hodnotu (referenci na objekt), je tato hodnota (reference) předána funkci. Pokud hodnotu neobsahuje, nic se nestane.

Stream<Integer> is1 = Stream.of(5, 7, 1, 8, 2);
Optional<Integer> max = is1.max(Integer::compareTo);
Stream<Integer> is2 = Stream.empty();
Optional<Integer> min = is2.min(Integer::compareTo);

Výpis výsledků do konzole (vypíše 8):

max.ifPresent((i) -> System.out.println(i));
min.ifPresent((i) -> System.out.println(i));

Přidání výsledků do listu:

List<Integer> results = new ArrayList<>();
max.ifPresent((i) -> results.add(i));
min.ifPresent((i) -> results.add(i));

Metoda Optional.ifPresent() zpracovává hodnotu v případě, že je přítomna. Dalším způsobem je použít metody, které vytvoří hodnotu (nebo něco provedou) v případě, že hodnota není přítomna. Pro tento případ se hodí metody optionalVariable.orElse(), optionalVariable.orElseGet(), optionalVariable.orElseThrow().

Stream<String> ss1 = Stream.of("lokomotiva", "auto", "letadlo", "raketa", "vzducholoď");
Optional<String> firstWithL = ss1.filter((word) -> word.startsWith("l")).findFirst();
Stream<String> ss2 = Stream.of("lokomotiva", "auto", "letadlo", "raketa", "vzducholoď");
Optional<String> anyWithQ = ss2.filter((word) -> word.startsWith("q")).findAny();

String withL = firstWithL.orElse("");
String withQ1 = anyWithQ.orElse("");

System.out.println(withL);
System.out.println(withQ1);

try {
	String withQ2 = anyWithQ.orElseThrow(Exception::new);
} catch (Exception e) {
	e.printStackTrace();
}

Výpis:

lokomotiva

java.lang.Exception
	at java.util.Optional.orElseThrow(...)

Metoda Optional.orElse() vrátí v případě, že Optional obsahuje null, náhradní hodnotu zadanou jako parametr. Pokud Optional obsahuje referenci, vrátí objekt. Metoda Optional.orElseThrow() vyhodí danou výjimku (pokud Optional obsahuje null).


Zdroj: HORSTMANN, Cay S. Java SE 8 for the really impatient. Upper Saddle River, NJ: Addison-Wesley, 2014, xv, 215 pages. ISBN 0321927761.

Proč používat Java Optional?

Není příliš velký rozdíl mezi:

Optional<T> op = ...;
op.get().someMethod();

vs

T val = ...;
val.someMethod();

Pokud se chceme vyhnout výjimce:

if (op.isPresent()) {
    T val = op.get();
    val.someMethod();
}

vs

if (val != null) {
    val.someMethod();
}

Pokud programátor Optional nepoužívá, ušetří dokonce pár znaků při psaní (nemusí volat get()). Proč tedy používat Optional?

Předchozí příspěvek na téma Optional se týkal třídy Optional z knihovny Guava. Tento příspěvek se věnuje třídě java.util.Optional, která je dostupná od Javy 8. Důvody pro jejich použití jsou ale obdobné. Guava Optional se používá v případě, že máte starší verzi Javy. V případě, že používáte Javu 8, knihovnu Guava nepotřebujete. 

Důvodem pro používání Optional je to, že null může mít více významů. Null může být prázdná hodnota, může představovat chybu, může to být též neinicializovaná proměnná. Pokud ale máme Optional<T> (Optional vlastně zabaluje hodnotu T), tak buď máme referenci na objekt typu T (Optional.isPresent() == true) a nebo referenci nemáme (Optional.isPresent() == false). V případě, že referenci nemáme, odpovídalo by to případu, kdy null vyjadřuje prázdnou hodnotu.

Další výhodou Optional je to, že bychom si při použití Optional měli uvědomit, že se jedná o hodnotu, která nepovinná (proto název optional) a podle toho se k ní chovat. U běžné proměnné nás nemusí napadnout, že je třeba provést kontrolu na to, zda třeba není null. To je důvod, proč některé metody vrací Optional. Dávají tím najevo, že nemusí vrátit žádnou hodnotu. Je to bezpečnější způsob práce než s null. Optional například vrací některé metody pracující s Stream (java.util.stream.Stream).

Stream<Integer> is1 = Stream.of(5, 7, 1, 8, 2);
Optional<Integer> max = is1.max(Integer::compareTo);

Stream<Integer> is2 = Stream.empty();
Optional<Integer> min = is2.min(Integer::compareTo);

Stream<String> ss1 = Stream.of("lokomotiva", "auto", "letadlo", "raketa", "vzducholoď");
Optional<String> firstWithL = ss1.filter((word) -> word.startsWith("l")).findFirst();

Stream<String> ss2 = Stream.of("lokomotiva", "auto", "letadlo", "raketa", "vzducholoď");
Optional<String> anyWithQ = ss2.filter((word) -> word.startsWith("q")).findAny();

Stream<String> ss3 = Stream.of("lokomotiva", "auto", "letadlo", "raketa", "vzducholoď");
Optional<String> shortest = ss3.min((word1, word2) -> word1.length() - word2.length());

Metody min(), max(), findAny(), findFirst() vrací Optional, protože Stream, který dostanou může být prázdný a tím pádem žádnou hodnotu nemusí najít (viz is2), nebo žádná taková hodnota nevyhovuje podmínkám (viz ss2.filter() na slova začínající na q -> žádné takové tam není).

Vlastní anotace v Javě

Anotace v Javě jsou způsobem, jak do kódu přidat meta informace (metadata, data o datech). Anotace se používají pro:

  • instrukce pro compiler
  • instrukce pro build-time
  • instrukce pro run-time

Java umožňuje vytvořit si vlastní anotace pomocí klíčového slova @interface.

@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {

	String value();
	int version() default 1; 
}

Použitá anotace @Retention(RetentionPolicy.RUNTIME) určuje, že tato anotace (@MyAnnotation) má být přístupná i v run-time. Anotace @MyAnnotation má dva elementy (value a version). Element version má nastavenu výchozí hodnotu na 1.

public @interface MyOtherAnnotation {

}

Anotace @MyAnnotation žádné elementy neobsahuje.

@MyOtherAnnotation
@MyAnnotation(value = "abcd")
public class MyClass {

}

Třída MyClass je anotována oběma anotacemi. Anotace @MyAnnotation má nastavenu hodnotu elementu value na „abcd“ a hodnotu version na 1 (default).

import java.lang.annotation.Annotation;

public class App {

	public static void main(String[] args) {
		
		Class clazz = MyClass.class;
		if (clazz.isAnnotationPresent(MyAnnotation.class)) {
			System.out.println("Je anotován: " + MyAnnotation.class.getSimpleName());
			
			MyAnnotation myAnnotation = (MyAnnotation) clazz.getAnnotation(MyAnnotation.class);
			System.out.println(myAnnotation.value());
			System.out.println(myAnnotation.version());
		}
		if (clazz.isAnnotationPresent(MyOtherAnnotation.class)) {
			System.out.println("Je anotován: " + MyOtherAnnotation.class.getSimpleName());
		}
	}
}

Výstup:

Je anotován: MyAnnotation
abcd
1

Díky tomu, že @MyAnnotation je anotováná @Retention(RetentionPolicy.RUNTIME) je možné k ní pomocí reflexe přistupovat i v run-time. Lze zjistit nejen to, zda daná třída je pomocí určité anotace anotována, ale také hodnoty elementů. Anotace @MyOtherAnnotation v runtime přítomna není.


Zdroj: tutorials.jenkov.com/java/annotations.html

Nekonečný Stream v Javě

Java 8 představila Stream, což je posloupnost hodnot, nad kterou je možné provádět operace. Je možné vytvořit i nekonečný Stream.

Stream<String> stringStream = Stream.generate(() -> UUID.randomUUID().toString());
Stream<String> constantStream = Stream.generate(() -> "word");
Stream<Double> doubleStream = Stream.generate(Math::random);
Stream<Integer> integerStream = Stream.iterate(0, (i) -> i + 1);
Stream<BigDecimal> bigDecimalStream = Stream.iterate(BigDecimal.ZERO, (bd) -> bd.add(BigDecimal.ONE));

Metoda Stream.generate() požaduje parametr typu Supplier<T>, což je funkční rozhraní obsahující metodu T get(). Metoda Stream.iterate() požaduje parametr typu T seed (počáteční element) a UnaryOperator<T>, což je funkční rozhraní dědící z funkčního rozhraní Function (místo typů <R, T> používá <T, T>, metoda apply(T) tedy nevrací typ R ale T).

Pokud nám stačí jen několik prvků streamu, můžeme použít operaci limit(n), která vezme n prvních prvků streamu.

System.out.println(Arrays.toString(stringStream.limit(5).toArray()));
System.out.println(Arrays.toString(constantStream.limit(5).toArray()));
System.out.println(Arrays.toString(doubleStream.limit(5).toArray()));
System.out.println(Arrays.toString(integerStream.limit(5).toArray()));
System.out.println(Arrays.toString(bigDecimalStream.limit(5).toArray()));

Výstup:

[cb2f9562-e468-4409-bd61-cc7f153adbc9, 45582575-fda8-48a4-9ecd-bd8e9ca87f42, 6338c4d5-7b67-4fff-b1ec-6a86e2bacec8, 9ba3c6b0-700a-4e78-b633-e6a8da9fb946, 04951054-8c43-43a6-a177-aeb2da7c7320]
[word, word, word, word, word]
[0.9250003903085224, 0.912794982359122, 0.8636553602757563, 0.5809828517176208, 0.002178508953346925]
[0, 1, 2, 3, 4]
[0, 1, 2, 3, 4]

Pozor na datový typ (na přetečení rozsahu), který je použit v nekonečném proudu.

Stream<Integer> is = Stream.iterate(1, (i) -> i * 10).limit(15);
Stream<BigDecimal> bds = Stream.iterate(BigDecimal.ONE, (bd) -> bd.multiply(new BigDecimal("10"))).limit(15);

System.out.println(Arrays.toString(is.toArray()));
System.out.println(Arrays.toString(bds.toArray()));

Výstup:

[1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 1410065408, 1215752192, -727379968, 1316134912, 276447232]
[1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000, 1000000000000, 10000000000000, 100000000000000]

Práce s historií v Bash

Velikost historie (počet uchovávaných příkazů) se nastavuje v souboru .bashrc v home adresáři (proměnné HISTSIZE a HISTFILESIZE). Historie v Bash se dá procházet pomocí šipek. Celou historii zobrazíme příkazem history. Výpis historie můžeme omezit přepínačem, pomocí kterého určíme, kolik příkazů z historie chceme zobrazit.

history 10 (zobrazí posledních 10 záznamů)

Pokud chceme provést nějaký příkaz z historie a nechce se nám dlouho listovat, stačí si historii zobrazit (u každého příkazu je číslo) a napsat !číslo_příkazu.

$ history
1 ls /etc
2 nano host
3 ls
4 ls -al
5 nano /etc/host
6 cd apt
7 pwd
8 cd /etc/apt
9 ls
10 nano sources.list
11 exit
12 history

V příkladu výše mám v historii celkem 12 příkazů. Pokud chci rychle provést příkaz nano /etc/host stačí mi napsat !5. Pokud chci vykonat poslední příkaz stačí napsat !!. Pro příkaz nano /etc/host jsem zapomněl uvést sudo. Stačí mi tedy napsat sudo !!.

Zdroj: digitalocean.com/community/tutorials/…

Otevření cmd ve složce

Program Příkazový řádek (cmd.exe) se dá otevřít v určité složce (programy v dané složce je možné rovnou spouštět bez zadání cesty). Otevřít v průzkumníkovi daný adresář -> stisknout klávesu Shift + pravým tlačítkem myši -> vybrat volbu „Zde otevřít příkazové okno“.

windows_otevreni_cmd

Pořadí při použití metody equals

V případě, že máme konstantu a tu porovnáváme s objektem, je vhodnější použít konstrukci konstanta.equals(objekt) než naopak. Důvod je jednoduchý. Vyhneme se tak možnosti chyby NullPointerException.

String MY_CONSTANT = "AB9922";
            
System.out.println(MY_CONSTANT.equals(MY_CONSTANT));
System.out.println(MY_CONSTANT.equals("ABCDEF"));
System.out.println(MY_CONSTANT.equals(null));

Výsledek.

true
false
false

Pokud to zkusíme opačně, kód nepůjde zkompilovat.

System.out.println(MY_CONSTANT.equals(MY_CONSTANT));
System.out.println("ABCDEF".equals(MY_CONSTANT));
// Unresolved compilation problem:  Cannot invoke equals(String) on the primitive type null
// System.out.println(null.equals(MY_CONSTANT));

Vědomě bychom konstrukci null.equals() určitě nepoužili, ale nevědomě se používá docela často. Zde je metoda, která vrací náhodný String. Vrácená hodnota tedy může být i null.

 private static String generateString() {
       int randomNumber = gen.nextInt(3);
      
       switch(randomNumber) {
              case 0:
                     return "AB9922";
              case 1:
                     return "XX8811";
              default:
                     return null;
       }
 }

Pokud budeme porovnávat objekty tímto způsobem,

 private static final String MY_CONSTANT = "AB9922";

 public static void main(String[] args) {
      
       for (int i = 0; i < 100; i++) {
              System.out.println(generateString().equals(MY_CONSTANT));
       }
 }

kód nám pravděpodobně bude nějakou chvíli fungovat, ale časem se určitě objeví NullPointerException.

true
true
false
Exception in thread "main" java.lang.NullPointerException

Předejít tomu můžeme změnou objektu, na kterém metodu equals() voláme.

for (int i = 0; i < 100; i++) {
      System.out.println(MY_CONSTANT.equals(generateString()));
}

Rychlé spuštění programu ve Windows

Operační systém Windows nabízí několik možností jak rychle spustit program (ať už pomocí klávesové zkratky či jinak). Uvádím zde několik způsobů:

Připnutí ikony programu na hlavní panel

Pravým tlačítkem na program -> Připnout na hlavní panel. Pokud z hlavního panelu chcete program odstranit, pravým tlačítkem na program na panelu -> Odepnout tento program z  hlavního panelu. Tímto způsobem je možné si upravit hlavní panel tak, aby obsahoval ty nejpoužívanější programy a ty díky tomu byly lehce přístupné.

Spuštění programu na hlavním panelu pomocí klávesové zkratky

Všechny programy připnuté k hlavnímu panelu mají své pořadové číslo (zleva doprava od jedničky). Tlačítko Start, ikona Prohledat Windows a Zobrazení úkolů se nepočítá.

windows_spusteni_programu

Na tomto snímku je můj hlavní panel. Pokud budu chtít pustit průzkumníka souborů, stisknu klávesu Windows + 1 (průzkumník Windows lze spustit i klávesovou zkratkou Win + E). Pokud budu chtít spustit Eclipse IDE, stisknu klávesu Windows + 4. Pokud program ZimNotes tak klávesu Windows + 2.

Přiřazení klávesové zkratky zástupci

Pokud máte na ploše zástupce programu, je možné mu přiřadit klávesovou zkratku. Klikněte pravým tlačítkem na ikonu programu -> Vlastnosti -> umístěte kurzor do textového pole s popiskem Klávesová zkratka a tiskněte nějaké písmeno. Automaticky se předvyplní Ctrl + Alt.

windows_spusteni_programu02

Na obrázku je programu (zástupci programu) PuTTY přiřazena klávesová zkratka Ctrl + Alt + Y.

Zdroje:

Guava Optional.orNull()

V jednom z předchozích příspěvků jsem se věnoval třídě Optional z knihovny Guava. Nezmínil jsem ale užitečnou metodu orNull(). Tato metoda buď vrátí referenci na objekt v případě, že isPresent() je true, nebo null. Může se to hodit třeba v případě, kdy ve svém kódu používáte třídu Optional, ale jiné metody požadují objekt, nebo null.

Optional str1 = Optional.of("world");
Optional str2 = Optional.absent();

System.out.println(str1.orNull());
System.out.println(str2.orNull());

Výsledek.

world
null

V následujícím kódu si všimněte metod getString01() a getString02(). Obě dělají to samé, jen getString02() je mnohem kratší a přehlednějsí.

public static void main(String[] args) {
	Optional str1 = Optional.of("world");
	Optional str2 = Optional.absent();
	
	System.out.println(getString01(str1));
	System.out.println(getString01(str2));
	System.out.println(getString02(str1));
	System.out.println(getString02(str2));
}

private static String getString01(Optional str) {
	if (str.isPresent()) {
		return str.get();
	} else {
		return null;
	}
}

private static String getString02(Optional str) {
	return str.orNull();
}

Metoda Arrays.asList()

Tato metoda vrací list fixní délky. To znamené, že jakýkoliv pokus o změnu velikosti tohoto listu skončí chybou UnsupportedOperationException.

Ať už se jedná o odebrání prvku,

List list = Arrays.asList("a", "b", "c", "b");
		
ListIterator iterator = list.listIterator();
while (iterator.hasNext()) {
	if (iterator.next().equals("b")) {
		iterator.remove();
	}
}

nebo o přidání prvku,

List list = Arrays.asList("a", "b", "c", "b");
		
ListIterator iterator = list.listIterator();
while (iterator.hasNext()) {
	if (iterator.next().equals("b")) {
		iterator.add("bbb");
	}
}

dostaneme následující chybu.

Exception in thread "main" java.lang.UnsupportedOperationException
	at java.util.AbstractList.add(AbstractList.java:148)
	at java.util.AbstractList$ListItr.add(AbstractList.java:438)
        ...

V případě, že chceme vytvořil list s proměnnou velikostí, můžeme použít parametrický konstruktor ArrayList(kolekce).

List list = new ArrayList(Arrays.asList("a", "b", "c", "b"));
		
ListIterator iterator = list.listIterator();
while (iterator.hasNext()) {
	if (iterator.next().equals("b")) {
		iterator.remove();
	}
}

Výsledkem je následující list: [a, c].