Back to Week 5
Lab Session

Week 5: Lab — Thinking in Objects

Hands-on Programming Exercises — Ch. 9 & 10

Progress0 / 13 completed

1Encapsulated & Immutable Student

Think about it: A university system stores student records. Once a student ID and name are registered, they should never change — that's immutability. But the GPA can be updated through a controlled setter with validation (0.0–4.0 only). This combines encapsulation (private fields, getters/setters) with immutability (no setter for id/name).
Create an ImmutableStudent class with private fields: id (int), name (String), and gpa (double). Provide getters for all fields, but a setter only for gpa with validation (0.0 to 4.0). The id and name are set only through the constructor and can never be changed. In main, create a student, try to update gpa with valid and invalid values.
Expected OutputID: 1001, Name: Ali, GPA: 3.5 Setting GPA to 5.0 (invalid)... ID: 1001, Name: Ali, GPA: 3.5 Setting GPA to 3.9 (valid)... ID: 1001, Name: Ali, GPA: 3.9
Your Code
Declare: private int id; private String name; private double gpa;. Constructor: ImmutableStudent(int id, String name, double gpa) { this.id = id; this.name = name; this.gpa = gpa; }. Only provide getters for id and name (no setters!). Setter for gpa: public void setGpa(double gpa) { if (gpa >= 0.0 && gpa <= 4.0) this.gpa = gpa; }.

2Variable Scope Explorer

Think about it: Understanding scope prevents bugs caused by variable shadowing. When a local variable has the same name as an instance variable, the local one takes priority. Using this lets you explicitly access the instance variable. This exercise makes you practice distinguishing between local and instance scope.
Create a ScopeTest class with an instance variable x = 10. Add a method demonstrate(int x) that receives a parameter named x (same name!). Inside the method, print: (1) the local parameter x, (2) the instance variable using this.x, (3) create a local variable y and print it. Then call the method from main with argument 50.
Expected OutputParameter x: 50 Instance x: 10 Local y: 99 Back in main — cannot access y here
Your Code
Instance variable: int x = 10;. Method: public void demonstrate(int x) { System.out.println("Parameter x: " + x); System.out.println("Instance x: " + this.x); int y = 99; System.out.println("Local y: " + y); }. The parameter x shadows the instance variable, so use this.x to access the instance one.

3Static Counter & Constant

Think about it: A pizza shop wants to track how many pizzas have been ordered and apply a fixed tax rate to every order. The order count should be shared across all Pizza objects (static variable), and the tax rate should be a constant that nobody can change (static final). This exercise practices static variables, static constants, and static methods.
Create a Pizza class with: instance fields name (String) and price (double), a static variable orderCount = 0, and a static final constant TAX_RATE = 0.16. The constructor increments orderCount. Add a method getTotalPrice() that returns price + price * TAX_RATE. Add a static method getOrderCount(). Create 3 pizzas in main.
Expected OutputMargherita total: 11.6 Pepperoni total: 16.24 Hawaiian total: 13.92 Total orders: 3
Your Code
Fields: String name; double price; static int orderCount = 0; public static final double TAX_RATE = 0.16;. Constructor: Pizza(String name, double price) { this.name = name; this.price = price; orderCount++; }. Method: public double getTotalPrice() { return price + price * TAX_RATE; }. Static: public static int getOrderCount() { return orderCount; }.

4Passing Objects — Circle Resizer

Think about it: When you pass a primitive to a method, it gets a copy — changes don't affect the original. But when you pass an object, the method receives a copy of the reference — so it can modify the original object's fields! This is a fundamental concept to understand in Java.
Create a Circle class with a private field radius, a constructor, getter, and setter. Write two static methods in the test class: doubleValue(int n) that doubles a primitive (won't affect the original), and doubleRadius(Circle c) that doubles a circle's radius (WILL affect the original). Demonstrate both in main.
Expected OutputBefore: num = 5 After doubleValue: num = 5 Before: radius = 10.0 After doubleRadius: radius = 20.0
Your Code
Primitive method: public static void doubleValue(int n) { n = n * 2; } — this only changes the local copy. Object method: public static void doubleRadius(Circle c) { c.setRadius(c.getRadius() * 2); } — this modifies the actual object because c is a reference to the same object.

5Array of Objects — Student Roster

Think about it: A professor needs to manage a roster of students. Using an array of objects, you can store multiple Student objects and loop through them to find the highest GPA, calculate the average, or print all students. Remember: creating the array doesn't create the objects — you must new each one!
Create a Student class with fields name (String) and gpa (double). Create an array of 4 students. Loop through the array to: (1) print each student, (2) find the student with the highest GPA, (3) calculate the average GPA.
Expected Output--- Roster --- Ali: 3.5 Sara: 3.9 Omar: 3.2 Lina: 3.7 Best student: Sara (GPA: 3.9) Average GPA: 3.575
Your Code
Constructor: Student(String name, double gpa) { this.name = name; this.gpa = gpa; }. Array: Student[] students = { new Student("Ali", 3.5), new Student("Sara", 3.9), new Student("Omar", 3.2), new Student("Lina", 3.7) };. Print: for (Student s : students) System.out.println(s.name + ": " + s.gpa);. Best: for (Student s : students) if (s.gpa > best.gpa) best = s;. Average: for (Student s : students) sum += s.gpa;.

6Aggregation vs. Composition — School System

Think about it: A School has an Address that is created when the school is built — that's composition (created inside). A School also has a Principal who was hired from outside — that's aggregation (passed in). This exercise asks you to implement both in one class.
Create an Address class with a field city. Create a Principal class with a field name. Create a School class that uses composition for Address (created inside the constructor with new Address(city)) and aggregation for Principal (passed in as a parameter). Add a display() method. Show that the Principal exists independently after the school is gone.
Expected OutputSchool: Petra School, City: Amman, Principal: Dr. Ahmad Principal still exists: Dr. Ahmad
Your Code
Address: Address(String city) { this.city = city; }. Principal: Principal(String name) { this.name = name; }. School constructor: School(String name, String city, Principal principal) { this.name = name; this.address = new Address(city); this.principal = principal; }. Key: new Address(city) = composition (inside), this.principal = principal = aggregation (passed in).

7String Processing — Password Validator

Think about it: A registration form needs to validate passwords. Using String methods, you can check length, whether it contains certain characters, and compare values. This exercise practices length(), contains(), charAt(), equals(), and toUpperCase().
Write a program that validates a password string. Check: (1) length is at least 8 using length(), (2) contains "A" or "a" using contains(), (3) first character is uppercase using charAt(0) and Character.isUpperCase(). Also demonstrate == vs .equals() with two identical strings created differently.
Expected OutputPassword: HelloJava123 Length OK (>= 8): true Contains 'a': true Starts with uppercase: true --- String Comparison --- s1 == s2 (literals): true s1 == s3 (new String): false s1.equals(s3): true
Your Code
Length: System.out.println("Length OK (>= 8): " + (password.length() >= 8));. Contains: System.out.println("Contains 'a': " + password.contains("a"));. Uppercase: System.out.println("Starts with uppercase: " + Character.isUpperCase(password.charAt(0)));. Comparison: System.out.println("s1 == s2 (literals): " + (s1 == s2)); etc.

8BigDecimal — Precise Invoice Calculator

Think about it: Financial applications must be exact. Using double, 0.1 + 0.2 = 0.30000000000000004. That error compounds across millions of transactions! BigDecimal gives exact results. This exercise also uses Integer.parseInt() for parsing quantities (wrapper classes).
Create an invoice calculator using BigDecimal. Parse quantity strings using Integer.parseInt() (wrapper class). Calculate subtotal = price × quantity using multiply(). Apply 16% tax using multiply(). Calculate total using add(). Compare results with double arithmetic. Use String constructor for BigDecimal!
Expected Output--- BigDecimal Invoice --- Item: Widget, Qty: 3, Price: 19.99 Subtotal: 59.97 Tax (16%): 9.5952 Total: 69.5652 --- double comparison --- double total: 69.56520000000001
Your Code
Subtotal: BigDecimal subtotal = price.multiply(quantity);. Tax: BigDecimal taxRate = new BigDecimal("0.16"); BigDecimal tax = subtotal.multiply(taxRate);. Total: BigDecimal total = subtotal.add(tax);. Always use the String constructor: new BigDecimal("0.16") not new BigDecimal(0.16)!

9Array of Objects — Product Inventory

Think about it: An online store needs to manage its product catalog. Each product has a name, price, and stock quantity. By storing products in an array of objects, you can loop through them to generate reports — find the most expensive item, and calculate the total inventory value (price × quantity for each). This is a very common real-world pattern!
Create a Product class with fields name (String), price (double), and quantity (int). Create an array of 3 products. Loop through the array to: (1) print each product, (2) find the most expensive product, (3) calculate the total inventory value (sum of price × quantity).
Expected Output--- Inventory --- Laptop: $800.0 x 5 Phone: $500.0 x 12 Tablet: $350.0 x 8 Most expensive: Laptop ($800.0) Total inventory value: $12800.0
Your Code
Constructor: Product(String name, double price, int quantity) { this.name = name; this.price = price; this.quantity = quantity; }. Array: Product[] products = { new Product("Laptop", 800.0, 5), new Product("Phone", 500.0, 12), new Product("Tablet", 350.0, 8) };. Print: for (Product p : products) System.out.println(p.name + ": $" + p.price + " x " + p.quantity);. Most expensive: for (Product p : products) if (p.price > expensive.price) expensive = p;. Value: for (Product p : products) totalValue += p.price * p.quantity;.

10Array of Objects — Employee Bonus Calculator

Think about it: An HR system stores employees with their department and salary. Using an array of objects, you can generate a bonus report (10% of salary for each), filter by department using .equals(), and find the highest-paid employee. This combines arrays of objects with String comparison and arithmetic.
Create an Employee class with fields name (String), department (String), and salary (double). Create an array of 4 employees. Loop to: (1) print each employee with a 10% bonus, (2) count how many work in "IT" using .equals(), (3) find the employee with the highest salary.
Expected Output--- Bonus Report --- Sami (IT): Salary=3000.0, Bonus=300.0 Nour (HR): Salary=2800.0, Bonus=280.0 Rami (IT): Salary=3500.0, Bonus=350.0 Dana (HR): Salary=2900.0, Bonus=290.0 IT employees: 2 Highest salary: Rami ($3500.0)
Your Code
Constructor: Employee(String name, String department, double salary) { this.name = name; this.department = department; this.salary = salary; }. Array: Employee[] employees = { new Employee("Sami", "IT", 3000), new Employee("Nour", "HR", 2800), new Employee("Rami", "IT", 3500), new Employee("Dana", "HR", 2900) };. Bonus: for (Employee e : employees) { double bonus = e.salary * 0.10; System.out.println(e.name + " (" + e.department + "): Salary=" + e.salary + ", Bonus=" + bonus); }. Count: if (e.department.equals("IT")) itCount++;. Top: if (e.salary > top.salary) top = e;.

11Composition — Car & Engine

Think about it: A Car has an Engine that is built specifically for it — the engine is created inside the Car constructor, not passed from outside. If the car is destroyed, the engine goes with it. This is composition: a strong "owns" relationship where the part cannot exist independently of the whole.
Create an Engine class with fields horsepower (int) and type (String). Create a Car class with a field model (String) and an Engine field. The Car constructor takes model, hp, and engineType and creates the Engine inside using new Engine(hp, engineType). Add a display() method. Show that when the car is set to null, the engine is gone too.
Expected OutputCar: Toyota Camry, Engine: 203HP V4 Car destroyed — engine is gone too (composition)
Your Code
Engine: Engine(int horsepower, String type) { this.horsepower = horsepower; this.type = type; }. Car constructor: Car(String model, int hp, String engineType) { this.model = model; this.engine = new Engine(hp, engineType); }. Key: new Engine(hp, engineType) is created INSIDE the Car — that's composition. Display: System.out.println("Car: " + model + ", Engine: " + engine.horsepower + "HP " + engine.type);.

12Aggregation — Team & Player

Think about it: A football Team has a captain and a topScorer, but players exist independently — they were recruited from outside and can join another team if this one is dissolved. This is aggregation: a weak "uses" relationship where the parts are passed in and survive on their own.
Create a Player class with fields name (String) and position (String). Create a Team class with name (String), captain (Player), and topScorer (Player). The Team constructor receives Player objects as parameters (aggregation — not created inside). Add a display() method. Show that players still exist after the team is set to null.
Expected OutputTeam: Al-Ahli Captain: Khalid (Midfielder) Top Scorer: Yazan (Forward) Team dissolved! Players still exist: Khalid, Yazan
Your Code
Player: Player(String name, String position) { this.name = name; this.position = position; }. Team constructor: Team(String name, Player captain, Player topScorer) { this.name = name; this.captain = captain; this.topScorer = topScorer; }. Key: players are passed in, NOT created inside — that's aggregation. Display: System.out.println("Team: " + name); System.out.println(" Captain: " + captain.name + " (" + captain.position + ")"); System.out.println(" Top Scorer: " + topScorer.name + " (" + topScorer.position + ")");.

13Composition + Aggregation — University Department

Think about it: A Department owns a Course that was designed specifically for it — composition (created inside). But the Instructor teaching the course was hired from outside and can teach elsewhere — aggregation (passed in). This exercise combines both relationships in one class, reinforcing the difference.
Create a Course class with fields title (String) and credits (int). Create an Instructor class with fields name (String) and specialization (String). Create a Department class that uses composition for Course (created inside with new Course(courseTitle, credits)) and aggregation for Instructor (passed in). Add a display() method. Show that the Instructor survives after the department is closed.
Expected OutputDepartment: Computer Science Course: Intro to DB (3 credits) Instructor: Dr. Huda — Databases Department closed! Instructor still exists: Dr. Huda
Your Code
Course: Course(String title, int credits) { this.title = title; this.credits = credits; }. Instructor: Instructor(String name, String specialization) { this.name = name; this.specialization = specialization; }. Department constructor: Department(String name, String courseTitle, int credits, Instructor instructor) { this.name = name; this.course = new Course(courseTitle, credits); this.instructor = instructor; }. Key: new Course(...) = composition (inside), this.instructor = instructor = aggregation (passed in). Display: print name, course.title, course.credits, instructor.name, instructor.specialization.