From 84798587fa8f1b29bd2c271e78eb4f10cbab9d30 Mon Sep 17 00:00:00 2001 From: Thimoty Barbieri Date: Tue, 13 Sep 2022 16:34:56 +0200 Subject: [PATCH 1/3] Simple solutions --- .../java/lambdasinaction/chap7/WordCount.java | 71 +++++++++++++++++-- 1 file changed, 64 insertions(+), 7 deletions(-) diff --git a/src/main/java/lambdasinaction/chap7/WordCount.java b/src/main/java/lambdasinaction/chap7/WordCount.java index 13ccce52..e255cac5 100644 --- a/src/main/java/lambdasinaction/chap7/WordCount.java +++ b/src/main/java/lambdasinaction/chap7/WordCount.java @@ -14,8 +14,15 @@ public class WordCount { public static void main(String[] args) { System.out.println("Found " + countWordsIteratively(SENTENCE) + " words"); System.out.println("Found " + countWords(SENTENCE) + " words"); + System.out.println("Found " + countWordsSimpleStream(SENTENCE) + " words"); + System.out.println("Found " + countWordsAccumulatorStream(SENTENCE) + " words"); } + + //-------- ITERATIVE SOLUTION + + + public static int countWordsIteratively(String s) { int counter = 0; boolean lastSpace = true; @@ -29,7 +36,57 @@ public static int countWordsIteratively(String s) { } return counter; } + + + + // -- WITH REGEX + + public static long countWordsSimpleStream(String s) { + return Arrays.stream(s.trim().split("\\s+")).count(); + } + + // -- WITH FOR EACH AND SIMPLE ACCUMULATOR CLASS + + public static long countWordsAccumulatorStream(String s) { + + Stream stream = s.chars().mapToObj(c -> (char) c); + + MyWordCounter wordCounter = new MyWordCounter(0, true); + + stream.forEach(wordCounter::accumulate); + + return wordCounter.getCounter(); + + } + + private static class MyWordCounter { + private int counter; + private boolean lastSpace; + + public MyWordCounter(int counter, boolean lastSpace) { + this.counter = counter; + this.lastSpace = lastSpace; + } + + public void accumulate(Character c) { + if (Character.isWhitespace(c)) { + if(!lastSpace) lastSpace = true; + } else { + if (lastSpace) { + counter=counter+1; + lastSpace= false; + } + } + } + + public int getCounter() { + return counter; + } + } + + //--- WITH COLLECTOR + public static int countWords(String s) { //Stream stream = IntStream.range(0, s.length()) // .mapToObj(SENTENCE::charAt).parallel(); @@ -39,13 +96,13 @@ public static int countWords(String s) { return countWords(stream); } - private static int countWords(Stream stream) { - WordCounter wordCounter = stream.reduce(new WordCounter(0, true), - WordCounter::accumulate, - WordCounter::combine); - return wordCounter.getCounter(); - } - + private static int countWords(Stream stream) { + WordCounter wordCounter = stream.reduce(new WordCounter(0, true), + WordCounter::accumulate, + WordCounter::combine); + return wordCounter.getCounter(); + } + private static class WordCounter { private final int counter; private final boolean lastSpace; From cdefb211e30a5d3609e91e253f6b8d0b152ca7d9 Mon Sep 17 00:00:00 2001 From: thimoty Date: Fri, 28 Apr 2023 13:37:18 +0200 Subject: [PATCH 2/3] Fixed optionals --- src/main/java/lambdasinaction/chap10/Car.java | 6 ++++- .../lambdasinaction/chap10/Insurance.java | 4 ++++ .../lambdasinaction/chap10/OptionalMain.java | 24 +++++++++++++++++-- .../java/lambdasinaction/chap10/Person.java | 6 ++++- 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/main/java/lambdasinaction/chap10/Car.java b/src/main/java/lambdasinaction/chap10/Car.java index 36f00727..889541fc 100644 --- a/src/main/java/lambdasinaction/chap10/Car.java +++ b/src/main/java/lambdasinaction/chap10/Car.java @@ -4,9 +4,13 @@ public class Car { - private Optional insurance; + private Optional insurance = Optional.empty(); public Optional getInsurance() { return insurance; } + + public void setInsurance(Insurance insurance) { + this.insurance = Optional.ofNullable(insurance); + } } diff --git a/src/main/java/lambdasinaction/chap10/Insurance.java b/src/main/java/lambdasinaction/chap10/Insurance.java index 69409686..5219281c 100644 --- a/src/main/java/lambdasinaction/chap10/Insurance.java +++ b/src/main/java/lambdasinaction/chap10/Insurance.java @@ -7,4 +7,8 @@ public class Insurance { public String getName() { return name; } + + public Insurance(String name) { + this.name = name; + } } diff --git a/src/main/java/lambdasinaction/chap10/OptionalMain.java b/src/main/java/lambdasinaction/chap10/OptionalMain.java index 1826cb4a..6e8295e0 100644 --- a/src/main/java/lambdasinaction/chap10/OptionalMain.java +++ b/src/main/java/lambdasinaction/chap10/OptionalMain.java @@ -5,9 +5,29 @@ public class OptionalMain { public String getCarInsuranceName(Optional person) { - return person.flatMap(Person::getCar) - .flatMap(Car::getInsurance) + return person.flatMap(Person::getCar) + .flatMap(Car::getInsurance) .map(Insurance::getName) .orElse("Unknown"); } + + + public static void main(String[] args) { + + Person person = new Person(); + Car car = new Car(); + Insurance insurance = new Insurance(null); // orElse Unknown + person.setCar(car); + //car.setInsurance(insurance); // Comment-de-comment to see short circuit the stream + + Optional p = Optional.of(person); + + Optional optInsurance = car.getInsurance(); + String optName = optInsurance.map(Insurance::getName).orElse("Unknown"); + System.out.println(optName); + + String name = new OptionalMain().getCarInsuranceName(p); + System.out.println(name); + + } } diff --git a/src/main/java/lambdasinaction/chap10/Person.java b/src/main/java/lambdasinaction/chap10/Person.java index 5e84e552..4b5c34d9 100644 --- a/src/main/java/lambdasinaction/chap10/Person.java +++ b/src/main/java/lambdasinaction/chap10/Person.java @@ -4,9 +4,13 @@ public class Person { - private Optional car; + private Optional car = Optional.empty(); public Optional getCar() { return car; } + + public void setCar(Car car) { + this.car = Optional.ofNullable(car); + } } From 2765ed8a14933dac0c14d0e93bf78809a57115d2 Mon Sep 17 00:00:00 2001 From: Thimoty Barbieri Date: Thu, 14 Mar 2024 12:56:05 +0100 Subject: [PATCH 3/3] Finally fixed the example to demonstrate new way of dealing with NullPointers in OOP Structures --- .gitignore | 1 + src/main/java/lambdasinaction/chap10/Car.java | 13 +- .../lambdasinaction/chap10/Insurance.java | 7 +- .../lambdasinaction/chap10/OptionalMain.java | 228 +++++++++++++++++- .../java/lambdasinaction/chap10/Person.java | 35 ++- 5 files changed, 275 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index b7d7ac33..b7efe069 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /target /local +/bin # Eclipse, Netbeans and IntelliJ files /.* diff --git a/src/main/java/lambdasinaction/chap10/Car.java b/src/main/java/lambdasinaction/chap10/Car.java index 36f00727..b7a66448 100644 --- a/src/main/java/lambdasinaction/chap10/Car.java +++ b/src/main/java/lambdasinaction/chap10/Car.java @@ -4,9 +4,16 @@ public class Car { - private Optional insurance; + private Insurance insurance; - public Optional getInsurance() { - return insurance; + public Optional getInsurance() { // An insurance is Optional + return Optional.ofNullable(insurance); } + + public Car(Insurance insurance) { + super(); + this.insurance = insurance; + } + + public Car() {} } diff --git a/src/main/java/lambdasinaction/chap10/Insurance.java b/src/main/java/lambdasinaction/chap10/Insurance.java index 69409686..15bd7359 100644 --- a/src/main/java/lambdasinaction/chap10/Insurance.java +++ b/src/main/java/lambdasinaction/chap10/Insurance.java @@ -4,7 +4,12 @@ public class Insurance { private String name; - public String getName() { + public String getName() { // A Name of an insurance is mandatory return name; } + + public Insurance(String name) { + super(); + this.name = name; + } } diff --git a/src/main/java/lambdasinaction/chap10/OptionalMain.java b/src/main/java/lambdasinaction/chap10/OptionalMain.java index 1826cb4a..b9ead781 100644 --- a/src/main/java/lambdasinaction/chap10/OptionalMain.java +++ b/src/main/java/lambdasinaction/chap10/OptionalMain.java @@ -1,13 +1,237 @@ package lambdasinaction.chap10; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + import java.util.*; +/** + * The intent of Java when releasing Optional was to use it as a return type, + * thus indicating that a method could return an empty value + * + * @author thimo + */ public class OptionalMain { - public String getCarInsuranceName(Optional person) { - return person.flatMap(Person::getCar) + public String getCarInsuranceName(Person person) { + return Optional.ofNullable(person) + .flatMap(Person::getCar) .flatMap(Car::getInsurance) .map(Insurance::getName) .orElse("Unknown"); } + + public static void main(String[] args) { + + OptionalMain om = new OptionalMain(); + + // Using isPresent() + om.givenOptional_whenIsPresentWorks_thenCorrect(); + + // Using OrElse, OrElseGet, OrElseThrow + om.usingOrElse_OrElseGet(); + + // Using filtering + om.whenOptionalFilterWorks_thenCorrect(); + + // Using flatMap + om.usingFlatMap(); + + ///////////////////////////////// + // Using Optional for Object Oriented Modeling + // OPTIONAL is used as a Return Type for fields that might return null, because + // they are not mandatory in a constructor set of the class + ///////////////////////////////////// + Insurance insurance = new Insurance("Generali"); + Car myFerrari = new Car(insurance); // But I can also create a Ferrari without insurance because of Car() + Person aFerrariPerson = new Person(myFerrari, "Thimoty"); + + // Now this is all OK because the chain is complete + System.out.println(om.getCarInsuranceName(aFerrariPerson)); + + // What about a person without a Car? We get a solid behavior without null checking + Person aBiker = new Person(null, "Valentino Rossi"); + System.out.println(om.getCarInsuranceName(aBiker)); + + // What about a person with a Car without insurance? Also We get a solid behavior without null checking in depth + Car aDangerousCar = new Car(); // Possible by design because we left a no-arg constructor + Person aPirate = new Person(aDangerousCar, "Jack Sparrow"); + System.out.println(om.getCarInsuranceName(aPirate)); + + + } + + + + // Using is present on a non-empty optional + public void givenOptional_whenIsPresentWorks_thenCorrect() { + + String name = "Hello"; + + Optional opt = Optional.ofNullable(null); + assertFalse(opt.isPresent()); + + Optional opt2 = Optional.of(name); + assertTrue(opt2.isPresent()); + + /** + * Instead of using: + * if(name != null) { + System.out.println(name.length()); + } + We can rewrite null checking in a functional way! + */ + opt2.ifPresent(nm -> System.out.println(nm.length())); + + } + + /** + * The orElse() method is used to retrieve the value wrapped inside an Optional + * instance. It takes one parameter, which acts as a default value. The orElse() + * method returns the wrapped value if it’s present, and its argument otherwise + */ + public void usingOrElse_OrElseGet() { + String nullName = null; + String name = Optional.ofNullable(nullName).orElse("john"); + assertEquals("john", name); + + String name2 = Optional.ofNullable(nullName).orElseGet(() -> "john"); + assertEquals("john", name2); + + + /** + * when using orElseGet() to retrieve the wrapped value, the getMyDefault() + * method is not even invoked since the contained value is present. + * + * However, when using orElse(), whether the wrapped value is present or not, + * the default object is created. So in this case, we have just created one + * redundant object that is never used + */ + String text = null; + + String defaultText = Optional.ofNullable(text).orElseGet(this::getMyDefault); + assertEquals("Default Value", defaultText); + + defaultText = Optional.ofNullable(text).orElse(getMyDefault()); + assertEquals("Default Value", defaultText); + + String text2 = "Text present"; + + System.out.println("Using orElseGet:"); + String defaultText2 = Optional.ofNullable(text2).orElseGet(this::getMyDefault); + assertEquals("Text present", defaultText2); + + System.out.println("Using orElse:"); + defaultText2 = Optional.ofNullable(text2).orElse(getMyDefault()); + assertEquals("Text present", defaultText2); + + /** + * OrElseThrow allows to handle absent values + */ + try { + String nullNameThrow = null; + Optional.ofNullable(nullNameThrow).orElseThrow(IllegalArgumentException::new); + } catch (IllegalArgumentException e) { + System.out.println("** An exception is thrown: "+e.getMessage()); + } + + + } + + /** + * We can run an inline test on our wrapped value with the filter method. It + * takes a predicate as an argument and returns an Optional object. If the + * wrapped value passes testing by the predicate, then the Optional is returned + * as-is. + * + * However, if the predicate returns false, then it will return an empty + * Optional: + */ + public void whenOptionalFilterWorks_thenCorrect() { + Integer year = 2016; + Optional yearOptional = Optional.of(year); + boolean is2016 = yearOptional.filter(y -> y == 2016).isPresent(); + assertTrue(is2016); + boolean is2017 = yearOptional.filter(y -> y == 2017).isPresent(); + assertFalse(is2017); + + // Typical use case of filter: instead of writing this: + /** + * public boolean priceIsInRange1(Modem modem) { boolean isInRange = false; + * + * if (modem != null && modem.getPrice() != null && (modem.getPrice() >= 10 && + * modem.getPrice() <= 15)) { + * + * isInRange = true; } return isInRange; } + * + * We can write using Optional implementation without having to check for null pointers + */ + assertTrue(priceIsInRange2(new Modem(10.0))); + assertFalse(priceIsInRange2(new Modem(9.9))); + assertFalse(priceIsInRange2(new Modem(null))); + assertFalse(priceIsInRange2(new Modem(15.5))); + assertFalse(priceIsInRange2(null)); + + } + + /** + * Old fashioned code + * @param modem + * @return + */ + public boolean priceIsInRange1(Modem modem) { + boolean isInRange = false; + + if (modem != null && modem.getPrice() != null + && (modem.getPrice() >= 10 + && modem.getPrice() <= 15)) { + + isInRange = true; + } + return isInRange; + } + + public boolean priceIsInRange2(Modem modem2) { + return Optional.ofNullable(modem2) + .map(Modem::getPrice) // It works because if the Optional is Empty, returns an Empty Optional, no null pointer + .filter(p -> p >= 10) // same as above + .filter(p -> p <= 15) + .isPresent(); // If you arrive here with an empty Optional you get false + } + + public String getMyDefault() { + System.out.println("Getting Default Value"); + return "Default Value"; + } + + private void usingFlatMap() { + Person person = new Person("Thimoty"); + Optional personOptional = Optional.of(person); + + // Using simply map, several steps to extract the value + Optional> nameOptionalWrapper = personOptional.map(Person::getName); + Optional nameOptional = nameOptionalWrapper.orElseThrow(IllegalArgumentException::new); + String name1 = nameOptional.orElse(""); + assertEquals("Thimoty", name1); + + // MUCH BETTER USING FLATMAP + String name = personOptional + .flatMap(Person::getName) + .orElse(""); + assertEquals("Thimoty", name); + } + +} + +class Modem { + private Double price; + + public Modem(Double price) { + this.price = price; + } + // standard getters and setters + public Double getPrice() { + return this.price; + } } diff --git a/src/main/java/lambdasinaction/chap10/Person.java b/src/main/java/lambdasinaction/chap10/Person.java index 5e84e552..ea0911ed 100644 --- a/src/main/java/lambdasinaction/chap10/Person.java +++ b/src/main/java/lambdasinaction/chap10/Person.java @@ -4,9 +4,38 @@ public class Person { - private Optional car; + private Car car; + private String name; - public Optional getCar() { - return car; + public Optional getCar() { // A car is optional + return Optional.ofNullable(car); } + + public Optional getName() { // A name is optional + return Optional.ofNullable(name); + } + + public Person() {} + + public Person(Car car, String name) { + super(); + this.car = car; + this.name = name; + } + + public Person(String name) { + super(); + this.name = name; + } + + public void setCar(Car car) { + this.car = car; + } + + public void setName(String name) { + this.name = name; + } + + + }