Proměnný počet parametrů v Javě

Java umožňuje zadat metodě libovolný počet parametrů pomocí trojtečkové notace. Vnitřně si Java vytvoří pole argumentů a toto pole předá metodě. Jedná se tedy o zjednodušený zápis oproti tomu, abyste museli sami vytvořit pole a to předat.

Mějmě proměnné

 String ahoj = "Ahoj";
 String nazdar = "Nazdar";
 String cau = "Čau";
 String[] pole = {"AA", "BB", "CC", "DD"};
 List<String> seznam = Arrays.asList("XXX", "YYY", "ZZZ");

a metodu

public static void method(Object ... params) {
    for (int i = 0; i < params.length; i++) {
        if (i > 0) {
            System.out.print(" | ");
        }
        System.out.print(params[i]);
    }
    System.out.println();
}

Metodu je možné zavolat bez argumentů, s jedním nebo několika argumenty a nebo za argument zadat pole objektů.

method();
method(ahoj, nazdar, cau);
method(pole);
method(seznam);
method(seznam.toArray());

Výsledek

Ahoj | Nazdar | Čau
AA | BB | CC | DD
[XXX, YYY, ZZZ]
XXX | YYY | ZZZ

Pokud chceme argumenty kombinovat (zadat jak argument tak pole argumentů zároveň) je potřeba vytvořit pole s argumenty a to předat metodě.

List<Object> objectParamList = new ArrayList<Object>();
objectParamList.add(cau);
objectParamList.add(new Date());
objectParamList.addAll(Arrays.asList(pole));
objectParamList.add(123);
objectParamList.addAll(seznam);
objectParamList.add(Math.random());
method(objectParamList.toArray());

Výsledek

Čau | Sun Jan 24 16:41:59 CET 2016 | AA | BB | CC | DD | 123 | XXX | YYY | ZZZ | 0.37927696865182803

Převod streamu primitivních datových typů na stream objektů

V předchozím příspěvku jsem se věnoval streamům, které jako elementy obsahují primitivní datové typy. Jednalo se o následující streamy: IntSteam, DoubleStream, LongStream. Pomocí metody boxed() je možné stream primitivních datových typů převést na stream objektů odpovídajícího datového typu.

// Převod na stream objektů.
Stream<Integer> si = IntStream.range(1, 10).boxed();
Stream<Long> sl = LongStream.of(1_415, 402_112, 999).boxed();
Stream<Double> ds = DoubleStream.generate(() -> Math.random()).limit(5).boxed();

// Zobrazení elementů streamu.
si.forEach(System.out::println);
sl.forEach(System.out::println);
ds.forEach(System.out::println);

Streamy primitivních datových typů v Javě

Java kromě Stream<Object> nabízí i streamy primitivních datových typu. Přesněji řečeno má takové streamy tři:

  • IntStream
  • DoubleStream
  • LongStream

Vytvoření streamu primitivních datových typů
K vytvoření streamu primitivních datových typů je možné, tak jako pro stream objektů, použít metody generate() a iterate().

// IntStream obsahující deset sedmiček.
IntStream isg = IntStream.generate(() -> 7).limit(10);
// IntStream s elementy 0, 1, 2, 3, 4.
IntStream isi = IntStream.iterate(0, i -> i + 1).limit(5);

// Nekonečný DoubleStream, který obsahuje náhodná čísla.
DoubleStream dsg = DoubleStream.generate(() -> Math.random());
// DoubleStream s elementy 1.1, 2.2, 4.4, 8.8, 17.6.
DoubleStream dsi = DoubleStream.iterate(1.1, i -> i + i).limit(5);

// Nekonečný LongStream, který obsahuje čísla 9876543210.
LongStream lsg = LongStream.generate(() -> 9876543210L);
// LongStream obsahující čísla (řády) od 1 miliónu po 1 bilión.
LongStream lsi = LongStream.iterate(1_000_000, num -> num * 10).limit(9);

Dalším způsobem je použít statickou metodu of() a do ní uvést výčet hodnot.

IntStream intStream = IntStream.of(1, 3, 5, 7);
DoubleStream doubleStream = DoubleStream.of(0.52, 15.24, 7.879);
LongStream longStream = LongStream.of(14_854, 4_854);

Stream je možné vytvořit taktéž z pole pomocí statické metody java.util.Arrays.stream().

int[] intArray = { 1, 2, 3, 4};
double[] doubleArray = {12.548, 0.99, 103.1};
long[] longArray = {12, 778_755_441_784L, 1_221};

// IntSteam s elementy 1, 2, 3, 4.
IntStream isFromArray = Arrays.stream(intArray);
// DoubleStream s elementy 0.99 a 103.1.
DoubleStream dsFromArray = Arrays.stream(doubleArray, 1, 3);
// LongStream s elementem 1221.
LongStream lsFromArray = Arrays.stream(longArray, 2, 3);

IntStream a LongStream mají navíc k dispozici statické metody range() a rangeClosed().

// IntSteam s elementy 0, 1, 2, 3, 4.
IntStream isFromRange = IntStream.range(0, 5);
// LongStream s elementy 100, 101, 102, 103.
LongStream lsFromRange = LongStream.rangeClosed(100, 103);

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

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]

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()));
}

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].