Optional

Optional is a wrapper class in Java that handles null values safely (for single values). If it’s false, it returns Optional.empty()

String name = getName();
System.out.println(name.length()); // crashes if name is null

Using map, flatMap, filter, orElse

Optional.of(15)
    .filter(x -> x > 10)   // keep if > 10
    .map(x -> x * 2)        // double it
    .orElse(0);             // default if empty → returns 30

map vs flatMap

Optional.of(1).map(x -> Optional.of(x));          // Optional(Optional(1))
Optional.of(1).flatMap(x -> Optional.of(x));      // Optional(1) 

For single values, use Optional to return Optional.empty() if empty. For collection of values, use Stream to return Stream.empty() if empty. Both avoids returning null


Replacing isPresent and get with orElse

System.out.println(Optional.ofNullable(name)
    .map(x -> "Hello " + x)
    .orElse("Please enter your name"));

Essentially orElse just replaces isPresent and get. isPresent and get defeats the purpose of Optional’s ability to return a Optional.empty() to handle null cases.


InfList

Stream<Integer> s = Stream.of(1, 2, 3);
s.map(x -> x + 1);   // ✅ works
s.map(x -> x * 2);   // ❌ crashes — stream already closed!

InfList<Integer> l = ...; 
l.map(x -> x + 1);   // ✅ works
l.map(x -> x * 2);   // ✅ still works

1. Creating a new instance

Server server(Customer customer) {
	return new Server(this.id, this.serviceTime);
}

Create new instance new immutability. Also, it’s new Server not new server.

2. Constructor

Server(int id, double serviceTime) {
	...
}

It should not be 
Server server(int id, double serviceTime) {
	...
}

3. Creating a new Server constructor (This is overloading!)

public class Dog {
    String name;
    int age;

    // Constructor 1: just a name
    public Dog(String name) {
        this.name = name;
        this.age = 0;
    }

    // Constructor 2: name and age
    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

4. You can initialise inside a constructor

Server(int id, double serviceTime) {
    this.id = id;
    this.serviceTime = serviceTime;
    this.readyTime = 0.0;  // or whatever default makes sense
}

5. The default toString method

ClassName@1b6d3586

@Override
public String toString() {
	return "server " + this.id;
}

JShell automatically calls toString() when you write an expression to display the value.

6. Fixed

private final double serviceTime = 1.0;

Shop(int number) {  // no serviceTime param
    this.number = number;
}

7. Updating the fields

private final InfList server;

...
// inside the constructor
this.servers

8. Iterate

It takes a seed value and a function to get the next value — but the seed and next function should be the same type. If I’m passing new Server(1, ...) as seed and i -> new Server(i + 1, ...) where i is a Server, not an int, I’m cooked.

9. InfList Syntax

class Shop {
    private final int number;
    private final double serviceTime;
    private final InfList<Server> servers;

    Shop(int number, double serviceTime) {
        this.number = number;
        this.serviceTime = serviceTime;
        this.servers = InfList.iterate(1, x -> x + 1) // Creates 1,2,3
			.map(i -> new Server(i, this.serviceTime)) // Maps server to each
			.limit(this.number); // Stops
    }

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

[Server(1, 1.0), Server(2, 1.0), ..., Server(n, 1.0)]

10. Objects in toString can call their own overrided methods

return "Shop:" + servers.reduce("", (acc, s) -> acc + "<" + s + ">");

When you do ”<” + s + ”>”, Java automatically calls s.toString() because of string concatenation — which hits your overridden toString() in Server and returns “server 1”, giving you <server 1>.

The acc is actually "", and so the you’re actually adding "" + “<server 1”.

11. What <> mean in the docs

class Maybe<T> {

    // X<T> doesn't convert type — stays as Maybe<T>
    Maybe<T> filter(Predicate<T> predicate)

    // <R> X<R> converts type — T goes in, R comes out
    <R> Maybe<R> map(Function<T, R> mapper)

}

12. When do I need to use <> and when do I not?

Is the class a “container” that can hold different types? → needs <> Is it a plain concrete class/primitive? → no <>

InfList<Server> servers;  // InfList holds Servers
Maybe<Server>             // Maybe holds a Server

int number;
double serviceTime;

13. Creating new instances

class Shop {
    private final int number;
    private final double serviceTime;
    private final InfList<Server> servers;

    Shop(int number, double serviceTime) {
        this.number = number;
        this.serviceTime = serviceTime;
        this.servers = InfList.iterate(1, x -> x + 1)
            .map(i -> new Server(i, this.serviceTime))
            .limit(this.number);
    }

    Shop(int number, double serviceTime, InfList<Server> servers) {
        this.number = number;
        this.serviceTime = serviceTime;
        this.servers = servers;
    }

	// This returns a new Shop but you update the thing!
    Shop update(Server server) {
        return new Shop(this.number, this.serviceTime, this.servers.map(x -> x.equals(server) ? server : x));
    }

14. Creating the equals method

@Override
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj instanceof Server server) {
        return this.id == server.id;
    }
    return false;
}

Actually here id and this.id is the same.
id / this.id = current object
server.id = other object

15. Storing the log in the constructor

Sometimes, instead of storing the output in the toString, you can store it within the constructor

class State {
    private final Shop shop;
    private final String log;

    State(Shop shop) {
        this.shop = shop;
        this.log = "";
    }

16. Maybe and Optionals

Think of them as wrappers / containers. A container that may or may not contain a Server.

Server              → actual worker
Maybe<Server>       → box that might contain a worker

17. State example / Map only works if the item exists

    State next(Customer customer) {
        String newtext = this.text + customer + " arrives\n";
        return this.shop.findServer(customer)
            .map(server -> {
                Shop updatedShop = this.shop.update(server.serve(customer));
                String updatedtext = newtext 
                    + customer + " served by " + server + "\n";
                return new State(updatedShop, updatedtext);
            })
            .orElse(new State(this.shop, newtext));
    }
    
If you find a server from findServer(customer), then map works.
Map takes this server, and does multi-line actions.