Adnotacje
Klasy anonimowe
Typy generyczne
Stream'y
Optional'e
Interfejsy funkcyjne
Wyrażenia Lambda
Referencje do metod
Metoda forEach dostępna w kolekcjach
Domyślne i statyczne metody w interfejsach
MetaSpace (brak PERM)
Data i Czas - nowy interfejs
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:
usuwane przez kompilator w trakcie kompilacji (RetentionPolicy.SOURCE)
umieszczanie w skompilowanej klasie, ale nie dostępne w trakcie uruchomienia programu (RetentionPolicy.CLASS) – domyślna wartość
dostępne w trakcie wykonywania programu (RetentionPolicy.RUNTIME)
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 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
}
|
| 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() |
| 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() |
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}
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
| 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) |
| 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)) |
@FunctionalInterface
public interface OwnConsumer {
void consumeString(String str);
}
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.
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); |
|
Referencja do metody |
Wyrażenie Lambda |
|
Consumer<String> c = System.out::println; |
Consumer<String> c = s -> System.out.println(s); |
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)
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*.