├── settings.gradle ├── src ├── main │ └── java │ │ ├── Product.java │ │ ├── streams │ │ ├── Order.java │ │ ├── Customer.java │ │ ├── Ordering.java │ │ ├── BoxedStreams.java │ │ ├── SumBigDecimals.java │ │ ├── FlatMapDemo.java │ │ └── AlternativeReduceDemo.java │ │ ├── optional │ │ ├── Manager.java │ │ ├── Department.java │ │ ├── ProductDAO.java │ │ ├── Company.java │ │ └── Product.java │ │ ├── concurrency │ │ ├── Product.java │ │ ├── AwaitQuiesence.java │ │ └── CompletableFutureDemos.java │ │ ├── got │ │ ├── Title.java │ │ ├── House.java │ │ ├── Member.java │ │ ├── MemberDAO.java │ │ ├── MemberDB.java │ │ └── InMemoryMemberDAO.java │ │ ├── interfaces │ │ ├── Animal.java │ │ ├── Bird.java │ │ ├── Horse.java │ │ ├── Company.java │ │ ├── Pegasus.java │ │ ├── Employee.java │ │ ├── MyCompany.java │ │ └── CompanyEmployee.java │ │ ├── lazy │ │ ├── OptionalExceptionDemo.java │ │ └── LazyStreams.java │ │ ├── sorting │ │ ├── Golfer.java │ │ ├── SortStrings.java │ │ └── SortGolfers.java │ │ ├── eam │ │ ├── before │ │ │ ├── UseResource.java │ │ │ └── Resource.java │ │ └── after │ │ │ ├── UseResource.java │ │ │ └── Resource.java │ │ ├── lambdas │ │ ├── Runner.java │ │ ├── RunnableDemo.java │ │ ├── MapDemo.java │ │ ├── Person.java │ │ └── UsePerson.java │ │ ├── primes │ │ ├── after │ │ │ ├── PrimeCalculator.java │ │ │ └── SumSqrtPrimes.java │ │ └── before │ │ │ ├── PrimeCalculator.java │ │ │ └── SumSqrtPrimes.java │ │ ├── IterateDemo.java │ │ ├── SummarizingDemo.java │ │ ├── FinderDemo.java │ │ ├── refactoring │ │ ├── before │ │ │ └── LoopsSortsAndIfs.java │ │ └── after │ │ │ └── LoopsSortsAndIfs.java │ │ ├── RandomDemo.java │ │ ├── dao │ │ └── Employee.java │ │ ├── UseProducts.java │ │ ├── datetime │ │ └── AntarcticaTimeZones.java │ │ ├── OptionalDemo.java │ │ ├── CollectorsDemo.java │ │ └── io │ │ └── ProcessDictionary.java └── test │ └── java │ ├── interfaces │ └── CompanyEmployeeTest.java │ ├── streams │ ├── SumEvens.java │ ├── SumBigDecimalsTest.java │ ├── StringExercises.java │ ├── FlatMapExercises.java │ ├── BigDecimalReduceExercises.java │ └── ParallelStreamExercises.java │ ├── lambdas │ ├── BinaryOperatorTest.java │ ├── FunctionalInterfacesTest.java │ ├── LazyEvaluationExercises.java │ ├── FileFilterExercises.java │ ├── FunctionExercises.java │ └── RunnableExercises.java │ ├── optional │ ├── ProductDAOTest.java │ └── CompanyTest.java │ ├── concurrency │ ├── AwaitQuiesenceTest.java │ ├── CompletableFutureDemosTest.java │ └── CompletableFutureTests.java │ ├── sorting │ └── BookSortingExercises.java │ └── got │ └── InMemoryMemberDAOTests.java ├── Java_Upgrade_Slides.pdf ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .github ├── dependabot.yml └── workflows │ └── gradle.yml ├── .gitignore ├── LICENSE ├── gradlew.bat ├── CLAUDE.md ├── README.md ├── gradlew └── labs.md /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'java_upgrade' 2 | 3 | -------------------------------------------------------------------------------- /src/main/java/Product.java: -------------------------------------------------------------------------------- 1 | public record Product(String name, double price) { 2 | } 3 | -------------------------------------------------------------------------------- /src/main/java/streams/Order.java: -------------------------------------------------------------------------------- 1 | package streams; 2 | 3 | public record Order(int id) { 4 | } 5 | -------------------------------------------------------------------------------- /Java_Upgrade_Slides.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kousen/java_upgrade/HEAD/Java_Upgrade_Slides.pdf -------------------------------------------------------------------------------- /src/main/java/optional/Manager.java: -------------------------------------------------------------------------------- 1 | package optional; 2 | 3 | public record Manager(String name) { 4 | } -------------------------------------------------------------------------------- /src/main/java/concurrency/Product.java: -------------------------------------------------------------------------------- 1 | package concurrency; 2 | 3 | public record Product(int id, String name) { 4 | } -------------------------------------------------------------------------------- /src/main/java/got/Title.java: -------------------------------------------------------------------------------- 1 | package got; 2 | 3 | public enum Title { 4 | SIR, LORD, LADY, KING, QUEEN 5 | } 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kousen/java_upgrade/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/java/interfaces/Animal.java: -------------------------------------------------------------------------------- 1 | package interfaces; 2 | 3 | public interface Animal { 4 | String speak(); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/interfaces/Bird.java: -------------------------------------------------------------------------------- 1 | package interfaces; 2 | 3 | public interface Bird extends Animal { 4 | default String speak() { 5 | return "chirp"; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/lazy/OptionalExceptionDemo.java: -------------------------------------------------------------------------------- 1 | package lazy; 2 | 3 | public class OptionalExceptionDemo { 4 | public static void main(String[] args) { 5 | 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/interfaces/Horse.java: -------------------------------------------------------------------------------- 1 | package interfaces; 2 | 3 | public interface Horse extends Animal { 4 | default String speak() { 5 | return "neigh"; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gradle 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "10:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /src/main/java/interfaces/Company.java: -------------------------------------------------------------------------------- 1 | package interfaces; 2 | 3 | public interface Company { 4 | default String getName() { 5 | return "Initech"; 6 | } 7 | 8 | // String getName(); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/interfaces/Pegasus.java: -------------------------------------------------------------------------------- 1 | package interfaces; 2 | 3 | public class Pegasus implements Horse, Bird { 4 | @Override 5 | public String speak() { 6 | return Horse.super.speak() + " " + Bird.super.speak(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/sorting/Golfer.java: -------------------------------------------------------------------------------- 1 | package sorting; 2 | 3 | public record Golfer(String first, String last, int score) implements Comparable { 4 | 5 | @Override 6 | public int compareTo(Golfer golfer) { 7 | return score - golfer.score; 8 | } 9 | } -------------------------------------------------------------------------------- /src/main/java/eam/before/UseResource.java: -------------------------------------------------------------------------------- 1 | package eam.before; 2 | 3 | public class UseResource { 4 | public static void main(String[] args) { 5 | Resource resource = new Resource(); 6 | resource.op1(); 7 | resource.op2(); 8 | // cleanup? 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /src/main/java/interfaces/Employee.java: -------------------------------------------------------------------------------- 1 | package interfaces; 2 | 3 | public interface Employee { 4 | String getFirst(); 5 | 6 | String getLast(); 7 | 8 | void doWork(); 9 | 10 | default String getName() { 11 | return String.format("%s %s", getFirst(), getLast()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/got/House.java: -------------------------------------------------------------------------------- 1 | package got; 2 | 3 | public enum House { 4 | ARRYN, 5 | BARATHEON, 6 | BOLTON, 7 | FREY, 8 | GREYJOY, 9 | LANNISTER, 10 | MARTELL, 11 | MORMONT, 12 | SNOW, 13 | TARLY, 14 | STARK, 15 | TARGARYEN, 16 | TULLY, 17 | TYRELL 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/lambdas/Runner.java: -------------------------------------------------------------------------------- 1 | package lambdas; 2 | 3 | import java.util.concurrent.Callable; 4 | 5 | public class Runner { 6 | public void invoke(Runnable r) { 7 | r.run(); 8 | } 9 | 10 | public T invoke(Callable c) throws Exception { 11 | return c.call(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | name: Java CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Set up JDK 17 13 | uses: actions/setup-java@v1 14 | with: 15 | java-version: 17 16 | - name: Build with Gradle 17 | run: ./gradlew build 18 | -------------------------------------------------------------------------------- /src/main/java/interfaces/MyCompany.java: -------------------------------------------------------------------------------- 1 | package interfaces; 2 | 3 | public class MyCompany implements Company { 4 | 5 | @Override 6 | public String getName() { 7 | return "Yoyodyne Propulsion Systems"; 8 | } 9 | 10 | public static void main(String[] args) { 11 | MyCompany co = new MyCompany(); 12 | System.out.println(co.getName()); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Java template 3 | *.class 4 | .classpath 5 | .project 6 | .settings/ 7 | 8 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 9 | hs_err_pid* 10 | 11 | .gradle/ 12 | build/ 13 | /bin/ 14 | .idea/ 15 | *.iml 16 | *.ipr 17 | 18 | # macOS system files 19 | .DS_Store 20 | 21 | # Claude Code configuration 22 | .claude/ -------------------------------------------------------------------------------- /src/main/java/primes/after/PrimeCalculator.java: -------------------------------------------------------------------------------- 1 | package primes.after; 2 | 3 | import java.util.stream.IntStream; 4 | 5 | public class PrimeCalculator { 6 | public static boolean isPrime(int number) { 7 | int max = (int) (Math.sqrt(number) + 1); 8 | return number > 1 && 9 | IntStream.range(2, max) 10 | .noneMatch(index -> number % index == 0); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/got/Member.java: -------------------------------------------------------------------------------- 1 | package got; 2 | 3 | import java.time.LocalDate; 4 | 5 | public record Member( 6 | Long id, 7 | Title title, 8 | String name, 9 | LocalDate dob, 10 | double salary, 11 | House house 12 | ) implements Comparable { 13 | 14 | @Override 15 | public int compareTo(Member member) { 16 | return id.compareTo(member.id); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/eam/after/UseResource.java: -------------------------------------------------------------------------------- 1 | package eam.after; 2 | 3 | public class UseResource { 4 | public static void main(String[] args) { 5 | // Resource r = new Resource(); 6 | // try { 7 | // r.op1(); 8 | // r.op2(); 9 | // } finally { 10 | // r.close(); 11 | // } 12 | 13 | Resource.use(resource -> { 14 | resource.op1(); 15 | resource.op2(); 16 | }); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/IterateDemo.java: -------------------------------------------------------------------------------- 1 | import java.time.LocalDate; 2 | import java.util.stream.Stream; 3 | 4 | public class IterateDemo { 5 | public static void main(String[] args) { 6 | Stream.iterate(100, n -> n + 2) 7 | .limit(20) 8 | .forEach(System.out::println); 9 | 10 | Stream.iterate(LocalDate.now(), date -> date.plusMonths(1)) 11 | .limit(12) 12 | .forEach(System.out::println); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/primes/before/PrimeCalculator.java: -------------------------------------------------------------------------------- 1 | package primes.before; 2 | 3 | public class PrimeCalculator { 4 | public static boolean isPrime(int number) { 5 | int max = (int) (Math.sqrt(number) + 1); 6 | 7 | boolean prime = true; 8 | for (int index = 2; index <= max; index++) { 9 | if (number % index == 0) { 10 | prime = false; 11 | break; 12 | } 13 | } 14 | return prime; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/eam/before/Resource.java: -------------------------------------------------------------------------------- 1 | package eam.before; 2 | 3 | public class Resource { 4 | public Resource() { 5 | System.out.println("Instance created"); 6 | } 7 | 8 | public void op1() { 9 | System.out.println("op1 called...."); 10 | } 11 | 12 | public void op2() { 13 | System.out.println("op2 called..."); 14 | } 15 | 16 | // @SuppressWarnings("deprecation") 17 | // protected void finalize() { 18 | // System.out.println("do any cleanup here..."); 19 | // } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/primes/after/SumSqrtPrimes.java: -------------------------------------------------------------------------------- 1 | package primes.after; 2 | 3 | import java.util.List; 4 | import java.util.stream.Stream; 5 | 6 | public class SumSqrtPrimes { 7 | public static void main(String[] args) { 8 | 9 | List sqrtOfFirst100Primes = 10 | Stream.iterate(2, e -> e + 1) 11 | .filter(PrimeCalculator::isPrime) 12 | .limit(100) 13 | .map(Math::sqrt) 14 | .toList(); 15 | System.out.println(sqrtOfFirst100Primes); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/primes/before/SumSqrtPrimes.java: -------------------------------------------------------------------------------- 1 | package primes.before; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class SumSqrtPrimes { 7 | public static void main(String[] args) { 8 | List sqrtOfFirst100Primes = new ArrayList<>(); 9 | 10 | int index = 2; 11 | while (sqrtOfFirst100Primes.size() < 100) { 12 | if (PrimeCalculator.isPrime(index)) { 13 | sqrtOfFirst100Primes.add(Math.sqrt(index)); 14 | } 15 | index++; 16 | } 17 | 18 | System.out.println(sqrtOfFirst100Primes); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/interfaces/CompanyEmployeeTest.java: -------------------------------------------------------------------------------- 1 | package interfaces; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | // Create a class called CompanyEmployee that implements both 6 | // the Company and Employee interfaces 7 | // Implement the necessary methods 8 | // Give the class a two-arg constructor that takes first and last name 9 | // Implement the getName method so that the test below passes 10 | public class CompanyEmployeeTest { 11 | 12 | @Test 13 | public void getName() { 14 | // CompanyEmployee emp = new CompanyEmployee("Peter", "Gibbons"); 15 | // assertEquals("Peter Gibbons works for Initech", emp.getName()); 16 | } 17 | } -------------------------------------------------------------------------------- /src/main/java/SummarizingDemo.java: -------------------------------------------------------------------------------- 1 | import java.util.DoubleSummaryStatistics; 2 | import java.util.stream.DoubleStream; 3 | 4 | public class SummarizingDemo { 5 | public static void main(String[] args) { 6 | DoubleSummaryStatistics stats = DoubleStream.generate(Math::random) 7 | .limit(1_000_000) 8 | .summaryStatistics(); 9 | 10 | System.out.println(stats); 11 | System.out.println(stats.getCount()); 12 | System.out.println(stats.getMin()); 13 | System.out.println(stats.getAverage()); 14 | System.out.println(stats.getMax()); 15 | System.out.println(stats.getSum()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/concurrency/AwaitQuiesence.java: -------------------------------------------------------------------------------- 1 | package concurrency; 2 | 3 | import java.util.concurrent.CompletableFuture; 4 | 5 | public class AwaitQuiesence { 6 | private String sleepThenReturnString() { 7 | try { 8 | Thread.sleep(100); 9 | } catch (InterruptedException ignored) { 10 | } 11 | return "42"; 12 | } 13 | 14 | public CompletableFuture supplyThenAccept() { 15 | return CompletableFuture.supplyAsync(this::sleepThenReturnString) 16 | .thenApply(Integer::parseInt) 17 | .thenApply(x -> 2 * x) 18 | .thenAccept(System.out::println); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/optional/Department.java: -------------------------------------------------------------------------------- 1 | package optional; 2 | 3 | import java.util.Optional; 4 | 5 | public class Department { 6 | private final String name; 7 | private Manager manager; 8 | 9 | public Department(String name) { 10 | this.name = name; 11 | } 12 | 13 | public String getName() { 14 | return name; 15 | } 16 | 17 | public Manager getManager() { 18 | return manager; 19 | } 20 | 21 | public void setManager(Manager manager) { 22 | this.manager = manager; 23 | } 24 | 25 | public Optional getOptionalManager() { 26 | return Optional.ofNullable(manager); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/FinderDemo.java: -------------------------------------------------------------------------------- 1 | import java.util.HashSet; 2 | import java.util.List; 3 | import java.util.Optional; 4 | 5 | public class FinderDemo { 6 | public static void main(String[] args) { 7 | List strings = List.of("this", "is", "a", "list", "of", "strings"); 8 | HashSet stringSet = new HashSet<>(strings); 9 | 10 | Optional first = strings.stream() 11 | .filter(s -> { 12 | System.out.println(Thread.currentThread().getName() + " with " + s); 13 | return s.length() == 2; 14 | }) 15 | .findFirst(); 16 | System.out.println(first); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/streams/Customer.java: -------------------------------------------------------------------------------- 1 | package streams; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class Customer { 7 | private final String name; 8 | private final List orders = new ArrayList<>(); 9 | 10 | public Customer(String name) { 11 | this.name = name; 12 | } 13 | 14 | public String getName() { 15 | return name; 16 | } 17 | 18 | public Customer addOrder(Order order) { 19 | orders.add(order); 20 | return this; 21 | } 22 | 23 | public List getOrders() { 24 | return orders; 25 | } 26 | 27 | @Override 28 | public String toString() { 29 | return name; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/interfaces/CompanyEmployee.java: -------------------------------------------------------------------------------- 1 | package interfaces; 2 | 3 | public class CompanyEmployee implements Company, Employee { 4 | private final String first; 5 | private final String last; 6 | 7 | public CompanyEmployee(String first, String last) { 8 | this.first = first; 9 | this.last = last; 10 | } 11 | 12 | @Override 13 | public String getName() { 14 | return String.format("%s works for %s", 15 | Employee.super.getName(), Company.super.getName()); 16 | } 17 | 18 | @Override 19 | public void doWork() { 20 | System.out.println("Working..."); 21 | } 22 | 23 | @Override 24 | public String getFirst() { 25 | return first; 26 | } 27 | 28 | @Override 29 | public String getLast() { 30 | return last; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/streams/Ordering.java: -------------------------------------------------------------------------------- 1 | package streams; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class Ordering { 7 | public static void main(String[] args) { 8 | List ints = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 9 | 10 | // Collect doubles into a generated list 11 | List doubles = ints.stream() 12 | .map(n -> n * 2) 13 | .parallel() 14 | .toList(); 15 | System.out.println(doubles); 16 | 17 | // Add doubles to the list using add inside foreach 18 | List doublesList = new ArrayList<>(); 19 | ints.stream() 20 | .map(n -> n * 2) 21 | .parallel() 22 | .forEach(doublesList::add); 23 | System.out.println(doublesList); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/refactoring/before/LoopsSortsAndIfs.java: -------------------------------------------------------------------------------- 1 | package refactoring.before; 2 | 3 | import java.util.*; 4 | 5 | public class LoopsSortsAndIfs { 6 | public static void main(String[] args) { 7 | String[] strings = "this is an array of strings".split(" "); 8 | 9 | var evenLengths = new ArrayList(); 10 | for (var s : strings) { 11 | if (s.length() % 2 == 0) { 12 | evenLengths.add(s.toUpperCase()); 13 | } 14 | } 15 | 16 | Collections.sort(evenLengths, new Comparator() { 17 | @Override 18 | public int compare(String s1, String s2) { 19 | return s1.length() - s2.length(); 20 | } 21 | }); 22 | 23 | for (var s : evenLengths) { 24 | System.out.println(s); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/optional/ProductDAO.java: -------------------------------------------------------------------------------- 1 | package optional; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.Optional; 6 | 7 | public class ProductDAO { 8 | private static final Map products = new HashMap<>(); 9 | 10 | static { 11 | products.put(1, new Product(1, "Football", 12.99)); 12 | products.put(2, new Product(2, "Basketball", 15.99)); 13 | products.put(3, new Product(3, "Baseball", 5.99)); 14 | } 15 | 16 | public Optional findById(Integer id) { 17 | return Optional.ofNullable(products.get(id)); 18 | } 19 | 20 | 21 | public Product getProductById(Integer id) { 22 | Optional optional = findById(id); 23 | // Create default product only if no product with that id 24 | return optional.orElseGet(() -> new Product(999, "No name", 0.00)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/streams/SumEvens.java: -------------------------------------------------------------------------------- 1 | package streams; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.List; 6 | import java.util.function.IntPredicate; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | 10 | public class SumEvens { 11 | private static final IntPredicate EVENS = n -> n % 2 == 0; 12 | private static final IntPredicate ODDS = n -> n % 2 != 0; 13 | 14 | @Test 15 | public void addEvenElements() { 16 | List integers = List.of(3, 1, 4, 1, 5, 9, 2, 6, 5); 17 | int sum = 0; 18 | for (int n : integers) { 19 | if (n % 2 == 0) { 20 | sum += n; 21 | } 22 | } 23 | assertEquals(12, sum); 24 | } 25 | 26 | @Test 27 | public void addEvenElementsUsingStreams() { 28 | } 29 | 30 | @Test 31 | public void addOddElementsUsingStreams() { 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/lambdas/BinaryOperatorTest.java: -------------------------------------------------------------------------------- 1 | package lambdas; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.List; 6 | import java.util.Optional; 7 | import java.util.function.BinaryOperator; 8 | 9 | import static org.junit.jupiter.api.Assertions.assertEquals; 10 | 11 | public class BinaryOperatorTest { 12 | @Test 13 | public void concatAsBinaryOperator() { 14 | BinaryOperator concat = String::concat; 15 | // concat = (s, str) -> s.concat(str); 16 | 17 | List strings = List.of("this", "is", "a", "list", "of", "strings"); 18 | Optional str = strings.stream() 19 | //.filter(s -> false) 20 | .reduce(concat); 21 | System.out.println(str.orElse("")); 22 | 23 | // Assert that the reduction produces expected result 24 | assertEquals("thisisalistofstrings", str.orElse("")); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/RandomDemo.java: -------------------------------------------------------------------------------- 1 | import java.util.OptionalInt; 2 | import java.util.Random; 3 | import java.util.stream.IntStream; 4 | 5 | public class RandomDemo { 6 | public static void main(String[] args) { 7 | Random r = new Random(); 8 | int sum = IntStream.generate(() -> r.nextInt(10)) 9 | .limit(10) 10 | .map(n -> { 11 | System.out.println("n = " + n); 12 | return n; 13 | }) 14 | .filter(n -> n % 2 == 0) // only even numbers 15 | .peek(System.out::println) 16 | .map(n -> n * 2) 17 | .sum(); 18 | System.out.println(sum); 19 | 20 | System.out.println("Filtering integers:"); 21 | OptionalInt first = IntStream.generate(() -> r.nextInt(10)) 22 | .peek(System.out::println) 23 | .filter(n -> n > 7) 24 | .findFirst(); 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/dao/Employee.java: -------------------------------------------------------------------------------- 1 | package dao; 2 | 3 | import java.util.Optional; 4 | 5 | public class Employee { 6 | private int id; 7 | private String first; 8 | private String middleInitial; 9 | private String last; 10 | 11 | public int getId() { 12 | return id; 13 | } 14 | 15 | public void setId(int id) { 16 | this.id = id; 17 | } 18 | 19 | public String getFirst() { 20 | return first; 21 | } 22 | 23 | public void setFirst(String first) { 24 | this.first = first; 25 | } 26 | 27 | public Optional getMiddleInitial() { 28 | return Optional.ofNullable(middleInitial); 29 | } 30 | 31 | public void setMiddleInitial(String middleInitial) { 32 | this.middleInitial = middleInitial; 33 | } 34 | 35 | public String getLast() { 36 | return last; 37 | } 38 | 39 | public void setLast(String last) { 40 | this.last = last; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/refactoring/after/LoopsSortsAndIfs.java: -------------------------------------------------------------------------------- 1 | package refactoring.after; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.Comparator; 6 | import java.util.List; 7 | 8 | public class LoopsSortsAndIfs { 9 | public static void main(String[] args) { 10 | String[] strings = "this is an array of strings".split(" "); 11 | 12 | List evenLengths = new ArrayList<>(); 13 | for (String s : strings) { 14 | if (s.length() % 2 == 0) { 15 | evenLengths.add(s.toUpperCase()); 16 | } 17 | } 18 | 19 | Collections.sort(evenLengths, new Comparator() { 20 | @Override 21 | public int compare(String s1, String s2) { 22 | return s1.length() - s2.length(); 23 | } 24 | }); 25 | 26 | for (String s : evenLengths) { 27 | System.out.println(s); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/UseProducts.java: -------------------------------------------------------------------------------- 1 | import java.util.List; 2 | import java.util.logging.Logger; 3 | 4 | public class UseProducts { 5 | private static final Logger logger = Logger.getLogger(UseProducts.class.getName()); 6 | 7 | public static void main(String[] args) { 8 | List products = List.of(new Product("football", 10), 9 | new Product("basketball", 12), new Product("baseball", 5)); 10 | 11 | List names = products.stream() // Stream 12 | .map(Product::name) // Stream 13 | // .peek(System.out::println) 14 | .toList(); 15 | 16 | // complex string arg always gets formed! 17 | // Arguments to methods always get evaluated 18 | logger.info("Product not found; available products are: " + names); 19 | 20 | // supplier.get() only invoked if loggable 21 | logger.info(() -> "Product not found; available products are: " + names); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/lambdas/RunnableDemo.java: -------------------------------------------------------------------------------- 1 | package lambdas; 2 | 3 | import java.util.concurrent.ExecutorService; 4 | import java.util.concurrent.Executors; 5 | 6 | public class RunnableDemo { 7 | public static void main(String[] args) { 8 | ExecutorService executorService = Executors.newFixedThreadPool(4); 9 | 10 | // Java 7 or earlier 11 | executorService.submit(new Runnable() { 12 | @Override 13 | public void run() { 14 | System.out.println("Inside an anonymous inner class"); 15 | } 16 | }); 17 | 18 | executorService.submit(() -> System.out.println("Inside expression lambda")); 19 | 20 | executorService.submit(() -> { 21 | System.out.println(Thread.currentThread().getName()); 22 | System.out.println("Inside block lambda"); 23 | }); 24 | 25 | Runnable runnable = () -> System.out.println("Assigned to a variable"); 26 | executorService.submit(runnable); 27 | 28 | executorService.shutdown(); 29 | 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/main/java/lambdas/MapDemo.java: -------------------------------------------------------------------------------- 1 | package lambdas; 2 | 3 | import java.util.Comparator; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | public class MapDemo { 8 | public static void main(String[] args) { 9 | var map = new HashMap(); 10 | map.put("c", 2); 11 | map.put("a", 1); 12 | map.put("d", 3); 13 | map.put("b", 2); 14 | 15 | map.computeIfAbsent("e", String::length); 16 | map.computeIfAbsent("b", String::length); 17 | map.computeIfPresent("b", (k, v) -> { 18 | System.out.printf("k = %s,v = %d%n", k, v); 19 | return v * 2; 20 | }); 21 | map.forEach((k,v) -> System.out.println(k + " maps to " + v)); 22 | 23 | map.entrySet().stream() 24 | .sorted(Map.Entry.comparingByKey(Comparator.reverseOrder())) 25 | .forEach(System.out::println); 26 | 27 | map.entrySet().stream() 28 | .sorted(Map.Entry.comparingByValue()) 29 | .forEach(System.out::println); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/streams/SumBigDecimalsTest.java: -------------------------------------------------------------------------------- 1 | package streams; 2 | 3 | import org.junit.jupiter.api.Disabled; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.math.BigDecimal; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | 10 | public class SumBigDecimalsTest { 11 | private final SumBigDecimals summer = new SumBigDecimals(); 12 | 13 | @Test 14 | public void sumFirstN_usingReduce() { 15 | BigDecimal answer = summer.sumFirstN_usingReduce(10); 16 | assertEquals(new BigDecimal("55"), answer); 17 | } 18 | 19 | @Test @Disabled("disable until demo") 20 | public void sumFirstNDoubledValues() { 21 | BigDecimal answer = summer.sumDoubles(10); 22 | 23 | // Used to show how reduce method without identity can give error 24 | assertEquals(new BigDecimal("110"), answer); 25 | } 26 | 27 | @Test 28 | public void sumFirstNDoubledValuesInitialized() { 29 | BigDecimal answer = summer.sumDoublesInitialized(10); 30 | assertEquals(new BigDecimal("110"), answer); 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /src/test/java/optional/ProductDAOTest.java: -------------------------------------------------------------------------------- 1 | package optional; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.Optional; 6 | import java.util.stream.IntStream; 7 | 8 | import static org.junit.jupiter.api.Assertions.*; 9 | 10 | public class ProductDAOTest { 11 | private final ProductDAO dao = new ProductDAO(); 12 | 13 | @Test 14 | public void findById_exists() { 15 | IntStream.rangeClosed(1, 3) 16 | .forEach(id -> assertTrue(dao.findById(id).isPresent())); 17 | } 18 | 19 | @Test 20 | public void findById_doesNotExist() { 21 | Optional optionalProduct = dao.findById(999); 22 | assertTrue(optionalProduct.isEmpty()); 23 | } 24 | 25 | @Test 26 | public void getProductById_exists() { 27 | Product product = dao.getProductById(1); 28 | assertEquals(1, product.getId().intValue()); 29 | } 30 | 31 | @Test 32 | public void getProductById_doesNotExist() { 33 | Product product = dao.getProductById(999); 34 | assertEquals(999, product.getId().intValue()); 35 | } 36 | } -------------------------------------------------------------------------------- /src/main/java/lambdas/Person.java: -------------------------------------------------------------------------------- 1 | package lambdas; 2 | 3 | import java.util.Objects; 4 | 5 | public class Person { 6 | private String name; 7 | 8 | public Person() {} 9 | 10 | public Person(String name) { 11 | this.name = name; 12 | } 13 | 14 | public Person(Person other) { 15 | this.name = other.name; 16 | } 17 | 18 | public Person(String... names) { 19 | this.name = String.join(" ", names); 20 | } 21 | 22 | public String getName() { 23 | return name; 24 | } 25 | 26 | public void setName(String name) { 27 | this.name = name; 28 | } 29 | 30 | @Override 31 | public boolean equals(Object o) { 32 | if (this == o) return true; 33 | if (!(o instanceof Person person)) return false; 34 | 35 | return Objects.equals(name, person.name); 36 | } 37 | 38 | @Override 39 | public int hashCode() { 40 | return name != null ? name.hashCode() : 0; 41 | } 42 | 43 | @Override 44 | public String toString() { 45 | return String.format("Person(%s)", name); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Ken Kousen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/java/datetime/AntarcticaTimeZones.java: -------------------------------------------------------------------------------- 1 | package datetime; 2 | 3 | import java.time.LocalDateTime; 4 | import java.time.ZoneId; 5 | import java.time.ZonedDateTime; 6 | import java.util.List; 7 | 8 | import static java.util.Comparator.comparingInt; 9 | 10 | public class AntarcticaTimeZones { 11 | public static void main(String[] args) { 12 | LocalDateTime now = LocalDateTime.now(); 13 | List antarcticZones = 14 | ZoneId.getAvailableZoneIds().stream() // Stream 15 | .filter(regionId -> regionId.contains("Antarctica")) 16 | .map(ZoneId::of) // Stream 17 | .map(now::atZone) // Stream 18 | .sorted(comparingInt( 19 | zoneId -> zoneId.getOffset().getTotalSeconds())) 20 | .toList(); 21 | 22 | antarcticZones.forEach(zdt -> 23 | System.out.printf("UTC%6s: %25s %7s%n", zdt.getOffset(), zdt.getZone(), 24 | zdt.getZone().getRules().isDaylightSavings(zdt.toInstant()))); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/eam/after/Resource.java: -------------------------------------------------------------------------------- 1 | package eam.after; 2 | 3 | import java.util.function.Consumer; 4 | import java.util.function.Function; 5 | 6 | public class Resource { 7 | private Resource() { 8 | System.out.println("Instance created"); 9 | } 10 | 11 | public void op1() { 12 | System.out.println("op1 called...."); 13 | } 14 | 15 | public void op2() { 16 | System.out.println("op2 called..."); 17 | } 18 | 19 | private void close() { 20 | System.out.println("do any cleanup here..."); 21 | } 22 | 23 | public static void use(Consumer consume) { 24 | Resource resource = new Resource(); 25 | try { 26 | consume.accept(resource); 27 | } finally { 28 | resource.close(); 29 | } 30 | } 31 | 32 | public static R useWithReturn(Function function) { 33 | R result = null; 34 | Resource resource = new Resource(); 35 | try { 36 | result = function.apply(resource); 37 | } finally { 38 | resource.close(); 39 | } 40 | return result; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/got/MemberDAO.java: -------------------------------------------------------------------------------- 1 | package got; 2 | 3 | import java.util.*; 4 | 5 | public interface MemberDAO { 6 | Optional findById(Long id); 7 | 8 | Optional findByName(String name); 9 | 10 | List findAllByHouse(House house); 11 | 12 | Collection getAll(); 13 | 14 | List startWithSandSortAlphabetically(); 15 | 16 | List lannisters_alphabeticallyByName(); 17 | 18 | List salaryLessThanAndSortByHouse(double max); 19 | 20 | List sortByHouseNameThenSortByNameDesc(); 21 | 22 | List houseByDob(House house); 23 | 24 | List kingsByNameDesc(); 25 | 26 | double averageSalary(); 27 | 28 | List namesSorted(House house); 29 | 30 | boolean salariesGreaterThan(double max); 31 | 32 | boolean anyMembers(House house); 33 | 34 | long howMany(House house); 35 | 36 | String houseMemberNames(House house); 37 | 38 | Optional highestSalary(); 39 | 40 | Map> royaltyPartition(); 41 | 42 | Map> membersByHouse(); 43 | 44 | Map numberOfMembersByHouse(); 45 | 46 | Map houseStats(); 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/OptionalDemo.java: -------------------------------------------------------------------------------- 1 | import java.util.List; 2 | import java.util.Optional; 3 | import java.util.stream.Stream; 4 | 5 | public class OptionalDemo { 6 | public static void main(String[] args) { 7 | Optional first = Stream.of(3, 1, 4, 1, 5, 9) 8 | .filter(n -> n > 10) 9 | .findFirst(); 10 | 11 | System.out.println(first); 12 | 13 | // System.out.println(first.isPresent() ? (int) first.get() : 0); 14 | 15 | int defaultValue = 0; 16 | System.out.println(first.orElse(defaultValue)); 17 | System.out.println(first.orElseGet(() -> defaultValue)); 18 | first.ifPresent(System.out::println); 19 | 20 | 21 | List strings = List.of("this", "is", "a", "list", "of", "strings"); 22 | Optional s = strings.stream() 23 | .findFirst(); 24 | 25 | System.out.println(s.orElse("No string found; legal values are: " + strings)); 26 | System.out.println(s.orElseGet(() -> "No string found; legal values are: " + strings)); 27 | System.out.println(s.orElseThrow(IllegalArgumentException::new)); // default constructor 28 | System.out.println(s.orElseThrow(() -> new IllegalArgumentException("Not available"))); 29 | s.ifPresent(System.out::println); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/concurrency/AwaitQuiesenceTest.java: -------------------------------------------------------------------------------- 1 | package concurrency; 2 | 3 | import org.junit.jupiter.api.Disabled; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.concurrent.CompletableFuture; 7 | import java.util.concurrent.ExecutionException; 8 | import java.util.concurrent.ForkJoinPool; 9 | import java.util.concurrent.TimeUnit; 10 | 11 | import static org.junit.jupiter.api.Assertions.*; 12 | 13 | public class AwaitQuiesenceTest { 14 | private final AwaitQuiesence aq = new AwaitQuiesence(); 15 | 16 | @Test 17 | public void get() throws InterruptedException, ExecutionException { 18 | CompletableFuture cf = aq.supplyThenAccept(); 19 | cf.get(); 20 | assertTrue(cf.isDone()); 21 | } 22 | 23 | @Test 24 | public void join() { 25 | CompletableFuture cf = aq.supplyThenAccept(); 26 | cf.join(); 27 | assertTrue(cf.isDone()); 28 | } 29 | 30 | @Test @Disabled("Causing issues with Github Action") 31 | public void awaitQuiesence() { 32 | CompletableFuture cf = aq.supplyThenAccept(); 33 | assertFalse(cf.isDone()); 34 | 35 | boolean result = ForkJoinPool.commonPool() 36 | .awaitQuiescence(1, TimeUnit.SECONDS); 37 | assertTrue(result); 38 | assertTrue(cf.isDone()); 39 | } 40 | } -------------------------------------------------------------------------------- /src/main/java/optional/Company.java: -------------------------------------------------------------------------------- 1 | package optional; 2 | 3 | import java.util.HashMap; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.Optional; 7 | 8 | public class Company { 9 | private final String name; 10 | 11 | private final Map departmentMap = 12 | new HashMap<>(); 13 | 14 | public Company(String name) { 15 | this.name = name; 16 | 17 | Department it = new Department("IT"); 18 | Department sales = new Department("Sales"); 19 | Department finance = new Department("Finance"); 20 | Department accounting = new Department("Accounting"); 21 | 22 | Manager mrSlate = new Manager("Mr Slate"); 23 | Manager mrBurns = new Manager("Mr Burns"); 24 | Manager janeway = new Manager("Admiral Janeway"); 25 | 26 | sales.setManager(mrBurns); 27 | finance.setManager(mrSlate); 28 | accounting.setManager(janeway); 29 | 30 | List.of(it, sales, finance, accounting).forEach( 31 | dept -> departmentMap.put(dept.getName(), dept) 32 | ); 33 | } 34 | 35 | public Optional getDepartment(String name) { 36 | return Optional.ofNullable(departmentMap.get(name)); 37 | } 38 | 39 | public String getName() { 40 | return name; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/streams/BoxedStreams.java: -------------------------------------------------------------------------------- 1 | package streams; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | import java.util.stream.Collectors; 7 | import java.util.stream.IntStream; 8 | 9 | public class BoxedStreams { 10 | public static void main(String[] args) { 11 | 12 | // IntStream.rangeClosed(1, 10) 13 | // .collect(Collectors.toList()); 14 | 15 | // Use mapToObj as an alternative to boxed 16 | @SuppressWarnings("SimplifyStreamApiCallChains") 17 | List list = IntStream.rangeClosed(1, 10) 18 | .mapToObj(Integer::valueOf) 19 | .toList(); 20 | System.out.println(list); 21 | 22 | List ints = IntStream.of(3, 1, 4, 1, 5, 9) 23 | // .collect(Collectors.toList()); 24 | .collect(ArrayList::new, ArrayList::add, ArrayList::addAll); 25 | System.out.println(ints); 26 | 27 | List listOfInt = IntStream.of(3, 1, 4, 1, 5, 9) 28 | .boxed() 29 | .toList(); 30 | System.out.println(listOfInt); 31 | 32 | // IntStream.of(3, 1, 4, 1, 5, 9) 33 | // .collect(Collectors.toCollection(ArrayList::new)); 34 | 35 | int[] intArray = IntStream.of(3, 1, 4, 1, 5, 9).toArray(); 36 | System.out.println(Arrays.toString(intArray)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/lazy/LazyStreams.java: -------------------------------------------------------------------------------- 1 | package lazy; 2 | 3 | import java.util.logging.Logger; 4 | import java.util.stream.IntStream; 5 | 6 | public class LazyStreams { 7 | private static final Logger logger = Logger.getLogger(LazyStreams.class.getName()); 8 | 9 | public static int multByTwo(int n) { 10 | System.out.printf("Inside multByTwo with arg %d on thread %s%n", 11 | n, Thread.currentThread().getName()); 12 | return n * 2; 13 | } 14 | 15 | public static boolean modByThree(int n) { 16 | System.out.printf("Inside modByThree with arg %d on thread %s%n", n, 17 | Thread.currentThread().getName()); 18 | return n % 3 == 0; 19 | } 20 | 21 | public static void main(String[] args) { 22 | // multiply numbers between 100 and 200 by 2, then find first n divisible by 3 23 | int firstEvenDoubleDivBy3 = IntStream.rangeClosed(100, 200) 24 | .map(n -> n * 2) 25 | .filter(n -> n % 3 == 0) 26 | .findFirst().orElse(0); 27 | System.out.println(firstEvenDoubleDivBy3); 28 | 29 | 30 | // Demonstrate laziness using print statements 31 | firstEvenDoubleDivBy3 = IntStream.rangeClosed(100, 2_000_000) 32 | // .parallel() 33 | .filter(LazyStreams::modByThree) 34 | .map(LazyStreams::multByTwo) 35 | .findFirst().orElse(0); 36 | System.out.printf("First even divisible by 3 is %d%n", firstEvenDoubleDivBy3); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/concurrency/CompletableFutureDemosTest.java: -------------------------------------------------------------------------------- 1 | package concurrency; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.concurrent.ExecutionException; 6 | 7 | import static org.junit.jupiter.api.Assertions.*; 8 | 9 | public class CompletableFutureDemosTest { 10 | private final CompletableFutureDemos demo = new CompletableFutureDemos(); 11 | 12 | @Test 13 | public void testRemote() throws Exception { 14 | Product product = demo.getProduct(1).get(); 15 | assertEquals(1, product.id()); 16 | } 17 | 18 | @Test 19 | public void testLocal() throws Exception { 20 | demo.getProduct(1).get(); 21 | Product product = demo.getProduct(1).get(); 22 | assertEquals(1, product.id()); 23 | } 24 | 25 | @Test 26 | public void testException() throws Exception { 27 | assertThrows(ExecutionException.class, () -> { 28 | demo.getProduct(666).get(); 29 | }); 30 | } 31 | 32 | @Test 33 | public void testExceptionWithCause() throws Exception { 34 | try { 35 | demo.getProduct(666).get(); 36 | fail("Houston, we have a problem..."); 37 | } catch (ExecutionException e) { 38 | assertEquals(ExecutionException.class, e.getClass()); 39 | assertEquals(RuntimeException.class, e.getCause().getClass()); 40 | } 41 | } 42 | 43 | @Test 44 | public void getProductAsync() throws Exception { 45 | Product product = demo.getProductAsync(1).get(); 46 | assertEquals(1, product.id()); 47 | } 48 | } -------------------------------------------------------------------------------- /src/main/java/streams/SumBigDecimals.java: -------------------------------------------------------------------------------- 1 | package streams; 2 | 3 | import java.math.BigDecimal; 4 | import java.util.stream.Stream; 5 | 6 | public class SumBigDecimals { 7 | 8 | public BigDecimal sumFirstN_asDoubles(int n) { 9 | double total = Stream.iterate(BigDecimal.ONE, bd -> bd.add(BigDecimal.ONE)) 10 | .limit(n) 11 | .mapToDouble(BigDecimal::doubleValue) 12 | .sum(); 13 | return BigDecimal.valueOf(total); 14 | } 15 | 16 | public BigDecimal sumFirstN_usingReduce(int n) { 17 | return Stream.iterate(BigDecimal.ONE, bd -> bd.add(BigDecimal.ONE)) 18 | .limit(n) 19 | .reduce(BigDecimal::add).orElse(BigDecimal.ZERO); 20 | } 21 | 22 | // Off by one error, because 1 is never doubled 23 | public BigDecimal sumDoubles(int n) { 24 | BigDecimal two = BigDecimal.valueOf(2); 25 | return Stream.iterate(BigDecimal.ONE, bd -> bd.add(BigDecimal.ONE)) 26 | .limit(n) 27 | .reduce((total, e) -> { 28 | System.out.println("total = " + total + ", e = " + e); 29 | return total.add(e.multiply(two)); 30 | }).orElse(BigDecimal.ZERO); 31 | } 32 | 33 | public BigDecimal sumDoublesInitialized(int n) { 34 | BigDecimal two = new BigDecimal("2"); 35 | return Stream.iterate(BigDecimal.ONE, bd -> bd.add(BigDecimal.ONE)) 36 | .limit(n) 37 | .reduce(BigDecimal.ZERO, (total, e) -> { 38 | System.out.println("total = " + total + ", e = " + e); 39 | return total.add(e.multiply(two)); 40 | }); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/optional/Product.java: -------------------------------------------------------------------------------- 1 | package optional; 2 | 3 | import java.util.Objects; 4 | 5 | public class Product { 6 | private Integer id; 7 | private String name; 8 | private double price; 9 | 10 | public Product(Integer id, String name, double price) { 11 | System.out.println("Inside Product constructor with id=" + id); 12 | this.id = id; 13 | this.name = name; 14 | this.price = price; 15 | } 16 | 17 | public Integer getId() { 18 | return id; 19 | } 20 | 21 | public void setId(Integer id) { 22 | this.id = id; 23 | } 24 | 25 | public String getName() { 26 | return name; 27 | } 28 | 29 | public void setName(String name) { 30 | this.name = name; 31 | } 32 | 33 | public double getPrice() { 34 | return price; 35 | } 36 | 37 | public void setPrice(double price) { 38 | this.price = price; 39 | } 40 | 41 | @Override 42 | public String toString() { 43 | return "Product{" + 44 | "id=" + id + 45 | ", name='" + name + '\'' + 46 | ", price=" + price + 47 | '}'; 48 | } 49 | 50 | @Override 51 | public boolean equals(Object o) { 52 | if (this == o) return true; 53 | if (o == null || getClass() != o.getClass()) return false; 54 | Product product = (Product) o; 55 | return Double.compare(product.price, price) == 0 && 56 | Objects.equals(id, product.id) && 57 | Objects.equals(name, product.name); 58 | } 59 | 60 | @Override 61 | public int hashCode() { 62 | return Objects.hash(id, name, price); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/CollectorsDemo.java: -------------------------------------------------------------------------------- 1 | import java.util.List; 2 | import java.util.Map; 3 | import java.util.Set; 4 | import java.util.concurrent.ConcurrentSkipListSet; 5 | import java.util.function.Function; 6 | import java.util.stream.Collectors; 7 | import java.util.stream.Stream; 8 | 9 | public class CollectorsDemo { 10 | public static void main(String[] args) { 11 | var nums = Stream.of("this", "is", "a", "collection", "of", "strings") 12 | .parallel() 13 | .map(String::length) 14 | // .map(n -> { 15 | // System.out.println(n); 16 | // return n; 17 | // }) 18 | // equivalent to: 19 | .peek(n -> { 20 | System.out.println("On " + Thread.currentThread().getName() 21 | + ", the value of n before the filter is " + n); 22 | }) 23 | .filter(n -> n % 2 == 0) 24 | .peek(n -> System.out.println("On " + Thread.currentThread().getName() 25 | + ", the value of n after the filter is " + n)) 26 | .toList(); 27 | System.out.println(nums); 28 | 29 | var set = Stream.of("this is a is a collection of strings".split(" ")) 30 | .collect(Collectors.toSet()); 31 | System.out.println(set); 32 | System.out.println(set.getClass().getName()); 33 | 34 | var collection = 35 | Stream.of("this is a is a collection of strings".split(" ")) 36 | .collect(Collectors.toCollection(ConcurrentSkipListSet::new)); 37 | 38 | var stringMap = 39 | Stream.of("this is a collection of strings".split(" ")) 40 | .collect(Collectors.toMap(Function.identity(), String::length)); 41 | System.out.println(stringMap); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/optional/CompanyTest.java: -------------------------------------------------------------------------------- 1 | package optional; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.Optional; 6 | 7 | import static org.junit.jupiter.api.Assertions.*; 8 | 9 | class CompanyTest { 10 | private final Company company = new Company("MyCo"); 11 | 12 | @Test 13 | void getDepartmentWithManager() { 14 | Optional dept = company.getDepartment("Accounting"); 15 | assertTrue(dept.isPresent()); 16 | Department department = dept.orElseThrow(); 17 | System.out.println(department.getName()); 18 | System.out.println(department.getManager()); 19 | System.out.println(department.getOptionalManager()); 20 | } 21 | 22 | @Test 23 | void getDepartmentWithoutManager() { 24 | Optional dept = company.getDepartment("IT"); 25 | assertTrue(dept.isPresent()); 26 | Department department = dept.orElseThrow(); 27 | System.out.println(department.getName()); 28 | System.out.println(department.getManager()); 29 | System.out.println(department.getOptionalManager()); 30 | } 31 | 32 | @Test 33 | void getOptionalDepartment() { 34 | Optional optionalDept = company.getDepartment("Whatever"); 35 | System.out.println(optionalDept.map(Department::getManager)); 36 | System.out.println(optionalDept.map(Department::getOptionalManager)); 37 | System.out.println(optionalDept.flatMap(Department::getOptionalManager)); 38 | } 39 | 40 | @Test 41 | void getOptionalDepartmentWithManager() { 42 | Optional optionalDept = company.getDepartment("Finance"); 43 | System.out.println(optionalDept.map(Department::getManager)); 44 | System.out.println(optionalDept.map(Department::getOptionalManager)); 45 | System.out.println(optionalDept.flatMap(Department::getOptionalManager)); 46 | } 47 | } -------------------------------------------------------------------------------- /src/main/java/streams/FlatMapDemo.java: -------------------------------------------------------------------------------- 1 | package streams; 2 | 3 | import java.util.List; 4 | import java.util.stream.Stream; 5 | 6 | public class FlatMapDemo { 7 | public static void main(String[] args) { 8 | 9 | Customer sheridan = new Customer("Sheridan"); 10 | Customer ivanova = new Customer("Ivanova"); 11 | Customer garibaldi = new Customer("Garibaldi"); 12 | 13 | sheridan.addOrder(new Order(1)) 14 | .addOrder(new Order(2)) 15 | .addOrder(new Order(3)); 16 | ivanova.addOrder(new Order(4)) 17 | .addOrder(new Order(5)); 18 | 19 | List customers = List.of(sheridan, ivanova, garibaldi); 20 | 21 | // map for 1-1 customer to name --> Stream 22 | customers.stream() 23 | .map(Customer::getName) // function 24 | .forEach(System.out::println); 25 | 26 | // map 1-many customer to orders --> Stream> 27 | customers.stream() 28 | .map(Customer::getOrders) // function> 29 | .forEach(System.out::println); 30 | 31 | // map 1-many customer to orders.stream() --> Stream> 32 | customers.stream() 33 | .map(customer -> customer.getOrders().stream()) // function> 34 | .forEach(System.out::println); 35 | 36 | // stream() on an empty collection is already an empty Stream 37 | customers.stream() 38 | .flatMap(customer -> customer.getOrders().stream()) // function> 39 | .forEach(System.out::println); 40 | 41 | // flatMap 1-many customer to orders.stream() --> Stream 42 | // Note: extra detail included just for illustration; 43 | // stream() on an empty collection already returns an empty stream 44 | customers.stream() 45 | .flatMap(customer -> 46 | customer.getOrders().size() == 0 ? Stream.empty() : 47 | customer.getOrders().stream()) 48 | .forEach(System.out::println); 49 | 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/streams/StringExercises.java: -------------------------------------------------------------------------------- 1 | package streams; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Comparator; 7 | import java.util.List; 8 | import java.util.function.Function; 9 | 10 | public class StringExercises { 11 | private final List strings = List.of("this", "is", "a", 12 | "list", "of", "strings"); 13 | 14 | @Test 15 | public void stringLengthSort_InnerClass() { // Java 5, 6, 7 16 | var mutableStrings = new ArrayList<>(strings); 17 | mutableStrings.sort(new Comparator() { 18 | @Override 19 | public int compare(String s1, String s2) { 20 | return s1.length() - s2.length(); 21 | } 22 | }); 23 | System.out.println(mutableStrings); 24 | } 25 | 26 | @Test 27 | public void stringLengthSort_lambda() { 28 | // Use lambda for the Comparator (reverse sort) 29 | 30 | // Use the "sorted" method on Stream 31 | } 32 | 33 | private int compareStrings(String s1, String s2) { 34 | return s1.length() - s2.length(); 35 | } 36 | 37 | @Test // Use a lambda that calls 'compareStrings' directly 38 | public void stringLengthSort_methodCall() { 39 | 40 | } 41 | 42 | @Test // Use a method ref to 'compareStrings' 43 | public void stringLengthSort_methodRef() { 44 | 45 | } 46 | 47 | @Test // Use Comparator.comparingInt 48 | public void stringLengthSort_comparingInt() { 49 | 50 | } 51 | 52 | @Test 53 | public void demoCollectors() { 54 | // Get only strings of even length 55 | // Add them to a LinkedList 56 | 57 | // Add the strings to a map of string to length 58 | 59 | // Filter out nulls, then print even-length strings 60 | 61 | // Function composition 62 | 63 | // Combine the two predicates and use the result to print non-null, even-length strings 64 | 65 | // f: A -> B, g: B -> C, (g.f)(x) = g(f(x)), A -> C 66 | } 67 | 68 | // generated by GitHub Copilot 69 | private Function compose(Function f, Function g) { 70 | return x -> g.apply(f.apply(x)); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/sorting/SortStrings.java: -------------------------------------------------------------------------------- 1 | package sorting; 2 | 3 | import java.util.Arrays; 4 | import java.util.Collections; 5 | import java.util.Comparator; 6 | import java.util.List; 7 | 8 | public class SortStrings { 9 | public static void main(String[] args) { 10 | List strings = Arrays.asList("this", "is", "a", "list", "of", "strings"); 11 | 12 | System.out.println(strings); 13 | 14 | System.out.println("Natural sort:"); 15 | Collections.sort(strings); 16 | System.out.println(strings); 17 | 18 | System.out.println("Sort by length using a Comparator impl anon inner class:"); 19 | Collections.sort(strings, new Comparator() { 20 | @Override 21 | public int compare(String s1, String s2) { 22 | return s1.length() - s2.length(); 23 | } 24 | }); 25 | System.out.println(strings); 26 | 27 | System.out.println("Reverse sort by length with a Comparator impl lambda expression:"); 28 | Collections.sort(strings, (s1, s2) -> s2.length() - s1.length()); 29 | System.out.println(strings); 30 | 31 | // Stream has a sorted() method that is not destructive 32 | System.out.println("Natural sort using Stream.sorted()"); 33 | List sorted = strings.stream() 34 | .sorted() 35 | .toList(); 36 | System.out.println(sorted); 37 | 38 | System.out.println("Reverse length sort using Stream.sorted(Comparator)"); 39 | sorted = strings.stream() 40 | .sorted((s1, s2) -> s2.length() - s1.length()) 41 | .toList(); 42 | System.out.println(sorted); 43 | 44 | System.out.println("Sort by length using Comparator.comparingInt()"); 45 | sorted = strings.stream() 46 | .sorted(Comparator.comparingInt(String::length)) 47 | .toList(); 48 | System.out.println(sorted); 49 | 50 | System.out.println("Sort by length, then equal lengths reverse alpha"); 51 | sorted = strings.stream() 52 | .sorted(Comparator.comparingInt(String::length) 53 | .thenComparing(Comparator.reverseOrder())) 54 | .toList(); 55 | System.out.println(sorted); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/lambdas/FunctionalInterfacesTest.java: -------------------------------------------------------------------------------- 1 | package lambdas; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.List; 6 | import java.util.function.Consumer; 7 | 8 | public class FunctionalInterfacesTest { 9 | 10 | @SuppressWarnings({"Convert2Lambda", "Anonymous2MethodRef"}) 11 | @Test 12 | public void implementConsumerUsingAnonInnerClass() throws Exception { 13 | Consumer consumer = new Consumer() { 14 | @Override 15 | public void accept(String s) { 16 | System.out.println(s); 17 | } 18 | }; 19 | consumer.accept("Hello, World!"); 20 | } 21 | 22 | @Test 23 | public void implementConsumerUsingLambda() throws Exception { 24 | // consumer.accept("Hello, World!"); 25 | } 26 | 27 | @Test 28 | public void implementConsumerUsingMethodReference() throws Exception { 29 | // consumer.accept("Hello, World!"); 30 | } 31 | 32 | @Test 33 | public void implementSupplierUsingAnonInnerClass() throws Exception { 34 | // assertEquals("Hello", supplier.get()); 35 | } 36 | 37 | @Test 38 | public void implementSupplierUsingLambda() throws Exception { 39 | // assertEquals("Hello", supplier.get()); 40 | } 41 | 42 | @Test 43 | public void implementSupplierUsingMethodReference() throws Exception { 44 | // Create a Supplier that calls Math.random() 45 | // assertTrue(supplier.get() >= 0.0); 46 | // assertTrue(supplier.get() <= 1.0); 47 | 48 | // Create a DoubleSupplier that does the same 49 | // assertTrue(doubleSupplier.getAsDouble() >= 0.0); 50 | // assertTrue(doubleSupplier.getAsDouble() <= 1.0); 51 | } 52 | 53 | @Test 54 | public void constructorReference() throws Exception { 55 | List stringList = List.of("a", "b", "b", "c", "d", "d"); 56 | // assertEquals(6, stringList.size()); 57 | 58 | // Add the strings to a Set 59 | // assertEquals(4, strings.size()); 60 | // assertEquals(HashSet.class, strings.getClass()); 61 | 62 | // Add the strings to a TreeSet 63 | // assertEquals(4, sortedStrings.size()); 64 | // assertEquals(TreeSet.class, sortedStrings.getClass()); 65 | // assertEquals("a", sortedStrings.first()); 66 | } 67 | 68 | @Test 69 | public void filterWithPredicate() throws Exception { 70 | // IntStream.of(3, 1, 4, 1, 5, 9) 71 | // .filter(n -> true) // accept even nums only 72 | // .forEach(n -> assertTrue(n % 2 == 0)); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/sorting/SortGolfers.java: -------------------------------------------------------------------------------- 1 | package sorting; 2 | 3 | import java.util.*; 4 | import java.util.stream.Collectors; 5 | 6 | public class SortGolfers { 7 | private final List golfers = Arrays.asList( 8 | new Golfer("Jack", "Nicklaus", 68), 9 | new Golfer("Tiger", "Woods", 70), 10 | new Golfer("Tom", "Watson", 70), 11 | new Golfer("Ty", "Webb", 68), 12 | new Golfer("Bubba", "Watson", 70), 13 | new Golfer("Rose", "Zhang", 71) 14 | ); 15 | 16 | // old-style sort 17 | public void oldSchoolSort() { 18 | Collections.sort(golfers); // Sort by score only (inside Golfer class, compareTo method) 19 | golfers.forEach(System.out::println); 20 | } 21 | 22 | // default sort is by score, using streams 23 | public void defaultSort() { 24 | golfers.stream() 25 | .sorted() 26 | .forEach(System.out::println); 27 | } 28 | 29 | // sort by score, then equal scores by last name 30 | public void sortByScoreThenLast() { 31 | golfers.stream() 32 | .sorted(Comparator.comparingInt(Golfer::score) 33 | .thenComparing(Golfer::last)) 34 | .forEach(System.out::println); 35 | } 36 | 37 | // sort by score, then by last, then by first 38 | public void sortByScoreThenLastThenFirst() { 39 | golfers.stream() 40 | .sorted(Comparator.comparingInt(Golfer::score) 41 | .thenComparing(Golfer::last) 42 | .thenComparing(Golfer::first)) 43 | .forEach(System.out::println); 44 | } 45 | 46 | public void partitionByScore() { 47 | Map> map = golfers.stream() 48 | .collect(Collectors.partitioningBy( 49 | golfer -> golfer.score() < 70)); 50 | 51 | map.forEach((k,v) -> { 52 | System.out.println(k); 53 | v.forEach(System.out::println); 54 | }); 55 | } 56 | 57 | public static void main(String[] args) { 58 | SortGolfers sg = new SortGolfers(); 59 | System.out.println("--- oldSchoolSort ---"); 60 | sg.oldSchoolSort(); 61 | System.out.println("--- defaultSort ---"); 62 | sg.defaultSort(); 63 | System.out.println("--- sortByScoreThenLast ---"); 64 | sg.sortByScoreThenLast(); 65 | System.out.println("--- sortByScoreThenLastThenFirst ---"); 66 | sg.sortByScoreThenLastThenFirst(); 67 | System.out.println("--- partitionByScore ---"); 68 | sg.partitionByScore(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/test/java/lambdas/LazyEvaluationExercises.java: -------------------------------------------------------------------------------- 1 | package lambdas; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.logging.Logger; 6 | 7 | import static org.junit.jupiter.api.Assertions.assertTrue; 8 | 9 | public class LazyEvaluationExercises { 10 | 11 | private final Logger logger = Logger.getLogger(LazyEvaluationExercises.class.getName()); 12 | 13 | private String getErrorMessage() { 14 | System.out.println("Generating error message..."); 15 | return "x should be true"; 16 | } 17 | 18 | private String getLogMessage() { 19 | System.out.println("Generating log message..."); 20 | return "log message"; 21 | } 22 | 23 | @Test 24 | void eagerEvaluation() { 25 | boolean x = true; 26 | 27 | // TODO: Call assertTrue with getErrorMessage() directly 28 | // Notice that the message is generated even when the assertion passes 29 | // assertTrue(x, getErrorMessage()); 30 | 31 | // TODO: Call logger.fine with getLogMessage() directly 32 | // Notice that the message is generated even at fine log level 33 | // logger.fine(getLogMessage()); 34 | } 35 | 36 | @Test 37 | void lazyEvaluationWithLambda() { 38 | boolean x = true; 39 | 40 | // TODO: Call assertTrue with a lambda that calls getErrorMessage() 41 | // The message should NOT be generated when the assertion passes 42 | // assertTrue(x, () -> ...); 43 | 44 | // TODO: Call logger.fine with a lambda that calls getLogMessage() 45 | // The message should NOT be generated when fine logging is disabled 46 | // logger.fine(() -> ...); 47 | } 48 | 49 | @Test 50 | void demonstrateLazyBenefit() { 51 | boolean condition = true; 52 | 53 | // TODO: Create an expensive operation that should only run when needed 54 | // Example: a method that concatenates many strings or does complex calculations 55 | 56 | // TODO: Show the difference between eager and lazy evaluation 57 | // 1. Call assertTrue with the expensive operation directly 58 | // 2. Call assertTrue with a lambda/supplier 59 | // 3. Observe the performance difference when condition is true 60 | } 61 | 62 | @Test 63 | void createCustomLazyLogger() { 64 | // TODO: Create a method that accepts a Supplier for lazy logging 65 | // void logIfEnabled(Supplier messageSupplier) { ... } 66 | 67 | // TODO: Test your custom lazy logger with both eager and lazy message generation 68 | // Compare the behavior when logging is enabled vs disabled 69 | } 70 | } -------------------------------------------------------------------------------- /src/main/java/concurrency/CompletableFutureDemos.java: -------------------------------------------------------------------------------- 1 | package concurrency; 2 | 3 | import java.util.Map; 4 | import java.util.concurrent.CompletableFuture; 5 | import java.util.concurrent.ConcurrentHashMap; 6 | import java.util.logging.Logger; 7 | 8 | public class CompletableFutureDemos { 9 | private final Logger logger = Logger.getLogger(this.getClass().getName()); 10 | 11 | private final Map cache = new ConcurrentHashMap<>(); 12 | 13 | private Product getLocal(int id) { 14 | return cache.get(id); 15 | } 16 | 17 | private Product getRemote(int id) { 18 | try { 19 | Thread.sleep(100); 20 | if (id == 666) { 21 | throw new RuntimeException("Evil request"); 22 | } 23 | } catch (InterruptedException ignored) { 24 | } 25 | return new Product(id, "name"); 26 | } 27 | 28 | public CompletableFuture getProduct(int id) { 29 | try { 30 | Product product = getLocal(id); 31 | if (product != null) { 32 | logger.info("getLocal with id=" + id); 33 | return CompletableFuture.completedFuture(product); 34 | } else { 35 | // Synchronous (simulating legacy system) 36 | logger.info("getRemote with id=" + id); 37 | CompletableFuture future = new CompletableFuture<>(); 38 | Product p = getRemote(id); 39 | cache.put(id, p); 40 | future.complete(p); 41 | return future; 42 | } 43 | } catch (Exception e) { 44 | logger.info("exception thrown"); 45 | CompletableFuture future = new CompletableFuture<>(); 46 | future.completeExceptionally(e); 47 | return future; 48 | } 49 | } 50 | 51 | public CompletableFuture getProductAsync(int id) { 52 | try { 53 | Product product = getLocal(id); 54 | if (product != null) { 55 | logger.info("getLocal with id=" + id); 56 | return CompletableFuture.completedFuture(product); 57 | } else { 58 | logger.info("getRemote with id=" + id); 59 | // Asynchronous 60 | return CompletableFuture.supplyAsync(() -> { 61 | Product p = getRemote(id); 62 | cache.put(id, p); 63 | return p; 64 | }); 65 | } 66 | } catch (Exception e) { 67 | logger.info("exception thrown"); 68 | CompletableFuture future = new CompletableFuture<>(); 69 | future.completeExceptionally(e); 70 | return future; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/lambdas/UsePerson.java: -------------------------------------------------------------------------------- 1 | package lambdas; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.LinkedList; 6 | import java.util.List; 7 | import java.util.stream.Collectors; 8 | 9 | public class UsePerson { 10 | public static void main(String[] args) { 11 | List names = List.of("John", "Paul", "George", "Ringo"); 12 | 13 | // Old-style way: 14 | List beatles = new ArrayList<>(); // Shared mutable state 15 | for (String name : names) { 16 | beatles.add(new Person(name)); 17 | } 18 | System.out.println(beatles); 19 | 20 | List people = names.stream() // Stream 21 | .map(name -> new Person(name)) // Stream 22 | .toList(); // Converts Stream to List 23 | System.out.println(people); 24 | 25 | people = names.stream() 26 | .map(Person::new) // uses the Person(String) ctr 27 | // .map(Person::new) // uses the Person(Person) ctr 28 | .toList(); 29 | System.out.println(people); 30 | 31 | Person[] peopleArray = names.stream() 32 | .map(Person::new) 33 | .toArray(Person[]::new); 34 | //.toArray(value -> new Person[value]); 35 | System.out.println(Arrays.toString(peopleArray)); 36 | 37 | List fullNames = List.of( 38 | "John Lennon", "Paul McCartney", "George Harrison", "Ringo Starr"); 39 | people = fullNames.stream() 40 | .map(name -> name.split(" ")) 41 | .map(Person::new) // use the Person(String...) ctr 42 | .collect(Collectors.toList()); 43 | System.out.println(people); 44 | System.out.println(people.getClass().getName()); 45 | 46 | // p1..p5 | p6..p10 | p11..p15 | p16..p20 // say you have 4 cores and run in parallel 47 | // l1 l2 l3 l4 48 | // list 49 | LinkedList linkedPersons = names.stream() 50 | .map(Person::new) 51 | .collect( 52 | () -> new LinkedList(), // Supplier 53 | (list, person) -> list.add(person), // BiConsumer 54 | (list1, list2) -> list1.addAll(list2)); // BiConsumer 55 | System.out.println(linkedPersons); 56 | 57 | linkedPersons = names.stream() 58 | .map(Person::new) 59 | .collect( 60 | LinkedList::new, // Supplier 61 | LinkedList::add, // BiConsumer 62 | LinkedList::addAll); // BiConsumer 63 | System.out.println(linkedPersons); 64 | 65 | linkedPersons = names.stream() 66 | .map(Person::new) 67 | .collect(Collectors.toCollection(LinkedList::new)); 68 | System.out.println(linkedPersons); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 1>&2 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 48 | echo. 1>&2 49 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 50 | echo location of your Java installation. 1>&2 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 1>&2 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 62 | echo. 1>&2 63 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 64 | echo location of your Java installation. 1>&2 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /src/main/java/got/MemberDB.java: -------------------------------------------------------------------------------- 1 | package got; 2 | 3 | import java.time.LocalDate; 4 | import java.time.Month; 5 | import java.util.List; 6 | 7 | // This class is a singleton: only a single instance ever exists 8 | // - private constructor 9 | // - static method to get the instance 10 | // - private static final field to hold the instance 11 | public class MemberDB { 12 | private static final MemberDB DB = new MemberDB(); 13 | 14 | // Notes: 15 | // - The birth dates are the same as the actors' birth dates. 16 | // - The salaries are completely made up 17 | private final List allMembers = List.of( 18 | new Member(1L, Title.LORD, "Eddard", LocalDate.of(1959, Month.APRIL, 17), 100000.0, House.STARK), 19 | new Member(2L, Title.LADY, "Catelyn", LocalDate.of(1964, Month.JANUARY, 17), 80000.0, House.STARK), 20 | new Member(3L, Title.LADY, "Arya", LocalDate.of(1997, Month.APRIL, 15), 50000.0, House.STARK), 21 | new Member(4L, Title.LADY, "Sansa", LocalDate.of(1996, Month.FEBRUARY, 21), 60000.0, House.STARK), 22 | new Member(5L, Title.SIR, "Bran", LocalDate.of(1999, Month.APRIL, 9), 10000.0, House.STARK), 23 | new Member(6L, Title.KING, "Robb", LocalDate.of(1986, Month.JUNE, 18), 100000.0, House.STARK), 24 | new Member(7L, Title.KING, "Jon", LocalDate.of(1986, Month.DECEMBER, 26), 90000.0, House.SNOW), 25 | new Member(8L, Title.SIR, "Jaime", LocalDate.of(1970, Month.JULY, 27), 120000.0, House.LANNISTER), 26 | new Member(9L, Title.LORD, "Tyrion", LocalDate.of(1969, Month.JUNE, 11), 70000.0, House.LANNISTER), 27 | new Member(10L, Title.LORD, "Tywin", LocalDate.of(1946, Month.OCTOBER, 10), 200000.0, House.LANNISTER), 28 | new Member(11L, Title.LADY, "Cersei", LocalDate.of(1973, Month.OCTOBER, 3), 120000.0, House.LANNISTER), 29 | new Member(12L, Title.QUEEN, "Daenerys", LocalDate.of(1987, Month.MAY, 1), 130000.0, House.TARGARYEN), 30 | new Member(13L, Title.LORD, "Viserys", LocalDate.of(1983, Month.NOVEMBER, 17), 100000.0, House.TARGARYEN), 31 | new Member(14L, Title.KING, "Robert", LocalDate.of(1964, Month.JANUARY, 14), 180000.0, House.BARATHEON), 32 | new Member(15L, Title.KING, "Joffrey", LocalDate.of(1992, Month.MAY, 20), 100000.0, House.BARATHEON), 33 | new Member(16L, Title.KING, "Tommen", LocalDate.of(1997, Month.SEPTEMBER, 7), 60000.0, House.BARATHEON), 34 | new Member(17L, Title.KING, "Stannis", LocalDate.of(1957, Month.MARCH, 27), 123456.0, House.BARATHEON), 35 | new Member(18L, Title.QUEEN, "Margaery", LocalDate.of(1982, Month.FEBRUARY, 11), 80000.0, House.TYRELL), 36 | new Member(19L, Title.SIR, "Loras", LocalDate.of(1988, Month.MARCH, 24), 70000.0, House.TYRELL), 37 | new Member(20L, Title.LADY, "Olenna", LocalDate.of(1938, Month.JULY, 20), 130000.0, House.TYRELL), 38 | new Member(21L, Title.LORD, "Roose", LocalDate.of(1963, Month.SEPTEMBER, 12), 100000.0, House.BOLTON), 39 | new Member(22L, Title.LORD, "Ramsay", LocalDate.of(1985, Month.MAY, 13), 140000.0, House.BOLTON) 40 | ); 41 | 42 | private MemberDB() {} 43 | 44 | public static MemberDB getInstance() { 45 | return DB; 46 | } 47 | 48 | public List getAllMembers() { 49 | return allMembers; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/lambdas/FileFilterExercises.java: -------------------------------------------------------------------------------- 1 | package lambdas; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.io.File; 6 | import java.io.FileFilter; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | import static org.junit.jupiter.api.Assertions.assertNotNull; 10 | 11 | @SuppressWarnings({"Convert2MethodRef", "Convert2Lambda", "Anonymous2MethodRef"}) 12 | public class FileFilterExercises { 13 | private final File root = new File("src/main/java"); 14 | 15 | @Test 16 | void listFiles() { 17 | File[] files = root.listFiles(); 18 | assertNotNull(files); 19 | // The exact count may vary based on project structure 20 | // But we expect at least some files/directories 21 | assert files.length > 0; 22 | } 23 | 24 | @Test 25 | void listDirectories_anonInnerClass() { 26 | // TODO: Implement FileFilter using anonymous inner class 27 | // Filter for directories only 28 | 29 | // File[] dirs = root.listFiles(new FileFilter() { 30 | // @Override 31 | // public boolean accept(File pathname) { 32 | // // Filter for directories 33 | // } 34 | // }); 35 | 36 | // assertNotNull(dirs); 37 | // assert dirs.length > 0; 38 | } 39 | 40 | @Test 41 | void listDirectories_expressionLambda() { 42 | // TODO: Implement the same FileFilter using expression lambda 43 | // File[] dirs = root.listFiles(pathname -> ...); 44 | 45 | // assertNotNull(dirs); 46 | // assert dirs.length > 0; 47 | } 48 | 49 | @Test 50 | void listDirectories_blockLambda() { 51 | // TODO: Implement using block lambda with logging 52 | // File[] dirs = root.listFiles(pathname -> { 53 | // System.out.println("Checking " + pathname); 54 | // // Return whether it's a directory 55 | // }); 56 | 57 | // assertNotNull(dirs); 58 | // assert dirs.length > 0; 59 | } 60 | 61 | @Test 62 | void listDirectories_methodReference() { 63 | // TODO: Implement using method reference 64 | // Hint: Look for a method in File class that checks if it's a directory 65 | // File[] dirs = root.listFiles(...); 66 | 67 | // assertNotNull(dirs); 68 | // assert dirs.length > 0; 69 | } 70 | 71 | @Test 72 | void listDirectories_assignToVariable() { 73 | // TODO: Create a FileFilter variable and assign lambda to it 74 | // FileFilter filter = ...; 75 | // File[] dirs = root.listFiles(filter); 76 | 77 | // assertNotNull(dirs); 78 | // assert dirs.length > 0; 79 | } 80 | 81 | @Test 82 | void listJavaFiles_filenameFilter() { 83 | // TODO: List only .java files using FilenameFilter (two parameters) 84 | // File[] javaSrcFiles = root.listFiles((dir, name) -> ...); 85 | 86 | // assertNotNull(javaSrcFiles); 87 | // Check that we found some .java files 88 | // for (File file : javaSrcFiles) { 89 | // assertTrue(file.getName().endsWith(".java")); 90 | // } 91 | } 92 | 93 | @Test 94 | void listJavaFiles_fileFilter() { 95 | // TODO: List only .java files using FileFilter (one parameter) 96 | // File[] javaSrcFiles = root.listFiles(file -> ...); 97 | 98 | // assertNotNull(javaSrcFiles); 99 | // for (File file : javaSrcFiles) { 100 | // assertTrue(file.getName().endsWith(".java")); 101 | // } 102 | } 103 | 104 | @Test 105 | void composedFilter() { 106 | // TODO: Create a filter for .java files that are larger than 1000 bytes 107 | // File[] largejavaFiles = root.listFiles(file -> ...); 108 | 109 | // assertNotNull(largejavaFiles); 110 | // for (File file : largejavaFiles) { 111 | // assertTrue(file.getName().endsWith(".java")); 112 | // assertTrue(file.length() > 1000); 113 | // } 114 | } 115 | } -------------------------------------------------------------------------------- /src/test/java/streams/FlatMapExercises.java: -------------------------------------------------------------------------------- 1 | package streams; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.List; 7 | import java.util.stream.Collectors; 8 | 9 | import static org.junit.jupiter.api.Assertions.*; 10 | 11 | public class FlatMapExercises { 12 | private List customers; 13 | 14 | @BeforeEach 15 | public void setUp() { 16 | Customer sheridan = new Customer("Sheridan"); 17 | Customer ivanova = new Customer("Ivanova"); 18 | Customer garibaldi = new Customer("Garibaldi"); 19 | 20 | sheridan.addOrder(new Order(1)) 21 | .addOrder(new Order(2)) 22 | .addOrder(new Order(3)); 23 | ivanova.addOrder(new Order(4)) 24 | .addOrder(new Order(5)); 25 | // garibaldi has no orders 26 | 27 | customers = List.of(sheridan, ivanova, garibaldi); 28 | } 29 | 30 | @Test 31 | public void getCustomerNames() { 32 | // TODO: Use map to get a List of customer names 33 | // List names = ... 34 | 35 | // assertEquals(3, names.size()); 36 | // assertTrue(names.contains("Sheridan")); 37 | // assertTrue(names.contains("Ivanova")); 38 | // assertTrue(names.contains("Garibaldi")); 39 | } 40 | 41 | @Test 42 | public void getOrdersPerCustomer() { 43 | // TODO: Use map to get a List> showing orders per customer 44 | // Note how this creates a nested structure 45 | // List> ordersPerCustomer = ... 46 | 47 | // assertEquals(3, ordersPerCustomer.size()); 48 | // assertEquals(3, ordersPerCustomer.get(0).size()); // Sheridan has 3 orders 49 | // assertEquals(2, ordersPerCustomer.get(1).size()); // Ivanova has 2 orders 50 | // assertEquals(0, ordersPerCustomer.get(2).size()); // Garibaldi has 0 orders 51 | } 52 | 53 | @Test 54 | public void getAllOrdersFlat() { 55 | // TODO: Use flatMap to get all orders as a flat List 56 | // List allOrders = ... 57 | 58 | // assertEquals(5, allOrders.size()); 59 | // assertTrue(allOrders.stream().anyMatch(o -> o.id() == 1)); 60 | // assertTrue(allOrders.stream().anyMatch(o -> o.id() == 5)); 61 | } 62 | 63 | @Test 64 | public void getAllOrderIds() { 65 | // TODO: Use flatMap to get all order IDs as a List 66 | // Hint: You'll need to flatMap to orders, then map to IDs 67 | // List orderIds = ... 68 | 69 | // assertEquals(5, orderIds.size()); 70 | // assertEquals(List.of(1, 2, 3, 4, 5), orderIds); 71 | } 72 | 73 | @Test 74 | public void findCustomersWithoutOrders() { 75 | // TODO: Find customers who have no orders 76 | // List customersWithoutOrders = ... 77 | 78 | // assertEquals(1, customersWithoutOrders.size()); 79 | // assertEquals("Garibaldi", customersWithoutOrders.get(0).getName()); 80 | } 81 | 82 | @Test 83 | public void countTotalOrders() { 84 | // TODO: Count the total number of orders across all customers 85 | // long totalOrders = ... 86 | 87 | // assertEquals(5L, totalOrders); 88 | } 89 | 90 | @Test 91 | public void getOrdersForCustomersWithOrders() { 92 | // TODO: Use flatMap to get orders only from customers who have orders 93 | // Hint: Use filter before flatMap 94 | // List ordersFromActiveCustomers = ... 95 | 96 | // assertEquals(5, ordersFromActiveCustomers.size()); 97 | } 98 | 99 | @Test 100 | public void combineCustomerNamesWithOrderIds() { 101 | // TODO: Create strings in format "CustomerName-OrderId" for all orders 102 | // Example: "Sheridan-1", "Sheridan-2", etc. 103 | // List customerOrderStrings = ... 104 | 105 | // assertEquals(5, customerOrderStrings.size()); 106 | // assertTrue(customerOrderStrings.contains("Sheridan-1")); 107 | // assertTrue(customerOrderStrings.contains("Ivanova-5")); 108 | } 109 | } -------------------------------------------------------------------------------- /src/main/java/streams/AlternativeReduceDemo.java: -------------------------------------------------------------------------------- 1 | package streams; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | import java.util.Optional; 6 | import java.util.stream.Stream; 7 | 8 | /** 9 | * Alternative demo for teaching reduce concepts if BigDecimal is already covered 10 | */ 11 | public class AlternativeReduceDemo { 12 | 13 | public static void main(String[] args) { 14 | System.out.println("=== String Concatenation Demo ==="); 15 | stringConcatenationDemo(); 16 | 17 | System.out.println("\n=== Custom Object Aggregation Demo ==="); 18 | customObjectDemo(); 19 | 20 | System.out.println("\n=== Matrix Operations Demo ==="); 21 | matrixOperationsDemo(); 22 | } 23 | 24 | // Demo 1: String concatenation (shows identity and associativity) 25 | static void stringConcatenationDemo() { 26 | List words = List.of("Java", "Streams", "are", "powerful"); 27 | 28 | // Without identity - returns Optional 29 | Optional concatenated = words.stream() 30 | .reduce((a, b) -> a + " " + b); 31 | System.out.println("Without identity: " + concatenated.orElse("")); 32 | 33 | // With identity 34 | String sentence = words.stream() 35 | .reduce("", (a, b) -> a + " " + b); 36 | System.out.println("With identity: " + sentence); 37 | 38 | // Better approach with joining collector 39 | String properSentence = String.join(" ", words); 40 | System.out.println("Using join: " + properSentence); 41 | } 42 | 43 | // Demo 2: Aggregating custom objects 44 | static void customObjectDemo() { 45 | record Order(String product, int quantity, double price) {} 46 | 47 | List orders = List.of( 48 | new Order("Laptop", 2, 999.99), 49 | new Order("Mouse", 5, 29.99), 50 | new Order("Keyboard", 3, 79.99) 51 | ); 52 | 53 | // Calculate total revenue 54 | double totalRevenue = orders.stream() 55 | .map(order -> order.quantity() * order.price()) 56 | .reduce(0.0, Double::sum); 57 | System.out.println("Total revenue: $" + totalRevenue); 58 | 59 | // Find order with maximum value 60 | Optional maxOrder = orders.stream() 61 | .reduce((o1, o2) -> 62 | (o1.quantity() * o1.price() > o2.quantity() * o2.price()) ? o1 : o2); 63 | System.out.println("Highest value order: " + maxOrder.orElse(null)); 64 | } 65 | 66 | // Demo 3: Matrix operations (advanced) 67 | static void matrixOperationsDemo() { 68 | // Representing a 2x2 matrix as a 4-element array 69 | List matrices = List.of( 70 | new double[]{1, 2, 3, 4}, // Matrix A 71 | new double[]{5, 6, 7, 8}, // Matrix B 72 | new double[]{9, 10, 11, 12} // Matrix C 73 | ); 74 | 75 | // Add all matrices element-wise 76 | double[] sumMatrix = matrices.stream() 77 | .reduce(new double[]{0, 0, 0, 0}, 78 | (result, matrix) -> { 79 | double[] sum = new double[4]; 80 | for (int i = 0; i < 4; i++) { 81 | sum[i] = result[i] + matrix[i]; 82 | } 83 | return sum; 84 | }); 85 | 86 | System.out.println("Sum of matrices: " + Arrays.toString(sumMatrix)); 87 | } 88 | 89 | // Additional demo showing parallel reduce challenges 90 | static void parallelReduceDemo() { 91 | // This shows why the combining function matters in parallel streams 92 | List letters = List.of("a", "b", "c", "d", "e"); 93 | 94 | // Sequential - predictable order 95 | String sequential = letters.stream() 96 | .reduce("", (a, b) -> a + b); 97 | System.out.println("Sequential: " + sequential); 98 | 99 | // Parallel - order might vary 100 | String parallel = letters.parallelStream() 101 | .reduce("", 102 | (a, b) -> a + b, // accumulator 103 | (a, b) -> a + b); // combiner 104 | System.out.println("Parallel: " + parallel); 105 | } 106 | } -------------------------------------------------------------------------------- /src/main/java/io/ProcessDictionary.java: -------------------------------------------------------------------------------- 1 | package io; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.FileReader; 5 | import java.io.IOException; 6 | import java.io.UncheckedIOException; 7 | import java.nio.file.Files; 8 | import java.nio.file.Path; 9 | import java.nio.file.Paths; 10 | import java.util.Comparator; 11 | import java.util.Map; 12 | import java.util.stream.Collectors; 13 | import java.util.stream.Stream; 14 | 15 | import static java.util.stream.Collectors.counting; 16 | import static java.util.stream.Collectors.groupingBy; 17 | 18 | @SuppressWarnings("DuplicatedCode") 19 | public class ProcessDictionary { 20 | private final Path dictionary = Paths.get("/usr/share/dict/words"); 21 | 22 | public long getMaxLength() { 23 | try (var words = Files.lines(dictionary)) { 24 | return words.mapToInt(String::length).max().orElse(0); 25 | } catch (IOException e) { 26 | throw new UncheckedIOException(e); 27 | } 28 | } 29 | 30 | public void printTenLongestWords() { 31 | System.out.println("\nTen Longest Words:"); 32 | long max = getMaxLength() - 5; 33 | try (var words = Files.lines(dictionary)) { 34 | words.filter(s -> s.length() > max) 35 | .sorted(Comparator.comparingInt(String::length).reversed() 36 | //.thenComparing(Comparator.reverseOrder())) 37 | ) 38 | .limit(10) 39 | .forEach(w -> 40 | System.out.printf("%s (%d)%n", w, w.length())); 41 | } catch (IOException e) { 42 | throw new UncheckedIOException(e); 43 | } 44 | } 45 | 46 | public void printWordsOfEachLength() { 47 | System.out.println("\nList of words of each length:"); 48 | try (var lines = Files.lines(dictionary)) { 49 | lines.filter(s -> s.length() > 20) 50 | .collect(Collectors.groupingBy(String::length)) // Map> 51 | .forEach((len, wordList) -> System.out.println(len + ": " + wordList)); 52 | } catch (IOException e) { 53 | throw new UncheckedIOException(e); 54 | } 55 | } 56 | 57 | public void printHowManyWordsOfEachLength() { 58 | System.out.println("\nNumber of words of each length:"); 59 | try (var lines = Files.lines(dictionary)) { 60 | lines.filter(s -> s.length() > 20) 61 | .collect(Collectors.groupingBy(String::length, Collectors.counting())) // Map 62 | .forEach((len, num) -> System.out.printf("%d: %d%n", len, num)); 63 | } catch (IOException e) { 64 | throw new UncheckedIOException(e); 65 | } 66 | } 67 | 68 | public void printSortedMapOfWords() { 69 | System.out.println("\nNumber of words of each length (desc order):"); 70 | try (var lines = Files.lines(dictionary)) { 71 | var map = lines.filter(s -> s.length() > 20) 72 | .collect(groupingBy(String::length, counting())); 73 | 74 | map.entrySet().stream() 75 | .sorted(Map.Entry.comparingByKey(Comparator.reverseOrder())) 76 | .forEach(e -> System.out.printf("Length %d: %d words%n", e.getKey(), e.getValue())); 77 | } catch (IOException e) { 78 | throw new UncheckedIOException(e); 79 | } 80 | } 81 | 82 | public void printSortedMapOfWordsUsingBufferedReader() { 83 | System.out.println("\nNumber of words of each length (desc order):"); 84 | try (Stream lines = 85 | new BufferedReader(new FileReader("/usr/share/dict/words")).lines()) { 86 | var map = lines.filter(s -> s.length() > 20) 87 | .collect(groupingBy(String::length, counting())); 88 | 89 | map.entrySet().stream() 90 | .sorted(Map.Entry.comparingByKey(Comparator.reverseOrder())) 91 | .forEach(e -> System.out.printf("Length %d: %d words%n", e.getKey(), e.getValue())); 92 | } catch (IOException e) { 93 | throw new UncheckedIOException(e); 94 | } 95 | } 96 | 97 | public static void main(String[] args) { 98 | var processDictionary = new ProcessDictionary(); 99 | processDictionary.printTenLongestWords(); 100 | processDictionary.printWordsOfEachLength(); 101 | processDictionary.printHowManyWordsOfEachLength(); 102 | processDictionary.printSortedMapOfWords(); 103 | processDictionary.printSortedMapOfWordsUsingBufferedReader(); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /CLAUDE.md: -------------------------------------------------------------------------------- 1 | # Project Context for Claude Code 2 | 3 | ## Overview 4 | This is a comprehensive Java training project focused on functional programming features in modern Java (Java 8+). The project provides hands-on exercises for students to learn functional programming concepts through practical examples. 5 | 6 | ## Project Structure 7 | ``` 8 | java_upgrade/ 9 | ├── src/main/java/ # Demo code and working examples 10 | ├── src/test/java/ # Exercise files with TODOs for students 11 | ├── labs.md # Comprehensive lab instructions 12 | ├── README.md # Course overview and documentation 13 | ├── CLAUDE.md # This file - project context for Claude Code 14 | └── Java_Upgrade_Slides.pdf # Course presentation slides 15 | ``` 16 | 17 | ## Key Features 18 | - 20 comprehensive exercises covering functional Java concepts 19 | - Main branch contains exercises with TODO comments 20 | - Solutions branch contains complete implementations 21 | - Each exercise builds on previous concepts 22 | - Modern Java idioms throughout (records, var, Stream.toList(), etc.) 23 | 24 | ## Recently Added Exercises 25 | - FileFilter Lambda Evolution (Exercise 7) 26 | - Lazy Evaluation with Suppliers (Exercise 8) 27 | - Book Sorting with Comparators (Exercise 12) 28 | - BigDecimal Reduce Operations (Exercise 14) 29 | - Parallel Streams (Exercise 14) 30 | 31 | ## Java Features Covered 32 | 1. **Lambda Expressions & Functional Interfaces** 33 | - Consumer, Supplier, Function, Predicate 34 | - Method references and constructor references 35 | - Functional interface composition 36 | - Lazy evaluation patterns 37 | 38 | 2. **Stream API** 39 | - Basic operations (map, filter, reduce) 40 | - FlatMap for nested structures 41 | - Collectors and advanced aggregations 42 | - Parallel stream processing 43 | - Performance considerations 44 | 45 | 3. **Modern Java Features** 46 | - Optional patterns and chaining 47 | - CompletableFuture async programming 48 | - Records as data carriers 49 | - Interface default/static methods 50 | - Virtual threads (Java 21+) 51 | 52 | ## Important Files and Patterns 53 | 54 | ### Exercise Files to Remember 55 | - `src/test/java/lambdas/FileFilterExercises.java` - Lambda evolution patterns 56 | - `src/test/java/lambdas/LazyEvaluationExercises.java` - Supplier and lazy evaluation 57 | - `src/test/java/sorting/BookSortingExercises.java` - Modern Comparator usage 58 | - `src/test/java/streams/BigDecimalReduceExercises.java` - Reduce operations 59 | - `src/test/java/streams/ParallelStreamExercises.java` - Parallel processing 60 | 61 | ### Demo Classes 62 | - `src/main/java/sorting/SortGolfers.java` - Comparator demo (uses record) 63 | - `src/main/java/streams/AlternativeReduceDemo.java` - Additional reduce examples 64 | 65 | ## Development Guidelines 66 | 1. **Code Style** 67 | - Use modern Java idioms (Stream.toList(), var where appropriate) 68 | - Prefer records over traditional POJOs for data carriers 69 | - Use method references where clearer than lambdas 70 | - Follow existing code formatting patterns 71 | 72 | 2. **Exercise Structure** 73 | - Each exercise should have clear TODO comments 74 | - Include sample expected results in comments 75 | - Provide hints for difficult concepts 76 | - Number exercises sequentially in labs.md 77 | 78 | 3. **Testing** 79 | - Exercises use JUnit 5 80 | - Tests should be self-contained 81 | - Include assertions to verify correct implementation 82 | - Use @Disabled annotation sparingly 83 | 84 | ## Git Workflow 85 | - **main branch**: Exercises with TODOs for students 86 | - **solutions branch**: Complete implementations 87 | - **Training branches**: Month-year pattern (e.g., java_jan2025) 88 | - Keep labs.md synchronized between branches 89 | - Use descriptive commit messages 90 | 91 | ## Building and Testing 92 | ```bash 93 | # Build project 94 | ./gradlew build 95 | 96 | # Run all tests 97 | ./gradlew test 98 | 99 | # Run specific test class 100 | ./gradlew test --tests "lambdas.FunctionalInterfacesTest" 101 | 102 | # Run specific test method 103 | ./gradlew test --tests "lambdas.FunctionalInterfacesTest.implementConsumerUsingLambda" 104 | ``` 105 | 106 | ## Recent Updates (January 2025) 107 | 1. Added FileFilter lambda evolution exercise 108 | 2. Added lazy evaluation with Suppliers exercise 109 | 3. Added BigDecimal reduce operations exercise 110 | 4. Added Book sorting with modern Comparators 111 | 5. Added basic parallel streams exercise 112 | 6. Converted Golfer class to record 113 | 7. Updated README.md with comprehensive documentation 114 | 8. Renumbered all exercises for clarity 115 | 116 | ## When Working on This Project 117 | - Check `labs.md` for exercise organization 118 | - Maintain consistency between main and solutions branches 119 | - Update exercise numbers if adding new content 120 | - Test both exercise and solution versions 121 | - Consider how concepts build on each other 122 | - Use descriptive variable names in examples 123 | 124 | ## Notes for Future Updates 125 | - JMH benchmarking examples are in a separate repository 126 | - Virtual threads features require Java 21+ 127 | - Some advanced Game of Thrones exercises are @Disabled by default 128 | - Consider adding more real-world examples as new Java versions release -------------------------------------------------------------------------------- /src/test/java/streams/BigDecimalReduceExercises.java: -------------------------------------------------------------------------------- 1 | package streams; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.math.BigDecimal; 6 | import java.util.List; 7 | import java.util.Optional; 8 | import java.util.stream.Stream; 9 | 10 | import static org.junit.jupiter.api.Assertions.*; 11 | 12 | public class BigDecimalReduceExercises { 13 | 14 | @Test 15 | public void sumFirstNBigDecimals() { 16 | // TODO: Sum the first 10 BigDecimal values (1, 2, 3, ... 10) 17 | // Use Stream.iterate to generate the values 18 | // Use reduce to sum them 19 | 20 | // BigDecimal sum = Stream.iterate(BigDecimal.ONE, bd -> bd.add(BigDecimal.ONE)) 21 | // .limit(10) 22 | // .reduce(...) 23 | 24 | // assertEquals(new BigDecimal("55"), sum); 25 | } 26 | 27 | @Test 28 | public void sumListOfPrices() { 29 | List prices = List.of( 30 | new BigDecimal("19.99"), 31 | new BigDecimal("35.50"), 32 | new BigDecimal("12.75"), 33 | new BigDecimal("8.00") 34 | ); 35 | 36 | // TODO: Sum all prices using reduce with identity 37 | // BigDecimal total = prices.stream() 38 | // .reduce(...) 39 | 40 | // assertEquals(new BigDecimal("76.24"), total); 41 | } 42 | 43 | @Test 44 | public void sumWithEmptyStream() { 45 | List emptyPrices = List.of(); 46 | 47 | // TODO: Sum an empty list - what happens without an identity? 48 | // Optional total = emptyPrices.stream() 49 | // .reduce(BigDecimal::add); 50 | 51 | // assertTrue(total.isEmpty()); 52 | 53 | // TODO: Now use reduce with identity - what's the result? 54 | // BigDecimal totalWithIdentity = emptyPrices.stream() 55 | // .reduce(BigDecimal.ZERO, BigDecimal::add); 56 | 57 | // assertEquals(BigDecimal.ZERO, totalWithIdentity); 58 | } 59 | 60 | @Test 61 | public void multiplyDiscounts() { 62 | // Suppose we have a series of discount factors (0.9 = 10% off, 0.8 = 20% off) 63 | List discountFactors = List.of( 64 | new BigDecimal("0.9"), // 10% off 65 | new BigDecimal("0.85"), // 15% off 66 | new BigDecimal("0.95") // 5% off 67 | ); 68 | 69 | // TODO: Calculate the final price after all discounts 70 | // Start with original price of 100 71 | BigDecimal originalPrice = new BigDecimal("100"); 72 | 73 | // BigDecimal finalPrice = discountFactors.stream() 74 | // .reduce(originalPrice, (price, discount) -> ...) 75 | 76 | // Calculate expected: 100 * 0.9 * 0.85 * 0.95 = 72.675 77 | // assertEquals(new BigDecimal("72.675"), finalPrice); 78 | } 79 | 80 | @Test 81 | public void demonstrateNonAssociativeOperation() { 82 | // This demonstrates why reduce needs an associative operation 83 | List values = List.of( 84 | new BigDecimal("10"), 85 | new BigDecimal("5"), 86 | new BigDecimal("2") 87 | ); 88 | 89 | // TODO: Try subtraction (which is NOT associative) 90 | // What happens with: (10 - 5) - 2 vs 10 - (5 - 2)? 91 | 92 | // BigDecimal resultNoIdentity = values.stream() 93 | // .reduce((a, b) -> a.subtract(b)) 94 | // .orElse(BigDecimal.ZERO); 95 | 96 | // assertEquals(new BigDecimal("3"), resultNoIdentity); 97 | 98 | // What about with identity? 99 | // BigDecimal resultWithIdentity = values.stream() 100 | // .reduce(BigDecimal.ZERO, (a, b) -> a.subtract(b)); 101 | 102 | // This gives a different result! Why? 103 | // assertEquals(new BigDecimal("-17"), resultWithIdentity); 104 | } 105 | 106 | @Test 107 | public void customAccumulator() { 108 | // Calculate both sum and count in one reduce operation 109 | List values = List.of( 110 | new BigDecimal("10.5"), 111 | new BigDecimal("20.3"), 112 | new BigDecimal("15.8"), 113 | new BigDecimal("5.5") 114 | ); 115 | 116 | // TODO: Create a custom accumulator to track sum and count 117 | // Hint: You'll need a helper class or use an array 118 | 119 | class SumAndCount { 120 | BigDecimal sum; 121 | int count; 122 | 123 | SumAndCount(BigDecimal sum, int count) { 124 | this.sum = sum; 125 | this.count = count; 126 | } 127 | } 128 | 129 | // SumAndCount result = values.stream() 130 | // .map(value -> new SumAndCount(value, 1)) 131 | // .reduce(new SumAndCount(BigDecimal.ZERO, 0), 132 | // (acc, elem) -> new SumAndCount( 133 | // acc.sum.add(elem.sum), 134 | // acc.count + elem.count 135 | // )); 136 | 137 | // Calculate average 138 | // BigDecimal average = result.sum.divide( 139 | // BigDecimal.valueOf(result.count), 140 | // 2, 141 | // BigDecimal.ROUND_HALF_UP 142 | // ); 143 | 144 | // assertEquals(new BigDecimal("13.03"), average); 145 | } 146 | } -------------------------------------------------------------------------------- /src/test/java/concurrency/CompletableFutureTests.java: -------------------------------------------------------------------------------- 1 | package concurrency; 2 | 3 | import org.junit.jupiter.api.Disabled; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.io.ByteArrayOutputStream; 7 | import java.io.PrintStream; 8 | import java.util.concurrent.*; 9 | 10 | import static org.junit.jupiter.api.Assertions.*; 11 | 12 | public class CompletableFutureTests { 13 | 14 | @Test 15 | public void joinAfterCancel() { 16 | CompletableFuture future = new CompletableFuture<>(); 17 | future.cancel(false); 18 | System.out.println( 19 | assertThrows(CancellationException.class, 20 | () -> future.join())); 21 | } 22 | 23 | @Test 24 | public void completeWithGet() { 25 | CompletableFuture future = new CompletableFuture<>(); 26 | boolean finished = future.complete("I'm done"); 27 | assertTrue(finished); 28 | try { 29 | assertEquals("I'm done", future.get()); 30 | } catch (InterruptedException | ExecutionException e) { 31 | e.printStackTrace(); 32 | } 33 | } 34 | 35 | @Test 36 | public void completeWithJoin() { 37 | CompletableFuture future = new CompletableFuture<>(); 38 | boolean finished = future.complete("I'm done"); 39 | assertTrue(finished); 40 | assertEquals("I'm done", future.join()); 41 | } 42 | 43 | @Test 44 | public void completeExceptionally() throws Exception { 45 | assertThrows(ExecutionException.class, 46 | () -> parseNumber("abc").get()); 47 | } 48 | 49 | @Test 50 | public void completeExceptionallyWithCause() { 51 | try { 52 | parseNumber("abc").get(); 53 | fail("Should not get here"); 54 | } catch (Exception e) { 55 | assertEquals(ExecutionException.class, e.getClass()); 56 | assertEquals(NumberFormatException.class, e.getCause() 57 | .getClass()); 58 | } 59 | } 60 | 61 | @Test 62 | public void completeLong() throws Exception { 63 | assertEquals(42, (long) parseNumber("42").get()); 64 | } 65 | 66 | private CompletableFuture parseNumber(String arg) { 67 | CompletableFuture future = new CompletableFuture<>(); 68 | try { 69 | future.complete(Long.parseLong(arg)); 70 | } catch (Exception e) { 71 | future.completeExceptionally(e); 72 | } 73 | return future; 74 | } 75 | 76 | @Test 77 | @Disabled("problems with CI server") 78 | public void supplyThenAccept() { 79 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 80 | System.setOut(new PrintStream(baos)); 81 | 82 | CompletableFuture.supplyAsync(() -> "42") 83 | .thenApply(Integer::parseInt) 84 | .thenApply(x -> 2 * x) 85 | .thenAccept(System.out::println); 86 | System.out.println("Running..."); 87 | 88 | String result = baos.toString(); 89 | System.out.println(result); 90 | assertTrue(result.contains("84")); 91 | assertTrue(result.contains("Running...")); 92 | } 93 | 94 | @Test 95 | @Disabled("problems with CI server") 96 | public void supplyThenAcceptAsyncWithExecutor() { 97 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 98 | System.setOut(new PrintStream(baos)); 99 | 100 | ExecutorService service = Executors.newFixedThreadPool(4); 101 | CompletableFuture.supplyAsync(() -> "42", service) 102 | .thenApply(Integer::parseInt) 103 | .thenApply(x -> 2 * x) 104 | .thenAccept(System.out::println); 105 | System.out.println("Running..."); 106 | 107 | String result = baos.toString(); 108 | System.out.println(result); 109 | assertTrue(result.contains("84")); 110 | assertTrue(result.contains("Running...")); 111 | 112 | } 113 | 114 | @Test 115 | public void compose() throws Exception { 116 | int x = 2; 117 | int y = 3; 118 | CompletableFuture completableFuture = 119 | CompletableFuture.supplyAsync(() -> x) 120 | .thenCompose(n -> CompletableFuture.supplyAsync(() -> n + y)); 121 | 122 | assertEquals(5, (int) completableFuture.get()); 123 | } 124 | 125 | @Test 126 | public void combine() throws Exception { 127 | int x = 2; 128 | int y = 3; 129 | CompletableFuture completableFuture = 130 | CompletableFuture.supplyAsync(() -> x) 131 | .thenCombine(CompletableFuture.supplyAsync(() -> y), 132 | (n1, n2) -> n1 + n2); 133 | 134 | assertEquals(5, (int) completableFuture.get()); 135 | } 136 | 137 | private CompletableFuture getIntegerCompletableFuture(String num) { 138 | return CompletableFuture.supplyAsync(() -> Integer.parseInt(num)) 139 | .handle((val, exc) -> val != null ? val : 0); 140 | } 141 | 142 | @Test 143 | public void handleWithException() throws Exception { 144 | String num = "abc"; 145 | CompletableFuture value = getIntegerCompletableFuture(num); 146 | assertEquals(0, (int) value.get()); 147 | } 148 | 149 | @Test 150 | public void handleWithoutException() throws Exception { 151 | String num = "42"; 152 | CompletableFuture value = getIntegerCompletableFuture(num); 153 | assertEquals(42, (int) value.get()); 154 | } 155 | 156 | } 157 | -------------------------------------------------------------------------------- /src/main/java/got/InMemoryMemberDAO.java: -------------------------------------------------------------------------------- 1 | package got; 2 | 3 | import java.time.LocalDate; 4 | import java.time.Month; 5 | import java.util.*; 6 | import java.util.stream.Collectors; 7 | 8 | public class InMemoryMemberDAO implements MemberDAO { 9 | private static final MemberDAO dao = new InMemoryMemberDAO(); 10 | 11 | private final List allMembers = Arrays.asList( 12 | new Member(1L, Title.LORD, "Eddard", LocalDate.of(1959, Month.APRIL, 17), 100000.0, House.STARK), 13 | new Member(2L, Title.LADY, "Catelyn", LocalDate.of(1964, Month.JANUARY, 17), 80000.0, House.STARK), 14 | new Member(3L, Title.LADY, "Arya", LocalDate.of(1997, Month.APRIL, 15), 50000.0, House.STARK), 15 | new Member(4L, Title.LADY, "Sansa", LocalDate.of(1996, Month.FEBRUARY, 21), 60000.0, House.STARK), 16 | new Member(5L, Title.SIR, "Bran", LocalDate.of(1999, Month.APRIL, 9), 10000.0, House.STARK), 17 | new Member(6L, Title.KING, "Robb", LocalDate.of(1986, Month.JUNE, 18), 100000.0, House.STARK), 18 | new Member(7L, Title.KING, "Jon", LocalDate.of(1986, Month.DECEMBER, 26), 90000.0, House.SNOW), 19 | new Member(8L, Title.SIR, "Jaime", LocalDate.of(1970, Month.JULY, 27), 120000.0, House.LANNISTER), 20 | new Member(9L, Title.LORD, "Tyrion", LocalDate.of(1969, Month.JUNE, 11), 70000.0, House.LANNISTER), 21 | new Member(10L, Title.LORD, "Tywin", LocalDate.of(1946, Month.OCTOBER, 10), 200000.0, House.LANNISTER), 22 | new Member(11L, Title.LADY, "Cersei", LocalDate.of(1973, Month.OCTOBER, 3), 120000.0, House.LANNISTER), 23 | new Member(12L, Title.QUEEN, "Daenerys", LocalDate.of(1987, Month.MAY, 1), 130000.0, House.TARGARYEN), 24 | new Member(13L, Title.LORD, "Viserys", LocalDate.of(1983, Month.NOVEMBER, 17), 100000.0, House.TARGARYEN), 25 | new Member(14L, Title.KING, "Robert", LocalDate.of(1964, Month.JANUARY, 14), 180000.0, House.BARATHEON), 26 | new Member(15L, Title.KING, "Joffrey", LocalDate.of(1992, Month.MAY, 20), 100000.0, House.BARATHEON), 27 | new Member(16L, Title.KING, "Tommen", LocalDate.of(1997, Month.SEPTEMBER, 7), 60000.0, House.BARATHEON), 28 | new Member(17L, Title.KING, "Stannis", LocalDate.of(1957, Month.MARCH, 27), 123456.0, House.BARATHEON), 29 | new Member(18L, Title.QUEEN, "Margaery", LocalDate.of(1982, Month.FEBRUARY, 11), 80000.0, House.TYRELL), 30 | new Member(19L, Title.SIR, "Loras", LocalDate.of(1988, Month.MARCH, 24), 70000.0, House.TYRELL), 31 | new Member(20L, Title.LADY, "Olenna", LocalDate.of(1938, Month.JULY, 20), 130000.0, House.TYRELL), 32 | new Member(21L, Title.LORD, "Roose", LocalDate.of(1963, Month.SEPTEMBER, 12), 100000.0, House.BOLTON), 33 | new Member(22L, Title.LORD, "Ramsay", LocalDate.of(1985, Month.MAY, 13), 140000.0, House.BOLTON) 34 | ); 35 | 36 | private InMemoryMemberDAO() {} 37 | 38 | public static MemberDAO getInstance() { 39 | return dao; 40 | } 41 | 42 | @Override 43 | public Optional findById(Long id) { 44 | return allMembers.stream() 45 | .filter(member -> member.id().equals(id)) 46 | .findFirst(); 47 | } 48 | 49 | @Override 50 | public Optional findByName(String name) { 51 | return allMembers.stream() 52 | .filter(member -> member.name().equals(name)) 53 | .findFirst(); 54 | } 55 | 56 | @Override 57 | public List findAllByHouse(House house) { 58 | return allMembers.stream() 59 | .filter(member -> member.house().equals(house)) 60 | .collect(Collectors.toList()); 61 | } 62 | 63 | @Override 64 | public Collection getAll() { 65 | return Collections.unmodifiableCollection(allMembers); 66 | } 67 | 68 | @Override 69 | public List startWithSandSortAlphabetically() { 70 | return List.of(); 71 | } 72 | 73 | @Override 74 | public List lannisters_alphabeticallyByName() { 75 | return List.of(); 76 | } 77 | 78 | @Override 79 | public List salaryLessThanAndSortByHouse(double max) { 80 | return List.of(); 81 | } 82 | 83 | @Override 84 | public List sortByHouseNameThenSortByNameDesc() { 85 | return List.of(); 86 | } 87 | 88 | @Override 89 | public List houseByDob(House house) { 90 | return List.of(); 91 | } 92 | 93 | @Override 94 | public List kingsByNameDesc() { 95 | return List.of(); 96 | } 97 | 98 | @Override 99 | public double averageSalary() { 100 | return 0; 101 | } 102 | 103 | @Override 104 | public List namesSorted(House house) { 105 | return List.of(); 106 | } 107 | 108 | @Override 109 | public boolean salariesGreaterThan(double max) { 110 | return false; 111 | } 112 | 113 | @Override 114 | public boolean anyMembers(House house) { 115 | return false; 116 | } 117 | 118 | @Override 119 | public long howMany(House house) { 120 | return 0; 121 | } 122 | 123 | @Override 124 | public String houseMemberNames(House house) { 125 | return ""; 126 | } 127 | 128 | @Override 129 | public Optional highestSalary() { 130 | return Optional.empty(); 131 | } 132 | 133 | @Override 134 | public Map> royaltyPartition() { 135 | return Map.of(); 136 | } 137 | 138 | @Override 139 | public Map> membersByHouse() { 140 | return Map.of(); 141 | } 142 | 143 | @Override 144 | public Map numberOfMembersByHouse() { 145 | return Map.of(); 146 | } 147 | 148 | @Override 149 | public Map houseStats() { 150 | return Map.of(); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/test/java/lambdas/FunctionExercises.java: -------------------------------------------------------------------------------- 1 | package lambdas; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.function.*; 8 | import java.util.stream.Collectors; 9 | 10 | import static org.junit.jupiter.api.Assertions.*; 11 | 12 | public class FunctionExercises { 13 | 14 | @Test 15 | public void implementFunction() { 16 | // TODO: Create a Function that returns string length 17 | // Function stringLength = ... 18 | 19 | // assertEquals(5, stringLength.apply("Hello")); 20 | // assertEquals(0, stringLength.apply("")); 21 | // assertEquals(11, stringLength.apply("Lambda Test")); 22 | } 23 | 24 | @Test 25 | public void implementFunctionWithMethodReference() { 26 | // TODO: Create the same Function using a method reference 27 | // Function stringLength = ... 28 | 29 | // assertEquals(5, stringLength.apply("Hello")); 30 | // assertEquals(0, stringLength.apply("")); 31 | } 32 | 33 | @Test 34 | public void functionComposition() { 35 | // TODO: Create two functions and compose them 36 | // 1. Function to get string length 37 | // 2. Function to convert to binary string 38 | // 3. Compose them to create Function 39 | 40 | // Function stringLength = ... 41 | // Function toBinary = ... 42 | // Function lengthToBinary = ... 43 | 44 | // assertEquals("101", lengthToBinary.apply("Hello")); // 5 in binary 45 | // assertEquals("1000", lengthToBinary.apply("JavaTest")); // 8 in binary 46 | } 47 | 48 | @Test 49 | public void andThenVsCompose() { 50 | // TODO: Demonstrate the difference between andThen and compose 51 | 52 | Function multiplyBy2 = x -> x * 2; 53 | Function add10 = x -> x + 10; 54 | 55 | // TODO: Create composed functions using both andThen and compose 56 | // Function andThenResult = ... 57 | // Function composeResult = ... 58 | 59 | // With x = 5: 60 | // andThen: (5 * 2) + 10 = 20 61 | // compose: (5 + 10) * 2 = 30 62 | 63 | // assertEquals(20, andThenResult.apply(5)); 64 | // assertEquals(30, composeResult.apply(5)); 65 | } 66 | 67 | @Test 68 | public void bifunctionExample() { 69 | // TODO: Create a BiFunction that returns 70 | // the sum of the lengths of two strings 71 | 72 | // BiFunction sumOfLengths = ... 73 | 74 | // assertEquals(9, sumOfLengths.apply("Hello", "Java")); 75 | // assertEquals(7, sumOfLengths.apply("Hi", "World")); 76 | // assertEquals(0, sumOfLengths.apply("", "")); 77 | } 78 | 79 | @Test 80 | public void unaryOperatorExample() { 81 | // TODO: Create a UnaryOperator that converts to uppercase 82 | // Note: UnaryOperator is Function 83 | 84 | // UnaryOperator toUpperCase = ... 85 | 86 | // assertEquals("HELLO", toUpperCase.apply("hello")); 87 | // assertEquals("JAVA", toUpperCase.apply("Java")); 88 | } 89 | 90 | @Test 91 | public void binaryOperatorExample() { 92 | // TODO: Create a BinaryOperator that returns the maximum 93 | // Note: BinaryOperator is BiFunction 94 | 95 | // BinaryOperator max = ... 96 | 97 | // assertEquals(10, max.apply(5, 10)); 98 | // assertEquals(7, max.apply(7, 3)); 99 | // assertEquals(0, max.apply(0, 0)); 100 | } 101 | 102 | @Test 103 | public void functionInStreams() { 104 | List words = List.of("lambda", "function", "java", "stream"); 105 | 106 | // TODO: Use Function in map operations to: 107 | // 1. Get lengths of all words 108 | // 2. Convert all words to uppercase 109 | // 3. Get first character of each word 110 | 111 | // List lengths = ... 112 | // List upperCase = ... 113 | // List firstChars = ... 114 | 115 | // assertEquals(List.of(6, 8, 4, 6), lengths); 116 | // assertEquals(List.of("LAMBDA", "FUNCTION", "JAVA", "STREAM"), upperCase); 117 | // assertEquals(List.of('l', 'f', 'j', 's'), firstChars); 118 | } 119 | 120 | @Test 121 | public void customFunctionForComplexMapping() { 122 | List sentences = List.of( 123 | "Hello World", 124 | "Java Programming", 125 | "Functional Interfaces" 126 | ); 127 | 128 | // TODO: Create a Function that maps each sentence to a Map 129 | // where keys are words and values are word lengths 130 | 131 | // Function> sentenceToWordLengths = ... 132 | 133 | // List> results = sentences.stream() 134 | // .map(sentenceToWordLengths) 135 | // .collect(Collectors.toList()); 136 | 137 | // assertEquals(2, results.get(0).size()); 138 | // assertEquals(5, results.get(0).get("Hello")); 139 | // assertEquals(5, results.get(0).get("World")); 140 | } 141 | 142 | @Test 143 | public void chainingMultipleFunctions() { 144 | // TODO: Create a chain of functions to process data: 145 | // 1. Parse string to integer 146 | // 2. Double the value 147 | // 3. Convert to hex string 148 | 149 | // Function parseInt = ... 150 | // Function doubleIt = ... 151 | // Function toHex = ... 152 | // Function parseDoubleToHex = ... 153 | 154 | // assertEquals("a", parseDoubleToHex.apply("5")); // 5 * 2 = 10 = 0xa 155 | // assertEquals("20", parseDoubleToHex.apply("16")); // 16 * 2 = 32 = 0x20 156 | } 157 | } -------------------------------------------------------------------------------- /src/test/java/lambdas/RunnableExercises.java: -------------------------------------------------------------------------------- 1 | package lambdas; 2 | 3 | import org.junit.jupiter.api.AfterEach; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.util.concurrent.CountDownLatch; 8 | import java.util.concurrent.ExecutorService; 9 | import java.util.concurrent.Executors; 10 | import java.util.concurrent.TimeUnit; 11 | import java.util.concurrent.atomic.AtomicReference; 12 | 13 | import static org.junit.jupiter.api.Assertions.*; 14 | 15 | public class RunnableExercises { 16 | private ExecutorService executorService; 17 | 18 | @BeforeEach 19 | public void setUp() { 20 | executorService = Executors.newFixedThreadPool(4); 21 | } 22 | 23 | @AfterEach 24 | public void tearDown() throws InterruptedException { 25 | executorService.shutdown(); 26 | executorService.awaitTermination(1, TimeUnit.SECONDS); 27 | } 28 | 29 | @Test 30 | public void implementRunnableAsAnonymousInnerClass() throws InterruptedException { 31 | // TODO: Submit a Runnable using an anonymous inner class 32 | // The run method should set the result to "Anonymous Inner Class" 33 | 34 | AtomicReference result = new AtomicReference<>(""); 35 | CountDownLatch latch = new CountDownLatch(1); 36 | 37 | // executorService.submit(new Runnable() { 38 | // @Override 39 | // public void run() { 40 | // // Set result and count down latch 41 | // } 42 | // }); 43 | 44 | // assertTrue(latch.await(1, TimeUnit.SECONDS)); 45 | // assertEquals("Anonymous Inner Class", result.get()); 46 | } 47 | 48 | @Test 49 | public void implementRunnableAsExpressionLambda() throws InterruptedException { 50 | // TODO: Submit the same Runnable using an expression lambda 51 | 52 | AtomicReference result = new AtomicReference<>(""); 53 | CountDownLatch latch = new CountDownLatch(1); 54 | 55 | // executorService.submit(() -> ...); 56 | 57 | // assertTrue(latch.await(1, TimeUnit.SECONDS)); 58 | // assertEquals("Expression Lambda", result.get()); 59 | } 60 | 61 | @Test 62 | public void implementRunnableAsBlockLambda() throws InterruptedException { 63 | // TODO: Submit a Runnable using a block lambda that: 64 | // 1. Stores the current thread name 65 | // 2. Sets the result to "Block Lambda" 66 | 67 | AtomicReference result = new AtomicReference<>(""); 68 | AtomicReference threadName = new AtomicReference<>(""); 69 | CountDownLatch latch = new CountDownLatch(1); 70 | 71 | // executorService.submit(() -> { 72 | // // Store thread name 73 | // // Set result 74 | // // Count down latch 75 | // }); 76 | 77 | // assertTrue(latch.await(1, TimeUnit.SECONDS)); 78 | // assertEquals("Block Lambda", result.get()); 79 | // assertTrue(threadName.get().startsWith("pool-")); 80 | } 81 | 82 | @Test 83 | public void assignLambdaToVariable() throws InterruptedException { 84 | // TODO: Create a lambda, assign it to a Runnable variable, then submit it 85 | 86 | AtomicReference result = new AtomicReference<>(""); 87 | CountDownLatch latch = new CountDownLatch(1); 88 | 89 | // Runnable task = ... 90 | // executorService.submit(task); 91 | 92 | // assertTrue(latch.await(1, TimeUnit.SECONDS)); 93 | // assertEquals("Assigned to Variable", result.get()); 94 | } 95 | 96 | @Test 97 | public void useMethodReference() throws InterruptedException { 98 | // TODO: Submit a Runnable using a method reference 99 | 100 | AtomicReference result = new AtomicReference<>(""); 101 | CountDownLatch latch = new CountDownLatch(1); 102 | 103 | // Create an instance method that can be used as a method reference 104 | Runnable methodRefExample = new Runnable() { 105 | @Override 106 | public void run() { 107 | result.set("Method Reference"); 108 | latch.countDown(); 109 | } 110 | }; 111 | 112 | // TODO: Submit using method reference instead of lambda 113 | // executorService.submit(methodRefExample::run); 114 | 115 | // assertTrue(latch.await(1, TimeUnit.SECONDS)); 116 | // assertEquals("Method Reference", result.get()); 117 | } 118 | 119 | @Test 120 | public void submitMultipleTasks() throws InterruptedException { 121 | // TODO: Submit multiple Runnables and wait for all to complete 122 | 123 | CountDownLatch latch = new CountDownLatch(3); 124 | StringBuilder result = new StringBuilder(); 125 | 126 | // TODO: Submit three tasks that append "First", "Second", "Third" to result 127 | // Note: Use synchronization since StringBuilder isn't thread-safe 128 | 129 | // executorService.submit(() -> { 130 | // synchronized (result) { 131 | // result.append("First"); 132 | // } 133 | // latch.countDown(); 134 | // }); 135 | 136 | // assertTrue(latch.await(1, TimeUnit.SECONDS)); 137 | // String finalResult = result.toString(); 138 | // assertTrue(finalResult.contains("First")); 139 | // assertTrue(finalResult.contains("Second")); 140 | // assertTrue(finalResult.contains("Third")); 141 | } 142 | 143 | @Test 144 | public void virtualThreadsDemo() throws InterruptedException { 145 | // TODO: If using Java 21+, create an executor with virtual threads 146 | // Otherwise, skip this test 147 | 148 | // Skip if not Java 21+ 149 | String javaVersion = System.getProperty("java.version"); 150 | if (!javaVersion.startsWith("21") && !javaVersion.startsWith("22") && !javaVersion.startsWith("23")) { 151 | return; 152 | } 153 | 154 | AtomicReference result = new AtomicReference<>(""); 155 | CountDownLatch latch = new CountDownLatch(1); 156 | 157 | // TODO: Create virtual thread executor 158 | // try (ExecutorService virtualExecutor = Executors.newVirtualThreadPerTaskExecutor()) { 159 | // virtualExecutor.submit(() -> { 160 | // result.set("Virtual Thread: " + Thread.currentThread()); 161 | // latch.countDown(); 162 | // }); 163 | // } 164 | 165 | // assertTrue(latch.await(1, TimeUnit.SECONDS)); 166 | // assertTrue(result.get().contains("Virtual")); 167 | } 168 | } -------------------------------------------------------------------------------- /src/test/java/streams/ParallelStreamExercises.java: -------------------------------------------------------------------------------- 1 | package streams; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.*; 6 | import java.util.concurrent.ConcurrentHashMap; 7 | import java.util.concurrent.ThreadLocalRandom; 8 | import java.util.concurrent.atomic.AtomicInteger; 9 | import java.util.stream.IntStream; 10 | import java.util.stream.Stream; 11 | 12 | import static org.junit.jupiter.api.Assertions.*; 13 | 14 | public class ParallelStreamExercises { 15 | 16 | @Test 17 | public void basicParallelStream() { 18 | // TODO: Convert the stream to parallel and observe thread names 19 | 20 | // Stream.of("apple", "banana", "cherry", "date", "elderberry") 21 | // .parallel() 22 | // .forEach(fruit -> System.out.println( 23 | // Thread.currentThread().getName() + ": " + fruit)); 24 | } 25 | 26 | @Test 27 | public void sequentialVsParallelOrder() { 28 | List sequential = new ArrayList<>(); 29 | List parallel = new ArrayList<>(); 30 | 31 | // TODO: Compare order of elements in sequential vs parallel streams 32 | 33 | // IntStream.range(0, 10) 34 | // .forEach(sequential::add); 35 | 36 | // IntStream.range(0, 10) 37 | // .parallel() 38 | // .forEach(parallel::add); 39 | 40 | // System.out.println("Sequential: " + sequential); 41 | // System.out.println("Parallel: " + parallel); 42 | // assertNotEquals(sequential, parallel); // Order likely different 43 | } 44 | 45 | @Test 46 | public void forEachOrderedMaintainsOrder() { 47 | List parallelOrdered = new ArrayList<>(); 48 | 49 | // TODO: Use forEachOrdered to maintain order with parallel stream 50 | 51 | // IntStream.range(0, 10) 52 | // .parallel() 53 | // .forEachOrdered(parallelOrdered::add); 54 | 55 | // assertEquals(List.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9), parallelOrdered); 56 | } 57 | 58 | @Test 59 | public void parallelReduction() { 60 | // TODO: Sum numbers using parallel stream 61 | 62 | // int sum = IntStream.rangeClosed(1, 1000) 63 | // .parallel() 64 | // .sum(); 65 | 66 | // assertEquals(500500, sum); 67 | } 68 | 69 | @Test 70 | public void findAnyVsFindFirst() { 71 | // TODO: Demonstrate difference between findFirst and findAny with parallel 72 | 73 | // Optional first = IntStream.range(0, 1000) 74 | // .parallel() 75 | // .filter(i -> i > 950) 76 | // .findFirst(); 77 | 78 | // Optional any = IntStream.range(0, 1000) 79 | // .parallel() 80 | // .filter(i -> i > 950) 81 | // .findAny(); 82 | 83 | // assertEquals(951, first.orElse(-1)); 84 | // assertTrue(any.orElse(-1) > 950); // Could be any matching value 85 | } 86 | 87 | @Test 88 | public void statefulOperationProblem() { 89 | // This demonstrates why stateful operations can be problematic 90 | List numbers = List.of(1, 2, 3, 4, 5); 91 | 92 | // Bad practice - don't do this! 93 | List doubledWrong = new ArrayList<>(); 94 | 95 | // TODO: Show the problem with stateful operation in parallel 96 | // Note: This is an anti-pattern! 97 | 98 | // numbers.parallelStream() 99 | // .map(n -> n * 2) 100 | // .forEach(doubledWrong::add); // Race condition! 101 | 102 | // Not guaranteed to have all elements or correct order 103 | // System.out.println("Wrong way: " + doubledWrong); 104 | 105 | // TODO: Show the correct way using collect 106 | 107 | // List doubledRight = numbers.parallelStream() 108 | // .map(n -> n * 2) 109 | // .collect(Collectors.toList()); 110 | 111 | // assertEquals(List.of(2, 4, 6, 8, 10), doubledRight); 112 | } 113 | 114 | @Test 115 | public void threadSafeCollector() { 116 | // TODO: Use a thread-safe collector for parallel operations 117 | 118 | // Map wordLengths = Stream.of( 119 | // "apple", "banana", "cherry", "date", "elderberry") 120 | // .parallel() 121 | // .collect(Collectors.toConcurrentMap( 122 | // word -> word, 123 | // String::length)); 124 | 125 | // assertEquals(5, wordLengths.get("apple")); 126 | // assertEquals(6, wordLengths.get("banana")); 127 | } 128 | 129 | @Test 130 | public void customThreadSafeAccumulator() { 131 | // TODO: Count elements using thread-safe accumulator 132 | 133 | // AtomicInteger count = new AtomicInteger(); 134 | 135 | // IntStream.range(0, 1000) 136 | // .parallel() 137 | // .forEach(i -> count.incrementAndGet()); 138 | 139 | // assertEquals(1000, count.get()); 140 | } 141 | 142 | @Test 143 | public void parallelStreamFromCollection() { 144 | List fruits = List.of( 145 | "apple", "banana", "cherry", "date", "elderberry", 146 | "fig", "grape", "honeydew", "kiwi", "lemon" 147 | ); 148 | 149 | // TODO: Check if stream is parallel by default 150 | // assertFalse(fruits.stream().isParallel()); 151 | 152 | // TODO: Create parallel stream from collection 153 | // assertTrue(fruits.parallelStream().isParallel()); 154 | 155 | // TODO: Convert sequential to parallel 156 | // assertTrue(fruits.stream().parallel().isParallel()); 157 | 158 | // TODO: Convert parallel back to sequential 159 | // assertFalse(fruits.parallelStream().sequential().isParallel()); 160 | } 161 | 162 | @Test 163 | public void parallelPerformanceConsiderations() { 164 | // Small datasets often perform worse with parallel streams 165 | // due to thread management overhead 166 | 167 | // TODO: Sum small dataset - parallel might be slower! 168 | List smallData = IntStream.range(0, 100) 169 | .boxed() 170 | .toList(); 171 | 172 | // int sum1 = smallData.stream() 173 | // .mapToInt(Integer::intValue) 174 | // .sum(); 175 | 176 | // int sum2 = smallData.parallelStream() 177 | // .mapToInt(Integer::intValue) 178 | // .sum(); 179 | 180 | // assertEquals(sum1, sum2); 181 | 182 | // Note: With small datasets, sequential is often faster! 183 | // Parallel streams shine with: 184 | // - Large datasets (thousands/millions of elements) 185 | // - CPU-intensive operations 186 | // - Independent operations (no shared state) 187 | } 188 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Functional Programming in Java 2 | 3 | A comprehensive training course covering modern Java features from Java 8 and beyond, with a focus on functional programming techniques. 4 | 5 | **Instructor:** Ken Kousen 6 | **Created:** July 2016 7 | **Last Updated:** May 2025 8 | 9 | ## Course Overview 10 | 11 | This course teaches developers how to use the functional programming features introduced in Java 8 and enhanced in subsequent versions. Students will learn to use lambda expressions, method references, streams, and other modern Java features to write more expressive and maintainable code. 12 | 13 | ## Prerequisites 14 | 15 | - Solid understanding of Java fundamentals 16 | - Experience with object-oriented programming 17 | - Java 8 or later installed (Java 17+ recommended) 18 | 19 | ## Repository Structure 20 | 21 | ``` 22 | java_upgrade/ 23 | ├── src/ 24 | │ ├── main/java/ # Demo code and examples 25 | │ │ ├── concurrency/ # CompletableFuture demonstrations 26 | │ │ ├── datetime/ # Date/time API examples 27 | │ │ ├── got/ # Game of Thrones data model 28 | │ │ ├── interfaces/ # Interface evolution examples 29 | │ │ ├── io/ # I/O processing examples 30 | │ │ ├── lambdas/ # Lambda expression demos 31 | │ │ ├── lazy/ # Lazy evaluation patterns 32 | │ │ ├── optional/ # Optional usage examples 33 | │ │ ├── sorting/ # Comparator and sorting demos 34 | │ │ └── streams/ # Stream operation examples 35 | │ └── test/java/ # Hands-on exercises 36 | │ ├── concurrency/ # CompletableFuture exercises 37 | │ ├── got/ # Advanced stream exercises 38 | │ ├── interfaces/ # Interface implementation 39 | │ ├── lambdas/ # Functional interface exercises 40 | │ ├── optional/ # Optional pattern exercises 41 | │ ├── sorting/ # Comparator exercises 42 | │ └── streams/ # Stream operation exercises 43 | ├── labs.md # Comprehensive exercise guide 44 | ├── CLAUDE.md # Project context for Claude Code 45 | └── Java_Upgrade_Slides.pdf # Course presentation slides 46 | ``` 47 | 48 | ## Topics Covered 49 | 50 | ### 1. Lambda Expressions and Functional Interfaces 51 | - Consumer, Supplier, Predicate, and Function interfaces 52 | - Lambda expression syntax and evolution 53 | - Method references and constructor references 54 | - Functional interface composition 55 | - Lazy evaluation patterns with Supplier 56 | 57 | ### 2. Stream API 58 | - Creating and transforming streams 59 | - Filtering, mapping, and reducing operations 60 | - Collectors and advanced aggregations 61 | - FlatMap for nested structures 62 | - Parallel stream processing 63 | - Performance considerations 64 | 65 | ### 3. Optional 66 | - Optional creation and usage patterns 67 | - Chaining operations with map and flatMap 68 | - Best practices and anti-patterns 69 | - Integration with DAO patterns 70 | 71 | ### 4. Modern Comparators 72 | - Comparator.comparing() and thenComparing() 73 | - Reverse ordering and custom comparisons 74 | - Sorting with records 75 | - Partitioning and grouping 76 | 77 | ### 5. Date/Time API 78 | - LocalDate, LocalTime, and LocalDateTime 79 | - Time zones and ZonedDateTime 80 | - Duration and Period calculations 81 | - Formatting and parsing 82 | 83 | ### 6. CompletableFuture 84 | - Asynchronous programming patterns 85 | - Chaining and combining futures 86 | - Exception handling in async code 87 | - Virtual threads (Java 21+) 88 | 89 | ### 7. Interface Evolution 90 | - Default and static methods 91 | - Multiple inheritance considerations 92 | - Functional interfaces in the JDK 93 | 94 | ### 8. Advanced Topics 95 | - Records as data carriers 96 | - BigDecimal operations with streams 97 | - Parallel processing best practices 98 | 99 | ## Exercises 100 | 101 | The course includes 20 comprehensive exercises covering all major topics: 102 | 103 | 1. **Lambda Expressions** (Exercises 1-8) 104 | - Functional interface implementations 105 | - Method reference patterns 106 | - Lambda evolution examples 107 | - Lazy evaluation 108 | 109 | 2. **Stream Operations** (Exercises 9-15) 110 | - Basic stream operations 111 | - Advanced collectors 112 | - Parallel streams 113 | - BigDecimal reduce operations 114 | 115 | 3. **Advanced Features** (Exercises 16-20) 116 | - Optional patterns 117 | - CompletableFuture 118 | - Interface implementation 119 | - Complex stream operations 120 | 121 | See [labs.md](labs.md) for detailed exercise instructions. 122 | 123 | ## Running the Exercises 124 | 125 | ### Using Gradle 126 | 127 | ```bash 128 | # Run all tests 129 | ./gradlew test 130 | 131 | # Run specific test class 132 | ./gradlew test --tests "lambdas.FunctionalInterfacesTest" 133 | 134 | # Run specific test method 135 | ./gradlew test --tests "lambdas.FunctionalInterfacesTest.implementConsumerUsingLambda" 136 | ``` 137 | 138 | ### Using Your IDE 139 | 140 | Most modern IDEs (IntelliJ IDEA, Eclipse, VS Code) support running JUnit tests directly: 141 | 1. Open the test file 142 | 2. Click the run button next to the test method or class 143 | 3. View results in the test runner panel 144 | 145 | ## Branches 146 | 147 | - `main` - Contains exercises with TODO comments for students 148 | - `solutions` - Contains complete solutions for all exercises 149 | - Training branches (e.g., `java_jan2025`) - Course-specific snapshots 150 | 151 | ## Getting Started 152 | 153 | 1. Clone the repository: 154 | ```bash 155 | git clone https://github.com/kousen/java_upgrade.git 156 | cd java_upgrade 157 | ``` 158 | 159 | 2. Ensure Java is installed: 160 | ```bash 161 | java -version # Should show Java 17 or later 162 | ``` 163 | 164 | 3. Build the project: 165 | ```bash 166 | ./gradlew build 167 | ``` 168 | 169 | 4. Open [labs.md](labs.md) and start with Exercise 1 170 | 171 | 5. Run tests to verify your solutions: 172 | ```bash 173 | ./gradlew test 174 | ``` 175 | 176 | ## Additional Resources 177 | 178 | - **Course Slides**: `Java_Upgrade_Slides.pdf` (dated, but still useful) 179 | - **Demo Code**: Browse `src/main/java` for working examples 180 | - **Solutions**: Check out the `solutions` branch to see completed exercises 181 | - **Java Documentation**: [Official Java Documentation](https://docs.oracle.com/en/java/) 182 | 183 | ## Author 184 | 185 | **Ken Kousen** 186 | - Email: ken.kousen@kousenit.com 187 | - Bluesky: [@kousenit.com](https://bsky.app/profile/kousenit.com) 188 | - LinkedIn: [Ken Kousen](https://www.linkedin.com/in/kenkousen/) 189 | - Website: [www.kousenit.com](http://www.kousenit.com) 190 | 191 | ## Books by the Author 192 | 193 | - *Mockito Made Clear* (Pragmatic Bookshelf, 2023) 194 | - *Help Your Boss Help You* (Pragmatic Bookshelf, 2021) 195 | - *Kotlin Cookbook* (O'Reilly, 2019) 196 | - *Modern Java Recipes* (O'Reilly, 2017) 197 | - *Gradle Recipes for Android* (O'Reilly, 2016) 198 | - *Making Java Groovy* (Manning, 2013) 199 | 200 | ## License 201 | 202 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 203 | 204 | ## Acknowledgments 205 | 206 | Special thanks to all students and organizations who have taken this course and provided valuable feedback to improve the material. 207 | 208 | --- 209 | 210 | *Note: This is an active training repository. Content is regularly updated to reflect the latest Java features and best practices.* -------------------------------------------------------------------------------- /src/test/java/sorting/BookSortingExercises.java: -------------------------------------------------------------------------------- 1 | package sorting; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.time.Year; 7 | import java.util.*; 8 | import java.util.stream.Collectors; 9 | 10 | import static org.junit.jupiter.api.Assertions.*; 11 | 12 | record Book(String title, String author, Year publishedYear, String isbn) implements Comparable { 13 | 14 | @Override 15 | public int compareTo(Book other) { 16 | // Default comparison by publication year 17 | return this.publishedYear.compareTo(other.publishedYear); 18 | } 19 | } 20 | 21 | public class BookSortingExercises { 22 | 23 | private List books; 24 | 25 | @BeforeEach 26 | public void setUp() { 27 | books = List.of( 28 | new Book("The Pragmatic Programmer", "David Thomas", Year.of(1999), "978-0201616224"), 29 | new Book("Modern Java Recipes", "Ken Kousen", Year.of(2017), "978-1491973172"), 30 | new Book("Effective Java", "Joshua Bloch", Year.of(2018), "978-0134685991"), 31 | new Book("Java Concurrency in Practice", "Brian Goetz", Year.of(2006), "978-0321349606"), 32 | new Book("Design Patterns", "Erich Gamma", Year.of(1994), "978-0201633610"), 33 | new Book("Refactoring", "Martin Fowler", Year.of(2019), "978-0134757599"), 34 | new Book("Mockito Made Clear", "Ken Kousen", Year.of(2023), "978-1680509632"), 35 | new Book("Head First Design Patterns", "Eric Freeman", Year.of(2004), "978-0596007126") 36 | ); 37 | } 38 | 39 | @Test 40 | public void sortByDefaultOrdering() { 41 | // TODO: Sort books by their natural ordering (publication year) 42 | // Hint: Use stream().sorted() 43 | 44 | // List sortedBooks = books.stream() 45 | // .sorted() 46 | // .toList(); 47 | 48 | // assertEquals(Year.of(1994), sortedBooks.get(0).publishedYear()); 49 | // assertEquals(Year.of(2023), sortedBooks.get(sortedBooks.size() - 1).publishedYear()); 50 | } 51 | 52 | @Test 53 | public void sortByTitle() { 54 | // TODO: Sort books alphabetically by title 55 | // Hint: Use Comparator.comparing() 56 | 57 | // List sortedByTitle = books.stream() 58 | // .sorted(Comparator.comparing(...)) 59 | // .toList(); 60 | 61 | // assertEquals("Design Patterns", sortedByTitle.get(0).title()); 62 | // assertEquals("The Pragmatic Programmer", sortedByTitle.get(sortedByTitle.size() - 1).title()); 63 | } 64 | 65 | @Test 66 | public void sortByAuthorThenTitle() { 67 | // TODO: Sort by author, then by title for books by the same author 68 | // Hint: Use Comparator.comparing().thenComparing() 69 | 70 | // List sortedByAuthorTitle = books.stream() 71 | // .sorted(Comparator.comparing(Book::author) 72 | // .thenComparing(...)) 73 | // .toList(); 74 | 75 | // Test that books are sorted by author 76 | // assertEquals("Brian Goetz", sortedByAuthorTitle.get(1).author()); 77 | // assertEquals("Martin Fowler", sortedByAuthorTitle.get(sortedByAuthorTitle.size() - 2).author()); 78 | } 79 | 80 | @Test 81 | public void sortByYearDescending() { 82 | // TODO: Sort by publication year in descending order (newest first) 83 | // Hint: Use Comparator.reversed() 84 | 85 | // List newestFirst = books.stream() 86 | // .sorted(...) 87 | // .toList(); 88 | 89 | // assertEquals(Year.of(2023), newestFirst.get(0).publishedYear()); 90 | // assertEquals(Year.of(1994), newestFirst.get(newestFirst.size() - 1).publishedYear()); 91 | } 92 | 93 | @Test 94 | public void sortByTitleLength() { 95 | // TODO: Sort by the length of the title (shortest first) 96 | // Hint: Use Comparator.comparingInt() 97 | 98 | // List byTitleLength = books.stream() 99 | // .sorted(Comparator.comparingInt(...)) 100 | // .toList(); 101 | 102 | // assertTrue(byTitleLength.get(0).title().length() <= byTitleLength.get(1).title().length()); 103 | } 104 | 105 | @Test 106 | public void partitionByPublicationYear() { 107 | // TODO: Partition books into those published before 2010 and after 108 | // Hint: Use Collectors.partitioningBy() 109 | 110 | // Map> partitioned = books.stream() 111 | // .collect(Collectors.partitioningBy( 112 | // book -> ...)); 113 | 114 | // List older = partitioned.get(true); 115 | // List newer = partitioned.get(false); 116 | 117 | // assertTrue(older.stream().allMatch(book -> book.publishedYear().getValue() < 2010)); 118 | // assertTrue(newer.stream().allMatch(book -> book.publishedYear().getValue() >= 2010)); 119 | } 120 | 121 | @Test 122 | public void groupByDecade() { 123 | // TODO: Group books by the decade they were published 124 | // Hint: Use Collectors.groupingBy() with year/10*10 125 | 126 | // Map> byDecade = books.stream() 127 | // .collect(Collectors.groupingBy( 128 | // book -> ...)); 129 | 130 | // assertEquals(2, byDecade.get(1990).size()); // 1990s 131 | // assertEquals(3, byDecade.get(2000).size()); // 2000s 132 | // assertEquals(3, byDecade.get(2010).size()); // 2010s 133 | // assertEquals(1, byDecade.get(2020).size()); // 2020s 134 | } 135 | 136 | @Test 137 | public void customComparatorChain() { 138 | // TODO: Create a complex sort: by publication year descending, 139 | // then by author ascending, then by title ascending 140 | 141 | // List complexSort = books.stream() 142 | // .sorted(Comparator.comparing(Book::publishedYear).reversed() 143 | // .thenComparing(...) 144 | // .thenComparing(...)) 145 | // .toList(); 146 | 147 | // Verify the sort worked correctly 148 | // Book first = complexSort.get(0); 149 | // Book second = complexSort.get(1); 150 | // assertTrue(first.publishedYear().getValue() >= second.publishedYear().getValue()); 151 | } 152 | 153 | @Test 154 | public void findBooksInYearRange() { 155 | // TODO: Find all books published between 2000 and 2010 (inclusive) 156 | // and sort them by title 157 | 158 | // List booksInRange = books.stream() 159 | // .filter(book -> ...) 160 | // .sorted(...) 161 | // .toList(); 162 | 163 | // assertEquals(3, booksInRange.size()); 164 | // assertTrue(booksInRange.stream() 165 | // .allMatch(book -> book.publishedYear().getValue() >= 2000 166 | // && book.publishedYear().getValue() <= 2010)); 167 | } 168 | 169 | @Test 170 | public void findKenKousenBooks() { 171 | // TODO: Find all books by Ken Kousen and sort by publication year 172 | 173 | // List kenBooks = books.stream() 174 | // .filter(book -> ...) 175 | // .sorted(...) 176 | // .toList(); 177 | 178 | // assertEquals(2, kenBooks.size()); 179 | // assertEquals("Modern Java Recipes", kenBooks.get(0).title()); 180 | // assertEquals("Mockito Made Clear", kenBooks.get(1).title()); 181 | } 182 | } -------------------------------------------------------------------------------- /src/test/java/got/InMemoryMemberDAOTests.java: -------------------------------------------------------------------------------- 1 | package got; 2 | 3 | import org.junit.jupiter.api.Disabled; 4 | import org.junit.jupiter.api.DisplayNameGeneration; 5 | import org.junit.jupiter.api.DisplayNameGenerator; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import java.util.*; 9 | 10 | import static org.assertj.core.api.Assertions.*; 11 | import static org.junit.jupiter.api.Assertions.assertAll; 12 | 13 | @Disabled("Remove this line to run tests") 14 | @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) 15 | public class InMemoryMemberDAOTests { 16 | private final MemberDAO dao = InMemoryMemberDAO.getInstance(); 17 | 18 | @Test 19 | void findById() { 20 | Optional member = dao.findById(1L); 21 | assertThat(member).isPresent(); 22 | assertThat(member.get().name()).isEqualTo("Eddard"); 23 | } 24 | 25 | @Test 26 | void findById_notFound() { 27 | Optional member = dao.findById(100L); 28 | assertThat(member).isEmpty(); 29 | } 30 | 31 | @Test 32 | void findByName() { 33 | Optional member = dao.findByName("Eddard"); 34 | assertThat(member).isPresent(); 35 | assertThat(member.get().id()).isEqualTo(1L); 36 | } 37 | 38 | @Test 39 | void findByName_notFound() { 40 | Optional member = dao.findByName("Ned"); 41 | assertThat(member).isEmpty(); 42 | } 43 | 44 | @Test 45 | void findAllByHouse() { 46 | List members = dao.findAllByHouse(House.STARK); 47 | assertThat(members).hasSize(6) 48 | .allMatch(member -> member.house() == House.STARK); 49 | } 50 | 51 | @Test 52 | void findAllByHouse_notFound() { 53 | List members = dao.findAllByHouse(House.GREYJOY); 54 | assertThat(members).isEmpty(); 55 | } 56 | 57 | @Test 58 | void getAll() { 59 | Collection members = dao.getAll(); 60 | assertThat(members).hasSize(22); 61 | } 62 | 63 | 64 | @Test 65 | public void startWithS_sortAlphabetically() { 66 | List members = dao.startWithSandSortAlphabetically(); 67 | 68 | assertAll( 69 | () -> assertThat(members.get(0).name()).isEqualTo("Sansa"), 70 | () -> assertThat(members.get(1).name()).isEqualTo("Stannis") 71 | ); 72 | } 73 | 74 | @Test 75 | public void lannisters_alphabeticallyByName() { 76 | List members = dao.lannisters_alphabeticallyByName(); 77 | 78 | List names = members.stream() 79 | .map(Member::name) 80 | .toList(); 81 | 82 | assertThat(names).containsExactly("Cersei", "Jaime", "Tyrion", "Tywin"); 83 | } 84 | 85 | @Test 86 | public void salaryLessThan_sortByHouse() { 87 | List members = dao.salaryLessThanAndSortByHouse(80000.0); 88 | 89 | assertThat(members).hasSize(6) 90 | .allMatch(member -> member.salary() < 80000.0); 91 | 92 | List houses = members.stream() 93 | .map(Member::house) 94 | .distinct() 95 | .toList(); 96 | 97 | assertThat(houses).containsExactly( 98 | House.BARATHEON, House.LANNISTER, House.STARK, House.TYRELL); 99 | } 100 | 101 | @Test 102 | public void sortByHouseName_sortByNameDesc() { 103 | List members = dao.sortByHouseNameThenSortByNameDesc(); 104 | 105 | assertThat(members).hasSize(22); 106 | } 107 | 108 | @Test 109 | public void starksByDob() { 110 | List members = dao.houseByDob(House.STARK); 111 | 112 | assertThat(members).hasSize(6) 113 | .allMatch(member -> member.house() == House.STARK); 114 | } 115 | 116 | @Test 117 | public void kingsByNameDesc() { 118 | List members = dao.kingsByNameDesc(); 119 | 120 | assertThat(members).hasSize(6) 121 | .allMatch(member -> member.title() == Title.KING); 122 | } 123 | 124 | @Test 125 | public void averageSalary() { 126 | double averageSalary = dao.averageSalary(); 127 | 128 | assertThat(averageSalary).isCloseTo(100611.64, within(0.1)); 129 | } 130 | 131 | @Test 132 | public void namesSorted() { 133 | List names = dao.namesSorted(House.STARK); 134 | 135 | assertThat(names).hasSize(6) 136 | .containsExactly("Arya", "Bran", "Catelyn", "Eddard", "Robb", "Sansa"); 137 | } 138 | 139 | @Test 140 | public void salariesGT100k() { 141 | assertThat(dao.salariesGreaterThan(100000.0)).isTrue(); 142 | } 143 | 144 | @Test 145 | public void greyjoys() { 146 | assertThat(dao.anyMembers(House.GREYJOY)).isFalse(); 147 | } 148 | 149 | @Test 150 | public void howManyLannisters() { 151 | long count = dao.howMany(House.LANNISTER); 152 | assertThat(count).isEqualTo(4); 153 | } 154 | 155 | @Test 156 | public void lannisterNames() { 157 | String lannisterNames = dao.houseMemberNames(House.LANNISTER); 158 | assertThat(lannisterNames).isEqualTo("Jaime, Tyrion, Tywin, Cersei"); 159 | } 160 | 161 | @Test 162 | public void highestSalary() { 163 | Optional member = dao.highestSalary(); 164 | 165 | assertThat(member).isPresent(); 166 | assertThat(member.get().name()).isEqualTo("Tywin"); 167 | } 168 | 169 | @Test 170 | public void royalty_or_not() { 171 | Map> map = dao.royaltyPartition(); 172 | assertAll( 173 | () -> assertThat(map.get(true)).hasSize(8), 174 | () -> assertThat(map.get(false)).hasSize(14) 175 | ); 176 | } 177 | 178 | @Test 179 | public void membersByHouse() { 180 | Map> houseListMap = dao.membersByHouse(); 181 | assertAll( 182 | () -> assertThat(houseListMap.get(House.STARK)).hasSize(6), 183 | () -> assertThat(houseListMap.get(House.LANNISTER)).hasSize(4), 184 | () -> assertThat(houseListMap.get(House.TARGARYEN)).hasSize(2), 185 | () -> assertThat(houseListMap.get(House.BARATHEON)).hasSize(4), 186 | () -> assertThat(houseListMap.get(House.TYRELL)).hasSize(3), 187 | () -> assertThat(houseListMap.get(House.BOLTON)).hasSize(2), 188 | () -> assertThat(houseListMap.get(House.SNOW)).hasSize(1) 189 | ); 190 | } 191 | 192 | @Test 193 | public void numberOfMembersByHouse() { 194 | Map memberCountByHouse = dao.numberOfMembersByHouse(); 195 | assertAll( 196 | () -> assertThat(memberCountByHouse.get(House.STARK)).isEqualTo(6), 197 | () -> assertThat(memberCountByHouse.get(House.LANNISTER)).isEqualTo(4), 198 | () -> assertThat(memberCountByHouse.get(House.TARGARYEN)).isEqualTo(2), 199 | () -> assertThat(memberCountByHouse.get(House.BARATHEON)).isEqualTo(4), 200 | () -> assertThat(memberCountByHouse.get(House.TYRELL)).isEqualTo(3), 201 | () -> assertThat(memberCountByHouse.get(House.BOLTON)).isEqualTo(2), 202 | () -> assertThat(memberCountByHouse.get(House.SNOW)).isEqualTo(1) 203 | ); 204 | 205 | } 206 | 207 | @Test 208 | public void houseStats() { 209 | Map stats = dao.houseStats(); 210 | assertAll( 211 | () -> assertThat(stats.get(House.STARK).getMax()).isEqualTo(100000.0), 212 | () -> assertThat(stats.get(House.STARK).getMin()).isEqualTo(10000.0), 213 | () -> assertThat(stats.get(House.STARK).getAverage()) 214 | .isCloseTo(66666.66, withinPercentage(0.01)) 215 | ); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit 88 | 89 | # Use the maximum available, or set MAX_FD != -1 to use that value. 90 | MAX_FD=maximum 91 | 92 | warn () { 93 | echo "$*" 94 | } >&2 95 | 96 | die () { 97 | echo 98 | echo "$*" 99 | echo 100 | exit 1 101 | } >&2 102 | 103 | # OS specific support (must be 'true' or 'false'). 104 | cygwin=false 105 | msys=false 106 | darwin=false 107 | nonstop=false 108 | case "$( uname )" in #( 109 | CYGWIN* ) cygwin=true ;; #( 110 | Darwin* ) darwin=true ;; #( 111 | MSYS* | MINGW* ) msys=true ;; #( 112 | NONSTOP* ) nonstop=true ;; 113 | esac 114 | 115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 116 | 117 | 118 | # Determine the Java command to use to start the JVM. 119 | if [ -n "$JAVA_HOME" ] ; then 120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 121 | # IBM's JDK on AIX uses strange locations for the executables 122 | JAVACMD=$JAVA_HOME/jre/sh/java 123 | else 124 | JAVACMD=$JAVA_HOME/bin/java 125 | fi 126 | if [ ! -x "$JAVACMD" ] ; then 127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 128 | 129 | Please set the JAVA_HOME variable in your environment to match the 130 | location of your Java installation." 131 | fi 132 | else 133 | JAVACMD=java 134 | if ! command -v java >/dev/null 2>&1 135 | then 136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | fi 142 | 143 | # Increase the maximum file descriptors if we can. 144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 145 | case $MAX_FD in #( 146 | max*) 147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 148 | # shellcheck disable=SC2039,SC3045 149 | MAX_FD=$( ulimit -H -n ) || 150 | warn "Could not query maximum file descriptor limit" 151 | esac 152 | case $MAX_FD in #( 153 | '' | soft) :;; #( 154 | *) 155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 156 | # shellcheck disable=SC2039,SC3045 157 | ulimit -n "$MAX_FD" || 158 | warn "Could not set maximum file descriptor limit to $MAX_FD" 159 | esac 160 | fi 161 | 162 | # Collect all arguments for the java command, stacking in reverse order: 163 | # * args from the command line 164 | # * the main class name 165 | # * -classpath 166 | # * -D...appname settings 167 | # * --module-path (only if needed) 168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 169 | 170 | # For Cygwin or MSYS, switch paths to Windows format before running java 171 | if "$cygwin" || "$msys" ; then 172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -classpath "$CLASSPATH" \ 214 | org.gradle.wrapper.GradleWrapperMain \ 215 | "$@" 216 | 217 | # Stop when "xargs" is not available. 218 | if ! command -v xargs >/dev/null 2>&1 219 | then 220 | die "xargs is not available" 221 | fi 222 | 223 | # Use "xargs" to parse quoted args. 224 | # 225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 226 | # 227 | # In Bash we could simply go: 228 | # 229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 230 | # set -- "${ARGS[@]}" "$@" 231 | # 232 | # but POSIX shell has neither arrays nor command substitution, so instead we 233 | # post-process each arg (as a line of input to sed) to backslash-escape any 234 | # character that might be a shell metacharacter, then use eval to reverse 235 | # that process (while maintaining the separation between arguments), and wrap 236 | # the whole thing up as a single "set" statement. 237 | # 238 | # This will of course break if any of these variables contains a newline or 239 | # an unmatched quote. 240 | # 241 | 242 | eval "set -- $( 243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 244 | xargs -n1 | 245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 246 | tr '\n' ' ' 247 | )" '"$@"' 248 | 249 | exec "$JAVACMD" "$@" 250 | -------------------------------------------------------------------------------- /labs.md: -------------------------------------------------------------------------------- 1 | # Labs for Functional Programming in Java 2 | 3 | This document contains the lab exercises for the Functional Programming in Java course. Each exercise has TODO comments where you should implement the solution. Complete solutions can be found in the `solutions` branch. 4 | 5 | ## Table of Contents 6 | 7 | - [Lambda Expressions and Functional Interfaces](#lambda-expressions-and-functional-interfaces) 8 | - [Exercise 1: Implement Consumer](#exercise-1-implement-consumer) 9 | - [Exercise 2: Implement Supplier](#exercise-2-implement-supplier) 10 | - [Exercise 3: Constructor References](#exercise-3-constructor-references) 11 | - [Exercise 4: Filter with Predicate](#exercise-4-filter-with-predicate) 12 | - [Exercise 5: Function Interface and Composition](#exercise-5-function-interface-and-composition) 13 | - [Exercise 6: Runnable and ExecutorService](#exercise-6-runnable-and-executorservice) 14 | - [Exercise 7: FileFilter Lambda Evolution](#exercise-7-filefilter-lambda-evolution) 15 | - [Exercise 8: Lazy Evaluation with Suppliers](#exercise-8-lazy-evaluation-with-suppliers) 16 | - [Stream Operations](#stream-operations) 17 | - [Exercise 9: FlatMap with Nested Data Structures](#exercise-9-flatmap-with-nested-data-structures) 18 | - [Exercise 10: Sum Even and Odd Numbers](#exercise-10-sum-even-and-odd-numbers) 19 | - [Exercise 11: String Sorting](#exercise-11-string-sorting) 20 | - [Exercise 12: Book Sorting with Comparators](#exercise-12-book-sorting-with-comparators) 21 | - [Exercise 13: Collectors Demo](#exercise-13-collectors-demo) 22 | - [Exercise 14: Parallel Streams](#exercise-14-parallel-streams) 23 | - [BigDecimal Stream Operations](#bigdecimal-stream-operations) 24 | - [Exercise 15: BigDecimal Reduce Operations](#exercise-15-bigdecimal-reduce-operations) 25 | - ["Optional" Exercises](#optional-exercises) 26 | - [Exercise 16: Optional with DAO Pattern](#exercise-16-optional-with-dao-pattern) 27 | - [Exercise 17: Optional Chaining](#exercise-17-optional-chaining) 28 | - [CompletableFuture Exercises](#completablefuture-exercises) 29 | - [Exercise 18: CompletableFuture Basics](#exercise-18-completablefuture-basics) 30 | - [Exercise 19: Await Quiescence](#exercise-19-await-quiescence) 31 | - [Interface Evolution](#interface-evolution) 32 | - [Exercise 20: Multiple Interface Implementation](#exercise-20-multiple-interface-implementation) 33 | - [Game of Thrones Exercises (Advanced)](#game-of-thrones-exercises-advanced) 34 | - [Running the Tests](#running-the-tests) 35 | - [Solutions](#solutions) 36 | - [Tips](#tips) 37 | - [Additional Resources](#additional-resources) 38 | 39 | ## Lambda Expressions and Functional Interfaces 40 | 41 | ### Exercise 1: Implement Consumer 42 | 43 | Open the test file `src/test/java/lambdas/FunctionalInterfacesTest.java` 44 | 45 | **Task:** Complete the following test methods by implementing `Consumer`: 46 | 47 | 1. `implementConsumerUsingLambda` - Use a lambda expression 48 | 2. `implementConsumerUsingMethodReference` - Use a method reference 49 | 50 | ```java 51 | @Test 52 | public void implementConsumerUsingLambda() { 53 | // TODO: Implement Consumer using a lambda expression 54 | // consumer.accept("Hello, World!"); 55 | } 56 | 57 | @Test 58 | public void implementConsumerUsingMethodReference() throws Exception { 59 | // TODO: Implement Consumer using a method reference 60 | // consumer.accept("Hello, World!"); 61 | } 62 | ``` 63 | 64 | [Back to Table of Contents](#table-of-contents) 65 | 66 | ### Exercise 2: Implement Supplier 67 | 68 | **Task:** Complete the three `Supplier` test methods: 69 | 70 | 1. `implementSupplierUsingAnonInnerClass` - Create a `Supplier` that returns "Hello" 71 | 2. `implementSupplierUsingLambda` - Same using a lambda 72 | 3. `implementSupplierUsingMethodReference` - Create a `Supplier` using `Math::random` 73 | 74 | ```java 75 | @Test 76 | public void implementSupplierUsingAnonInnerClass() throws Exception { 77 | // TODO: Create a Supplier that returns "Hello" 78 | // assertEquals("Hello", supplier.get()); 79 | } 80 | 81 | @Test 82 | public void implementSupplierUsingLambda() throws Exception { 83 | // TODO: Create a Supplier using a lambda 84 | // assertEquals("Hello", supplier.get()); 85 | } 86 | 87 | @Test 88 | public void implementSupplierUsingMethodReference() throws Exception { 89 | // TODO: Create a Supplier that calls Math.random() 90 | // assertTrue(supplier.get() >= 0.0); 91 | // assertTrue(supplier.get() <= 1.0); 92 | 93 | // TODO: Create a DoubleSupplier that does the same 94 | // assertTrue(doubleSupplier.getAsDouble() >= 0.0); 95 | // assertTrue(doubleSupplier.getAsDouble() <= 1.0); 96 | } 97 | ``` 98 | 99 | [Back to Table of Contents](#table-of-contents) 100 | 101 | ### Exercise 3: Constructor References 102 | 103 | **Task:** Complete the `constructorReference` test method: 104 | 105 | 1. Add strings to a `HashSet` using constructor reference 106 | 2. Add strings to a `TreeSet` using constructor reference 107 | 108 | ```java 109 | @Test 110 | public void constructorReference() throws Exception { 111 | List stringList = List.of("a", "b", "b", "c", "d", "d"); 112 | // assertEquals(6, stringList.size()); 113 | 114 | // TODO: Add the strings to a Set 115 | // assertEquals(4, strings.size()); 116 | // assertEquals(HashSet.class, strings.getClass()); 117 | 118 | // TODO: Add the strings to a TreeSet 119 | // assertEquals(4, sortedStrings.size()); 120 | // assertEquals(TreeSet.class, sortedStrings.getClass()); 121 | // assertEquals("a", sortedStrings.first()); 122 | } 123 | ``` 124 | 125 | [Back to Table of Contents](#table-of-contents) 126 | 127 | ### Exercise 4: Filter with Predicate 128 | 129 | **Task:** Complete the predicate to accept only even numbers: 130 | 131 | ```java 132 | @Test 133 | public void filterWithPredicate() { 134 | // TODO: Fix the filter predicate to accept even numbers only 135 | // IntStream.of(3, 1, 4, 1, 5, 9) 136 | // .filter(n -> true) // accept even nums only 137 | // .forEach(n -> assertTrue(n % 2 == 0)); 138 | } 139 | ``` 140 | 141 | [Back to Table of Contents](#table-of-contents) 142 | 143 | ### Exercise 5: Function Interface and Composition 144 | 145 | Open the test file `src/test/java/lambdas/FunctionExercises.java` 146 | 147 | **Task:** Complete various Function interface exercises: 148 | 149 | 1. Implement basic Function interface 150 | 2. Use method references with Functions 151 | 3. Compose functions using `andThen` and `compose` 152 | 4. Work with BiFunction, UnaryOperator, and BinaryOperator 153 | 5. Use Functions in stream operations 154 | 155 | ```java 156 | @Test 157 | public void implementFunction() { 158 | // TODO: Create a Function that returns string length 159 | // Function stringLength = ... 160 | 161 | // assertEquals(5, stringLength.apply("Hello")); 162 | // assertEquals(0, stringLength.apply("")); 163 | } 164 | 165 | @Test 166 | public void functionComposition() { 167 | // TODO: Create and compose functions 168 | // Function stringLength = ... 169 | // Function toBinary = ... 170 | // Function lengthToBinary = ... 171 | 172 | // assertEquals("101", lengthToBinary.apply("Hello")); // 5 in binary 173 | } 174 | ``` 175 | 176 | [Back to Table of Contents](#table-of-contents) 177 | 178 | ### Exercise 6: Runnable and ExecutorService 179 | 180 | Open the test file `src/test/java/lambdas/RunnableExercises.java` 181 | 182 | **Task:** Evolve from anonymous inner classes to lambda expressions: 183 | 184 | 1. Implement Runnable as anonymous inner class 185 | 2. Convert to expression lambda 186 | 3. Use block lambda 187 | 4. Assign lambda to variable 188 | 5. Work with ExecutorService 189 | 6. Explore virtual threads (Java 21+) 190 | 191 | ```java 192 | @Test 193 | public void implementRunnableAsAnonymousInnerClass() throws InterruptedException { 194 | // TODO: Submit a Runnable using an anonymous inner class 195 | AtomicReference result = new AtomicReference<>(""); 196 | CountDownLatch latch = new CountDownLatch(1); 197 | 198 | // executorService.submit(new Runnable() { ... }); 199 | 200 | // assertTrue(latch.await(1, TimeUnit.SECONDS)); 201 | // assertEquals("Anonymous Inner Class", result.get()); 202 | } 203 | ``` 204 | 205 | [Back to Table of Contents](#table-of-contents) 206 | 207 | ### Exercise 7: FileFilter Lambda Evolution 208 | 209 | Open the test file `src/test/java/lambdas/FileFilterExercises.java` 210 | 211 | **Task:** Implement the `FileFilter` interface using different approaches: 212 | 213 | 1. Anonymous inner class implementation 214 | 2. Expression lambda 215 | 3. Block lambda with logging 216 | 4. Method reference 217 | 5. Lambda assigned to variable 218 | 6. FilenameFilter vs FileFilter 219 | 7. Composed filters 220 | 221 | ```java 222 | @Test 223 | void listDirectories_anonInnerClass() { 224 | // TODO: Implement FileFilter using anonymous inner class 225 | // File[] dirs = root.listFiles(new FileFilter() { 226 | // @Override 227 | // public boolean accept(File pathname) { 228 | // // Filter for directories 229 | // } 230 | // }); 231 | } 232 | 233 | @Test 234 | void listDirectories_methodReference() { 235 | // TODO: Implement using method reference 236 | // File[] dirs = root.listFiles(...); 237 | } 238 | ``` 239 | 240 | [Back to Table of Contents](#table-of-contents) 241 | 242 | ### Exercise 8: Lazy Evaluation with Suppliers 243 | 244 | Open the test file `src/test/java/lambdas/LazyEvaluationExercises.java` 245 | 246 | **Task:** Understand lazy evaluation using `Supplier`: 247 | 248 | 1. Compare eager vs lazy evaluation in assertions 249 | 2. Demonstrate lazy logging with Java's Logger 250 | 3. Create custom lazy evaluation scenarios 251 | 4. Understand when to use Supplier for performance 252 | 253 | ```java 254 | @Test 255 | void eagerEvaluation() { 256 | boolean x = true; 257 | // TODO: Call assertTrue with getErrorMessage() directly 258 | // Notice that the message is generated even when the assertion passes 259 | } 260 | 261 | @Test 262 | void lazyEvaluationWithLambda() { 263 | boolean x = true; 264 | // TODO: Call assertTrue with a lambda that calls getErrorMessage() 265 | // The message should NOT be generated when the assertion passes 266 | } 267 | ``` 268 | 269 | [Back to Table of Contents](#table-of-contents) 270 | 271 | ## Stream Operations 272 | 273 | ### Exercise 7: FlatMap with Nested Data Structures 274 | 275 | Open the test file `src/test/java/streams/FlatMapExercises.java` 276 | 277 | **Task:** Work with nested data structures using map and flatMap: 278 | 279 | 1. Use `map` to transform customers to names 280 | 2. Observe nested structures with `map` 281 | 3. Use `flatMap` to flatten nested collections 282 | 4. Filter and transform with `flatMap` 283 | 5. Combine operations for complex transformations 284 | 285 | ```java 286 | @Test 287 | public void getAllOrdersFlat() { 288 | // TODO: Use flatMap to get all orders as a flat List 289 | // List allOrders = ... 290 | 291 | // assertEquals(5, allOrders.size()); 292 | } 293 | 294 | @Test 295 | public void combineCustomerNamesWithOrderIds() { 296 | // TODO: Create strings in format "CustomerName-OrderId" for all orders 297 | // List customerOrderStrings = ... 298 | 299 | // assertTrue(customerOrderStrings.contains("Sheridan-1")); 300 | } 301 | ``` 302 | 303 | [Back to Table of Contents](#table-of-contents) 304 | 305 | ### Exercise 9: Sum Even and Odd Numbers 306 | 307 | Open the test file `src/test/java/streams/SumEvens.java` 308 | 309 | **Task:** Complete the following methods using streams: 310 | 311 | 1. `addEvenElementsUsingStreams` - Sum all even numbers 312 | 2. `addOddElementsUsingStreams` - Sum all odd numbers 313 | 314 | ```java 315 | @Test 316 | public void addEvenElementsUsingStreams() { 317 | // TODO: Use streams to sum even elements 318 | // List integers = List.of(3, 1, 4, 1, 5, 9, 2, 6, 5); 319 | // assertEquals(12, sum); 320 | } 321 | 322 | @Test 323 | public void addOddElementsUsingStreams() { 324 | // TODO: Use streams to sum odd elements 325 | // List integers = List.of(3, 1, 4, 1, 5, 9, 2, 6, 5); 326 | // assertEquals(24, sum); 327 | } 328 | ``` 329 | 330 | [Back to Table of Contents](#table-of-contents) 331 | 332 | ### Exercise 10: String Sorting 333 | 334 | Open the test file `src/test/java/streams/StringExercises.java` 335 | 336 | **Task:** Complete various string sorting methods: 337 | 338 | ```java 339 | @Test 340 | public void stringLengthSort_lambda() { 341 | // TODO: Use lambda for the Comparator (reverse sort) 342 | 343 | // TODO: Use the "sorted" method on Stream 344 | } 345 | 346 | @Test 347 | public void stringLengthSort_methodCall() { 348 | // TODO: Use a lambda that calls 'compareStrings' directly 349 | } 350 | 351 | @Test 352 | public void stringLengthSort_methodRef() { 353 | // TODO: Use a method ref to 'compareStrings' 354 | } 355 | 356 | @Test 357 | public void stringLengthSort_comparingInt() { 358 | // TODO: Use Comparator.comparingInt 359 | } 360 | ``` 361 | 362 | [Back to Table of Contents](#table-of-contents) 363 | 364 | ### Exercise 12: Book Sorting with Comparators 365 | 366 | Open the test file `src/test/java/sorting/BookSortingExercises.java` 367 | 368 | **Task:** Practice sorting using modern Java Comparator methods: 369 | 370 | 1. Sort by natural ordering (publication year) 371 | 2. Sort by title alphabetically 372 | 3. Sort by author, then by title 373 | 4. Sort by year descending (newest first) 374 | 5. Sort by title length 375 | 6. Partition books by publication year 376 | 7. Group books by decade 377 | 8. Create complex multi-level sorts 378 | 9. Filter and sort books in a specific year range 379 | 10. Find books by a specific author 380 | 381 | ```java 382 | @Test 383 | public void sortByAuthorThenTitle() { 384 | // TODO: Sort by author, then by title for books by the same author 385 | // Hint: Use Comparator.comparing().thenComparing() 386 | } 387 | 388 | @Test 389 | public void groupByDecade() { 390 | // TODO: Group books by the decade they were published 391 | // Hint: Use Collectors.groupingBy() with year/10*10 392 | } 393 | 394 | @Test 395 | public void findKenKousenBooks() { 396 | // TODO: Find all books by Ken Kousen and sort by publication year 397 | } 398 | ``` 399 | 400 | **Key Concepts:** 401 | - `Comparator.comparing()` 402 | - `thenComparing()` for secondary sorts 403 | - `reversed()` for descending order 404 | - `comparingInt()` for numeric values 405 | - `Collectors.partitioningBy()` and `groupingBy()` 406 | 407 | [Back to Table of Contents](#table-of-contents) 408 | 409 | ### Exercise 13: Collectors Demo 410 | 411 | **Task:** Complete the `demoCollectors` test method: 412 | 413 | ```java 414 | @Test 415 | public void demoCollectors() { 416 | // TODO: Get only strings of even length 417 | // TODO: Add them to a LinkedList 418 | 419 | // TODO: Add the strings to a map of string to length 420 | 421 | // TODO: Filter out nulls, then print even-length strings 422 | 423 | // TODO: Function composition 424 | 425 | // TODO: Combine the two predicates and use the result to print non-null, even-length strings 426 | } 427 | ``` 428 | 429 | [Back to Table of Contents](#table-of-contents) 430 | 431 | ### Exercise 14: Parallel Streams 432 | 433 | Open the test file `src/test/java/streams/ParallelStreamExercises.java` 434 | 435 | **Task:** Learn the basics of parallel stream processing: 436 | 437 | 1. Convert streams to parallel and observe thread behavior 438 | 2. Understand ordering differences between sequential and parallel 439 | 3. Use `forEachOrdered` to maintain order 440 | 4. Perform parallel reductions 441 | 5. Compare `findFirst` vs `findAny` behavior 442 | 6. Avoid stateful operations in parallel streams 443 | 7. Use thread-safe collectors 444 | 8. Understand when to use parallel streams 445 | 446 | ```java 447 | @Test 448 | public void basicParallelStream() { 449 | // TODO: Convert the stream to parallel and observe thread names 450 | // Stream.of("apple", "banana", "cherry", "date", "elderberry") 451 | // .parallel() 452 | // .forEach(fruit -> System.out.println( 453 | // Thread.currentThread().getName() + ": " + fruit)); 454 | } 455 | 456 | @Test 457 | public void statefulOperationProblem() { 458 | // TODO: Learn why stateful operations are problematic in parallel 459 | // This is an anti-pattern example! 460 | } 461 | 462 | @Test 463 | public void parallelPerformanceConsiderations() { 464 | // TODO: Understand when parallel streams are beneficial 465 | // Hint: Large datasets, CPU-intensive operations, independent work 466 | } 467 | ``` 468 | 469 | **Key Concepts:** 470 | - `parallel()` and `sequential()` methods 471 | - Thread safety with parallel operations 472 | - When to use parallel streams (performance considerations) 473 | - Ordering guarantees with `forEachOrdered` 474 | - Appropriate collectors for parallel operations 475 | 476 | **Note:** For detailed performance analysis with benchmarks, see the separate JMH project repository. 477 | 478 | [Back to Table of Contents](#table-of-contents) 479 | 480 | ## BigDecimal Stream Operations 481 | 482 | ### Exercise 15: BigDecimal Reduce Operations 483 | 484 | Open the test file `src/test/java/streams/BigDecimalReduceExercises.java` 485 | 486 | **Task:** Implement reduce operations with BigDecimal: 487 | 488 | 1. Sum first N BigDecimal values using `Stream.iterate` 489 | 2. Sum a list of prices with and without identity 490 | 3. Handle empty streams with reduce 491 | 4. Apply multiple discount factors 492 | 5. Understand non-associative operations 493 | 6. Create custom accumulators 494 | 495 | ```java 496 | @Test 497 | public void sumFirstNBigDecimals() { 498 | // TODO: Use Stream.iterate and reduce to sum 1+2+...+10 499 | // BigDecimal sum = Stream.iterate(BigDecimal.ONE, bd -> bd.add(BigDecimal.ONE)) 500 | // .limit(10) 501 | // .reduce(...) 502 | } 503 | 504 | @Test 505 | public void sumWithEmptyStream() { 506 | // TODO: Compare reduce with and without identity on empty stream 507 | // What's the difference in return types? 508 | } 509 | ``` 510 | 511 | **Alternative Demo:** Study `AlternativeReduceDemo.java` for examples using: 512 | - String concatenation 513 | - Custom object aggregation 514 | - Matrix operations 515 | - Parallel reduce considerations 516 | 517 | [Back to Table of Contents](#table-of-contents) 518 | 519 | ## "Optional" Exercises 520 | 521 | Open the test files in `src/test/java/optional/` 522 | 523 | ### Exercise 16: Optional with DAO Pattern 524 | 525 | **File:** `ProductDAOTest.java` 526 | 527 | **Task:** Run the tests to understand: 528 | - How `Optional` is used in DAO methods 529 | - The difference between `findById` (returns `Optional`) and `getProductById` 530 | 531 | [Back to Table of Contents](#table-of-contents) 532 | 533 | ### Exercise 17: Optional Chaining 534 | 535 | **File:** `CompanyTest.java` 536 | 537 | **Task:** Study the test to understand: 538 | - How to chain `Optional` operations using `map` and `flatMap` 539 | - When to use `map` vs `flatMap` with nested `Optional` values 540 | 541 | [Back to Table of Contents](#table-of-contents) 542 | 543 | ## CompletableFuture Exercises 544 | 545 | Open the test files in `src/test/java/concurrency/` 546 | 547 | ### Exercise 18: CompletableFuture Basics 548 | 549 | **File:** `CompletableFutureTests.java` 550 | 551 | **Task:** Run and study the tests to understand: 552 | - Creating `CompletableFuture` instances 553 | - Chaining operations (`thenApply`, `thenAccept`, `thenCompose`) 554 | - Combining futures (`thenCombine`) 555 | - Exception handling (`handle`, `exceptionally`) 556 | - Using `allOf` and `anyOf` 557 | 558 | [Back to Table of Contents](#table-of-contents) 559 | 560 | ### Exercise 19: Await Quiescence 561 | 562 | **File:** `AwaitQuiesenceTest.java` 563 | 564 | **Task:** Understand the differences between: 565 | - `get()` - blocks and throws checked exceptions 566 | - `join()` - blocks and throws unchecked exceptions 567 | - `Thread.awaitQuiescence()` - waits for virtual threads (Java 21+) 568 | 569 | [Back to Table of Contents](#table-of-contents) 570 | 571 | ## Interface Evolution 572 | 573 | ### Exercise 20: Multiple Interface Implementation 574 | 575 | Open the test file `src/test/java/interfaces/CompanyEmployeeTest.java` 576 | 577 | **Task:** Create a class that implements both `Company` and `Employee` interfaces, handling any default method conflicts. 578 | 579 | ```java 580 | @Test 581 | public void createClass() { 582 | // TODO: Create a class that implements both Company and Employee 583 | // TODO: Handle the conflict with the getSalary() default method 584 | // Company company = new MyCompany("MyCompany, Inc."); 585 | // assertEquals(0.0, company.getSalary()); 586 | } 587 | ``` 588 | 589 | [Back to Table of Contents](#table-of-contents) 590 | 591 | ## Game of Thrones Exercises (Advanced) 592 | 593 | Open the test file `src/test/java/got/InMemoryMemberDAOTests.java` 594 | 595 | **Note:** These tests are `@Disabled` by default. Remove the annotation to enable them. 596 | 597 | **Tasks:** Implement the following stream operations: 598 | - Finding by ID, name, and house 599 | - Filtering and mapping 600 | - Custom sorting 601 | - Averaging and summing 602 | - Grouping and partitioning 603 | - Advanced collectors 604 | 605 | [Back to Table of Contents](#table-of-contents) 606 | 607 | ## Running the Tests 608 | 609 | From the command line: 610 | ```bash 611 | # Run all tests 612 | ./gradlew test 613 | 614 | # Run a specific test class 615 | ./gradlew test --tests "lambdas.FunctionalInterfacesTest" 616 | 617 | # Run a specific test method 618 | ./gradlew test --tests "lambdas.FunctionalInterfacesTest.implementConsumerUsingLambda" 619 | ``` 620 | 621 | From your IDE: 622 | - Use the built-in test runner 623 | - Right-click on test classes or methods to run them 624 | 625 | ## Solutions 626 | 627 | Complete solutions for all exercises can be found in the `solutions` branch: 628 | 629 | ```bash 630 | git checkout solutions 631 | ``` 632 | 633 | Compare your implementations with the solutions to learn different approaches. 634 | 635 | ## Tips 636 | 637 | 1. Start with the simpler exercises (lambdas and functional interfaces) before moving to streams 638 | 2. Use your IDE's auto-completion to explore available methods 639 | 3. Refer to the Java API documentation for `Stream`, `Optional`, and `CompletableFuture` 640 | 4. The test names often hint at the expected implementation approach 641 | 5. If stuck, look at similar patterns in the demo classes under `src/main/java` 642 | 643 | ## Additional Resources 644 | 645 | - `Java_Upgrade_Slides.pdf` - Course slides with theoretical background 646 | - Demo classes in `src/main/java`: 647 | - `CollectorsDemo.java` - Advanced collector examples 648 | - `OptionalDemo.java` - Optional usage patterns 649 | - `CompletableFutureDemos.java` - Async programming examples 650 | - `UseProducts.java` - Practical stream operations --------------------------------------------------------------------------------