Strings, Garbage Collection, Wrapper Classes & Object Relationships — Ch. 9 & 10
length(), charAt(), substring(), and case conversion is essential for any real application."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.text.length(), text.charAt(7), text.substring(7), text.toUpperCase(), text.toLowerCase(). All are instance methods called on the String object.indexOf() finds a position, contains() checks existence, and replace() swaps content — all without modifying the original String (Strings are immutable!)."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().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");.split() and String.join() are the two methods that handle this in Java — used constantly in data processing and file I/O."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.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.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." 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.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".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.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"));@, 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.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".@: 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 &&.free() or delete. Understanding when an object becomes eligible for GC (and why) is a core concept for writing memory-efficient Java programs.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.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.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.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().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".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.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.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.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.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.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");.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.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.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.