Saltar al contenido
ES EN

Solved exercise: inheritance, constructors and super in Java

Updated on

Exam Code Java Object-oriented programming Exam level Tested code

Solved Java exam exercise: an employee hierarchy with an abstract class, constructors chained with super and this, and salaries built on super.salary().

Exam Code: Solved exercise: inheritance, constructors and super in Java
Object-Oriented Programming · Midterm ExamQuestion 2 · 2.5 points · 30 min

A consulting firm wants to compute the monthly payroll of its staff. Model the staff as a class hierarchy in Java and obtain each person's salary through inheritance. Each class must add only what distinguishes it from its superclass.

The root is the abstract class Employee. It has two private final fields, id (String) and base (int), and a single constructor protected Employee(String id, int base). It declares the abstract method String type() and the method int salary(), which returns the base. Its toString() returns the id, the type and the salary separated by single spaces.

The subclasses are as follows. Developer has base 1800 and earns 25 per overtime hour. It offers the public constructor Developer(String id, int overtimeHours) and the constructor protected Developer(String id, int base, int overtimeHours). SeniorDeveloper extends Developer, has base 2400, keeps the overtime pay and adds 150 per on-call shift. Its constructor is SeniorDeveloper(String id, int overtimeHours, int onCallShifts).

SalesRep has base 1500 and adds 8% of its sales, truncated to whole units (integer division). Its constructor is SalesRep(String id, int sales). Manager has a fixed base of 3500 and the constructor Manager(String id). The values returned by type() are, respectively, Developer, Senior, Sales and Manager.

Mandatory requirements: every subclass constructor must invoke a superclass constructor with super(...), or delegate to another constructor of the same class with this(...). No subclass may access the fields of Employee directly. Every overridden salary() must build on super.salary() instead of repeating the superclass formula.

Input (standard input): an integer n (0 ≤ n ≤ 1000) followed by n lines. Each line starts with a type letter and an id without spaces: D id overtimeHours, S id overtimeHours onCallShifts, R id sales or M id. All numbers are integers between 0 and 1000000, and the input is always valid.

Output: store every employee in a List<Employee> in input order, then traverse the list and print one line per employee using the toString() format. Finish with a line TOTAL t, where t is the sum of all salaries. If the staff list is empty, print only TOTAL 0.

Sample input

5
D EMP001 4
S EMP002 2 3
R EMP003 1234
M EMP004
R EMP005 12500

Expected output

EMP001 Developer 1900
EMP002 Senior 2900
EMP003 Sales 1598
EMP004 Manager 3500
EMP005 Sales 2500
TOTAL 12398
Abstract class Employee with private final fields, a protected constructor and an abstract type() method
0.75
Correct constructor chaining: super(...) as the first statement, this(...) in Developer, and the base salary passed up from SeniorDeveloper
1.0
salary() overridden by reusing super.salary(), with no duplicated formulas, and a polymorphic traversal of the staff list
0.5
Input parsing and output in the exact format, including the empty staff list
0.25

Hints

Hint 1 · Where does each piece of data live?

The id and the base are shared by everyone, so they belong in Employee. Overtime hours only make sense in Developer (and, through inheritance, in SeniorDeveloper). On-call shifts only exist for the senior, and sales only for SalesRep. If a field appears in two sibling classes, it is probably in the wrong place.

Hint 2 · How does the senior get a different base?

SeniorDeveloper cannot assign base, because the field is private in Employee. It has to pass the value upwards, so it needs a Developer constructor that takes the base as a parameter. To avoid duplicated code, the public Developer constructor can delegate to that one with this(...), passing 1800.

Hint 3 · How do you add pay without repeating formulas?

Think of the salary as layers. Employee contributes the base, Developer adds overtime and SeniorDeveloper adds on-call shifts. Each layer calls super.salary() and adds its own part. The senior then inherits the overtime calculation without writing it again.

Solution

Explained solution

The heart of the exercise is a clean division of responsibilities. Each class stores only its own data, receives through its constructor whatever the superclass needs, and hands it over with super(...). The five classes are written as static nested classes of Main so everything fits in one file. In a real project they would live in separate files inside a package, but the design would be identical.

The program reads every token from the input and builds each employee with a switch on the type letter. It stores the objects in the list and then traverses it, treating every object as an Employee. That final loop never asks which concrete class it is dealing with. Dynamic dispatch picks the right type() and salary() for each object, which is exactly what the hierarchy is designed to deliver.

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class Main {

    static abstract class Employee {
        private final String id;
        private final int base;

        protected Employee(String id, int base) {
            this.id = id;
            this.base = base;
        }

        public String getId() {
            return id;
        }

        public int getBase() {
            return base;
        }

        public abstract String type();

        public int salary() {
            return base;
        }

        @Override
        public String toString() {
            return id + " " + type() + " " + salary();
        }
    }

    static class Developer extends Employee {
        private static final int DEVELOPER_BASE = 1800;
        private static final int HOURLY_RATE = 25;
        private final int overtimeHours;

        public Developer(String id, int overtimeHours) {
            this(id, DEVELOPER_BASE, overtimeHours);
        }

        protected Developer(String id, int base, int overtimeHours) {
            super(id, base);
            this.overtimeHours = overtimeHours;
        }

        @Override
        public String type() {
            return "Developer";
        }

        @Override
        public int salary() {
            return super.salary() + HOURLY_RATE * overtimeHours;
        }
    }

    static class SeniorDeveloper extends Developer {
        private static final int SENIOR_BASE = 2400;
        private static final int ON_CALL_BONUS = 150;
        private final int onCallShifts;

        public SeniorDeveloper(String id, int overtimeHours, int onCallShifts) {
            super(id, SENIOR_BASE, overtimeHours);
            this.onCallShifts = onCallShifts;
        }

        @Override
        public String type() {
            return "Senior";
        }

        @Override
        public int salary() {
            return super.salary() + ON_CALL_BONUS * onCallShifts;
        }
    }

    static class SalesRep extends Employee {
        private static final int SALES_BASE = 1500;
        private static final int PERCENT = 8;
        private final int sales;

        public SalesRep(String id, int sales) {
            super(id, SALES_BASE);
            this.sales = sales;
        }

        @Override
        public String type() {
            return "Sales";
        }

        @Override
        public int salary() {
            return super.salary() + sales * PERCENT / 100;
        }
    }

    static class Manager extends Employee {
        private static final int MANAGER_BASE = 3500;

        public Manager(String id) {
            super(id, MANAGER_BASE);
        }

        @Override
        public String type() {
            return "Manager";
        }
    }

    public static void main(String[] args) throws IOException {
        String input = new String(System.in.readAllBytes()).trim();
        String[] tk = input.isEmpty() ? new String[0] : input.split("\\s+");
        int p = 0;
        int n = tk.length == 0 ? 0 : Integer.parseInt(tk[p++]);

        List<Employee> staff = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            String kind = tk[p++];
            String id = tk[p++];
            Employee created = switch (kind) {
                case "D" -> new Developer(id, Integer.parseInt(tk[p++]));
                case "S" -> {
                    int hours = Integer.parseInt(tk[p++]);
                    int shifts = Integer.parseInt(tk[p++]);
                    yield new SeniorDeveloper(id, hours, shifts);
                }
                case "R" -> new SalesRep(id, Integer.parseInt(tk[p++]));
                case "M" -> new Manager(id);
                default -> throw new IllegalArgumentException("Unknown type: " + kind);
            };
            staff.add(created);
        }

        StringBuilder out = new StringBuilder();
        long total = 0;
        for (Employee e : staff) {
            out.append(e).append('\n');
            total += e.salary();
        }
        out.append("TOTAL ").append(total).append('\n');
        System.out.print(out);
    }
}

Employee is abstract because there is no such thing as a generic employee: every real object belongs to some subclass. Its fields are private final, so no code outside the class can change them and they are assigned exactly once, in the constructor. That constructor is protected. Calling it from outside the hierarchy makes no sense, but subclasses must call it, because it is the only one and Java will not generate a no-argument constructor.

Developer has two constructors. The public one fixes the base by calling this(id, DEVELOPER_BASE, overtimeHours). The protected one receives the base from outside, hands it over with super(id, base) and then assigns overtimeHours. Note the order: a call to super or this must be the first statement of a constructor, which is why a single constructor can never contain both.

SeniorDeveloper uses that protected constructor with super(id, SENIOR_BASE, overtimeHours). When a senior is created, the chain runs from the top down. First id and base are initialised in Employee, then overtimeHours in Developer, and finally onCallShifts. Without the three-parameter constructor the senior would have no way to change the base, because it is private.

The salary() methods form layers. In the example test, the senior EMP002 with 2 overtime hours and 3 on-call shifts gets 2400 from Employee, plus 50 for overtime in Developer, plus 450 for the shifts: 2900. The overtime calculation is written once, and the senior reuses it through super.salary(), which invokes the version of its direct superclass.

SalesRep computes sales * PERCENT / 100 in integer arithmetic, multiplying before dividing. With sales of 1234 the commission is 98 (not 98.72) and the salary is 1598; with 12500 it is 1000 and the salary is 2500. The limits test includes sales of 99, which contribute 7, and the maximum of 1000000, which contributes 80000. The product peaks at 8000000, comfortably inside an int.

Manager does not override salary(): it inherits the one from Employee and returns the base. In main the list is a List<Employee>, and the final loop never checks the type of any object. out.append(e) calls toString(), which calls type() and salary(), and the virtual machine chooses at run time the version belonging to the actual class. The total is accumulated in a long as a precaution.

The parsing handles the empty staff edge case. With 0 the loop never runs and only TOTAL 0 is printed, as the second test checks. The third test has a single senior with no overtime and no shifts. It verifies that the salary is exactly the base of 2400, so the senior's base has not been mixed up with the 1800 of Developer.

Code and tests on GitLab

Test cases

CaseInputExpected outputActual outputResult
Statement example5 D EMP001 4 S EMP002 2 3 R EMP003 1234 M EMP004 R EMP005 12500EMP001 Developer 1900 EMP002 Senior 2900 EMP003 Sales 1598 EMP004 Manager 3500 EMP005 Sales 2500 TOTAL 12398EMP001 Developer 1900 EMP002 Senior 2900 EMP003 Sales 1598 EMP004 Manager 3500 EMP005 Sales 2500 TOTAL 12398OK
Empty staff0TOTAL 0TOTAL 0OK
Single senior with no extras1 S EMP999 0 0EMP999 Senior 2400 TOTAL 2400EMP999 Senior 2400 TOTAL 2400OK
Truncation and limit values3 R A100 99 R B100 1000000 D C100 0A100 Sales 1507 B100 Sales 81500 C100 Developer 1800 TOTAL 84807A100 Sales 1507 B100 Sales 81500 C100 Developer 1800 TOTAL 84807OK
Senior versus developer2 S X1 10 4 D X2 10X1 Senior 3250 X2 Developer 2050 TOTAL 5300X1 Senior 3250 X2 Developer 2050 TOTAL 5300OK

Actual outputs: code compiled with Java 21.0.12.1 (Temurin) and run in an isolated container on 8 October 2026.

Complexity

Time: Θ(n + L), where n is the number of employees and L is the length of the input. Reading and splitting the input is linear in L, and creating each object takes constant time. In the final loop each call to salary() climbs at most three levels of the hierarchy. That depth is a fixed constant and does not depend on n.

Memory: Θ(n + L). The list holds n objects of constant size, the token array takes space proportional to the input, and the StringBuilder accumulates n lines of bounded length before printing them in one go. The statement imposes this cost, because it asks you to store the whole staff list first and traverse it afterwards. With n ≤ 1000 the cost is negligible.

Common mistakes

  • Writing a subclass constructor without calling super(...) when Employee has no no-argument constructor. Java inserts an implicit super(), and the code does not compile.
  • Putting this.overtimeHours = overtimeHours; before super(id, base), or trying to use both this(...) and super(...) in the same constructor. Either call must be the first statement.
  • Declaring base as protected, or dropping final, so that SeniorDeveloper can overwrite it after construction. The base should travel up the constructor chain instead.
  • Repeating the full overtime formula in SeniorDeveloper.salary() instead of calling super.salary(). If the hourly rate changes, two classes then have to be edited.
  • Computing the commission as sales / 100 * 8. Dividing first loses precision, so sales of 1234 give 96 instead of 98.
  • Traversing the list with chains of instanceof checks to decide what to print. This throws away the benefit of overriding type() and salary() in each class.

Variants

Add an intern who extends Developer

You are asked for an Intern class with base 900 that earns overtime at half the rate. The clean solution is to give Developer a protected constructor that also receives the hourly rate and stores it in a final field. The public constructor and the senior's constructor delegate to it passing 25, and the intern calls super(id, 900, overtimeHours, 12). Intern only overrides type(), because the salary() formula does not change.

Print the staff sorted by salary

If you must print from highest to lowest salary, breaking ties by id, sort the list before the loop: staff.sort(Comparator.comparingInt(Employee::salary).reversed().thenComparing(Employee::getId)). The comparator works on the Employee type and handles every subclass through the same dynamic dispatch that toString() relies on. The time cost becomes Θ(n log n).

Article generated with AI.larebelion

Byline

· Chief editor · English edition · London

“A good hierarchy shows itself when each class knows little and knows it exactly once.”

Comentarios

Publicar un comentario