Java 8 na wypasie – biblioteka Vavr
Java 8 na wypasie – biblioteka Vavr
Artykuł ten bazuje na wpisie – http://www.baeldung.com/vavr – gorąco zachęcam do zapoznania się również z nim.
Biblioteka vavr rozszerza możliwości programowania funkcyjnego w Java 8:
<dependencies> <dependency> <groupId>io.vavr</groupId> <artifactId>vavr</artifactId> <version>0.9.0</version> </dependency> </dependencies>
Wartość Null
Poniżej zamieszczony kod może powodować błąd w przypadku braku weryfikacji wartości null:
public static void nullWithoutVavr() { Object object = null; if (object == null) { object = "obj"; } }
biblioteka vavr ten problem eliminuje zamieniając wartość null na wartość None:
public static void nullWithVavr() { Option<Object> obj = Option.of(null); System.out.println(obj); }
wynik:
None Process finished with exit code 0
inny przykład to zwrócenie wartości default w momencie kiedy wystąpi null:
public static void nullWithVavrDefaultValue() { Object object = null; Option<Object> objectOption = Option.of(object); System.out.println( objectOption.getOrElse("javaleader.pl")); }
wynik:
javaleader.pl Process finished with exit code 0
Tuple (krotka)
Reprezentacja krotek (tupli – niemodyfikowalna struktura danych) nie wystepuje w Javie. Z użyciem biblioteki Vavr tupla dwuelementowa przedstawia się nastepująco:
public static void tuple() { Tuple2<String, String> java8 = Tuple.of("pl", "Javaleader"); System.out.println(java8._1); System.out.printf(java8._2); }
wynik:
pl Javaleader Process finished with exit code 0
Konstrukcja Try
Jeśli metoda wyrzuca wyjątek to należy przy jej wywołaniu opakować ją w strukturę try & catch co zmniejsza czytelność naszego kodu. Biblioteka vavr daje większe możliwości zwiększając czytelność kodu:
public static void tryIsFailue() { Try<Integer> result = Try.of(() -> 1 / 0); System.out.println(result.isFailure()); }
wynik:
true Process finished with exit code 0
zwrócenie wartości domyślnej w momencie wystąpienia błędu:
public static void tryWithDefaultValue() { Try<Integer> result = Try.of(() -> 1 / 0); System.out.println(result.getOrElse(0)); }
wynik:
0 Process finished with exit code 0
Interfejsy funkcyjne
Biblioteka vavr udostępnia więcej interfejsów funkcyjnych niż te dostępne w Java 8:
Interfejs Function z 5 parametrami:
public static void functionInterfaceWith5Arguments() { Function5<Integer, Integer, Integer, Integer, Integer, Integer> integersSum = (a, b, c, d, e) -> a + b + c + d + e; Integer sum = integersSum.apply(1,2,3,4,5); System.out.println(sum); }
wynik:
15 Process finished with exit code 0
Możliwe jest także użycie referencji do metody:
private static int sumOf5Integers(int a, int b, int c, int d, int e) { return a + b + c + d + e; } public static void functionInterfaceWith5Arguments() { Function5<Integer, Integer, Integer, Integer, Integer, Integer> integersSum = FunctionalProgramming::sumOf5Integers; Integer sum = integersSum.apply(1,2,3,4,5); System.out.println(sum); }
Kolekcje
Kolekcje w Javie to struktury modyfikowalne. Struktury modyfikowalne mają to do siebie, że nie są bezpieczne w wielowątkowych aplikacjach. Są oczywiście rozwiązania dla kolekcji które mają być bezpieczne wątkowo ale nie są to wydajne mechanizmy. Ponadto kolekcja która tworzona jest jako niemodyfikowalna w momencie próby dodania nowego elementu zwraca błąd:
public static void immutableCollectionThrowsError() { java.util.List<String> javaleader = Arrays.asList("javaleader"); java.util.List<String> list = Collections.unmodifiableList(javaleader); list.add(".pl"); }
Exception in thread "main" java.lang.UnsupportedOperationException at java.util.Collections$UnmodifiableCollection.add(Collections.java:1055) at pl.javaleader.FunctionalProgramming.immutableCollectionThrowsError(FunctionalProgramming.java:62) at pl.javaleader.FunctionalProgramming.main(FunctionalProgramming.java:79)
Z pomocą przychodzi nowe API dla kolekcji z użyciem biblioteki vavr!
Listy z użyciem Api biblioteki vavr nie posiadają metody add(). Zamiast tego posiadają metody append() i prepend() które odpowiednio dodają element na końcu listy i na początku tworząc nową listę co z kolei skutkuje że stara lista pozostaje niemodyfikowalna!
public static void imutableCollectionWithVavr() { List<Integer> intList1 = List.of(1, 2, 3); List<Integer> intList2 = intList1.append(4); System.out.println(intList1 == intList2); // false }
Walidatory
Walidatory:
import io.vavr.control.Validation;
dają możliwość akumulacji wszystkich błędów dla walidowanych obiektów. Zakładamy dla użytkownika następujące reguły walidacji:
- email musi być prawidowy,
- wiek musi być powyżej >18.
Klasa użytkownik:
@Getter @Setter @AllArgsConstructor class User { private String email; private int age; }
piszemy walidator:
class UserValidator { String EMAIL_ERR = "Invalid email"; String AGE_ERR = "Age must be grater than 18"; public Validation<Seq<String>, User> validateUser(String email, int age) { return Validation.combine(validateEmail(email), validateAge(age)).ap(User::new); } private Validation<String, String> validateEmail(String name) { String invalidChars = name.replaceAll("^(.+)@(.+)$", ""); return invalidChars.isEmpty() ? Validation.valid(name) : Validation.invalid(EMAIL_ERR + invalidChars); } private Validation<String, Integer> validateAge(int age) { return age < 18 ? Validation.invalid(AGE_ERR) : Validation.valid(age); } }
metoda która waliduje użytkownika:
public static void testUserValidator() { UserValidator userValidator = new UserValidator(); Validation<Seq<String>, User> valid = userValidator.validateUser("kontakt@javaleader.pl", 30); Validation<Seq<String>, User> invalid = userValidator.validateUser("kontaktjavaleader.pl", 14); System.out.println(valid.toString()); System.out.println(invalid.toString()); invalid.getError().forEach(error -> System.out.println(error)); }
wynik:
Valid(pl.javaleader.User@7106e68e) Invalid(List(Invalid email kontaktjavaleader.pl, Age must be grater than 18)) Invalid email kontaktjavaleader.pl Age must be grater than 18
Pattern Matching
Instrukcje if lub wyrażenia case nie są dobrym mechanizmem sprawdzania wartości parametru wejściowego jeśli występuje więcej warunków:
instrukcja if():
int input = 1; String output = ""; if (input == 0) { output = "zero"; } if (input == 1) { output = "one"; }
instrukcja case():
switch (input) { case 0: output = "zero"; break; case 1: output = "one"; break; }
zamiast tego lepiej użyć konstrukcji:
public static void patternMatching() { int input = 3; String output = Match(input).of( Case($(1), "one"), Case($(2), "two"), Case($(3), "three"), Case($(), "?")); System.out.println(output); }
wynik:
three Process finished with exit code 0
inny przykład:
public static void showHelp() { System.out.println("help..."); }
public static void patternMatching() { String arg = "-h"; Match(arg).of( Case($(isIn("-h", "--help")), o -> run(FunctionalProgramming::showHelp)), Case($(), o -> run(() -> { throw new IllegalArgumentException(); })) ); }
wynik:
help...
Obiekty Lazy
Obiekty typu Lazy są ewaluowane tylko raz w momencie pierwszego odwołania. W kolejnych wywołaniach zwracany jest ten sam wynik zapisany w cache’u. Zwiększa to wydajność aplikacji ponieważ nie trzeba potwrzać za każdym razem wykonywania kodu który jest niezbędny do wygenerowania wyniku:
public static void lazy() { Lazy<Double> lazy = Lazy.of(Math::random); System.out.println(lazy.isEvaluated()); double val1 = lazy.get(); System.out.println(lazy.isEvaluated()); System.out.println(val1); double val2 = lazy.get(); System.out.println(lazy.isEvaluated()); System.out.println(val2); }
wynik:
false true 0.24536330044331622 true 0.24536330044331622
Leave a comment