THE SYLLABUS TASK
What you need to build
Create a billing system with generic menu items.
Full syllabus requirements: classes, fields and methods
n (1–50), then item name, price and category for each item, one value per line. Each entry represents one billed item. An illustrative 10% discount applies only when subtotal is strictly greater than 1500.
UNDERSTAND THE IDEA
Explanation
MenuItem<T1,T2> makes the item-name and category types generic while keeping price numeric. The driver uses ArrayList<MenuItem<String,String>> rather than a raw collection.
generateBill accepts a typed list using wildcards and sums each item’s price. A DoubleUnaryOperator lambda implements the stated threshold rule; the syllabus supplies the threshold but not a rate, so 10% is explicitly an example assumption.
TURN THE IDEA INTO STEPS
Step-by-step algorithm
- Read validated item records into a typed ArrayList.
- Sum their prices with generateBill.
- Apply the lambda to the subtotal.
- Calculate the discount as subtotal minus discounted bill.
- Display categories and formatted bill amounts.
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.
Lambda discount threshold
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 MenuItem<T1,T2> {
final T1 itemName;final double price;final T2 category;
MenuItem(T1 name,double price,T2 category){if(!Double.isFinite(price)||price<0||price>1000000)throw new IllegalArgumentException("Invalid price.");itemName=name;this.price=price;this.category=category;}
}
static double generateBill(List<? extends MenuItem<?,?>> items){double total=0;for(MenuItem<?,?> item:items)total+=item.price;return total;}
public static void main(String[] args){
try{
int n=integer(1,50);ArrayList<MenuItem<String,String>> items=new ArrayList<>();
for(int i=0;i<n;i++)items.add(new MenuItem<>(text(),number(0,1000000),text()));
double total=generateBill(items);java.util.function.DoubleUnaryOperator discount=value->value>1500?value*0.90:value;
for(MenuItem<String,String> item:items)System.out.println(item.itemName+" | "+item.category);
money("Subtotal",total);money("Discount",total-discount.applyAsDouble(total));money("Bill",discount.applyAsDouble(total));
}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–25Parameterise item name and category types
static class MenuItem<T1,T2> {
final T1 itemName;final double price;final T2 category;
MenuItem(T1 name,double price,T2 category){if(!Double.isFinite(price)||price<0||price>1000000)throw new IllegalArgumentException("Invalid price.");itemName=name;this.price=price;this.category=category;}
}MenuItem
Line 26Read prices from a list with wildcard item types
static double generateBill(List<? extends MenuItem<?,?>> items){double total=0;for(MenuItem<?,?> item:items)total+=item.price;return total;}The extends wildcard accepts compatible lists of MenuItem instances. The method only reads each item and adds its price; it does not need to know the concrete name/category type arguments.
Lines 27–30Read concrete String/String menu items
public static void main(String[] args){
try{
int n=integer(1,50);ArrayList<MenuItem<String,String>> items=new ArrayList<>();
for(int i=0;i<n;i++)items.add(new MenuItem<>(text(),number(0,1000000),text()));main uses ArrayList
Line 31Define a numeric discount operation with a lambda
double total=generateBill(items);java.util.function.DoubleUnaryOperator discount=value->value>1500?value*0.90:value;generateBill computes the subtotal. DoubleUnaryOperator maps one double to another: totals strictly above 1500 receive 10% off; exactly 1500 does not satisfy the > condition.
Lines 32–33Display the items and derive discount and final bill
for(MenuItem<String,String> item:items)System.out.println(item.itemName+" | "+item.category);
money("Subtotal",total);money("Discount",total-discount.applyAsDouble(total));money("Bill",discount.applyAsDouble(total));Print each item’s name/category. The discount amount is subtotal minus the lambda’s discounted result, and Bill is that result. Applying this stateless lambda twice gives the same answer.
Lines 34–36Report 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
- A subtotal of exactly 1500 receives no discount.
- A subtotal of 1501 receives discount 150.10.
- The final bill becomes 1350.90.
- Each stored item retains its chosen name/category types.
CHECK THE BEHAVIOUR
Sample input & output
These outputs were captured by running the exact delivered Java project.
Sample 1
2
Book
1000
Study
Course
600
Learning
Book | Study
Course | Learning
Subtotal: 1600.00
Discount: 160.00
Bill: 1440.00
Sample 2
1
Item
1500
Demo
Item | Demo
Subtotal: 1500.00
Discount: 0.00
Bill: 1500.00
Common mistakes
- Do not use raw ArrayList<MenuItem> when type parameters can be supplied.
- Use >1500 rather than ≥1500.
- The price is double, not a generic arithmetic type.
WHY THIS GROWTH RATE?
Time and space complexity
O(n+T) billing/output work for n items and T printed text; O(n) item storage.
generateBill visits every item once. The discount lambda performs a fixed number of numeric operations after that sum.
The typed collection stores n references and n item objects. Generics enforce compile-time types; they do not make the sum sublinear or copy the whole list.
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.
What do T1 and T2 represent in MenuItem?
They parameterize the item-name and category types. In the driver both are String, while price remains double for arithmetic.
Why avoid the raw ArrayList<MenuItem> form from the task wording?
A parameterized form preserves compile-time type checks and avoids unchecked/raw-type warnings. It still fulfills the intended generic-item collection task.
Why does generateBill use wildcard type parameters?
It needs only the numeric price and does not depend on the particular name/category types. Wildcards allow appropriately typed MenuItem lists without erasing them to raw types.
Which functional interface is used for the discount lambda?
DoubleUnaryOperator accepts one double and returns one double. That matches transforming a subtotal into a discounted bill.
Does a subtotal of exactly 1500 receive this discount?
No. The task says total greater than 1500, so the lambda uses a strict comparison.
Why is the discount rate published as an assumption?
The syllabus specifies the threshold but not a percentage. Stating the example rate makes results reproducible without inventing an unstated official billing rule.