Back to Week 5
Lab Session 2

Week 5: Lab 2 — Thinking in Objects

Strings, Garbage Collection, Wrapper Classes & Object Relationships — Ch. 9 & 10

Progress0 / 11 completed

1String Fundamentals

Think about it: Strings are objects in Java, not primitives. Every time you need to inspect or slice a piece of text — like extracting a username from an email, or checking the first character of input — you rely on String methods. Knowing length(), charAt(), substring(), and case conversion is essential for any real application.
Given the string "Hello, World!", use String methods to: (1) print its length, (2) print the character at index 7, (3) print the substring starting at index 7, (4) print the string in uppercase, (5) print the string in lowercase. Put all logic in main.
Expected OutputLength: 13 Char at 7: W Substring from 7: World! Uppercase: HELLO, WORLD! Lowercase: hello, world!
Your Code
Use: text.length(), text.charAt(7), text.substring(7), text.toUpperCase(), text.toLowerCase(). All are instance methods called on the String object.

2String Searching & Replacing

Think about it: Search and replace is one of the most common text operations — think find-and-replace in a word processor, or sanitizing user input before storing it in a database. indexOf() finds a position, contains() checks existence, and replace() swaps content — all without modifying the original String (Strings are immutable!).
Given "Java is awesome and Java is fun": (1) print the first index of "Java" using indexOf(), (2) print the last index of "Java" using lastIndexOf(), (3) print whether it contains "awesome", (4) print a new string with "Java" replaced by "Python" using replace().
Expected OutputOriginal: Java is awesome and Java is fun First indexOf Java: 0 Last indexOf Java: 20 Contains awesome: true After replace: Python is awesome and Python is fun
Your Code
s.indexOf("Java") returns 0. s.lastIndexOf("Java") returns 20. s.contains("awesome") returns true. s.replace("Java", "Python") returns a new string — Strings are immutable, so assign to a new variable: String result = s.replace("Java", "Python");.

3String Split & Join

Think about it: CSV files store data as comma-separated values. Reading a row means splitting it into parts. Generating a report means joining parts back together with a custom separator. split() and String.join() are the two methods that handle this in Java — used constantly in data processing and file I/O.
Given "Alice,Bob,Charlie,Diana": (1) split it by "," using split() and print the number of parts, (2) print each part by index using a for loop, (3) join the parts back using String.join() with " | " as separator and print the result.
Expected OutputParts: 4 [0]: Alice [1]: Bob [2]: Charlie [3]: Diana Joined: Alice | Bob | Charlie | Diana
Your Code
String[] parts = csv.split(","); gives you an array. parts.length is 4. Loop: for (int i = 0; i < parts.length; i++) System.out.println("[" + i + "]: " + parts[i]);. Join: String result = String.join(" | ", parts); — note: String.join() is a static method that takes the delimiter first, then the array.

4Advanced String Operations

Think about it: Real applications constantly deal with messy string input — extra spaces, inconsistent casing, mixed formats. Methods like trim(), equalsIgnoreCase(), startsWith(), endsWith(), and compareTo() let you normalize and compare text reliably. String.format() gives you precise control over output layout — essential for reports, logs, and UI labels.
Given the messy input " hello WORLD ": (1) trim it and print, (2) check if the trimmed+lowercased version starts with "hello", (3) check if it ends with "world", (4) check if it equals ignoring case "hello world". Then: (5) use String.format() to print a formatted student record with name "Ali", score 95, grade "A". Finally: (6) use compareTo() to compare "apple" and "banana" and print whether the result is negative, zero, or positive.
Expected OutputTrimmed: "hello WORLD" Starts with 'hello': true Ends with 'world': true equalsIgnoreCase "hello world": true Formatted: Name: Ali, Score: 95, Grade: A Compare "apple" vs "banana": negative
Your Code
trimmed.toLowerCase().startsWith("hello") and .endsWith("world"). trimmed.equalsIgnoreCase("hello world") compares ignoring case. String.format("Name: %s, Score: %d, Grade: %s", "Ali", 95, "A") — use %s for strings, %d for integers. "apple".compareTo("banana") returns a negative number (a < b lexicographically) — use an if/else to print "negative", "zero", or "positive".

5Palindrome Check

Think about it: A palindrome is a word or phrase that reads the same forwards and backwards — "racecar", "madam", "level". Palindrome checks are a classic String problem that tests your ability to reverse a string and compare. For phrases, you must also handle spaces and case differences before comparing.
Write a static method isPalindrome(String s) that returns true if the input is a palindrome (ignore spaces and case). Test it in main with: "racecar", "hello", "madam", "A man a plan a canal Panama", "Java". Print each word followed by whether it is a palindrome or not.
Expected Outputracecar → palindrome hello → not a palindrome madam → palindrome A man a plan a canal Panama → palindrome Java → not a palindrome
Your Code
Strip spaces and lowercase: s = s.replace(" ", "").toLowerCase();. Reverse: String rev = new StringBuilder(s).reverse().toString();. Return s.equals(rev). In main: System.out.println(word + " → " + (isPalindrome(word) ? "palindrome" : "not a palindrome"));

6Email Validation

Think about it: Before storing a user's email in a database, you must validate it. A valid email must contain exactly one @, have text before it, have a . after it, and must not start or end with @. This exercise practices combining multiple String methods — contains(), indexOf(), lastIndexOf(), substring() — to build a real validation rule without using regular expressions.
Write a static method isValidEmail(String email) that returns true if: (1) the email contains exactly one @, (2) there is text before the @, (3) there is a . after the @, (4) the email does not end with @ or .. Test in main with: "ali@example.com", "not-an-email", "@example.com", "ali@", "ali@domain", "hello@world.org".
Expected Outputali@example.com → valid not-an-email → invalid @example.com → invalid ali@ → invalid ali@domain → invalid hello@world.org → valid
Your Code
One @: email.indexOf("@") == email.lastIndexOf("@"). Text before @: email.indexOf("@") > 0. Dot after @: email.indexOf(".", email.indexOf("@")) > email.indexOf("@") + 1. Not ending with @ or .: !email.endsWith("@") && !email.endsWith("."). Chain with &&.

7Garbage Collection & Object Lifecycle

Think about it: In Java, memory is managed automatically. The Garbage Collector (GC) runs in the background and reclaims memory from objects that are no longer reachable — objects with no variable pointing to them. Unlike C/C++, you never call free() or delete. Understanding when an object becomes eligible for GC (and why) is a core concept for writing memory-efficient Java programs.
Create a Car class with a String model field and a constructor that prints "Car created: [model]". In main: (1) create c1 = new Car("Toyota") and c2 = new Car("Honda"), (2) print "Both cars in use", (3) print how many references point to each, (4) set c1 = null — explain in a print statement that Toyota is now eligible for GC, (5) make c2 point to a new Car("BMW") — explain that Honda is now also eligible, (6) print "Only BMW is reachable now: " + c2.model.
Expected OutputCar created: Toyota Car created: Honda Both cars in use c1 references Toyota, c2 references Honda c1 = null → Toyota has no references → eligible for GC c2 = new Car("BMW") → Honda has no references → eligible for GC Car created: BMW Only BMW is reachable now: BMW
Your Code
Key concept: an object becomes eligible for GC when no variable holds a reference to it. Setting c1 = null removes the only reference to Toyota — GC can reclaim it. Reassigning c2 = new Car("BMW") creates a new object and drops the Honda reference — Honda is now also eligible. The JVM's GC runs automatically; you never need to explicitly free memory in Java.

8Integer Wrapper Class

Think about it: The Integer wrapper class bridges the gap between the primitive int and the object world. You need it when working with collections (like ArrayList<Integer>), parsing user input from strings, or using utility constants like Integer.MAX_VALUE. Java's autoboxing and auto-unboxing automatically convert between int and Integer.
Demonstrate the Integer wrapper: (1) parse "42" to int using Integer.parseInt(), (2) box 100 using Integer.valueOf() and auto-unbox it back to int, (3) print Integer.MAX_VALUE and Integer.MIN_VALUE, (4) compare 50 and 80 using Integer.compare(), (5) print binary representation of 50 using Integer.toBinaryString().
Expected OutputParsed int: 42 Boxed: 100, Unboxed: 100 MAX_VALUE: 2147483647 MIN_VALUE: -2147483648 Compare 50 vs 80: -1 50 in binary: 110010
Your Code
int parsed = Integer.parseInt("42");. Integer boxed = Integer.valueOf(100); int unboxed = boxed; (auto-unboxing). Integer.MAX_VALUE = 2147483647. Integer.compare(50, 80) returns negative (50 < 80). Integer.toBinaryString(50) returns "110010".

9Double Wrapper Class

Think about it: The Double wrapper class handles floating-point objects and provides critical utility methods for edge cases like NaN (Not a Number — result of 0.0/0.0) and Infinity (result of 1.0/0.0). These edge cases appear in real systems: division results, sensor readings, or financial calculations that overflow. Always use Double.isNaN() and Double.isInfinite() to guard against them.
Demonstrate the Double wrapper: (1) parse "3.14" using Double.parseDouble(), (2) box 2.718 using Double.valueOf() and auto-unbox it, (3) check if Double.POSITIVE_INFINITY is infinite using Double.isInfinite(), (4) check if Double.NaN is NaN using Double.isNaN(), (5) compare 3.14 and 2.71 using Double.compare(), (6) print Double.MAX_VALUE.
Expected OutputParsed: 3.14 Boxed: 2.718, Unboxed: 2.718 isInfinite: true isNaN: true Compare 3.14 vs 2.71: 1 MAX_VALUE: 1.7976931348623157E308
Your Code
double parsed = Double.parseDouble("3.14");. Double boxed = Double.valueOf(2.718); double unboxed = boxed;. Double.isInfinite(Double.POSITIVE_INFINITY) = true. Double.isNaN(Double.NaN) = true. Double.compare(3.14, 2.71) returns positive (3.14 > 2.71). Double.MAX_VALUE is a constant.

10Composition — House Has Rooms

Think about it: Composition models a "strong ownership" relationship — the child cannot exist without the parent. A House owns its Rooms: the rooms are created inside the House constructor and don't exist independently. If the House is demolished (set to null), the rooms go with it. This is different from aggregation, where the contained objects exist independently.
Create a Room class with String type and double area fields and a constructor. Create a House class with String address, a Room bedroom, and a Room livingRoom. In the House constructor (takes only address), create both rooms internally: bedroom as new Room("Bedroom", 20.0) and livingRoom as new Room("Living Room", 35.0). Add a display() method. In main, create a house, display it, set it to null.
Expected OutputHouse: 123 Main St Bedroom: 20.0 m² Living Room: 35.0 m² House demolished — rooms are gone too (composition)
Your Code
Room constructor: Room(String type, double area) { this.type = type; this.area = area; }. House constructor: House(String address) { this.address = address; this.bedroom = new Room("Bedroom", 20.0); this.livingRoom = new Room("Living Room", 35.0); }. Display: System.out.println("House: " + address); System.out.println(" " + bedroom.type + ": " + bedroom.area + " m\u00b2"); System.out.println(" " + livingRoom.type + ": " + livingRoom.area + " m\u00b2");.

11Aggregation — Company Has Employees

Think about it: Aggregation models a "weak ownership" relationship — the child can exist independently of the parent. A Company has Employees, but if the company closes, the employees still exist and can work elsewhere. The employees are created outside and passed into the Company — the Company just holds a reference. This is the key distinction from composition.
Create an Employee class with String name and String role and a constructor. Create a Company class with String name, an Employee ceo, and an Employee manager. The Company constructor takes all three (name, ceo, manager) and assigns the employee references from the parameters (do NOT use new Employee inside). Add a display() method. In main, create employees first, pass them to the company, set company to null, then confirm employees still exist.
Expected OutputCompany: TechCorp CEO: Ahmad Manager: Sara Company closed! Employees still exist: Ahmad, Sara
Your Code
Employee: Employee(String name, String role) { this.name = name; this.role = role; }. Company constructor: Company(String name, Employee ceo, Employee manager) { this.name = name; this.ceo = ceo; this.manager = manager; } — no new Employee inside! Display: System.out.println("Company: " + name); System.out.println(" CEO: " + ceo.name); System.out.println(" Manager: " + manager.name);. Key: after c = null, e1 and e2 still have references — they are NOT garbage collected.