The previous six lessons have given you separate pieces: the structure of a Java file, variables and their types, operators and console input/output. This lesson assembles them all into the first real program of the course: BiblioTechApp, the application Nexus Software will use to register the return of a book from its technical library and print the receipt with the corresponding fine. We will not write it in one go: we will build it in four successive versions, each improving on the previous one, which is exactly how software is developed in real life. At the end we will honestly analyse what it still cannot do and which module of the course will solve each shortcoming.
Contents
- The program specification
- Version 1: the skeleton with fixed data
- Version 2: asking the user for the data
- Version 3: correct calculations with the ternary operator
- Version 4: formatted output and the fine cap
- The complete final code, commented
- Compiling and running
BiblioTechApp - Test data sets and expected output
- The program's limitations and how they will be solved
- Common Mistakes and Tips
- Exercises
- The program specification
Before writing a single line, it is worth pinning down what the program has to do. This is the first thing done in any professional project, and it saves an enormous amount of work.
Name: BiblioTechApp
Package: com.nexussoftware.bibliotech
Expected behaviour:
- Show a welcome header with the company and application names.
- Ask on the console for the details of a loan return:
- Employee name
- Book title
- ISBN
- Days elapsed since it was lent
- Calculate:
- The days late, which can never be negative.
- The delay expressed in whole weeks and leftover days.
- The fine: days late multiplied by the daily rate.
- The cap: the fine can never exceed the established maximum.
- The loan status: on time or overdue.
- Show a formatted, readable receipt with all that data.
Business rules of the Nexus Software library:
| Rule | Value | Constant |
|---|---|---|
| Standard loan duration | 15 days | LOAN_DAYS |
| Rate per day late | €0.25 | DAILY_RATE |
| Maximum fine per loan | €20.00 | MAX_FINE |
| Minor delay threshold | 7 days | MINOR_THRESHOLD |
Technical constraints of this version: a single main method, no conditionals, no loops, no classes of your own. Everything you need you have already learnt.
flowchart TD
A["Show header"] --> B["Ask for employee, title,<br/>ISBN and elapsed days"]
B --> C["Calculate days late<br/>(clamped to zero)"]
C --> D["Break down into<br/>weeks and days"]
D --> E["Calculate base fine<br/>= days late x rate"]
E --> F["Apply the<br/>MAX_FINE cap"]
F --> G["Determine status<br/>and severity"]
G --> H["Print formatted<br/>receipt with printf"]
- Version 1: the skeleton with fixed data
The first version asks for nothing: the data is written into the code. It serves to validate the structure and the calculations without the complication of input.
package com.nexussoftware.bibliotech;
public class BiblioTechApp {
public static void main(String[] args) {
final String COMPANY_NAME = "Nexus Software";
final int LOAN_DAYS = 15;
final double DAILY_RATE = 0.25;
String employee = "Marta Ruiz";
String title = "Effective Java";
String isbn = "978-0000000001";
int elapsedDays = 27;
int daysLate = elapsedDays - LOAN_DAYS;
double fine = daysLate * DAILY_RATE;
System.out.println("=== BiblioTech - " + COMPANY_NAME + " ===");
System.out.println("Employee: " + employee);
System.out.println("Book: " + title);
System.out.println("ISBN: " + isbn);
System.out.println("Days late: " + daysLate);
System.out.println("Fine: " + fine + " EUR");
}
}Output:
=== BiblioTech - Nexus Software === Employee: Marta Ruiz Book: Effective Java ISBN: 978-0000000001 Days late: 12 Fine: 3.0 EUR
It works, but it has three obvious flaws that we will fix one by one:
- The data is fixed. To process another return you have to edit the code and recompile.
- The calculations are naive. If the book is returned after 9 days,
daysLatewould be-6and the fine-1.5 EUR: the library paying the employee. - The output is poor.
3.0 EURis not a presentable amount, and the labels are not aligned.
- Version 2: asking the user for the data
We replace the fixed values with Scanner reading, applying the rule from the previous lesson: everything is read with nextLine() and converted with Integer.parseInt where needed.
package com.nexussoftware.bibliotech;
import java.util.Scanner;
public class BiblioTechApp {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
final String COMPANY_NAME = "Nexus Software";
final int LOAN_DAYS = 15;
final double DAILY_RATE = 0.25;
System.out.println("=== BiblioTech - " + COMPANY_NAME + " ===");
System.out.println("Loan return record");
System.out.println();
System.out.print("Employee : ");
String employee = input.nextLine();
System.out.print("Book title : ");
String title = input.nextLine();
System.out.print("ISBN : ");
String isbn = input.nextLine();
System.out.print("Elapsed days : ");
int elapsedDays = Integer.parseInt(input.nextLine());
int daysLate = elapsedDays - LOAN_DAYS;
double fine = daysLate * DAILY_RATE;
System.out.println();
System.out.println("Employee: " + employee);
System.out.println("Book: " + title);
System.out.println("ISBN: " + isbn);
System.out.println("Days late: " + daysLate);
System.out.println("Fine: " + fine + " EUR");
input.close();
}
}It is already a useful program: the same binary serves any return. But the negative-numbers flaw is still there. Try it by entering 9 for the elapsed days and you will see Fine: -1.5 EUR.
- Version 3: correct calculations with the ternary operator
This is where the ternary operator comes in to clamp the delay to zero, and while we are at it we add the breakdown into weeks and the loan status. This is the fragment that changes:
// The delay can never be negative: if it is returned early, it is 0.
int daysLate = elapsedDays > LOAN_DAYS
? elapsedDays - LOAN_DAYS
: 0;
// Breaking down the delay: / gives the whole weeks, % the leftover days.
int weeksLate = daysLate / 7;
int leftoverDays = daysLate % 7;
// Days of term remaining (0 if it has already expired).
int daysRemaining = elapsedDays < LOAN_DAYS
? LOAN_DAYS - elapsedDays
: 0;
double baseFine = daysLate * DAILY_RATE;
// Text labels chosen with ternaries.
String status = daysLate > 0 ? "OVERDUE" : "ON TIME";
String severity = daysLate == 0 ? "-"
: daysLate <= MINOR_THRESHOLD ? "MINOR"
: "SEVERE";This block concentrates a good part of what was learnt in the operators lesson: comparison, subtraction, integer division, modulo, multiplication with automatic promotion from int to double, a simple ternary and a nested ternary. None of this requires if.
- Version 4: formatted output and the fine cap
Two things are missing: applying the fine cap and presenting the receipt as a document. Both are solved with what we saw in lesson 01-06.
// Cap: if the base fine exceeds the maximum, the maximum is charged.
double finalFine = baseFine > MAX_FINE ? MAX_FINE : baseFine;
String capApplied = baseFine > MAX_FINE ? "YES" : "NO";
// Formatted receipt: labels left-aligned in width 22,
// amounts right-aligned with exactly two decimals.
System.out.println("=".repeat(WIDTH));
System.out.printf("%-22s %s%n", "Employee:", employee);
System.out.printf("%-22s %8.2f EUR%n", "FINE TO PAY:", finalFine);With this, 3.0 becomes 3.00 and the columns line up. We now have the complete program.
- The complete final code, commented
package com.nexussoftware.bibliotech;
import java.util.Scanner;
/**
* BiblioTechApp - Management system for the internal technical library
* of Nexus Software.
*
* Version 0.1 from module 1: linear record of a loan return, with
* delay and fine calculation, and printing of a receipt.
*
* @author BiblioTech Team - Nexus Software
* @version 0.1
*/
public class BiblioTechApp {
public static void main(String[] args) {
// ---------------------------------------------------------------
// 1. RESOURCES
// ---------------------------------------------------------------
// A single Scanner for the whole application, connected to the
// keyboard. Closing a Scanner over System.in closes System.in, so
// we never create more than one.
Scanner input = new Scanner(System.in);
// ---------------------------------------------------------------
// 2. BUSINESS RULES
// ---------------------------------------------------------------
// All the constants go together at the top: changing a rate means
// editing ONE line, not hunting for the number all over the
// program. 'final' guarantees nobody changes them by accident.
final String COMPANY_NAME = "Nexus Software";
final String VERSION = "0.1";
final int LOAN_DAYS = 15; // standard loan duration
final double DAILY_RATE = 0.25; // euros per day late
final double MAX_FINE = 20.0; // absolute cap on the fine
final int MINOR_THRESHOLD = 7; // up to 7 days, the delay is minor
final int DAYS_PER_WEEK = 7;
final int WIDTH = 52; // width of the receipt lines
// ---------------------------------------------------------------
// 3. WELCOME HEADER
// ---------------------------------------------------------------
// "=".repeat(WIDTH) builds the separator line without typing
// 52 equals signs by hand. repeat() exists since Java 11.
System.out.println("=".repeat(WIDTH));
System.out.printf(" BiblioTech v%s - %s%n", VERSION, COMPANY_NAME);
System.out.println(" Loan return record");
System.out.println("=".repeat(WIDTH));
System.out.println();
// ---------------------------------------------------------------
// 4. DATA COLLECTION
// ---------------------------------------------------------------
// We use print (without ln) so that the cursor stays on the same
// line as the question.
// ALL reading is done with nextLine(): that way no line break is
// ever left pending in the input stream, which is what causes the
// classic nextInt()-followed-by-nextLine() problem.
System.out.print("Employee : ");
String employee = input.nextLine();
System.out.print("Book title : ");
String title = input.nextLine();
System.out.print("ISBN : ");
String isbn = input.nextLine();
System.out.print("Elapsed days : ");
// The text read is converted to a number with parseInt.
// If the user types something that is not an integer, the program
// ends with NumberFormatException (handled in module 6).
int elapsedDays = Integer.parseInt(input.nextLine());
// ---------------------------------------------------------------
// 5. CALCULATIONS
// ---------------------------------------------------------------
// Days late clamped to zero. Without the ternary, an early return
// would give a negative delay and therefore a negative fine:
// the library paying the employee.
int daysLate = elapsedDays > LOAN_DAYS
? elapsedDays - LOAN_DAYS
: 0;
// Days of term remaining. It only makes sense if it has not expired.
int daysRemaining = elapsedDays < LOAN_DAYS
? LOAN_DAYS - elapsedDays
: 0;
// Breaking down the delay: integer division gives the whole weeks
// and the modulo gives the leftover days.
int weeksLate = daysLate / DAYS_PER_WEEK;
int leftoverDays = daysLate % DAYS_PER_WEEK;
// Base fine. daysLate is an int and DAILY_RATE is a double:
// Java promotes the int to a double before multiplying.
double baseFine = daysLate * DAILY_RATE;
// Fine cap. The amount charged never exceeds MAX_FINE.
double finalFine = baseFine > MAX_FINE ? MAX_FINE : baseFine;
String capApplied = baseFine > MAX_FINE ? "YES" : "NO";
// Text labels derived from the calculations.
String status = daysLate > 0 ? "OVERDUE" : "ON TIME";
// Nested ternary, read as a ladder from top to bottom.
String severity = daysLate == 0 ? "-"
: daysLate <= MINOR_THRESHOLD ? "MINOR"
: "SEVERE";
// Receipt reference code: the employee's initial in uppercase
// + the last four digits of the ISBN.
// charAt(0) takes the first character; substring cuts from the
// given position to the end.
String reference = String.format("%c%s-%s",
employee.charAt(0),
title.substring(0, 3).toUpperCase(),
isbn.substring(isbn.length() - 4));
// ---------------------------------------------------------------
// 6. RECEIPT
// ---------------------------------------------------------------
System.out.println();
System.out.println("=".repeat(WIDTH));
System.out.println(" RETURN RECEIPT");
System.out.println("=".repeat(WIDTH));
// %-22s aligns the label to the LEFT in width 22, creating a
// uniform column. %s sticks the value right after it.
System.out.printf("%-22s %s%n", "Reference:", reference);
System.out.printf("%-22s %s%n", "Employee:", employee);
System.out.printf("%-22s %s%n", "Book:", title);
System.out.printf("%-22s %s%n", "ISBN:", isbn);
System.out.println("-".repeat(WIDTH));
// %5d aligns the integers to the RIGHT in width 5, so that the
// units land in the same column and can be compared at a glance.
System.out.printf("%-22s %5d days%n", "Standard loan:", LOAN_DAYS);
System.out.printf("%-22s %5d days%n", "Elapsed:", elapsedDays);
System.out.printf("%-22s %5d days%n", "Days remaining:", daysRemaining);
System.out.printf("%-22s %5d days (%d wk. and %d days)%n",
"Late by:", daysLate, weeksLate, leftoverDays);
System.out.println("-".repeat(WIDTH));
// %8.2f: total width 8, exactly 2 decimals. That is what turns a
// raw 3.0 into a presentable 3.00.
System.out.printf("%-22s %8.2f EUR%n", "Daily rate:", DAILY_RATE);
System.out.printf("%-22s %8.2f EUR%n", "Fine calculated:", baseFine);
System.out.printf("%-22s %8.2f EUR%n", "Maximum fine:", MAX_FINE);
System.out.printf("%-22s %8s%n", "Cap applied:", capApplied);
System.out.println("-".repeat(WIDTH));
System.out.printf("%-22s %8.2f EUR%n", "TOTAL TO PAY:", finalFine);
System.out.printf("%-22s %s (%s)%n", "Status:", status, severity);
System.out.println("=".repeat(WIDTH));
// Warning on the error channel: it is not part of the receipt and
// can be redirected separately when running the program.
System.err.println("[warning] Version 0.1: the program does not validate input.");
// ---------------------------------------------------------------
// 7. RELEASING RESOURCES
// ---------------------------------------------------------------
input.close();
}
}
- Compiling and running
BiblioTechApp
BiblioTechAppThe file must be on the path corresponding to its package:
bibliotech/
└── src/
└── main/
└── java/
└── com/
└── nexussoftware/
└── bibliotech/
└── BiblioTechApp.javaStanding in src/main/java:
# Compile. You give the PATH of the file, with slashes.
# -d sends the .class files to a separate 'out' folder, away from the source.
javac -d ../../../out com/nexussoftware/bibliotech/BiblioTechApp.java
# Run. You give the qualified CLASS NAME, with dots.
# -cp tells the java command where to look for the compiled classes.
java -cp ../../../out com.nexussoftware.bibliotech.BiblioTechAppIf you prefer to work without a package for a quick test, remove the first line (package ...;) and use the Java 11+ single-file mode:
And if redirecting the output is useful to you, remember that the receipt and the warning travel on different channels:
# The receipt is saved to a file; the warning still appears on screen
java -cp ../../../out com.nexussoftware.bibliotech.BiblioTechApp > receipt.txtIn the IDE, just press the run button on the class; IntelliJ and Eclipse run exactly these two commands underneath.
- Test data sets and expected output
Testing a program means running it with data chosen deliberately to cover different situations. These are the two cases you should verify:
| Data | Set A: overdue loan | Set B: on-time loan |
|---|---|---|
| Employee | Marta Ruiz |
Diego Alonso |
| Title | Effective Java |
Refactoring |
| ISBN | 978-0000000001 |
978-0000000003 |
| Elapsed days | 27 |
9 |
| Expected days late | 12 | 0 |
| Expected days remaining | 0 | 6 |
| Breakdown | 1 week and 5 days | 0 weeks and 0 days |
| Fine calculated | 3.00 EUR | 0.00 EUR |
| Cap applied | NO | NO |
| Total to pay | 3.00 EUR | 0.00 EUR |
| Status | OVERDUE (SEVERE) | ON TIME (-) |
| Reference | MEFF-0001 | DREF-0003 |
Complete output for set A
==================================================== BiblioTech v0.1 - Nexus Software Loan return record ==================================================== Employee : Marta Ruiz Book title : Effective Java ISBN : 978-0000000001 Elapsed days : 27 ==================================================== RETURN RECEIPT ==================================================== Reference: MEFF-0001 Employee: Marta Ruiz Book: Effective Java ISBN: 978-0000000001 ---------------------------------------------------- Standard loan: 15 days Elapsed: 27 days Days remaining: 0 days Late by: 12 days (1 wk. and 5 days) ---------------------------------------------------- Daily rate: 0.25 EUR Fine calculated: 3.00 EUR Maximum fine: 20.00 EUR Cap applied: NO ---------------------------------------------------- TOTAL TO PAY: 3.00 EUR Status: OVERDUE (SEVERE) ====================================================
Complete output for set B
==================================================== BiblioTech v0.1 - Nexus Software Loan return record ==================================================== Employee : Diego Alonso Book title : Refactoring ISBN : 978-0000000003 Elapsed days : 9 ==================================================== RETURN RECEIPT ==================================================== Reference: DREF-0003 Employee: Diego Alonso Book: Refactoring ISBN: 978-0000000003 ---------------------------------------------------- Standard loan: 15 days Elapsed: 9 days Days remaining: 6 days Late by: 0 days (0 wk. and 0 days) ---------------------------------------------------- Daily rate: 0.25 EUR Fine calculated: 0.00 EUR Maximum fine: 20.00 EUR Cap applied: NO ---------------------------------------------------- TOTAL TO PAY: 0.00 EUR Status: ON TIME (-) ====================================================
Note the value of set B: the "on time" case is the one that proves the ternary works. Without it, the delay would be -6 and the fine -1.50 EUR. Choosing data that exercises the edge cases, and not just the normal case, is the heart of software testing (module 11).
A third case worth trying on your own: Nuria Vidal, Design Patterns, ISBN 978-0000000002, 120 days. The delay would be 105 days and the calculated fine 26.25 EUR, above the maximum: the program should show Cap applied: YES and a total of 20.00 EUR.
- The program's limitations and how they will be solved
BiblioTechApp works, but it is a toy program and it is worth being explicit about why. Recognising the shortcomings of your own code is one of the most valuable skills a developer can have.
| Limitation | What happens today | When it is solved |
|---|---|---|
| It does not validate input | If you type twenty-seven for the days, the program crashes with NumberFormatException |
Module 2 (checking with hasNextInt) and module 6 (catching the exception and reacting) |
| It does not repeat | It processes one return and ends. For the next one you have to run it again | Module 2: loops and an interactive menu (lesson 02-06) |
| It does not make complex decisions | Everything is resolved with ternaries, which only choose values, they do not run different actions | Module 2: if, switch |
| The data is scattered | title, author, isbn and available are loose variables with no relationship between them |
Module 3: the Book class will group them into a type of their own |
| It only handles one book and one employee | There is no way to manage a catalog of hundreds of titles | Module 5: ArrayList and HashMap |
| It saves nothing | When you close the program, the receipt disappears forever | Module 7: writing files, CSV |
| It notifies nobody | Nobody receives a reminder before the term expires | Module 8: background tasks |
| It only works on your machine | A colleague cannot check the catalog from their own computer | Modules 9 and 12: networking and a web application |
It uses double for money |
The amounts can accumulate rounding errors | Module 10: BigDecimal |
| There are no tests | You check the output by eye, running it by hand | Module 11: JUnit |
Notice the logic of the course: each later module removes one concrete, visible limitation of the program you have just written. You will not learn loops because it is the next topic, but because today your program only processes one return. You will not learn classes just because, but because today title and author are two variables with nothing binding them together.
Common Mistakes and Tips
- Forgetting the extra
input.nextLine()after anextInt(). It does not happen in this program because all reading usesnextLine(), but if you mix methods it will come back. - Typing
0,25instead of0.25when entering decimals.Double.parseDoubleexpects the dot, whatever separatorprintfdisplays. substring(isbn.length() - 4)failing. If the ISBN has fewer than 4 characters, it throwsStringIndexOutOfBoundsException. In this version we assume correct input.employee.charAt(0)failing if the user presses Enter without typing anything: an empty string has no character at position 0.- Using
%dwith adoublein the total'sprintf. It causesIllegalFormatConversionException. - Forgetting
%nin one of theprintfs, which glues two receipt lines together. - Scattering the constants through the code. Group them at the top: it is the difference between changing the rate in ten seconds and hunting for
0.25across the whole file. - Tip: when a calculation has several steps (base fine → cap → total), create one variable per step with a descriptive name. A single giant expression is impossible to debug.
- Tip: run the program with all three data sets (normal delay, on time, cap exceeded) every time you modify it. It is your first manual test suite, and in module 11 you will turn it into an automated one.
- Tip: keep this version of
BiblioTechApp.java. In module 2 you will start from it to add the interactive menu, and comparing the two versions will show you your own progress.
Exercises
Exercise 1: Extending the receipt with the deposit and the due notice
Modify BiblioTechApp to add to the receipt:
- A deposit of 10 % of the book's price (ask for it on the console as a
double), which is refunded in full if there is no delay and is reduced by the amount of the fine if there is. The deposit to refund can never be negative. - A due-soon notice: a piece of text valued
"DUE SOON"if there are 3 days or fewer of term left (and it has not expired yet),"IN PROGRESS"if there are more, and"OVERDUE"if there is already a delay.
Use only ternaries, no if. Format every amount with two decimals.
Exercise 2: Loan percentages and statistics
Extend the receipt with three calculated indicators, all with two decimals:
- The percentage of the term consumed:
elapsedDays / LOAN_DAYS * 100. Careful with integer division. - The average cost per elapsed day: the final fine divided by the elapsed days.
- The percentage of the maximum fine represented by the final fine.
Show all three aligned in an "INDICATORS" section of the receipt.
Exercise 3: A receipt for two simultaneous loans
Without using loops or arrays, extend the program so that it registers the return of two books from the same employee and shows a combined receipt with:
- The details of both books in a table aligned with
printf. - The individual fine for each one.
- The accumulated total, subject to a joint
MAX_FINEcap. - Which of the two books has accrued the greater delay (with the ternary operator).
Reflect at the end: what would happen if Nexus Software wanted to process ten books? Write down your answer; we will come back to it in module 5.
Solutions
Solution 1
Add the reading of the price and this block of calculations and output (the rest of the program does not change):
// --- Additional reading ---
System.out.print("Book price : ");
double bookPrice = Double.parseDouble(input.nextLine());
// --- Deposit and notice calculations ---
final double DEPOSIT_PERCENTAGE = 0.10; // 10 % of the price
final int NOTICE_THRESHOLD = 3; // days before warning of expiry
// The deposit is 10 % of the price: double * double -> double.
double deposit = bookPrice * DEPOSIT_PERCENTAGE;
// The fine is deducted, but it can never end up negative:
// the ternary clamps the result to zero.
double depositReturned = deposit - finalFine > 0
? deposit - finalFine
: 0.0;
// The withheld amount is the part of the deposit the library keeps.
double depositWithheld = deposit - depositReturned;
// Due notice: a ladder of ternaries.
// It is evaluated top to bottom and stops at the first true condition.
String notice = daysLate > 0 ? "OVERDUE"
: daysRemaining <= NOTICE_THRESHOLD ? "DUE SOON"
: "IN PROGRESS";
// --- Additional output ---
System.out.println("-".repeat(WIDTH));
System.out.printf("%-22s %8.2f EUR%n", "Book price:", bookPrice);
System.out.printf("%-22s %8.2f EUR%n", "Deposit paid:", deposit);
System.out.printf("%-22s %8.2f EUR%n", "Deposit withheld:", depositWithheld);
System.out.printf("%-22s %8.2f EUR%n", "DEPOSIT TO REFUND:", depositReturned);
System.out.printf("%-22s %s%n", "Notice:", notice);Checking with the three cases:
| Case | Price | Deposit (10 %) | Fine | Withheld | To refund | Notice |
|---|---|---|---|---|---|---|
| Marta, 27 days | 45.90 | 4.59 | 3.00 | 3.00 | 1.59 | OVERDUE |
| Diego, 9 days | 38.75 | 3.88 | 0.00 | 0.00 | 3.88 | IN PROGRESS |
| Diego, 13 days | 38.75 | 3.88 | 0.00 | 0.00 | 3.88 | DUE SOON |
The key point of the exercise is the order of the ternary ladder: the daysLate > 0 check must come first. If it were placed later, an expired loan with daysRemaining equal to 0 would fall into the "DUE SOON" branch, which would be wrong.
Solution 2
// --- Indicators ---
// Careful: elapsedDays and LOAN_DAYS are ints, so the division
// would be an INTEGER one. We have to force a double first.
double termPercentage = (double) elapsedDays / LOAN_DAYS * 100;
// Average cost per elapsed day. Here finalFine is already a double,
// so the promotion is automatic and no cast is needed.
double averageDailyCost = finalFine / elapsedDays;
// What percentage of the cap the amount charged represents.
double maxFinePercentage = finalFine / MAX_FINE * 100;
System.out.println("-".repeat(WIDTH));
System.out.println(" INDICATORS");
System.out.println("-".repeat(WIDTH));
System.out.printf("%-22s %8.2f %%%n", "Term consumed:", termPercentage);
System.out.printf("%-22s %8.4f EUR/day%n", "Average daily cost:", averageDailyCost);
System.out.printf("%-22s %8.2f %%%n", "Of maximum fine:", maxFinePercentage);Output for set A (Marta Ruiz, 27 days, fine 3.00):
---------------------------------------------------- INDICATORS ---------------------------------------------------- Term consumed: 180.00 % Average daily cost: 0.1111 EUR/day Of maximum fine: 15.00 %
Three important details:
- The
(double)cast is mandatory in the first calculation. Without it,27 / 15would give1(integer division) and the percentage would be100.00 %instead of180.00 %. It is the trap from lesson 01-05 in a real case. %%prints a literal percent sign. A lone%would makeprintftry to read it as the start of a specifier and throwUnknownFormatConversionException.%.4ffor the daily cost because with two decimals the value0.1111would round to0.11and lose its meaning. The precision must be chosen according to the magnitude of the data.
Solution 3
// ================= SECOND BOOK =================
System.out.println();
System.out.println("--- Second book ---");
System.out.print("Book title 2 : ");
String title2 = input.nextLine();
System.out.print("ISBN 2 : ");
String isbn2 = input.nextLine();
System.out.print("Elapsed days 2 : ");
int elapsedDays2 = Integer.parseInt(input.nextLine());
// The same calculations as for the first one, duplicated.
// This duplication is precisely what METHODS (module 3) and
// LOOPS over collections (module 5) will solve.
int daysLate2 = elapsedDays2 > LOAN_DAYS
? elapsedDays2 - LOAN_DAYS
: 0;
double baseFine2 = daysLate2 * DAILY_RATE;
// Combined total, subject to a single cap for the whole return.
double grossTotal = baseFine + baseFine2;
double finalTotal = grossTotal > MAX_FINE ? MAX_FINE : grossTotal;
// Which one has accrued the greater delay.
String mostOverdueBook = daysLate >= daysLate2 ? title : title2;
int highestDelay = daysLate >= daysLate2 ? daysLate : daysLate2;
// ================= COMBINED RECEIPT =================
System.out.println();
System.out.println("=".repeat(WIDTH));
System.out.println(" COMBINED RETURN RECEIPT");
System.out.println("=".repeat(WIDTH));
System.out.printf("%-22s %s%n", "Employee:", employee);
System.out.println("-".repeat(WIDTH));
// Table header and two rows with the same format.
System.out.printf("%-24s %-16s %5s %9s%n", "BOOK", "ISBN", "LATE", "FINE");
System.out.printf("%-24s %-16s %5d %9.2f%n", title, isbn, daysLate, baseFine);
System.out.printf("%-24s %-16s %5d %9.2f%n", title2, isbn2, daysLate2, baseFine2);
System.out.println("-".repeat(WIDTH));
System.out.printf("%-22s %8.2f EUR%n", "Sum of fines:", grossTotal);
System.out.printf("%-22s %8.2f EUR%n", "TOTAL TO PAY:", finalTotal);
System.out.printf("%-22s %s (%d days)%n", "Highest delay:", mostOverdueBook, highestDelay);
System.out.println("=".repeat(WIDTH));Sample output with Marta Ruiz, "Effective Java" (27 days) and "Design Patterns" (40 days):
==================================================== COMBINED RETURN RECEIPT ==================================================== Employee: Marta Ruiz ---------------------------------------------------- BOOK ISBN LATE FINE Effective Java 978-0000000001 12 3.00 Design Patterns 978-0000000002 25 6.25 ---------------------------------------------------- Sum of fines: 9.25 EUR TOTAL TO PAY: 9.25 EUR Highest delay: Design Patterns (25 days) ====================================================
The final reflection in this exercise is the most important of the whole module. For ten books you would need title1 … title10, isbn1 … isbn10, daysLate1 … daysLate10: thirty variables, thirty identical reading blocks and thirty copied calculations. The code would be three times longer, impossible to maintain, and typing daysLate3 where you meant daysLate4 would be enough to produce a wrong receipt that nobody would notice.
That pain has three solutions and you will see them in this order: loops (module 2) remove the repetition of the process; classes (module 3) group title, isbn and daysLate into a single Book object; and collections (module 5) store as many books as needed in a single variable. It is no coincidence that the course follows exactly that path.
Conclusion
You have written your first complete, working Java program. BiblioTechApp welcomes the user, collects the details of a return on the console, calculates the days late clamped to zero with the ternary operator, breaks them down into weeks and days with integer division and the modulo, obtains the fine by automatically promoting int to double, applies a cap to it, derives status and severity labels with nested ternaries, composes a reference with String methods and prints a perfectly aligned receipt with printf. All of it with a single main, well-grouped final constants and a single, properly closed Scanner.
With this lesson you close module 1. You know what Java is and how it runs, you have a working development environment, you understand the anatomy of a source file, you handle variables, types, literals and conversions, you have a grip on the operators and their traps, and you know how to communicate with the user on the console in both directions. And, above all, you know exactly what your program lacks: it does not repeat, it does not decide, it does not validate and it remembers nothing.
In module 2, Control Flow, you will start to fix that: with if and switch your program will make real decisions, with while and for it will process as many returns as needed, with break and continue it will fine-tune that control, you will learn to debug step by step to see what happens inside your code, and it will all culminate in the BiblioTech interactive menu, the version that no longer ends after a single loan.
Java Programming Course
Module 1: Introduction to Java
- Introduction to Java
- Setting Up the Development Environment
- Basic Syntax and Structure
- Variables and Data Types
- Operators
- Console Input and Output
- Your First Complete Program: BiblioTech
Module 2: Control Flow
- Conditional Statements
- Loops
- Switch Statements
- Break and Continue
- Debugging and Execution Traces
- Project: The BiblioTech Interactive Menu
Module 3: Object-Oriented Programming
- Introduction to OOP
- Classes and Objects
- Methods
- Constructors
- Inheritance
- Polymorphism
- Encapsulation
- Abstraction
- The Object Class: equals, hashCode and toString
Module 4: Advanced Object-Oriented Programming
- Interfaces
- Abstract Classes
- Inner Classes
- Anonymous Classes
- Lambda Expressions
- Functional Interfaces and Method References
- Enums and Records
Module 5: Data Structures and Collections
- Arrays
- The Collections Framework
- ArrayList
- LinkedList
- HashMap
- HashSet
- Queue and Deque
- Stack
- Sorting and Searching Collections
Module 6: Exception Handling
- Introduction to Exceptions
- The Try-Catch Block
- Throw and Throws
- Custom Exceptions
- The Finally Block
- Try-with-resources and AutoCloseable
- Error Handling Strategies and Logging
Module 7: File Input/Output
- Reading Files
- Writing Files
- File Streams
- BufferedReader and BufferedWriter
- Serialization
- The NIO.2 API: Path and Files
- Interchange Formats: CSV and Properties
Module 8: Multithreading and Concurrency
- Introduction to Multithreading
- Creating Threads
- Thread Lifecycle
- Synchronization
- Concurrency Utilities
- Concurrent Collections and Atomic Variables
- Asynchronous Tasks with CompletableFuture
Module 9: Networking
- Introduction to Networking
- Sockets
- ServerSocket
- DatagramSocket and DatagramPacket
- URL and HttpURLConnection
- The Modern HTTP Client
Module 10: Advanced Topics
- Generics
- Annotations
- Reflection
- Java 8 Features: Streams and Optional
- Dates and Times with java.time
- Java 9 and Beyond
- Memory, Garbage Collection and Performance
Module 11: Java Frameworks and Libraries
- Introduction to Java Frameworks
- Spring Framework
- Hibernate
- JUnit
- Maven
- Advanced Testing with Mockito
- Essential Ecosystem Libraries
