Java 8

Skala do drukowania: 80%
Obecne przed Javą 8
Nowości w Javie 8
Obecne przed Javą 8
Adnotacje

Adnotacje służą do przekazywania meta danych, innymi słowy przekazują dane o danych (kodzie źródłowym).

Definicja adnotacji Override – adnotacja napisana z użyciem adnotacji:

@Target(ElementType.METHOD) - możliwość stosowania do np: TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, ANNOTATION_TYPE

@Retention(RetentionPolicy.SOURCE) - jak długo adnotacje mają być przechowywane: możliwe wartości: SOURCE, CLASS, RUNTIME.

public @interface Override {}

Informacje o "cyklu życia" adnotacji definiowane są przez adnotację @Retention:

Klasy anonimowe

Klasy anonimowe to klasy wewnętrzne, które mają dokładnie jedną instancję, bez deklaracji. Do wywołania konstruktora klasy anonimowej niezbędny jest interfejs (w związku z brakiem deklaracji a tym samym nazwy). W klasach wewnętrznych (również anonimowych) można używać stałych lub efektywnie stałych.

Typy generyczne

Typy generyczne to swojego rodzaju "szablony", dzięki którym możemy unikać niepotrzebnego rzutowania.


Deklaracja typu generycznego (możliwość parametryzowania jednym lub większą ilością obiektów)

public class FuncyBox <T,S> {
    private T first;
    private S second;

    public Pair(T first, S second) {
        this.first = first;
        this.second = second;
    }
}

Przypisanie wartości:

FancyBox<Orange, Integer> orangeBox = new BoxOnSteroids<>(new Orange(), 123);

Wildcard - metody przyjmujące jako argument obiekty generyczne (mogące rónież być ograniczone z góry (extends) lub z dołu (super)).

Klasa Figure rozszerzana jest przez klasę Rectangle, a ta rozszerzana jest przez klasę Square.

Deklaracja metody z użyciem typu generycznego:

private static <T> void method1(FancyBox<T> box) {

Deklaracja metody z użyciem wildcard:

private static <T> void method2(FancyBox<?> box) {
(symbol <T> przed zwracanym typem jest opcjonalny)

Przyjmuje jako argument typy rozszerzające/implementujuące Rectangle (Rectangle i Square):
private static void method3(FancyBox<? extends Rectangle> box) {
Wewnątrz metody do pola z typem generycznym można przypisać wyłącznie null. (zebezpieczenie przed przypisaniem "rodzica" na dziecko.
Przyjmuje jako argument typy rozszerzane przez Rectangle (Rectangle i Figure):
private static void method4(FancyBox<? super Rectangle> box) {
Wewnątrz metody do pola z typem generycznym można przypisać obiekty Rectangle i Square.
private static void methodExtends(FancyBox<? extends Rectangle> box) {
    box.object = null;
    box.object = new Square(); // DNC
    box.object = new Rectangle(); // DNC
    box.object = new Figure(); // DNC
}
private static void methodSuper(FancyBox<? super Rectangle> box) {
    box.object = null;
    box.object = new Square();
    box.object = new Rectangle();
    box.object = new Figure(); // DNC
}
Nowości w javie 8
Stream'y
Return BaseStream (Stream, IntStream, LongStream, DoubleStream):
Name Return Stream Description Example
peek() równoważny wykonuje operacje na każdym elemencie s.peek(v -> System.out.println(v.substring(0, 2)))
onClose() równoważny dodaje moduł obsługi (clouser) s.onClose(closeHandler)
parallel() równoważny* równoległy s.parallel()
sequential() równoważny* sekwencyjny s.sequential()
filter() obrobiony filtruje s.fiter(v -> v > 2)
flatMap() obrobiony spłaszcza tablicę, listę, zbiór s.flatMap(Collection::stream)
map() obrobiony wykonuje operacje na każdym elemencie s.map(v -> v = v.substring(0, 2))
limit() obrobiony odbina stream do zadanej długości s.limit(3)
distinct() obrobiony zwraca stream z usuniętymi duplikatami s.distinct()
skip() obrobiony obcina stream o n pierwszych elementów s.skip(3)
sorted() obrobiony sortuje stream s.sorted()
unordered() obrobiony desortuje stream s.unordered()
Terminal operations:
Name Return Stream Description Example
findAny() Optional<T> zwraca dowolny spełniający oczekiwania element s.findAny()
findFirst() Optional<T> zwraca pierwszy spełniający oczekiwania element s.findFirst()
reduce() Optional<T> zwraca Optional (lub np. String z tablicy Stringów) s.reduce((v1, v2) -> v1.rate > v2.rate ? v1 : v2)
min() Optional<T> zwraca minimalną wartość w oparciu o komparator s.min(Comparator.comparing(Integer::valueOf))
max() Optional<T> zwraca maksymalną wartość w oparciu o komparator s.max(Comparator.comparing(Integer::valueOf))
anyMatch() obrobooleanbiony sprawdza czy jakikolwiek element spełnia założenie s.anyMatch(v -> v > 1)
allMatch() boolean sprawdza czy wszystkie elementy spełniają założenie s.allMatch(v -> v > 1)
noneMatch() boolean sprawdza czy wszystkie elementy nie speł. założenia s.noneMatch(v -> v > 1)
isParallel() boolean czy operacja terminalowa byłaby wykon. równolegle? s.isParallel()
collect() List, Set, Map... zwraca kolekcję wybranego typu s.collect(Collectors.toList())
count() long zwraca ilość elementów streama longiem s.count()
forEach() void wykonuje operację na każdym z elementów s.forEach(System.out::println)
forEachOrdered() void wykonuje operację na każdym z elementów po kolei s.forEachOrdered(System.out::println)
close() void zamyka streama s.close()
GroupingBy - grupowanie listy Stringów i sumowanie ilości wystapień:
List<String> items = Arrays.asList("apple", "apple", "banana", "apple", "orange", "banana");
Map<String, Long> result = items.stream().collect(
    Collectors.groupingBy(
        Function.identity(),      // zwraca samgo siebie (w tym przypadku Stringa – element listy)
        Collectors.counting()     // zlicza ilość wystąpień
    )
);
result => {orange=1, banana=2, apple=3}
Grupowanie obiektów po nazwie wybranego pola i sumowanie:
List<Item> fruits = Arrays.asList(
    new Item("apple", 10, new BigDecimal("9.99")),
    new Item("banana", 20, new BigDecimal("19.99")),
    new Item("orange", 10, new BigDecimal("29.99")),
    new Item("apple", 10, new BigDecimal("9.99")),
    new Item("banana", 10, new BigDecimal("19.99")),
    new Item("apple", 20, new BigDecimal("9.99")));

stream().collect(Collectors.groupingBy(field, Collectors.method))

result

Collectors.method

Map<String, Long> counting = fruits.stream().collect(
    Collectors.groupingBy(Item::getName, Collectors.counting()));
counting => {
    orange=1,
    banana=2,
    apple=3}
Collectors.counting()
Map<String, Integer> sum = fruits.stream().collect(
    Collectors.groupingBy(Item::getName,
    Collectors.summingInt(Item::getQty)));
sum => {
    orange=10,
    banana=30,
    apple=40}
Collectors.summingInt(Item::getQty)
Map<Integer, List<String>> result2 = fruits.stream().collect(
    Collectors.groupingBy(
        Item::getQty,
        Collectors.mapping(Item::getName, Collectors.toList())));
result2 => {
    20=[banana, apple],
    10=[apple, orange, apple, banana]}
Collectors.mapping(Item::getName, 
Collectors.toList())

Stream.empty() - zwraca pustego streama (np zamiast return null;)

Stream.builder() - zwraca Stream.Buildera np. Stream.Builder<Employee>

Do buildera dodajemy elementy za pomocą metody accept LUB add (która wywołuje accept() i zwraca this).

Stream.build() - tworzy Streama ze Stream.Builder'a

Stream.of(array) LUB Stream.of(obj1, obj2, obj3) - zwraca streama, zawierającego elementy tablicy lub poszczególne obiekty

Optional'e
Return Optionals:
Name Description Example
filter() filtruje op.filter(s -> s.contains("d")
map() wykonuje operacje na każdym elemencie op.map(o -> o.substring(2))
empty() wraca Optional.empty op.empty()
of() zwraca Optional z wartością, dla null rzuca wyjątkiem Optional.of(Object)
ofNullable() zwraca Optional z wartością, dla null zwraca Optional.empty Optional.ofNullable(Object)
Terminal operations:
Name Return Description Example
get() Object zwraca wartość, którą zawiera Optional op.get()
orElse() Object zwraca wartość lub coś innego op.orElse("nicNieByłoWOptional")
isPresent() boolean czy istnieje wartość? op.isPresent()
ifPresent() void wykonuje operacje jeśli element istnieje op.ifPresent(o -> System.out.println(o))
Interfejsy funkcyjne
Kod klasy: OwnConsumer.java
@FunctionalInterface
public interface OwnConsumer {
    void consumeString(String str);
}
Kod klasy: InterfaceExamples.java
public class InterfaceExamples {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("raz", "i dwa");
        printList1(list, str -> System.out.println(str));
        printList2(list, str -> System.out.println(str));
    }
 
    public static void printList1(List<String> list, OwnConsumer consumer) {
        list.forEach(consumer::consumeString); // zamiast używać referencji do metody, równie dobrze można by to zrobić po "staremu" w pętli for
    }
 
    public static void printList2(List<String> list, Consumer<String> consumer) {
        list.forEach(consumer::accept); // zamiast używać referencji do metody, równie dobrze można by to zrobić po "staremu" w pętli for
    }
}

Dzięki takiemu wykorzystaniu interfejsów funkcyjnych, możemy do metody przekazać wyrażenie Lambda jako parametr.

Jeśli interfejs nie będzie oznaczony adnotacją @FunctionalInterface, powyższy kod będzie działał tak samo.

Wyrażenia Lambda

Składnia:

<lista parametrów> -> <ciało wyrażenia>


Przykładowe wyrażenie bezparametrowe

Przykładowe wyrażenie z jednym parametrem

Przykładowe wyrażenie z wieloma parametrami

() -> "some return value"

x -> x * x

(x, y) -> System.out.println(x * y)


Podawanie typów parametrów jest opcjonalne. Kompilator jest w stanie poznać te parametry z kontekstu w którym znajduje się dane wyrażenie lambda.

Przykładowe wyrażenie lambda z podanymi typami parametrów:

>(Integer x, Long y) -> System.out.println(x * y)

Przykładowe wieloliniowe wyrażenie Lambda:

x -> {
    int y = x * x;
    return y;
}

Każde wyrażenie Lambda jest jest instancją dowolnego interfejsu funkcyjnego (posiadającego tylko jedną metodę abstrakcyjną).

Przykłady interfejsów funkcyjnych: UnaryOperator, Supplier, Consumer, BiConsumer, Function, Predicate, BiPredicate.

Jednoarg. operator:
UnaryOperator<Integer> square = x -> x * x;
Dostawca:
Supplier<String> someString = () -> "some return value";
Konsument:
Consumer<String> consumer = s -> System.out.println(s);
Bi-konsument:
BiConsumer<Integer, Long> multiplier = (Integer x, Long y) -> System.out.println(x * y);

Odwołanie do konstruktora przy użyciu wyrażenia Lambda

Równoważny kod bez wyrażeń Lambda

Supplier<Object> objectCreator = Object::new;

System.out.println(objectCreator.get());

System.out.println(new Object());


Odwołania do metody przy użyciu wyrażenia Lambda

Równoważny kod bez wyrażeń Lambda

Object objectInstance = new Object();

IntSupplier equalsMethodOnObject = objectInstance::hashCode;

System.out.println(equalsMethodOnObject.getAsInt());

Object objectInstance = new Object();

System.out.println(objectInstance.hashCode());

List<Integer> numbers = Arrays.asList(1, 3, 2, 4);

Consumer<Integer> integerConsumer = n -> System.out.println(n);

numbers.forEach(integerConsumer);

List<Integer> numbers = Arrays.asList(1, 2, 3, 4);

numbers.forEach(System.out::println);

Referencje do metod

Referencja do metody

Wyrażenie Lambda

Consumer<String> c = System.out::println;

Consumer<String> c = s -> System.out.println(s);

Referencje do metod mogą zastępować jedynie proste (z jedną metodą) wyrażenia Lambda.
Wyróżniamy 4 typy referencji do metod:

Metoda forEach dostępna w kolekcjach

List<String> list = Arrays.asList("jeden", "dwa", "trzy");
list.forEach(System.out::println); // zamiast używać referencji do metody, równie dobrze można by to zrobić po "staremu" w pętli for

Domyślne i statyczne metody w interfejsach

Metody

Ciało

default, static, private (private od Javy 9)

obowiązkowe

public, abstract i "package-private"

niemożliwe

MetaSpace (brak PERM)

Przed Java 8
New Generation
(Eden + 2 x Survivor)
Old Generation
Perm Generation
HEAP
(-Xmx)
Native Memory
Java 8
New Generation
(Eden + 2 x Survivor)
Old Generation
HEAP
(-Xmx)
METASPACE
(-XX:MetaspaceSize)
Native Memory
Data i czas

Klasy LocalDate, LocalTime i LocalDateTime mają prywatne konstruktory a ich instancję tworzymy przez statyczną metodę of.

Ponadto używamy w tych klasach metod: plus*, minus*, get*, isBefore, isAfter, isEqual.

Klasy Period (okres - dla Date) i Duration (czas trwania - dla Time) mają prywatne konstruktory a ich instancję tworzymy poprzez of*.