workshops beyond legacy code
Kamil Lolo
Dlaczego Java 8?
...czyli zapomniane elementy języka
Podczas używania JDBC można spotkać się z problemem połączeń do bazy danych, które zawsze trzeba pamiętać zamknąć na końcu w bloku
finally. Java 7 wprowadza mechanizm który pozwala wyeliminować ten boilerplate.
Connection con = null;
try {
con = ConnectionUtil.getDBConnection();
callProcedure("FOO_PROCEDURE", con);
}
catch (SQLException e) {
e.printStackTrace();
}
finally {
if(con !=null) {
try {
con.close();
}
catch(Exception e) {
}
}
}
Podczas używania JDBC można spotkać się z problemem połączeń do bazy danych, które zawsze trzeba pamiętać zamknąć na końcu w bloku
finally. Java 7 wprowadza mechanizm który pozwala wyeliminować ten boilerplate.
Connection con = null;
try {
con = ConnectionUtil.getDBConnection();
callProcedure("FOO_PROCEDURE", con);
}
catch (SQLException e) {
e.printStackTrace();
}
finally {
if(con !=null) {
try {
con.close();
}
catch(Exception e) {
}
}
}
try (final Connection con = ConnectionUtil.getDBConnection()) {
callProcedure("FOO_PROCEDURE", con);
}
catch (SQLException e) {
e.printStackTrace();
}
mechanizm pozwala definiować dowolną liczbę zmiennych które mają zostać zamknięte. Oddziela się je średnikiem. Jeżeli metoda close ma throws to musimy wyjątek złapać i obsłużyć.
static class ClosableUnicorn implements AutoCloseable {
void magic() {
System.out.println("magic");
}
@Override
public void close() throws IOException {
System.out.println("on close");
}
}
public static void main(final String... args) {
try (final ClosableUnicorn unicorn = new ClosableUnicorn()) {
unicorn.magic();
}
catch (IOException e) {
e.printStackTrace();
}
}
static String readFirstLineFromFile(String path) throws IOException {
try (BufferedReader br =
new BufferedReader(new FileReader(path))) {
return br.readLine();
}
}
Java 9 wprowadza możliwość wykorzystywania zmiennych w bloku try-resources które są effective final.
void foo() throws IOException {
BufferedReader br = new BufferedReader(new FileReader(""));
try (br) {
br.readLine();
//br = null; ERROR
}
//br = null; ERROR
}
void doSomethingWith(Connection connection) throws Exception {
try(connection) {
connection.doSomething();
}
}
W enumach można definiować metody abstrakcyjne. (od jakiej wersji?)
W jaki sposób taką metodę można zaimplementować?
enum Employe {
JUNIOR,
REGULAR,
CHEF;
public abstract long getSalary();
}
enum Employe {
JUNIOR(500) {
public float getSalary() {
return baseSalary;
}
},
REGULAR(1000) {
public float getSalary() {
return baseSalary + (REGULAR_BONUS * baseSalary);
}
},
CHEF(2000) {
public float getSalary() {
return baseSalary + (CHEF_BONUS * baseSalary);
}
};
final static float REGULAR_BONUS = 0.3f;
final static float CHEF_BONUS = 0.5f;
final long baseSalary;
Employe(final long baseSalary) {
this.baseSalary = baseSalary;
}
public abstract float getSalary();
}
Java 7 wprowadza możliwość używania podkreśleń podczas wpisywania literałów liczb. Jednak od Javy 9 nie będzie można używać podkreślenia jako nazwy zmiennej.
long bigNumber = 1_000_000; // correct in Java 7+
long _ = 1000; // incorect in Java 9
Podczas deklaracji zmiennych generycznych przed Java 7 obowiązkowe było podanie po obu stronach znaku równa się typu jakiego ma być zmienna. Informacja ta jednak może być wywnioskowana na podstawie kontekstu przez kompilator i już nie jesteśmy zmuszeni do wpisywania tej nadmiarowej informacji, wystarczy wpisać "<>". Pominięcie operatora diamentu spowoduje że kompilator uzna zmienną za surowego typu i nie będziemy mogli skorzystać z dobrodziejstw generyków.
List<String> names = new ArrayList<String>();
Map<String, List<Integer>> map = new HashMap<String, List<Integer>>();
// można zastosować skrócony zapis
Map<String, List<Integer>> map = new HashMap<>();
Często w metodach realizujących logikę biznesową dochodzi do sytuacji gdzie nasz kod może wyrzucić kilka różnych wyjątków, a my je procesujemy w taki sam sposób. Wymaga to od nas napisania kilku bloków catch. Java 7 wprowadziła mechanizm który potrafi nam tutaj ułatwić życie.
try {
foo();
}
catch(final IOException e) {
processError(e);
}
catch(final MethodNotFoundException e) {
processError(e);
}
catch(final IllegalArgumentException e) {
processError(e);
}
Często w metodach realizujących logikę biznesową dochodzi do sytuacji gdzie nasz kod może wyrzucić kilka różnych wyjątków, a my je procesujemy w taki sam sposób. Wymaga to od nas napisania kilku bloków catch. Java 7 wprowadziła mechanizm który potrafi nam tutaj ułatwić życie.
try {
foo();
}
catch(final IOException e) {
processError(e);
}
catch(final MethodNotFoundException e) {
processError(e);
}
catch(final IllegalArgumentException e) {
processError(e);
}
try {
foo();
}
catch(final IOException | MethodNotFoundException | IllegalArgumentException e) {
processError(e);
}
Od Javy 7 można używać w wyrażeniach switch typów String.
public String getTypeOfDayWithSwitchStatement(String dayOfWeekArg) {
String typeOfDay;
switch (dayOfWeekArg) {
case "Monday":
typeOfDay = "Start of work week";
break;
case "Tuesday":
case "Wednesday":
case "Thursday":
typeOfDay = "Midweek";
break;
case "Friday":
typeOfDay = "End of work week";
break;
case "Saturday":
case "Sunday":
typeOfDay = "Weekend";
break;
default:
throw new IllegalArgumentException("Invalid day of the week: " + dayOfWeekArg);
}
return typeOfDay;
}
...czyli wszystko co jest teraz sexy
public static void main(String... args) {
List<String> names = Arrays.asList("Mateusz", "Zosia", "Ania");
names.sort(new Comparator<String>() {
@Override
public int compare(final String s1, final String s2) {
return s1.compareTo(s2);
}
});
}
public interface Comparator<T> {
int compare(T o1, T o2);
}
public static void main(String... args) {
List<String> names = Arrays.asList("Mateusz", "Zosia", "Ania");
names.sort(new Comparator<String>() {
@Override
public int compare(final String s1, final String s2) {
return s1.compareTo(s2);
}
});
}
public interface Comparator<T> {
int compare(T o1, T o2);
}
lista zawiera obiekty typu string
i tylko takie mogę tutaj sortować
dostaje dwa parametry typu T
zwracam int
jedyna ważna linijka
public static void main(String... args) {
List<String> names = Arrays.asList("Mateusz", "Zosia", "Ania");
names.sort(new Comparator<String>() {
@Override
public int compare(final String s1, final String s2) {
return s1.compareTo(s2);
}
});
names.sort(
// nasza pierwsza lambda!
(final String s1, final String s2) -> {
return s1.compareTo(s2);
}
);
}
names.sort((s1, s2) -> { return s1.compareTo(s2); });
names.sort((s1, s2) -> s1.compareTo(s2));
new Thread(() -> System.out.println("hello from another thread")).start();
foo( param -> System.out.print(param));
Runnable r = () -> System.out.println();
// Lambda to tylko implementacja interfejsu z jedna metodą abstrakcyjna
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
return "some string";
}
};
Callable<String> callable = () -> "some string";
One key difference between using Anonymous class and Lambda expression is the use of this keyword. For anonymous class ‘this’ keyword resolves to anonymous class, whereas for lambda expression ‘this’ keyword resolves to enclosing class where lambda is written.
public class Demo {
public static void main(final String... args) {
final Demo demo = new Demo();
}
Demo() {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(this); // com.comarch.training.Demo$1@69991479
}
}).start();
new Thread(() -> System.out.println(this)).start(); // demo
}
@Override
public String toString() {
return "demo";
}
}
@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
@FunctionalInterface
interface MyFI {
// ERROR: Multiple non-overriding abstract methods found in interface
void method1();
void method2();
}
static abstract class F {
public abstract void foo();
}
public static void main(String... args) {
//Error: Target type of lambda conversion must be an interface
F f = () -> {};
}
static String lastName = "";
public static void main(String... args) throws InterruptedException {
String userName = "";
Runnable r1 = () -> lastName = "Henio";
// ERROR: local variables referenced from a lambda
// expression must be final or effectively final
Runnable r2 = () -> userName = "Henio";
new Thread(r).start();
Thread.sleep(100);
System.out.println(lastName);
}
final String httpRequestMethod = httpRequest.getMethod();
Response response = httpRequest.getBody()
.map(body -> requestBuilder.method(
httpRequestMethod,
Entity.entity(body,MediaType.APPLICATION_JSON_TYPE))
)
.orElse(requestBuilder.method(httpRequestMethod));
if (body.isPresent()) {
return requestBuilder.method(httpRequestMethod,Entity.entity(body.get(),MediaType.APPLICATION_JSON_TYPE)
} else {
return requestBuilder.method(httpRequestMethod)
}
private static interface IMagicAlgorithm {
long calculate(long x);
}
private static long calculate(long x) {
return x * x;
}
public static void main(String... args) throws InterruptedException {
// obliczenia zawarte w lambda - slabe do utrzymania
IMagicAlgorithm algorithm1 = x -> x * x;
// wywolanie istenijacej metody - nadal nadmiarowy kod
IMagicAlgorithm algorithm2 = x -> calculate(x);
// referencja do metody!
IMagicAlgorithm algorithm3 = Demo::calculate;
}
-lambda tworzy anonimową metodę, co w przypadku kiedy mam już metodę i chcemy ją wykorzystać?
-wisienka na torcie lambd
public class Demo {
@FunctionalInterface
private static interface IMagicAlgorithm {
long calculate(long x);
}
private long calculate(long x) {
return x * x;
}
private class Inner {
public void foo() {
// obliczenia zawarte w lambda - slabe do utrzymania
IMagicAlgorithm algorithm1 = x -> x * x;
// wywolanie istenijacej metody - nadal nadmiarowy kod
IMagicAlgorithm algorithm2 = x -> calculate(x);
// referencja do metody!
IMagicAlgorithm algorithm3 = Demo.this::calculate;
}
}
}
public void foo() {
final char[] numbers = {'A', 'b', 'C'};
final String result = Stream.of(numbers)
.map(String::valueOf) // metoda statyczna
.map(String::toLowerCase) // metoda instancji ( s-> s.toLowerCase() )
.map(this::appendeSeparator) // metoda obiektu
.map(WorkshopTest::trimmer) // metoda obiektu
.collect(Collectors.joining());
System.out.println(result);
}
private static String trimmer(final String input) {
return input.trim();
}
private String appendeSeparator(final String input) {
return input + "-";
}
public interface Vehicle {
default void print() {
System.out.println("I am a vehicle!");
}
}
public interface FourWheeler {
default void print() {
System.out.println("I am a four wheeler!");
}
}
public class Car implements Vehicle, FourWheeler {
public void print() { //bez tej implementacji kompilator zglosi błąd
Vehicle.super.print();
}
}
W interfejsach można również deklarować metody statyczne oraz pola które z domysłu są finalne i statyczne. Odwołujemy się do tego jak do pól, metod statycznych w klasach
public interface Vehicle {
String broomSound = "pffff";
static void broomBroom() {
broomSound =""; // error
System.out.println(broomSound); // dostep w metodzie statycznej
}
}
public Properties foo(final Properties input) throws Exception {
logger.info("start, with params: {}", input);
final Connection dbConnection = getDBConnection();
Properties result = new Properties();
try {
validateInput(input);
result = callDbProcedure(input, dbConnection);
if(result.getProperty("error") != null) {
throw new Exception(result.getProperty("error"));
}
} catch (Exception e) {
System.out.println(e.getMessage());
System.out.println("Rolling back...");
dbConnection.rollback();
} finally {
dbConnection.close();
}
logger.info("end");
return result;
}
public void shoulDoSomething(final Properties input ) throws Exception {
runInTransaction( connection-> {
validateInput(input);
return callDbProcedure(input, connection);
});
}
public Properties runInTransaction(final Function<Connection, Properties> callback) throws Exception {
logger.info("start, with params: {}", input);
final Connection dbConnection = getDBConnection();
Properties result = new Properties();
try {
result = callback.apply(dbConnection);
if(result.getProperty("error") != null) {
throw new IllegalStateException(result.getProperty("error"));
}
} catch (Exception e) {
System.out.println(e.getMessage());
System.out.println("Rolling back...");
dbConnection.rollback();
} finally {
dbConnection.close();
}
logger.info("end");
return result;
}
...czyli wszystko co jest teraz sexy
Zestaw operacji jest nich przedstawiany bez ujawniania tego jak są zaimplementowane i jak wygląda przepływ danych. Mówimy co ma być zrobione a nie jak.
Kod zmienia stan programu, korzystamy z zmiennych. Musimy określić nie tylko co ma być zrobione ale również jak.
users.forEach(System.out::print);
// nie interesuje mnie czy
// w metodzie jest for,
// for-each czy while
// mowie co ma być zrobione
for (int i = 0; i < users.size(); i++) {
System.out.println(users);
}
Programowanie gdzie najważniejszą rolę pełnią funkcje oraz abstrakcyjne ujęcie przepływu sterowania i operacji na danych za pomocą funkcji aby uniknąć efektów ubocznych i ograniczyć modyfikację stanu aplikacji.
Co byście przekazali jako parametr takiej metody?
static class UserFromService {
private final String userName;
public UserFromService(final String userName) {
this.userName = userName;
}
public String getUserName() {
return userName;
}
}
public static void main(final String... args) {
final UserFromService userFromService = new UserFromService("Kamil");
final Properties data = new Properties();
// funkcja jak pobrac pole userName z obiektu userFromService
// i jeżeli nie jest null to wrzucić do Properties
// pod podanym kluczem.
setIfNotNull(????);
}
W językach funkcyjnych funkcje występują na tych samych zasadach co obiekty innych typów.
public static void main(final String... args) {
final UserFromService userFromService = new UserFromService("Kamil");
final Properties data = new Properties();
setIfNotNull(userFromService::getUserName, data::put, "firstName");
}
public static <T> void setIfNotNull( final Supplier<T> getter,
final BiConsumer<String, T> setter,
final String key ) {
final T t = getter.get();
if (null != t) {
setter.accept(key, t);
}
}
private static final Function<String, String> initUserName = (param) -> "userName2:" + param;
private static String getLassName(final Function<String, String> callback) {
final Function<String, String> afterFn = element -> element.toLowerCase();
return callback
.andThen(afterFn)
.apply(" Kowalski");
}
Given the same input, the function always result in the same output
Function composition
Składanie funkcji pozwala nam budować z funkcji, które pełnią rolę cegiełek, pełnowartościowe aplikacje.
public class Demo {
private static final BigDecimal USDToPLNRate = new BigDecimal("3.86");
private static final BigDecimal amountOfDollar1 = new BigDecimal("23");
private static final BigDecimal amountOfDollar2 = new BigDecimal("43");
@FunctionalInterface
private interface ExchangeRateConverter extends BiFunction<BigDecimal, BigDecimal, BigDecimal> {
default Function<BigDecimal, BigDecimal> curryRate(BigDecimal t) {
return u -> apply(t, u);
}
}
public static void main(final String... args) {
final Function<BigDecimal, BigDecimal> converter = ((ExchangeRateConverter) BigDecimal::multiply)
.curryRate(USDToPLNRate) // currying - rozwijanie funkcji
.andThen(Demo::afterConversion)
.compose(Demo::beforeConversion);
converter.apply(amountOfDollar1);
converter.apply(amountOfDollar2);
}
private static BigDecimal beforeConversion(BigDecimal amount) {
System.out.println("Kwota przed konwersja:" + amount);
return amount;
}
private static BigDecimal afterConversion(BigDecimal amount) {
System.out.println("Po konwersji:" + amount);
return amount;
}
}
Lazy evaluation
Kiedy wykona się kod z poprzedniego przykładu?
interfejs | parametry | zwraca | opis | metoda | inne metody |
---|---|---|---|---|---|
Runnable | - | void | uruchamia logikę bez parametrów i wyniku | run | - |
Supplier | - | T | Dostarcza wartość typu T | get | - |
Consumer | T | void | Pobiera wartość typu T | accept | andThen |
BiConsumer | T,U | void | Pobiera wartość typu T i U | accept | andThen |
Function | T | R | Funkcja z parametrem typu T | apply | compose, andThen, identity |
BiFunction | T,U | R | Funkcja z parametrem typu T i U | apply | andThen |
UnaryOperator | T |
T | Operator jednoargumentowy dla typu T | apply | compose, andThen, identity |
BinaryOperator | T,T | T | Operator dwuargumentowy dla typu T | apply | andThen, maxBy, minBy |
Predicate | T | boolean | Funkcja zwracająca wartość logiczną | test | and, or, negate, isEqual |
BiPredicate | T,U | boolean | Dwuargumentowa funkcja zwracająca wartość logiczną | test | and, or, negate |
jako rodzaj IoC
// 1
final int[] data = {1, 2, 3};
Arrays.stream(data).forEach(System.out::print);
// 2
final List<String> strings = Arrays.asList("a","b");
strings.stream().forEach(System.out::print);
// 3
Stream.of("a","b").forEach(System.out::print);
// 4
Stream.generate(strings.iterator()::next)
.limit(strings.size())
.forEach(System.out::print);
// 5
IntStream.range(0,3).forEach(System.out::print);
// 6
Stream.empty().forEach(System.out::print);
// 7
Stream<String> streamBuilder =
Stream.<String>builder().add("a").add("b").add("c").build();
// tworzy strumień intów
IntStream intStream = IntStream.range(1, 3);
// strumień intów łącznie z liczbami krańcowymi
LongStream longStream = LongStream.rangeClosed(1, 3);
// Tworzy strumień DoubleStream
Random random = new Random();
DoubleStream doubleStream = random.doubles(3);
// dodatkowe metody arytmetyczne
int[] numbers = {2,4,23,21};
Arrays.stream(numbers).average().ifPresent(System.out::println); // 12.5
Wszystkie operacje jakie możemy wykonywać na strumieniu dzielą się na natychmiastowe zwracają one referencje do strumienia i nie wykonują żadnej logiki. Dzięki czemu strumienie są leniwe. Drugi rodzaj operacji jaki możemy wykonać na strumieniu to operacja terminująca strumień która zwraca najczęściej jakiś obiekt wynikowy i wymusza wykonanie wszystkich operacji w strumieniu.
// 1
holdings
.stream() // utworzenie strumienia
.sorted() // operacja natychmiastowa
.distinct() // operacja natychmiastowa
.count(); // operacja terminująca
// 2
final List<String> names = Arrays.asList("Anna", "Aneta", "Zosia", "Henia");
final Stream<String> userStream = names.stream();
userStream.forEach(System.out::println);
//java.lang.IllegalStateException: stream has already been operated upon or close
userStream.forEach(System.out::println);
final String[] balances = {"0,01", "19.99", "100 "};
// z wykorzystanie referncji do metody
Arrays.stream(balances).forEach(System.out::println);
// z wykorzystaniem wyrazenia Lambda
Arrays.stream(balances).forEach(current -> System.out.println(current) );
final String[] balances = {"0,01", "19.99", "100 "};
Arrays.stream(balances)
.map(String::trim) // .map( current -> current.trim() )
.map(balance -> balance.replace(",", "."))
.map(BigDecimal::new) // .map( current -> new BigDecimal(current) )
.forEach(System.out::println);
<R> Stream<R> map(Function<? super T, ? extends R> mapper)
final Human[] people = {new Human(18), new Human(25)};
Arrays.stream(people)
// .map(Human::getAge) uzycie tego spowoduje zbedny boxing to Integer!!
.mapToInt(Human::getAge)
.forEach(System.out::println);
class Human {
int age;
Human(int age) {
this.age = age;
}
public int getAge() {
return age;
}
}
class Book {
private String name;
public Book(final String name) {
this.name = name;
}
// getter/setter
}
class Library {
private String name;
private List<Book> books;
public Library(
final String name,
final List<Book> books ) {
this.name = name;
this.books = books;
}
// getter/setter
}
final List<Library> libraries = Arrays.asList(
new Library(
"publiczna",
Arrays.asList(new Book("Java for dummies"))
),
new Library(
"uniwersystecka",
Arrays.asList(new Book("Thinking in Java"))
)
);
libraries.stream()
.flatMap(library -> library.books.stream())
.forEach(System.out::println);
final List<String> names = Arrays.asList("Anna", "Aneta", "Zosia", "Henia");
names.stream()
.filter(name -> name.startsWith("A"))
.forEach(System.out::println); // operacja terminująca
Stream<T> filter(Predicate<? super T> predicate)
final List<Library> libraries = Arrays.asList(
new Library("publiczna",
Arrays.asList(
new Book("Java for dummies"),
new Book("Thinking in Java")
)
),
new Library("uniwersystecka",
Arrays.asList(
new Book("Thinking in Java"))
)
);
libraries.stream()
.flatMap(
library -> library.books.stream()
)
.distinct()
.forEach(System.out::println);
class Book {
private String name;
public Book(final String name) {
this.name = name;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null ||
getClass() != o.getClass())
return false;
final Book book = (Book) o;
return name != null ?
name.equals(book.name)
: book.name == null;
}
@Override
public int hashCode() {
return name != null ? name.hashCode() : 0;
}
}
Stream
.of("one", "two", "three", "four")
.filter(e -> e.length() > 3)
.peek(e -> System.out.println("Filtered value: " + e))
.map(String::toUpperCase)
.peek(e -> System.out.println("Mapped value: " + e))
.collect(Collectors.toList());
Arrays.asList("Hania","Marian", "Stefan", "Amadeusz", "Franek")
.stream()
.sorted()
.sorted(Comparator.reverseOrder())
.sorted((o1, o2) -> o1.compareTo(o2))
.forEach(System.out::println);
final Random random = new Random();
IntStream
.generate(random::nextInt)
.limit(3)
.forEach(System.out::println);
String[] countries = {"poland", "russia", "france" , "germany"};
Arrays.stream(countries)
.skip(1)
.forEach(System.out::println);
Jest to metoda terminująca pozwalająca przepakować dane ze strumienia w jakiś obiekt docelowy używając przy tym dodatkowej logiki. Na wejściu przyjmuje obiekt klasy Collector. Zbiór predefiniowanych Collectorów znajduje się w klasie Collectors.
toList() | Zamienia dane ze strumienia w liste |
---|---|
toSet() | Zamienia dane ze strumienia w set |
toCollection() | Pozwala zamienić dane na kolekcje danego typu. Jako parametr podajemy referencje do kontruktora |
toMap() | Pozwala zmapować strumień do mapy. Wymaga podania funkcji mapującej klucz (najczęściej wykorzystywana to Function.identity()) oraz mapującej wartość |
joining() | Pozwala na łączenie Stringów. Można podać separator, prefix, postfix |
counting() | Zlicza elementy jako long |
summarizingDouble()/Long/Int | Dostarcza takich statystyk jak: średnia, min, max, liczba, suma. Do każdych w tych danych są również osobne metody. |
groupingBy() | Grupuje dane do listy |
partitioningBy() | Grupuje dane po podanym predykacie |
private Stream<String> getFilteredCountryStream(final String[] countries) {
final Predicate<String> isEnoughtLenght = country -> country.length() > 6;
return Arrays.stream(countries).filter(isEnoughtLenght);
}
final String[] countries = {"Polska", "Malta", "Afganistan", "Turkmenistan"};
// 1
final List<String> filteredCountries1 = getFilteredCountryStream(countries).collect(toList());
System.out.println(filteredCountries1.getClass());// ArrayList
// 2
final List<String> filteredCountries2 = getFilteredCountryStream(countries)
.collect(toCollection(LinkedList::new));
// 3
final Set<String> countriesSet = getFilteredCountryStream(countries).collect(toSet());
// 4
final Map<String, String> counrtyMap = getFilteredCountryStream(countries)
.collect(toMap(Function.identity(), String::toUpperCase));
// 5
final String counrtyStr = getFilteredCountryStream(countries).collect(joining());
System.out.println(counrtyStr); // AfganistanTurkmenistan
// 6
final String counrtyStr2 = getFilteredCountryStream(countries).collect(joining(","));
System.out.println(counrtyStr2); // Afganistan,Turkmenistan
// 7
final String counrtyStr3 = getFilteredCountryStream(countries).collect(joining(",","To sa kraje:","."));
System.out.println(counrtyStr3); // To sa kraje:Afganistan,Turkmenistan.
myStream.reduce("", (s1, s2) -> s1 + s2); // brak kompilacji do StringBuilder
myStream.collect(Collectors.joining()); // korzysta z StringBuildera
myStream.collect(
StringBuilder::new,
StringBuilder::append,
StringBuilder::append
);
Metoda pozwala wykonać operację grupowania elementów strumienia do mapy podobną do GROUP BY z SQL za pomocą funkcji klasyfikującej. Dodatkowo metoda występuje w wersji wielowątkowej: groupingByConcurrent
+----------+------------+-----------------+
| Name | City | Number of Sales |
+----------+------------+-----------------+
| Alice | London | 200 |
| Bob | London | 150 |
| Charles | New York | 160 |
| Dorothy | Hong Kong | 190 |
+----------+------------+-----------------+
// kod imperatywny:
Map<String, List<Employee>> result = new HashMap<>();
for (Employee e : employees) {
String city = e.getCity();
List<Employee> empsInCity = result.get(city);
if (empsInCity == null) {
empsInCity = new ArrayList<>();
result.put(city, empsInCity);
}
empsInCity.add(e);
}
// kod deklaratywny
Map<String, List<Employee>> employeesByCity = employees.stream().collect(groupingBy(Employee::getCity));
Metoda GroupingBy jest dostepna w 3 wersjach:
// 1. Mapa z setem postow gdzie kluczem jest typ
Map<BlogPostType, Set<BlogPost>> postsPerType
= posts.stream().collect(groupingBy(BlogPost::getType, toSet()));
// 2. mapa postow danego autora gdzie posty sa jako mapa per typ
Map<String, Map<BlogPostType, List>> map
= posts.stream().collect(groupingBy(BlogPost::getAuthor, groupingBy(BlogPost::getType)));
// 3. mapa post gdzie wartoscia jest suma polubien
Map<BlogPostType, Integer> likesPerType
= posts.stream().collect(groupingBy(BlogPost::getType, summingInt(BlogPost::getLikes)));
// 4. Uzycie metody joining
Map<BlogPostType, String> postsPerType = posts.stream()
.collect(groupingBy(BlogPost::getType, mapping(BlogPost::getTitle, joining(", ", "Post titles: [", "]"))));
// 1
Map<Boolean, List<Person>> adult = persons
.stream()
.collect(
partitioningBy(
person -> person.getAge() > 18
)
);
// 2
Map<Boolean, Set<String>> adult = persons
.stream()
.collect(
partitioningBy(
person -> person.getAge() > 18,
mapping(Person::getName, toSet())
)
);
String[] countries = {"poland", "Albania", "france", "germany"};
Arrays.stream(countries)
.skip(1)
.min(String::compareTo) // Albania
.ifPresent(System.out::println); // prawidlowe uzycia optional
String[] countries = {"poland", "Albania", "france", "germany"};
Arrays.stream(countries)
.skip(1)
.anyMatch(item -> item.startsWith("Z")); // false
final String[] countries = {"Polska", "Malta", "Afganistan", "Turkmenistan"};
final Predicate<String> isEnoughtLenght = country -> country.length() > 6;
Arrays.stream(countries)
.parallel()
.peek(it -> System.out.println(it + " w watku: " + Thread.currentThread().getName()))
.filter(isEnoughtLenght)
.count();
-----------------------------------------------------------
Wypisze:
Polska w watku: ForkJoinPool.commonPool-worker-3
Turkmenistan w watku: ForkJoinPool.commonPool-worker-2
Malta w watku: ForkJoinPool.commonPool-worker-1
Afganistan w watku: main
int count = 0;
public void bu() {
IntStream.range(0, 10_000)
.parallel()
.forEach(i -> count++);
System.out.println("\n count = " + count); // 7433, 9195
}
AtomicInteger count = new AtomicInteger(0);
@Test
public void bu() {
IntStream.range(0, 10_000)
.parallel()
.forEach(i -> count.incrementAndGet());
System.out.println("\n count = " + count); // 10000
}
Co robi ten kod legacy?
Czy dostaniemy ArrayIndexOutOfBoundsException?
public static String fooWithoutStream() {
final List<String> result = new LinkedList<>();
String nameList = "";
for (int i = 0; i < holdings.size(); i++) {
for (int j = 0; j < holdings.get(i).getCompanies().size(); ++i) {
for (int k = 0; k < holdings.get(i).getCompanies().get(j).getUsers().size(); k++) {
User user = holdings.get(i).getCompanies().get(j).getUsers().get(k);
if (!result.contains(user.getFirstName())) {
result.add(user.getFirstName());
}
}
}
}
result.sort(new Comparator<String>() {
@Override
public int compare(final String o1, final String o2) {
return o1.compareTo(o2);
}
});
for (String name : result) {
nameList += name + " ";
}
return nameList.trim();
}
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi nec metus justo. Aliquam erat volutpat.
public static String fooWithStream() {
return holdings.stream()
.flatMap(holding -> holding.getCompanies().stream())
.flatMap(company -> company.getUsers().stream())
.map(User::getFirstName)
.distinct()
.sorted()
.collect(Collectors.joining(" "));
}
public static String fooWithoutStream() {
final List<String> result = new LinkedList<>();
String nameList = "";
for (int i = 0; i < holdings.size(); i++) {
for (int j = 0; j < holdings.get(i).getCompanies().size(); ++i /** blad */) {
for (int k = 0; k < holdings.get(i).getCompanies().get(j).getUsers().size(); k++) {
User user = holdings.get(i).getCompanies().get(j).getUsers().get(k);
if (!result.contains(user.getFirstName())) {
result.add(user.getFirstName());
}
}
}
}
result.sort(new Comparator<String>() {
@Override
public int compare(final String o1, final String o2) {
return o1.compareTo(o2);
}
});
for (String name : result) {
nameList += name + " ";
}
return nameList.trim();
}
List<Integer> list = Arrays.asList(1, 2, 3);
// Old school
for (Integer i : list)
for (int j = 0; j < i; j++)
System.out.println(i * j);
// "Modern"
list.forEach(i -> {
IntStream.range(0, i).forEach(j -> {
System.out.println(i * j);
});
});
Optional jest to klasa z pakietu java.util, która przechowuję jakąś wartość w środku. Tą wartością może być null, stąd też Optional z założenia ma pomagać unikać błędów typu NullPointerException.
Klasa jest generyczna, stąd też może przechowywać dowolnego typu zmienną.
// 1
Map<String, Optional<List<Optional<User>>>> usersInCompany; // łot !?
// 2
class User {
private final String firstName;
User(final String firstName) {
this.firstName = firstName;
}
public Optional<String> getFirstName() {
return Optional.of(this.firstName); // OK!
}
}
// 1
Optional<String> optString = Optional.of(x);
// 2
if (optString.isPresent()) {
foo(optString.get());
}
// 3
optString.ifPresent(this::foo);
static Optional empty() | Metoda fabrykująca pustego optionala. |
---|---|
static Optional of(T value) | Metoda fabrykująca Optionala z podaną wartością. Jeżeli przekazany obiekt jest nullem to otrzymamy nullpointer. |
static Optional ofNullable(T value) | Jak wyżej, ale bez null pointera |
Optional(T value) | Konstruktor również wymaga żeby parametr nie był nullem |
T get() | Pobranie wartości przechowywanej przez Optional |
boolean isPresent() | Sprawdzenie czy obiekt w Optional ma wartośc null |
void ifPresent(Consumer consumer) | Wykonanie logiki w przypadku kiedy w Optional znajdują się dane. |
filter, map, flatMap | Metody analogiczne jak w stream |
<X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) | Wyrzuca wyjątek jeżeli pole w Optional ma wartość null |
T orElseGet(Supplier other) | Pobiera domyślną wartość z przekazanej metody |
T orElse(T other) | Przekazujemy domyślną wartość która zostanie zwrócona w przypadku kiedy Optional nie przechowuje danych |
public char firstChar(String s) {
if (s != null && !s.isEmpty())
return s.charAt(0);
else
throw new IllegalArgumentException();
}
Optional.ofNullable(s)
.filter(s -> !s.isEmpty())
.map(s -> s.charAt(0))
.orElseThrow(IllegalArgumentException::new);
private String foo(final Holding h) {
if (h != null) {
final Company c = h.getCompanies().get(0);
if (c != null && c.getUsers() != null) {
final User u = c.getUsers().get(0);
if (u != null && u.getFirstName() != null) {
final String result = u.getFirstName();
if (result.length() > 0) {
return result;
}
}
}
}
return "404-not-found";
}
Co robi ta funkcja?
private String getCompanyFirstUserName1(final Holding holding) {
if (holding != null) {
final Company company = holding.getCompanies().get(0);
if (company != null && company.getUsers() != null) {
final User user = company.getUsers().get(0);
if (user != null && user.getFirstName() != null) {
final String result = user.getFirstName();
if (result.length() > 0) {
return result;
}
}
}
}
return "not found";
}
private String getCompanyFirstUserName2(final Holding holding) {
return Optional.ofNullable(holding)
.map(Holding::getCompanies)
.map(Vector::firstElement)
.map(Company::getUsers)
.map(Vector::firstElement)
.map(User::getFirstName)
.filter(name -> name.length() > 0)
.orElse("not found");
}
...pomówmy chwilę o javascript
Nashorn pozwala wywoływać funkcje js z poziomu kodu Javy. Do takich funkcji można nawet przekazać obiekty Javy jako parametry.
var fun1 = function(name) {
print('Hi there from Javascript, ' + name);
return "greetings from javascript";
};
var fun2 = function (object) {
print("JS Class Definition: " +
Object.prototype.toString.call(object));
};
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.eval(new FileReader("script.js"));
Invocable invocable = (Invocable) engine;
Object result = invocable.invokeFunction("fun1", "Peter Parker");
System.out.println(result);
System.out.println(result.getClass());
// Hi there from Javascript, Peter Parker
// greetings from javascript
// class java.lang.String
Z poziomu kodu js możemy wywoływac kod javy. Do klas Javy odwołujemy się wtedy poprzez: Java.type.
var MyJavaClass = Java.type('my.package.MyJavaClass');
var result = MyJavaClass.fun1('John Doe');
print(result);
// Hi there from Java, John Doe
// greetings from java
static String fun1(String name) {
System.out.format("Hi there from Java, %s", name);
return "greetings from java";
}
...o tym co nas czeka za chwilę
Not yet.
List<String> userPermits = ???
List<String> defaultPermit = Arrays.asList("transfer","login","history");
// 1
Optional
.ofNullable(users)
.stream() // NOWOŚĆ!
.forEach(this::activatePermitOnDatabase)
// 2 - wykorzystanie domyslnej wartosci
Optional
.ofNullable(users)
.orElse(Arrays.asList("hania","ela"))
.stream() // stream z klasy List
.forEach(this::activatePermitOnDatabase)
public Stream<T> stream()
public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction);
public Optional<T> or(Supplier<? extends Optional<? extends T>> supplier)
wejdźmy w świat streamów
programistyczne
Get a Taste of Lambdas and Get Addicted to Streams
https://www.youtube.com/watch?v=1OpAgZvYXLQ
Design Patterns in the Light of Lambda Expressions
https://www.youtube.com/watch?v=e4MT_OguDKg
Refactoring to Functional Style with Java 8
https://www.youtube.com/watch?v=wjF1WqGhoQI&t=3131s
Czas na podsumowanie zdobytej wiedzy i wasze opinie.