Skip to lab content

WEEK 3 · OBJECT ORIENTED PROGRAMMING LAB

Salary slips using a nonstatic inner class

EXPERIMENT 05Java 11+Console program

THE SYLLABUS TASK

What you need to build

Design and implement a Java program to generate monthly salary slips using inner classes. You must define an Outer Class Employee and an Inner Class SalaryBreakup, where the inner class accesses and calculates salary components using the outer class data.
Full syllabus requirements: classes, fields and methods
Design and implement a Java program to generate monthly salary slips using inner classes. You must define an Outer Class Employee and an Inner Class SalaryBreakup, where the inner class accesses and calculates salary components using the outer class data. Outer Class: Employee Attributes: int id, String name, double baseSalary, String department Constructors Required: Default constructor, Parameterized constructor using this(id, name, baseSalary, department) Methods to Implement: displayEmployeeDetails(), calculateSalary(), generateSlip() Inner Class: SalaryBreakup Attributes: double hra, double da, double pf, double tax, double netPay Methods to Implement: computeBreakup(), printBreakup()
Input format & conventions

Employee ID, name, base salary and department, one per line. Example rates on base salary: HRA 20%, DA 10%, PF 12%, tax 5%. These are lab assumptions, not statutory rates.

UNDERSTAND THE IDEA

Explanation

Employee is the enclosing class, while each SalaryBreakup belongs to a specific Employee instance. The inner class directly reads that instance’s private baseSalary.

The default and shorter overloaded constructors delegate to the full constructor using this(...). computeBreakup fills allowance and deduction fields; netPay combines them. generateSlip prints details and the computed breakup.

TURN THE IDEA INTO STEPS

Step-by-step algorithm

  1. Construct the employee using validated details.
  2. Create its SalaryBreakup instance.
  3. Compute allowances and deductions from the outer base salary.
  4. Calculate net pay as base+HRA+DA−PF−tax.
  5. Print the default example and the requested employee’s slip.

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.

Salary breakup through an inner class

Salary breakup through an inner class

Open full-size flowchart ↗

Java 11+

Complete Java source

Main.java

Download .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 Employee {
        private final int id;
        private final String name, department;
        private final double baseSalary;
        Employee() { this(0, "Unassigned", 0, "Unassigned"); }
        Employee(int id, String name) { this(id, name, 0, "Unassigned"); }
        Employee(int id, String name, double baseSalary, String department) {
            if (!Double.isFinite(baseSalary) || baseSalary < 0 || baseSalary > 1000000) throw new IllegalArgumentException("Invalid salary.");
            this.id=id; this.name=name; this.baseSalary=baseSalary; this.department=department;
        }
        void displayEmployeeDetails() { System.out.println(id + " | " + name + " | " + department); }
        double calculateSalary() { SalaryBreakup breakup = new SalaryBreakup(); breakup.computeBreakup(); return breakup.netPay; }
        void generateSlip() { displayEmployeeDetails(); SalaryBreakup breakup = new SalaryBreakup(); breakup.computeBreakup(); breakup.printBreakup(); }
        class SalaryBreakup {
            double hra, da, pf, tax, netPay;
            void computeBreakup() {
                hra=baseSalary*0.20; da=baseSalary*0.10; pf=baseSalary*0.12; tax=baseSalary*0.05;
                netPay=baseSalary+hra+da-pf-tax;
            }
            void printBreakup() { money("Base",baseSalary); money("HRA",hra); money("DA",da); money("PF",pf); money("Tax",tax); money("Net pay",netPay); }
        }
    }
    // id/name/base-salary/department. Rates are illustrative, not statutory rules.
    public static void main(String[] args) {
        try {
            Employee employee = new Employee(integer(1,1000000),text(),number(0,1000000),text());
            System.out.println("Default employee:"); new Employee().generateSlip();
            System.out.println("Salary slip:"); employee.generateSlip(); money("calculateSalary()",employee.calculateSalary());
        } 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 Main

The 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.

Start with main →

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 employee identity and salary in the outer object
static class Employee {
        private final int id;
        private final String name, department;
        private final double baseSalary;

Employee is static relative to Main. Its identity and base salary fields belong to each Employee instance and are final after construction.

Lines 26–31Chain overloaded constructors into one full initialiser
        Employee() { this(0, "Unassigned", 0, "Unassigned"); }
        Employee(int id, String name) { this(id, name, 0, "Unassigned"); }
        Employee(int id, String name, double baseSalary, String department) {
            if (!Double.isFinite(baseSalary) || baseSalary < 0 || baseSalary > 1000000) throw new IllegalArgumentException("Invalid salary.");
            this.id=id; this.name=name; this.baseSalary=baseSalary; this.department=department;
        }

The no-argument and two-argument constructors call the full constructor with defaults. The full constructor validates salary and assigns fields. Overloading is chosen by argument list at compile time.

Lines 32–34Create a salary-breakup helper for the current employee
        void displayEmployeeDetails() { System.out.println(id + " | " + name + " | " + department); }
        double calculateSalary() { SalaryBreakup breakup = new SalaryBreakup(); breakup.computeBreakup(); return breakup.netPay; }
        void generateSlip() { displayEmployeeDetails(); SalaryBreakup breakup = new SalaryBreakup(); breakup.computeBreakup(); breakup.printBreakup(); }

The methods print identity or create a new SalaryBreakup, compute its values and return net pay or print the slip. Each helper is attached to the Employee instance whose method created it.

Lines 35–40Use a non-static inner class to access the enclosing salary
        class SalaryBreakup {
            double hra, da, pf, tax, netPay;
            void computeBreakup() {
                hra=baseSalary*0.20; da=baseSalary*0.10; pf=baseSalary*0.12; tax=baseSalary*0.05;
                netPay=baseSalary+hra+da-pf-tax;
            }

SalaryBreakup can read its enclosing Employee’s baseSalary without receiving a separate employee argument. It computes HRA 20%, DA 10%, PF 12% and tax 5%, then adds allowances and subtracts deductions.

Lines 41–43Print the breakup and close both nested scopes
            void printBreakup() { money("Base",baseSalary); money("HRA",hra); money("DA",da); money("PF",pf); money("Tax",tax); money("Net pay",netPay); }
        }
    }

printBreakup formats each component and net pay. The following braces close SalaryBreakup and Employee. These rates are illustrative values in this exercise, not statutory payroll rules.

Lines 44–49Construct an employee and demonstrate both salary methods
    // id/name/base-salary/department. Rates are illustrative, not statutory rules.
    public static void main(String[] args) {
        try {
            Employee employee = new Employee(integer(1,1000000),text(),number(0,1000000),text());
            System.out.println("Default employee:"); new Employee().generateSlip();
            System.out.println("Salary slip:"); employee.generateSlip(); money("calculateSalary()",employee.calculateSalary());

main reads the real employee fields, prints a default employee’s slip, then the supplied employee’s slip and calculateSalary result. Separate breakup objects use the same outer base salary and therefore give consistent calculations.

Lines 50–52Report 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

  1. Base salary 20000 gives HRA 4000 and DA 2000.
  2. PF is 2400 and illustrative tax is 1000.
  3. Net pay is 20000+4000+2000−2400−1000 = 22600.
  4. calculateSalary returns the same net value independently.

CHECK THE BEHAVIOUR

Sample input & output

These outputs were captured by running the exact delivered Java project.

Sample 1

INPUT
1
Asha
20000
CSE
OUTPUT
Default employee:
0 | Unassigned | Unassigned
Base: 0.00
HRA: 0.00
DA: 0.00
PF: 0.00
Tax: 0.00
Net pay: 0.00
Salary slip:
1 | Asha | CSE
Base: 20000.00
HRA: 4000.00
DA: 2000.00
PF: 2400.00
Tax: 1000.00
Net pay: 22600.00
calculateSalary(): 22600.00

Sample 2

INPUT
2
Ravi
0
Training
OUTPUT
Default employee:
0 | Unassigned | Unassigned
Base: 0.00
HRA: 0.00
DA: 0.00
PF: 0.00
Tax: 0.00
Net pay: 0.00
Salary slip:
2 | Ravi | Training
Base: 0.00
HRA: 0.00
DA: 0.00
PF: 0.00
Tax: 0.00
Net pay: 0.00
calculateSalary(): 0.00

Common mistakes

  • A nonstatic SalaryBreakup needs an enclosing Employee instance.
  • Use this(...) as the constructor’s first statement.
  • Do not recursively delegate a constructor to its own signature.

WHY THIS GROWTH RATE?

Time and space complexity

O(1) numeric work and extra state per slip; output depends on name/department length.

The breakup uses a fixed number of arithmetic operations and fields. It does not scan a collection or iterate according to salary magnitude.

Generating n slips would take O(n), apart from text output. The enclosing reference and five component fields occupy fixed per-instance storage.

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

6 focused questions

Try answering aloud, then expand to check your reasoning. Questions focus on this program’s classes, state and behavior.

How does SalaryBreakup access Employee.baseSalary?

A nonstatic inner-class instance is associated with an enclosing Employee instance. It can access that instance’s private data without a separate salary parameter.

Why is SalaryBreakup not declared static?

The exercise specifically requires it to use the outer employee’s data. A static nested class would not have an implicit enclosing Employee reference.

What does the default Employee constructor’s this(...) call do?

It delegates initialization to the full constructor using placeholder values. This keeps the field assignments in one place.

Where must a constructor-delegation this(...) call appear?

It must be the first statement in that constructor. It cannot be placed after ordinary assignments or made to recursively invoke the same constructor.

Why are allowance and deduction calculations separated from printing?

computeBreakup produces the component state, while printBreakup presents it. This makes the arithmetic usable by calculateSalary as well as by the slip display.

Why are these salary percentages explicitly stated?

The syllabus supplies component names but no rates. Stating demonstration rates makes the output reproducible and avoids implying a real payroll policy.