Practical engineering: build something
A buffer session to reinforce OOP, troubleshoot, and build one small integrated program end to end.
Finished reading?
Mark this session so you can track where you are.
A buffer session to reinforce OOP, troubleshoot, and build one small integrated program end to end.
Finished reading?
Mark this session so you can track where you are.
Thirty-three sessions ago you opened an empty file and did not know how to print a single line. You now know variables, control flow, arrays, and the whole of object-oriented design. This session adds no new keyword. Instead it does the thing the whole course was building toward: take everything you have, and build one small program, end to end.
JDBC code that talks to a database, without memorising it.JavaFX window with buttons, without memorising it.Up to now each session taught one idea in isolation. A loop here, a constructor there. Real programs are not like that. A real program is many small ideas working at once: an object holds state, a method changes it, an array holds many of those objects, a loop walks them, and an if decides what to do. Engineering is the skill of fitting those pieces together so the whole thing does something useful and does not fall over.
Learning the pieces and building with the pieces are two different skills. You have the first. This session is your first real go at the second.
We will build a tiny library: a program that keeps a list of books, lets you borrow one, return it, and see what is still on the shelf. Small enough to finish, real enough to feel like software. We will design it first, build the core so it actually runs, then bolt on the keyboard menu at the end.
Before any Java, ask the two questions from why objects exist: what things are in this program, and for each thing, what does it have (state) and what can it do (behavior)? Read the description again and underline the nouns.
“A library keeps a list of books. You can borrow a book and return it.” Two nouns stand out: Library and Book. That is two classes.
title and an author.Book objects.available: is it on the shelf or out?count of how many books it holds.add, borrow, giveBack, and listAvailable.Book is one row in the catalogue.Library owns many Book objects.Start with the smaller class. A Book has three fields and a constructor that sets a fresh book as available. Nothing here is new. Read it, then press Run and step through to watch the object being built and one field flip.
public class Main { public static void main(String[] args) { Book b = new Book("Dune", "Herbert"); System.out.println(b.title + " by " + b.author); System.out.println("Available: " + b.available); b.available = false; System.out.println("Available: " + b.available); }} class Book { String title; String author; boolean available; Book(String title, String author) { this.title = title; this.author = author; this.available = true; }}Dune by Herbert Available: true Available: false
The constructor uses this.title = title to tell the field apart from the parameter of the same name, exactly as in the this session. A brand new book is available = true because a book just added to the library is on the shelf. Setting b.available = false is what borrowing will do.
Now the Library. It holds a Book[] and a running count of how many slots are filled. Each method does one job. Build it in your head in this order:
add: make a new Book, drop it in the array at count, then increase count.find: walk the filled slots and return the book whose title matches, or null if there is none.borrow: find the book, and if it is on the shelf, flip available to false.giveBack: find the book and flip available back to true.listAvailable: walk the books and print only the ones still on the shelf.Here is the whole thing, running. This is the heart of the program and the interpreter runs it for real. Press Run and step through: watch count climb as books are added, and watch one available flag flip when a book is borrowed.
public class Main { public static void main(String[] args) { Library library = new Library(); library.add("Dune", "Herbert"); library.add("Emma", "Austen"); library.add("Solaris", "Lem"); System.out.println("On the shelf:"); library.listAvailable(); library.borrow("Emma"); library.borrow("Mistborn"); System.out.println("After one loan:"); library.listAvailable(); library.giveBack("Emma"); System.out.println("After the return:"); library.listAvailable(); }} class Book { String title; String author; boolean available; Book(String title, String author) { this.title = title; this.author = author; this.available = true; }} class Library { Book[] shelf = new Book[10]; int count = 0; void add(String title, String author) { shelf[count] = new Book(title, author); count = count + 1; } Book find(String title) { for (int i = 0; i < count; i++) { if (shelf[i].title.equals(title)) { return shelf[i]; } } return null; } void borrow(String title) { Book b = find(title); if (b == null) { System.out.println("No book called " + title); } else if (b.available) { b.available = false; System.out.println("You borrowed " + b.title); } else { System.out.println(b.title + " is already out"); } } void giveBack(String title) { Book b = find(title); if (b != null) { b.available = true; System.out.println("You returned " + b.title); } } void listAvailable() { for (int i = 0; i < count; i++) { if (shelf[i].available) { System.out.println(" " + shelf[i].title + " by " + shelf[i].author); } } }}On the shelf: Dune by Herbert Emma by Austen Solaris by Lem You borrowed Emma No book called Mistborn After one loan: Dune by Herbert Solaris by Lem You returned Emma After the return: Dune by Herbert Emma by Austen Solaris by Lem
Trace what just happened. Three add calls leave count at 3 and three real Book objects in the first three slots. borrow("Emma") finds Emma and flips her available to false, so the next listing skips her. borrow("Mistborn") finds nothing, so find returns null and we print a polite message instead of crashing. giveBack("Emma") puts her back, and the final listing shows all three again.
borrow checks if (b == null) first. find can legitimately return null when no book matches. Reading b.title on a null would throw the NullPointerException you met in references. Handling the “not found” case is not an afterthought; it is half of writing a method that does not fall over.The core logic is done and proven. The last piece is letting a person drive it from the keyboard with a menu loop, using Scanner from the console session. The interpreter here cannot read the keyboard, so this part is shown as a read-along block. The logic it calls is the same Library you just ran live.
import java.util.Scanner; public class Main { public static void main(String[] args) { Library library = new Library(); library.add("Dune", "Herbert"); library.add("Emma", "Austen"); Scanner in = new Scanner(System.in); boolean running = true; while (running) { System.out.println("1) Add book"); System.out.println("2) Borrow book"); System.out.println("3) List available"); System.out.println("0) Quit"); System.out.print("> "); int choice = in.nextInt(); in.nextLine(); if (choice == 1) { System.out.print("Title: "); String title = in.nextLine(); System.out.print("Author: "); String author = in.nextLine(); library.add(title, author); } else if (choice == 2) { System.out.print("Title to borrow: "); String title = in.nextLine(); library.borrow(title); } else if (choice == 3) { library.listAvailable(); } else if (choice == 0) { running = false; System.out.println("Goodbye."); } } }}1) Add book 2) Borrow book 3) List available 0) Quit > 3 Dune by Herbert Emma by Austen > 2 Title to borrow: Emma You borrowed Emma > 0 Goodbye.
Look at how thin the menu is. It reads a number, then calls one method on library. All the real thinking lives in the Library and Book classes, which is exactly why we could test them live without a keyboard. That separation, a thin layer that talks to the user sitting on top of solid logic, is one of the most important habits in all of software.
Keep your logic separate from your input and output. Logic you can test on its own is logic you can trust. The menu is just a steering wheel; the engine is the classes underneath.
Our library forgets everything the moment the program ends. Real software remembers, and the usual place it remembers is a database: a program built to store tables of data and answer questions about them quickly and safely. JDBC, the Java Database Connectivity API, is the standard way Java talks to one. You will meet it; for now we only want you to recognise its shape.
Think of a database table as a spreadsheet the program owns. A books table might have one row per book, with columns for title and author. JDBC lets your Java code send it instructions written in SQL, a small language for asking databases to store and fetch rows. The Java side follows the same four beats every time.
Here is a small program that creates a table, inserts a book, and reads it back. The interpreter cannot run this (no database, no java.sql), so read it for the shape, not to memorise it.
import java.sql.Connection;import java.sql.DriverManager;import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.Statement; public class Main { public static void main(String[] args) throws Exception { String url = "jdbc:sqlite:library.db"; Connection conn = DriverManager.getConnection(url); System.out.println("Connected."); Statement create = conn.createStatement(); create.execute("CREATE TABLE IF NOT EXISTS books (title TEXT, author TEXT)"); PreparedStatement insert = conn.prepareStatement("INSERT INTO books (title, author) VALUES (?, ?)"); insert.setString(1, "Dune"); insert.setString(2, "Herbert"); int rows = insert.executeUpdate(); System.out.println("Inserted " + rows + " row."); Statement query = conn.createStatement(); ResultSet rs = query.executeQuery("SELECT title, author FROM books"); while (rs.next()) { System.out.println(rs.getString("title") + " by " + rs.getString("author")); } conn.close(); }}Connected. Inserted 1 row. Dune by Herbert
Read it against the four beats. getConnection is connect. prepareStatement with the ? placeholders is prepare. executeUpdate and executeQuery are run. conn.close() is close. The ResultSet is a cursor you walk with rs.next(), one row at a time, the same way you walked an array with a loop.
? marks in prepareStatement are not just tidy. Gluing user input straight into an SQL string is the classic security hole called SQL injection, where a crafted title could run commands you never meant. Placeholders keep the value and the command strictly apart. You do not need the detail yet; just file away that the ? form is the safe one.Every program so far has printed text to a console. Most software people actually use has a graphical user interface: windows, buttons, text boxes you click and type into. JavaFX is the modern toolkit for building those in Java. As with JDBC, you only need to recognise its shape for now, so the vocabulary stops being scary the day you meet it.
JavaFX uses a small set of words that build on each other. Learn the nesting and the rest follows:
Button, a Label, or a TextField.VBox (a vertical stack), that arranges controls.Here is the smallest real JavaFX app: a window with a button that counts clicks. The interpreter cannot open a window, so this is read-along. The output shown is what the label reads after three clicks.
import javafx.application.Application;import javafx.scene.Scene;import javafx.scene.control.Button;import javafx.scene.control.Label;import javafx.scene.layout.VBox;import javafx.stage.Stage; public class Main extends Application { private int clicks = 0; public void start(Stage stage) { Label label = new Label("Clicked 0 times"); Button button = new Button("Click me"); button.setOnAction(event -> { clicks = clicks + 1; label.setText("Clicked " + clicks + " times"); }); VBox root = new VBox(label, button); Scene scene = new Scene(root, 240, 120); stage.setTitle("Click counter"); stage.setScene(scene); stage.show(); } public static void main(String[] args) { launch(args); }}(a window opens titled "Click counter") (after three clicks the label reads:) Clicked 3 times
Read the nesting outward. A Button and a Label go into a VBox; the VBox goes into a Scene; the Scene goes into the Stage; the stage is shown. The interesting line is setOnAction: it hands the button a small piece of behavior to run on every click. That arrow form is a lambda from the enums and lambdas session, behavior passed around as a value.
A graphical program does not run top to bottom like a script. It sets up the window, then waits. Your event handlers are called back by the toolkit whenever the user does something. This is the same callback idea as the button: you describe what should happen, the framework decides when.
You have come a long way. Here is the route, phase by phase, so you can see how each idea sat on the one before it. Every link goes back to where it was taught.
From an empty file to arrays. setup and the JVM, variables and types, operators and casting, if-else and switch, loops, arrays, and two-dimensional arrays.
Where the Book and Library in this session came from. why objects, classes and objects, methods, constructors, encapsulation, this and references, inheritance, overriding and super, polymorphism, and the OOP practice session.
abstract classes, interfaces, choosing between them, static and final, and inner classes.
The tools this capstone used. packages and imports, Scanner and the console, reading files, writing files, and exception handling.
strings and builders, string practice, threads, synchronization, enums and lambdas, and annotations and tooling.
Try each one yourself first, then open the answer.
Library program, after the three add calls, what is the value of count, and what is sitting in shelf[3]?borrow, why must the if (b == null) check come before the else if (b.available) check, rather than after?getConnection, prepareStatement, executeQuery, conn.close().Take these away. They continue exactly what we just did.
Library program into the playground yourself. Then add a new method countAvailable() that returns how many books are still on the shelf as an int, and print the count before and after a borrow. It should drop by one.Library so each Book also has a borrower field (a String, the name of who has it). When a book is borrowed, set borrower; when returned, clear it. Add a method that lists every book that is currently out and who has it.Library before adding the menu.Library program for it to remember its books after the program closes. You do not need to write the code; describe which of the four beats would replace add and which would replace listAvailable.