Step 7: Put it together
Finally, a compact version of the whole system using more modern Java: a record for immutable books, streams to filter and group, and a Library that tracks who borrowed what. It prints which books are still available, how many loans each member has, and the oldest available book.
import java.util.*;
import java.util.stream.*;
public class Main {
record Book(String isbn, String title, String author, int year) {}
static class Library {
private final Map<String, Book> catalogue = new LinkedHashMap<>();
private final Map<String, String> loans = new HashMap<>(); // isbn -> member
void add(Book b) { catalogue.put(b.isbn(), b); }
boolean borrow(String member, String isbn) {
if (!catalogue.containsKey(isbn) || loans.containsKey(isbn)) return false;
loans.put(isbn, member);
return true;
}
List<Book> available() {
return catalogue.values().stream().filter(b -> !loans.containsKey(b.isbn())).collect(Collectors.toList());
}
Map<String, Long> loansPerMember() {
return loans.values().stream().collect(Collectors.groupingBy(m -> m, TreeMap::new, Collectors.counting()));
}
}
public static void main(String[] args) {
Library lib = new Library();
lib.add(new Book("1", "Clean Code", "Robert Martin", 2008));
lib.add(new Book("2", "Refactoring", "Martin Fowler", 1999));
lib.add(new Book("3", "Design Patterns", "Gamma et al.", 1994));
lib.add(new Book("4", "Effective Java", "Joshua Bloch", 2001));
System.out.println(lib.borrow("Ada", "1"));
System.out.println(lib.borrow("Ada", "2"));
System.out.println(lib.borrow("Sam", "1"));
System.out.println(lib.borrow("Sam", "3"));
System.out.println("Available: " + lib.available().stream().map(Book::title).collect(Collectors.toList()));
System.out.println("Loans: " + lib.loansPerMember());
System.out.println("Oldest: " + lib.available().stream().min(Comparator.comparingInt(Book::year)).get());
}
}true
true
false
true
Available: [Effective Java]
Loans: {Ada=2, Sam=1}
Oldest: Book[isbn=4, title=Effective Java, author=Joshua Bloch, year=2001]You have designed classes with clear responsibilities, an interface that hides storage, rules enforced by exceptions, a relational database and a port to a second language. Where next: connect the repository to a real database with JDBC, add a returnedOn date and late fees, expose it as a web API (the Node.js project shows the idea), and write unit tests for the rules. In the Git course you can see how to keep a project like this under version control.
What is the main benefit of splitting data, rules and storage into separate classes?
Finish the quick check (and any code exercise) to complete this lesson, or mark it done yourself.
Tip: use the left and right arrow keys to move between lessons.