THE SYLLABUS TASK
What you need to build
Design and implement a Library Book Catalog System using OOP concepts in Java.
Full syllabus requirements: classes, fields and methods
One value per line: book count n (1–20); title, author, publisher, year, ISBN and price for each book; search keyword; discount percentage (0–100).
UNDERSTAND THE IDEA
Explanation
A Book combines catalog attributes with behavior. The default constructor creates a placeholder, the parameterized constructor fills real details, and the copy constructor creates an independent object.
The driver stores Book references in an array, discounts a copy of the first book and then displays the unchanged originals. matches performs case-insensitive substring matching on title or author, using Locale.ROOT for consistent case conversion.
TURN THE IDEA INTO STEPS
Step-by-step algorithm
- Read validated book records into an array.
- Construct and display a default book.
- Copy the first book and discount only that copy.
- Display the original catalog.
- Check each original book against the title/author keyword.
SEE THE CORE OPERATION
Flowchart
This chart focuses on the named operation or core OOP decision. Diamonds show conditions; labeled branches show outcomes. Read it with the complete algorithm and source for input handling and other operations. Scroll within the diagram on a phone.
Copy construction and discount
Main.java
import java.util.*;
public class Main {
private static final Scanner IN = new Scanner(System.in).useLocale(Locale.ROOT);
static String text() {
if (!IN.hasNextLine()) throw new IllegalArgumentException("Incomplete input.");
String value = IN.nextLine().trim();
if (value.isEmpty() || value.length() > 200) throw new IllegalArgumentException("Text must have 1–200 characters.");
return value;
}
static int integer(int low, int high) {
int value = Integer.parseInt(text());
if (value < low || value > high) throw new IllegalArgumentException("Integer out of range.");
return value;
}
static double number(double low, double high) {
double value = Double.parseDouble(text());
if (!Double.isFinite(value) || value < low || value > high) throw new IllegalArgumentException("Number out of range.");
return value;
}
static void money(String label, double value) { System.out.printf(Locale.ROOT, "%s: %.2f%n", label, value); }
static class Book {
private String title, author, publisher, isbn;
private int year;
private double price;
Book() { this("Untitled", "Unknown", "Unknown", 2026, "Not assigned", 0); }
Book(String title, String author, String publisher, int year, String isbn, double price) {
if (title == null || author == null || publisher == null || isbn == null || year < 1 || year > 9999 || !Double.isFinite(price) || price < 0 || price > 1000000)
throw new IllegalArgumentException("Invalid book details.");
this.title = title; this.author = author; this.publisher = publisher;
this.year = year; this.isbn = isbn; this.price = price;
}
Book(Book other) { this(other.title, other.author, other.publisher, other.year, other.isbn, other.price); }
boolean matches(String keyword) {
String query = keyword.toLowerCase(Locale.ROOT);
return title.toLowerCase(Locale.ROOT).contains(query) || author.toLowerCase(Locale.ROOT).contains(query);
}
void applyDiscount(double percentage) {
if (!Double.isFinite(percentage) || percentage < 0 || percentage > 100) throw new IllegalArgumentException("Discount must be 0–100.");
price *= 1 - percentage / 100;
}
void displayDetails() {
System.out.printf(Locale.ROOT, "%s | %s | %s | %d | %s | %.2f%n", title, author, publisher, year, isbn, price);
}
}
// n, then title/author/publisher/year/isbn/price for each book, then keyword, discount.
public static void main(String[] args) {
try {
int n = integer(1, 20); Book[] books = new Book[n];
for (int i = 0; i < n; i++) books[i] = new Book(text(), text(), text(), integer(1, 9999), text(), number(0, 1000000));
String keyword = text(); double discount = number(0, 100);
System.out.println("Default constructor:"); new Book().displayDetails();
Book copy = new Book(books[0]); copy.applyDiscount(discount);
System.out.println("Discounted copy of first book:"); copy.displayDetails();
System.out.println("Original catalog:"); for (Book book : books) book.displayDetails();
System.out.println("Search matches:"); boolean found = false;
for (Book book : books) if (book.matches(keyword)) { book.displayDetails(); found = true; }
if (!found) System.out.println("No matching books.");
} catch (IllegalArgumentException e) { System.out.println("Invalid input: " + e.getMessage()); }
}
}
Copy a source file or download the project to preserve its file names.
USE THE RIGHT ENVIRONMENT
How to compile and run
Install a JDK (Java 11 or newer; JDK 17 is suitable), extract this project’s ZIP into its own folder, then open a terminal in that folder.
javac -encoding UTF-8 Main.java
java MainThe standalone source is named Main.java and its entry class is Main. You may also paste it into an online Java compiler and supply the sample input there. Choose Java explicitly; these pages do not change the site compiler or import code automatically.
Each experiment has its own folder because many use the public class Main. Compile and run one experiment at a time.
UNDERSTAND THE JAVA CODE
Line-by-line explanation
These steps follow the exact Java source above. Related statements are grouped; line numbers count blank lines and restart for each file. Start with main to follow execution, then open the constructors, methods or event handlers it uses.
Main.java
Lines 1–4Import utilities and create the shared input reader
import java.util.*;
public class Main {
private static final Scanner IN = new Scanner(System.in).useLocale(Locale.ROOT);java.util.* imports utility type names such as Scanner, Locale and collections. Main is the public class matching Main.java. The private static final IN field holds one shared Scanner for System.in; final prevents replacing that reference. The scanner uses Locale.ROOT.
Lines 5–10Read one nonempty trimmed input line
static String text() {
if (!IN.hasNextLine()) throw new IllegalArgumentException("Incomplete input.");
String value = IN.nextLine().trim();
if (value.isEmpty() || value.length() > 200) throw new IllegalArgumentException("Text must have 1–200 characters.");
return value;
}text checks whether a line exists, reads it and removes leading/trailing whitespace. Reject empty text and lengths above 200 by throwing IllegalArgumentException. Otherwise return the string. One field is read per line, so these helpers do not mix nextInt with leftover newlines.
Lines 11–15Parse and range-check an integer field
static int integer(int low, int high) {
int value = Integer.parseInt(text());
if (value < low || value > high) throw new IllegalArgumentException("Integer out of range.");
return value;
}Integer.parseInt converts the returned string to int or throws NumberFormatException, a subtype of IllegalArgumentException. Reject values outside the caller’s inclusive low/high limits, then return the accepted integer.
Lines 16–20Parse and range-check a finite decimal field
static double number(double low, double high) {
double value = Double.parseDouble(text());
if (!Double.isFinite(value) || value < low || value > high) throw new IllegalArgumentException("Number out of range.");
return value;
}Double.parseDouble reads the string as a double. Double.isFinite rejects NaN and infinities as well as the explicit low/high range check. Return only a finite accepted value; exceptions transfer control to a matching catch block.
Line 21Format a labelled numeric result consistently
static void money(String label, double value) { System.out.printf(Locale.ROOT, "%s: %.2f%n", label, value); }money is a formatting helper: %s prints the label, %.2f prints two decimal places, and %n ends the line. Locale.ROOT fixes locale-sensitive formatting. The helper can also display non-currency quantities; it does not change the stored value.
Lines 22–25Keep each book’s fields inside its object
static class Book {
private String title, author, publisher, isbn;
private int year;
private double price;Book is a static nested class, so it does not require a Main instance. Its private string, integer and double fields store the title, author, publisher, ISBN, year and price separately for each Book object.
Line 26Delegate the default constructor to the full constructor
Book() { this("Untitled", "Unknown", "Unknown", 2026, "Not assigned", 0); }this(...) must be the first constructor action. It supplies placeholder details and zero price to the parameterised constructor, avoiding a second copy of the initialisation rules.
Lines 27–32Validate and assign the supplied book details
Book(String title, String author, String publisher, int year, String isbn, double price) {
if (title == null || author == null || publisher == null || isbn == null || year < 1 || year > 9999 || !Double.isFinite(price) || price < 0 || price > 1000000)
throw new IllegalArgumentException("Invalid book details.");
this.title = title; this.author = author; this.publisher = publisher;
this.year = year; this.isbn = isbn; this.price = price;
}The parameterised constructor rejects null text references, an unsupported year and a non-finite or out-of-range price. this.title refers to the object field rather than the same-named parameter. Store all accepted values before the constructor finishes.
Line 33Construct a separate book object from an existing book
Book(Book other) { this(other.title, other.author, other.publisher, other.year, other.isbn, other.price); }This copy constructor passes the other book’s fields to the full constructor. The new Book has its own price field. Shared String references are safe for this example because String objects are immutable.
Lines 34–37Match a keyword against title or author
boolean matches(String keyword) {
String query = keyword.toLowerCase(Locale.ROOT);
return title.toLowerCase(Locale.ROOT).contains(query) || author.toLowerCase(Locale.ROOT).contains(query);
}Convert the query and stored strings to lowercase with Locale.ROOT, then use contains for substring search. Logical OR accepts a match in either field. The stored original title and author are not changed.
Lines 38–41Apply a checked percentage discount to this book only
void applyDiscount(double percentage) {
if (!Double.isFinite(percentage) || percentage < 0 || percentage > 100) throw new IllegalArgumentException("Discount must be 0–100.");
price *= 1 - percentage / 100;
}Require a finite percentage from 0 to 100. Multiplying price by 1 - percentage/100 keeps the undiscounted fraction. For 200 at 10%, price becomes 180. The receiver object determines which book changes.
Lines 42–45Display one book’s details
void displayDetails() {
System.out.printf(Locale.ROOT, "%s | %s | %s | %d | %s | %.2f%n", title, author, publisher, year, isbn, price);
}
}printf prints the fields in the supplied order and formats price to two decimal places. The last brace closes Book, not Main.
Lines 46–49Start main and create an array of book references
// n, then title/author/publisher/year/isbn/price for each book, then keyword, discount.
public static void main(String[] args) {
try {
int n = integer(1, 20); Book[] books = new Book[n];The comment describes input order. public static void main is the application entry point. Inside try, require 1–20 books and reserve that many reference slots; the individual Book objects are created next.
Lines 50–51Read and construct each catalog entry
for (int i = 0; i < n; i++) books[i] = new Book(text(), text(), text(), integer(1, 9999), text(), number(0, 1000000));
String keyword = text(); double discount = number(0, 100);For every slot, read the six fields and call new Book with validated year and price values. Then read the search keyword and discount percentage.
Lines 52–54Demonstrate the default and copy constructors
System.out.println("Default constructor:"); new Book().displayDetails();
Book copy = new Book(books[0]); copy.applyDiscount(discount);
System.out.println("Discounted copy of first book:"); copy.displayDetails();Create and display a placeholder book. Copy the first catalog object into a new Book, discount that copy and display it. The original first book’s price remains unchanged.
Lines 55–58Display the originals and all matching books
System.out.println("Original catalog:"); for (Book book : books) book.displayDetails();
System.out.println("Search matches:"); boolean found = false;
for (Book book : books) if (book.matches(keyword)) { book.displayDetails(); found = true; }
if (!found) System.out.println("No matching books.");Print the original catalog, then test each book with matches. found records whether at least one match printed. Only print the no-match message if the full scan found none.
Lines 59–61Report invalid input and close the enclosing scopes
} catch (IllegalArgumentException e) { System.out.println("Invalid input: " + e.getMessage()); }
}
}The catch handles IllegalArgumentException, including invalid numeric strings from the helpers, and prints its message. The final braces close main and Main. Java objects become eligible for garbage collection when no live reference needs them; this program does not manually free them.
FOLLOW THE STATE
Dry run
- An original book priced at 100 is copied.
- A 10% discount makes the copy’s price 90.
- The original still displays 100 because it is a different object.
- Searching rao also matches author Rao after case normalization.
CHECK THE BEHAVIOUR
Sample input & output
These outputs were captured by running the exact delivered Java project.
Sample 1
2
Java Basics
Rao
Tech Press
2025
ISBN1
400
Python Guide
Asha
Learn Press
2024
ISBN2
300
rao
10
Default constructor:
Untitled | Unknown | Unknown | 2026 | Not assigned | 0.00
Discounted copy of first book:
Java Basics | Rao | Tech Press | 2025 | ISBN1 | 360.00
Original catalog:
Java Basics | Rao | Tech Press | 2025 | ISBN1 | 400.00
Python Guide | Asha | Learn Press | 2024 | ISBN2 | 300.00
Search matches:
Java Basics | Rao | Tech Press | 2025 | ISBN1 | 400.00
Sample 2
1
Data Structures
Ravi
Tech
2026
ISBN3
200
missing
0
Default constructor:
Untitled | Unknown | Unknown | 2026 | Not assigned | 0.00
Discounted copy of first book:
Data Structures | Ravi | Tech | 2026 | ISBN3 | 200.00
Original catalog:
Data Structures | Ravi | Tech | 2026 | ISBN3 | 200.00
Search matches:
No matching books.
Common mistakes
- Assigning another variable to a Book reference does not copy the object.
- Apply the discount formula percentage/100.
- Do not compare searchable text using ==.
WHY THIS GROWTH RATE?
Time and space complexity
Catalog work scales with n and text lengths; array storage O(n), apart from stored strings.
Creating, displaying and checking n books requires at least O(n) object visits. Case conversion and substring matching also depend on title/author/keyword lengths; a conservative naive matching bound is O(nLK).
The copy and discount use a fixed number of fields. Sharing immutable String references is safe here, while the copy has its own primitive price field. Output cost includes all printed text.
These bounds describe the implemented data processing. Input/output text length, scheduling, framework rendering and runtime/library internals are identified separately where they matter. Educational limits bound the actual examples.
PREPARE FOR YOUR LAB VIVA
Related viva questions & answers
Try answering aloud, then expand to check your reasoning. Questions focus on this program’s classes, state and behavior.
Why does Book copy = original fail to demonstrate a copy constructor?
It creates a second reference to the same object. Changing the price through either reference changes that one object rather than producing an independent copy.
Why can this copy constructor reuse String references safely?
Strings are immutable. Sharing title and author strings does not permit one book to mutate those string objects, while each book still stores its own price.
What is initialized by the default Book constructor?
It delegates to the parameterized constructor with placeholder catalog fields and a zero price. This avoids duplicating initialization logic.
How does matches support both title and author searches?
It normalizes the keyword and each searchable field to lowercase, then accepts a substring match in either title or author.
Why is the discount applied to a copy in the driver?
It visibly proves that copy construction creates an independent Book object. The discounted copy can be shown beside the unchanged original catalog.
Is an array of Book objects the same as creating all its books?
No. new Book[n] creates n reference slots initially containing null. Each slot must then receive a constructed Book before its methods can be called.