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.