Good Implementation of orElse()

maybe.map(x -> logic handling assuming value exists)
			.orElse(logic handling if value doesn’t exist)

3 ways to sort

.sort()

Where natural ordering is present.

// If classes already have Comparable built in
List<Integer> nums = List.of(3, 1, 2); nums.stream().sorted().toList(); // works, Integer already implements Comparable

// Use lambda
events.stream()
      .sorted((e1, e2) -> e1.getPriority() - e2.getPriority())
      .toList();

// Pass a Comparator.comparing into it
events.stream()
      .sorted(Comparator.comparing(Event::getPriority))
      .toList();

Comparable

// Event.java
public class Event implements Comparable<Event> {
    ...
    @Override
    public int compareTo(Event other) {
        return this.priority - other.priority; 
        // sorts by priority, ascending
    }
    ...
}

// Main.java
...
    List<Event> sorted = events.stream()
            .sorted() // calls compareTo() internally
            .toList();
...
}

Comparator

Allows us to sort objects that do not implement the Comparable interface. Comparator is an interface used to define a sorting order for objects outside of the class itself. In other words, the actual class no longer needs to implement Comparable and define a compareTo method. I just outsource it to the Comparator interface.

Comparator Interface

interface Comparator<T> {
    int compare(T o1, T o2);
}

Returns:
- negative → o1 comes before o2
- zero → o1 equals o2
- positive → o1 comes after o2

Implementing the Comparator Interface by creating a class

// Sort Person by age ascending
class AgeComparator implements Comparator<Person> {
    public int compare(Person p1, Person p2) {
        return Integer.compare(p1.age, p2.age);
    }
}

// Sort Person by name ascending
class NameComparator implements Comparator<Person> {
    public int compare(Person p1, Person p2) {
        return p1.name.compareTo(p2.name); // String already has compareTo
    }
}

Using the class

// Sort by age
Stream.of(new Person(..), ..)
      .sorted(new AgeComparator())
      .toList();

// Sort by name
Stream.of(new Person(..), ..)
      .sorted(new NameComparator())
      .toList();

1. No parameter constructor and methods

This is possible.

class JustRide implements Service {
    private static final int FARE = 22;
    private static final int SURCHARGE = 500;
    private static final int PEAK_START = 600;
    private static final int PEAK_END = 900;

    public int computeFare(int distance, int numPassengers, int timeOfService) {
        return FARE * distance +
            (timeOfService >= PEAK_START && timeOfService <= PEAK_END
                ? SURCHARGE
                : 0);
    }

    @Override
    public String toString() {
        return "JustRide";
    }
}

2. IS-A Relationships

// Concrete Class

class Vehicle { }
class Car extends Vehicle { }

Car IS-A Vehicle

// Interface

interface Service {
    int computeFare(int distance, int passengers, int time);
}

class JustRide implements Service {
    public int computeFare(int distance, int passengers, int time) {
        return 0;
    }
}

JustRide IS-A Service

// Interface extend Interface

interface Shape {
    double area();
}

interface Shape3D extends Shape {
    double volume();
}

Shape3D IS-A Shape

// Transitive Inheritance

class Animal {}
class Mammal extends Animal {}
class Dog extends Mammal {}

Dog IS-A Mammal
Dog IS-A Animal

// Assignment

Service s = new JustRide();

JustRide IS-A Service

3. HAS-A Relationships

// Instance fields

class Engine { }

class Car {
    private Engine engine;
}

Car HAS-A Engine

// Composition

class Car {
    private Engine engine = new Engine();
}

Car HAS-A Engine

// Provided Externally

class Car {
    private Engine engine;

    Car(Engine engine) {
        this.engine = engine;
    }
}

Car HAS-A Engine

4. You don’t need to implement Service just because your method wants to use it as a parameter

class Request {
	...
	int computerFare(Service service) {
	}
	...
}

5. Importing Streams and Lists

import java.util.List;
import java.util.stream.Stream;

6. Records automatically generate accessors

record Pair<T,U>(T t, U u) {}
p.u()
p.t()

7. Solving Cyclic Dependencies

Problem

class Driver {

    Stream<String> rank() {
        NormalCab cab = new NormalCab();
        return cab.getServices().sorted();
    }
}

class NormalCab {

    Stream<String> getServices() {
        Driver d = new Driver();
        return Stream.of("JustRide", "TakeACab");
    }
}

getServices() is implemented in NormalCab.
Driver has no idea what getServices() is.

Solving via Abstract Class

abstract class Driver {

    Stream<String> rank() {
        return getServices().sorted();
    }

    abstract Stream<String> getServices();
}

class NormalCab extends Driver {

    Stream<String> getServices() {
        return Stream.of("JustRide", "TakeACab");
    }
}

Driver knows about getServices().
NormalCab needs to implement it.

Solving via Constructor Injection

class Driver {

    private final Stream<String> services;

    Driver(Stream<String> services) {
        this.services = services; // Stored here
    }

    Stream<String> rank() {
        return services.sorted();
    }
}

class NormalCab extends Driver {

    NormalCab() {
        super(Stream.of("JustRide", "TakeACab"));
    }
}