Project Based Learning in Java (24CSH-301 / 24ITH-301) - Lab 2

Experiment 1.2: Product Management, Library System, and Student Information System

Create Java programs to manage product details, library systems, and student information using classes, inheritance, and abstraction.

Aim Implement Java programs to demonstrate object-oriented concepts through product management, library systems, and a student-teacher information system using constructors, inheritance, and abstraction.
Objectives To learn about classes, and about the encapsulation and aggregation concepts in Java.
Mapped CO CO1, CO2

Lab Brief

What You Need To Build

Easy Level

Product Class

Write a Java program to create a Product class with attributes id, name, and price. The program should demonstrate the use of constructors and methods to display product details.

  • Keep all three attributes private.
  • Initialise them through a constructor using this.
  • Display the state with a dedicated method.
  • Read the details from the user with Scanner.
Medium Level

Library Management System

Write a Java program to implement a library management system. The program should use a base class Book and derived classes Fiction and NonFiction.

  • Share title, author, price via protected fields.
  • Chain constructors with super(...).
  • Override displayDetails() in both subclasses.
  • Hold either subclass in a Book reference.
Hard Level

Student Information System

Design a student information system using an abstract class Person with attributes name, age, and a method displayDetails(). Derive Student and Teacher classes that override it.

  • Declare displayDetails() as abstract in Person.
  • Student adds rollNumber.
  • Teacher adds subject.
  • Never create a Person object directly.
Input/Apparatus used: Hardware - minimum 384 MB RAM, 100 GB hard disk. Software - JDK with any Java IDE such as Eclipse, NetBeans, or IntelliJ IDEA, or a plain terminal with javac and java.

From the Lab Manual

Reading Material

Java - What is OOP?

OOP stands for Object-Oriented Programming.

Procedural programming is about writing procedures or methods that perform operations on the data, while object-oriented programming is about creating objects that contain both data and methods.

Object-oriented programming has several advantages over procedural programming:

  • OOP is faster and easier to execute.
  • OOP provides a clear structure for the programs.
  • OOP helps to keep the Java code DRY - "Don't Repeat Yourself" - and makes the code easier to maintain, modify, and debug.
  • OOP makes it possible to create full reusable applications with less code and shorter development time.

Java - What are Classes and Objects?

Classes and objects are the two main aspects of object-oriented programming.

A class is a template for objects, and an object is an instance of a class. When the individual objects are created, they inherit all the variables and methods from the class.

Java Inheritance (Subclass and Superclass)

In Java, it is possible to inherit attributes and methods from one class to another. We group the "inheritance concept" into two categories:

  • Subclass (child) - the class that inherits from another class.
  • Superclass (parent) - the class being inherited from.

To inherit from a class, use the extends keyword. In the example below, the Car class (subclass) inherits the attributes and methods from the Vehicle class (superclass):

class Vehicle {
    protected String brand = "Ford";  // Vehicle attribute

    public void honk() {              // Vehicle method
        System.out.println("Tuut, tuut!");
    }
}

class Car extends Vehicle {
    private String modelName = "Mustang";  // Car attribute

    public static void main(String[] args) {
        // Create a myCar object
        Car myCar = new Car();

        // Call the honk() method (from the Vehicle class) on the myCar object
        myCar.honk();

        // Display the brand attribute (from the Vehicle class)
        // and the modelName attribute (from the Car class)
        System.out.println(myCar.brand + " " + myCar.modelName);
    }
}
Why and when to use inheritance? It is useful for code reusability: reuse attributes and methods of an existing class when you create a new class.

Before Coding

Prerequisite Concepts

Experiment 1.1 used one class as a container for main. This experiment is where object-oriented programming actually starts: every task is modelled as classes first, and main only creates objects and calls their methods. Revise these ideas before writing any code.

Classes and Objects

A class is a template that bundles data (fields) and behaviour (methods). An object is an instance of that template created with new, which allocates memory and runs the constructor. One class can produce any number of independent objects.

class Product { /* fields + methods */ }

Product p1 = new Product(101, "Mouse", 799.5);
Product p2 = new Product(102, "Keyboard", 1499.0);

Constructors and this

A constructor has the same name as the class, no return type, and runs exactly once per object at creation. When a parameter shadows a field, this.field refers to the object's own copy.

public Product(int id, String name, double price) {
    this.id = id;        // field = parameter
    this.name = name;
    this.price = price;
}

Encapsulation

Keep fields private and expose behaviour through public methods. Outside code cannot corrupt the state directly; it must go through methods the class controls. All three tasks follow this pattern.

private double price;               // hidden data
public void displayProductDetails() // controlled access
{ ... }

Inheritance: extends and super

A subclass inherits the fields and methods of its superclass with extends. Its constructor must first initialise the inherited part by calling super(...) - and that call has to be the first statement.

class Fiction extends Book {
    public Fiction(String title, String author, double price) {
        super(title, author, price); // run Book's constructor first
    }
}

The protected Modifier

private would hide title even from Fiction; public would expose it to everyone. protected sits in between: the field is visible inside the class, its subclasses, and the same package - exactly what a base class like Book needs.

class Book {
    protected String title;   // Fiction and NonFiction can use it
    protected String author;
    protected double price;
}

Method Overriding and Runtime Polymorphism

A subclass replaces an inherited method by declaring it again with the same signature - mark it with @Override so the compiler checks the match. Which version runs is decided at runtime by the object's actual type, not the reference type.

Book b = new Fiction("The Guide", "R. K. Narayan", 350);
b.displayDetails();   // Fiction's version runs, not Book's

Abstract Classes and Methods

An abstract class cannot be instantiated - it exists only to be extended. An abstract method has no body; it is a contract that every concrete subclass must fulfil by overriding it.

abstract class Person {
    public abstract void displayDetails(); // no body here
}
// new Person(...)  -> compile error

Aggregation (HAS-A)

Inheritance models IS-A (a Fiction IS-A Book). Aggregation models HAS-A: one class holds references to objects of another class that can exist independently. A library HAS books; the books are not a kind of library.

class Library {
    private Book[] shelf;   // a Library HAS-A collection of Books
}

Easy Level

Product Class: Constructor and Display Method

Problem statement: Write a Java program to create a Product class with attributes id, name, and price. The program should demonstrate the use of constructors and methods to display product details.

Class Design

Member Purpose
id, name, price Private fields - product state hidden from outside code (encapsulation)
Product(id, name, price) Constructor - copies the parameters into the fields using this
displayProductDetails() Prints the ID, name, and price of this object
ProductDemo.main Reads input, creates one Product, calls the display method
Product class structure and object creation flow Product - id : int - name : String - price : double + Product(id, name, price) + displayProductDetails() private state, public behaviour new Product(id, name, price) main passes the values read by Scanner Constructor runs once this.id = id; this.name = name; ... Object holds its own state id, name, price stored in the object displayProductDetails() prints the stored state
Figure 1. The Product class: private fields, one constructor, one display method.

Algorithm

  1. Start the program and create a Scanner object.
  2. Read the product ID with nextInt(), then consume the leftover newline with nextLine().
  3. Read the product name with nextLine() so names with spaces work.
  4. Read the product price with nextDouble().
  5. Create a Product object - the constructor copies the three values into the private fields.
  6. Call displayProductDetails() to print the ID, name, and price.
  7. Close the scanner and end the program.

Java Program

import java.util.Scanner;

class Product {
    // Attributes
    private int id;
    private String name;
    private double price;

    // Constructor to initialise Product
    public Product(int id, String name, double price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    // Method to display product details
    public void displayProductDetails() {
        System.out.println("\nProduct Details:");
        System.out.println("ID: " + id);
        System.out.println("Name: " + name);
        System.out.println("Price: Rs." + price);
    }
}

public class ProductDemo {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        // Taking product details from the user
        System.out.print("Enter Product ID: ");
        int id = scanner.nextInt();
        scanner.nextLine(); // Consume newline

        System.out.print("Enter Product Name: ");
        String name = scanner.nextLine();

        System.out.print("Enter Product Price: Rs.");
        double price = scanner.nextDouble();

        // Creating Product object
        Product product = new Product(id, name, price);

        // Displaying product details
        product.displayProductDetails();

        scanner.close();
    }
}

Sample Output

Enter Product ID: 101
Enter Product Name: Wireless Mouse
Enter Product Price: Rs.799.5

Product Details:
ID: 101
Name: Wireless Mouse
Price: Rs.799.5
Lab note: The scanner.nextLine() right after nextInt() is not optional. nextInt() leaves the newline in the buffer, so without that extra call the product name would silently come out empty. This is the single most common bug in this experiment.

Medium Level

Library Management System: Book, Fiction, NonFiction

Problem statement: Write a Java program to implement a library management system. The program should use a base class Book and derived classes Fiction and NonFiction.

Who Does What

Class Responsibility
Book (base) Holds protected title, author, price; prints the common fields in displayDetails()
Fiction Calls super(...), overrides displayDetails() to add its heading, then reuses the base version
NonFiction Same as Fiction but with the Non-Fiction heading
LibraryManagementSystem Reads input and decides which subclass to instantiate; stores both in Book references

Keywords Doing the Work

Keyword Role here
extends Makes Fiction and NonFiction inherit everything in Book
protected Lets subclasses see the fields while still hiding them from unrelated code
super super(...) chains constructors; super.displayDetails() reuses the base printing
@Override Asks the compiler to verify the method really replaces an inherited one
Book inheritance hierarchy Book # title, author, price + Book(title, author, price) + displayDetails() extends extends Fiction + Fiction(...) calls super(...) + displayDetails() @Override NonFiction + NonFiction(...) calls super(...) + displayDetails() @Override Book b = new Fiction(...) - the override runs at runtime (polymorphism)
Figure 2. Fiction and NonFiction inherit from Book and override displayDetails().

Algorithm

  1. Read the type, title, author, and price for the first book.
  2. Create a Fiction or NonFiction object based on the type and store it in a Book reference.
  3. Consume the leftover newline, then repeat both steps for the second book.
  4. Call displayDetails() on both references - the overridden subclass version runs (runtime polymorphism).
  5. Inside each override, print the heading, then call super.displayDetails() for the common fields.

Java Program

import java.util.Scanner;

// Base class: Book
class Book {
    protected String title;
    protected String author;
    protected double price;

    // Constructor
    public Book(String title, String author, double price) {
        this.title = title;
        this.author = author;
        this.price = price;
    }

    // Method to display book details (overridden in derived classes)
    public void displayDetails() {
        System.out.println("Title: " + title);
        System.out.println("Author: " + author);
        System.out.println("Price: Rs." + price);
    }
}

// Derived class: Fiction
class Fiction extends Book {
    public Fiction(String title, String author, double price) {
        super(title, author, price);
    }

    // @Override is an annotation, not code: it asks the compiler to verify
    // that this method really overrides Book's displayDetails()
    @Override
    public void displayDetails() {
        System.out.println("\nFiction Book Details:");
        super.displayDetails();
    }
}

// Derived class: NonFiction
class NonFiction extends Book {
    public NonFiction(String title, String author, double price) {
        super(title, author, price);
    }

    // @Override annotation again: compiler-checked override
    @Override
    public void displayDetails() {
        System.out.println("\nNon-Fiction Book Details:");
        super.displayDetails();
    }
}

public class LibraryManagementSystem {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        // Taking input for the first book
        System.out.println("Book 1:");
        System.out.print("Type (Fiction/Non-Fiction): ");
        String type1 = scanner.nextLine();

        System.out.print("Title: ");
        String title1 = scanner.nextLine();

        System.out.print("Author: ");
        String author1 = scanner.nextLine();

        System.out.print("Price: Rs.");
        double price1 = scanner.nextDouble();
        scanner.nextLine(); // Consume newline

        // Creating object based on type
        Book book1 = (type1.equalsIgnoreCase("Fiction"))
                ? new Fiction(title1, author1, price1)
                : new NonFiction(title1, author1, price1);

        // Taking input for the second book
        System.out.println("\nBook 2:");
        System.out.print("Type (Fiction/Non-Fiction): ");
        String type2 = scanner.nextLine();

        System.out.print("Title: ");
        String title2 = scanner.nextLine();

        System.out.print("Author: ");
        String author2 = scanner.nextLine();

        System.out.print("Price: Rs.");
        double price2 = scanner.nextDouble();

        // Creating object based on type
        Book book2 = (type2.equalsIgnoreCase("Fiction"))
                ? new Fiction(title2, author2, price2)
                : new NonFiction(title2, author2, price2);

        // Displaying book details
        book1.displayDetails();
        book2.displayDetails();

        scanner.close();
    }
}

Sample Output

Book 1:
Type (Fiction/Non-Fiction): Fiction
Title: The Guide
Author: R. K. Narayan
Price: Rs.350

Book 2:
Type (Fiction/Non-Fiction): Non-Fiction
Title: Wings of Fire
Author: A. P. J. Abdul Kalam
Price: Rs.450

Fiction Book Details:
Title: The Guide
Author: R. K. Narayan
Price: Rs.350.0

Non-Fiction Book Details:
Title: Wings of Fire
Author: A. P. J. Abdul Kalam
Price: Rs.450.0
Polymorphism in action: book1 is declared as Book but holds a Fiction object, so book1.displayDetails() runs the Fiction version - the decision happens at runtime (dynamic method dispatch). And because the overrides call super.displayDetails(), the common printing lives in exactly one place instead of being copy-pasted into every subclass.

Hard Level

Student Information System: Abstract Person, Student, Teacher

Problem statement: Design a student information system using Java with the following features: use an abstract class Person with attributes name, age, and methods like displayDetails(). Create derived classes Student and Teacher to override displayDetails() and add unique attributes like rollNumber for students and subject for teachers.

Class Design

Class Members
Person (abstract) protected name, age; constructor; abstract displayDetails() with no body
Student Adds private rollNumber; overrides displayDetails()
Teacher Adds private subject; overrides displayDetails()
StudentInformationSystem Reads input, builds one Student and one Teacher, displays both

Abstract Class Rules

Rule Consequence here
Cannot be instantiated new Person(...) is a compile-time error
May have fields and constructors Person(name, age) runs via super(name, age) from each subclass
Abstract method has no body displayDetails() is only a contract in Person
Concrete subclasses must override Student and Teacher each supply their own version
Abstract Person hierarchy with Student and Teacher Person (abstract) # name, age + Person(name, age) + displayDetails() : abstract extends extends Student - rollNumber : int + displayDetails() @Override Teacher - subject : String + displayDetails() @Override new Person(...) - not allowed (compile error)
Figure 3. Person defines the contract; Student and Teacher implement it with their own attributes.

Algorithm

  1. Read the student's name, age, and roll number; consume the leftover newline after the numeric reads.
  2. Create a Student object - its constructor calls super(name, age), then stores rollNumber.
  3. Read the teacher's name, age, and subject the same way.
  4. Create a Teacher object - its constructor calls super(name, age), then stores subject.
  5. Call displayDetails() on each object - the subclass implementation runs.
  6. Note that Person itself is never instantiated; it only defines the shared state and the contract.

Java Program

import java.util.Scanner;

// Abstract class Person
abstract class Person {
    protected String name;
    protected int age;

    // Constructor
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // Abstract method to be overridden by derived classes
    public abstract void displayDetails();
}

// Derived class Student
class Student extends Person {
    private int rollNumber;

    // Constructor
    public Student(String name, int age, int rollNumber) {
        super(name, age);
        this.rollNumber = rollNumber;
    }

    // Overriding displayDetails method
    // (@Override is an annotation - a compile-time check, not runtime code)
    @Override
    public void displayDetails() {
        System.out.println("\nStudent Details:");
        System.out.println("Name: " + name);
        System.out.println("Age: " + age);
        System.out.println("Roll Number: " + rollNumber);
    }
}

// Derived class Teacher
class Teacher extends Person {
    private String subject;

    // Constructor
    public Teacher(String name, int age, String subject) {
        super(name, age);
        this.subject = subject;
    }

    // Overriding displayDetails method
    @Override
    public void displayDetails() {
        System.out.println("\nTeacher Details:");
        System.out.println("Name: " + name);
        System.out.println("Age: " + age);
        System.out.println("Subject: " + subject);
    }
}

public class StudentInformationSystem {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        // Input for Student
        System.out.println("Add Student:");
        System.out.print("Name: ");
        String studentName = scanner.nextLine();

        System.out.print("Age: ");
        int studentAge = scanner.nextInt();

        System.out.print("Roll Number: ");
        int rollNumber = scanner.nextInt();
        scanner.nextLine(); // Consume newline

        Student student = new Student(studentName, studentAge, rollNumber);

        // Input for Teacher
        System.out.println("\nAdd Teacher:");
        System.out.print("Name: ");
        String teacherName = scanner.nextLine();

        System.out.print("Age: ");
        int teacherAge = scanner.nextInt();
        scanner.nextLine(); // Consume newline

        System.out.print("Subject: ");
        String subject = scanner.nextLine();

        Teacher teacher = new Teacher(teacherName, teacherAge, subject);

        // Display details
        student.displayDetails();
        teacher.displayDetails();

        scanner.close();
    }
}

Sample Output

Add Student:
Name: Aman Kumar
Age: 19
Roll Number: 1101

Add Teacher:
Name: Neha Sharma
Age: 38
Subject: Java Programming

Student Details:
Name: Aman Kumar
Age: 19
Roll Number: 1101

Teacher Details:
Name: Neha Sharma
Age: 38
Subject: Java Programming
Why abstract, not a normal base class? A plain Person could be instantiated, but a "person" with no role makes no sense in this system - only students and teachers do. Declaring the class abstract makes that rule a compile-time guarantee, and the abstract displayDetails() forces every future subclass (say, Staff) to provide its own display logic. Its constructor still runs - every super(name, age) call executes it.

Review

Quiz and Viva Questions

1. What is a class and what is an object?

A class is a template that defines fields and methods; an object is an instance of that class created with new. One class can produce many objects, each with its own copy of the fields.

2. What is a constructor, and how is it different from a method?

A constructor initialises a new object. It has the same name as the class, has no return type (not even void), and runs automatically exactly once when new creates the object - it cannot be called again like a normal method.

3. What do you mean by encapsulation?

Encapsulation is bundling data and the methods that operate on it inside one class, and hiding the data with private so it can only be reached through controlled public methods - like Product's fields and displayProductDetails().

4. What is inheritance in Java?

Inheritance lets a subclass acquire the fields and methods of a superclass using extends, so common code is written once in the base class - Fiction and NonFiction reuse everything in Book.

5. Is multiple inheritance possible in Java?

Not with classes - a class can extend only one class, which avoids the diamond ambiguity problem. Java achieves a similar effect through interfaces: a class can implement any number of them.

6. What is the scope of the protected access modifier?

A protected member is accessible within its own class, all classes in the same package, and subclasses even in other packages. That is why Fiction can read title while unrelated code cannot.

7. What does the super keyword do?

super(...) calls the superclass constructor and must be the first statement of the subclass constructor; super.method() calls the superclass version of an overridden method, as in super.displayDetails().

8. What is method overriding, and how is it different from overloading?

Overriding is a subclass redefining an inherited method with the same signature; the choice is made at runtime. Overloading is the same class having multiple methods with the same name but different parameter lists; the choice is made at compile time.

9. What is an abstract class? Can it be instantiated?

An abstract class is declared with the abstract keyword and may contain abstract methods (without a body). It cannot be instantiated with new - it exists only to be extended by concrete subclasses.

10. Can an abstract class have a constructor?

Yes. It cannot be called with new, but it runs whenever a subclass constructor calls super(...) - exactly how Person(name, age) initialises the shared fields for Student and Teacher.

11. What is runtime polymorphism (dynamic method dispatch)?

When an overridden method is called through a base-class reference, the JVM picks the version belonging to the object's actual type at runtime. Book book1 = new Fiction(...) followed by book1.displayDetails() runs the Fiction version.

12. What is aggregation, and how does it differ from inheritance?

Aggregation is a HAS-A relationship: one class holds references to objects of another class that can exist independently (a library has books). Inheritance is an IS-A relationship (a fiction book is a book). Use inheritance for specialisation, aggregation for composition of parts.

13. Why is @Override written above the overriding methods?

It asks the compiler to verify that the method really overrides an inherited one. Without it, a small typo like displaydetails() would silently create a new method instead of overriding, and the base version would run.

Before Submission

Lab Submission Checklist