Java Fundamentals

Collections, File I/O, and Exception Handling

Lecture 8

Working with collections, reading/writing files, and handling errors properly

Lecture Overview

In this lecture, we'll cover three essential topics for practical Java programming:

  • Collections Framework - Managing groups of objects efficiently
  • File I/O - Reading from and writing to files
  • Exception Handling - Proper error management
These skills are crucial for building real applications like the bank account system and geometry exercises!

Part 1: The Collections Framework

Why do we need collections?
  • Arrays have fixed size - what if we don't know the size in advance?
  • We need flexible data structures that can grow and shrink
  • We need different behaviors: unique items, key-value pairs, ordered vs unordered
The Collections Framework provides ready-to-use data structures!

Collections Hierarchy Overview

The main interfaces in the Collections Framework:
  • List - Ordered collection (allows duplicates)
    • ArrayList, LinkedList
  • Set - Unordered collection (no duplicates)
    • HashSet, TreeSet
  • Map - Key-value pairs
    • HashMap, TreeMap

ArrayList - Dynamic Arrays

ArrayList is the most commonly used collection:
import java.util.ArrayList;
import java.util.List;

public class ArrayListExample {
    public static void main(String[] args) {
        // Create an ArrayList of Strings
        List<String> names = new ArrayList<>();

        // Add elements
        names.add("Alice");
        names.add("Bob");
        names.add("Charlie");

        // Access by index
        String first = names.get(0); // "Alice"

        // Size
        int size = names.size(); // 3

        // Check if contains
        boolean hasBob = names.contains("Bob"); // true
    }
}

ArrayList Operations

List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.add("Charlie");

// Insert at specific position
names.add(1, "David"); // [Alice, David, Bob, Charlie]

// Remove by index
names.remove(0); // [David, Bob, Charlie]

// Remove by value
names.remove("Bob"); // [David, Charlie]

// Update element
names.set(0, "Eve"); // [Eve, Charlie]

// Check if empty
boolean isEmpty = names.isEmpty(); // false

// Clear all elements
names.clear();

Iterating Over Lists

There are several ways to iterate:
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.add("Charlie");

// 1. For-each loop (recommended)
for (String name : names) {
    System.out.println(name);
}

// 2. Traditional for loop
for (int i = 0; i < names.size(); i++) {
    System.out.println(names.get(i));
}

// 3. Iterator
Iterator<String> iterator = names.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}

HashSet - Unique Elements

HashSet stores unique elements (no duplicates):
import java.util.HashSet;
import java.util.Set;

public class HashSetExample {
    public static void main(String[] args) {
        Set<String> uniqueNames = new HashSet<>();

        uniqueNames.add("Alice");
        uniqueNames.add("Bob");
        uniqueNames.add("Alice"); // Duplicate - won't be added!

        System.out.println(uniqueNames.size()); // 2
        System.out.println(uniqueNames); // [Alice, Bob] (order not guaranteed)

        // Check membership
        if (uniqueNames.contains("Alice")) {
            System.out.println("Alice is in the set");
        }
    }
}

HashMap - Key-Value Pairs

HashMap stores associations between keys and values:
import java.util.HashMap;
import java.util.Map;

public class HashMapExample {
    public static void main(String[] args) {
        Map<String, Integer> ages = new HashMap<>();

        // Put key-value pairs
        ages.put("Alice", 25);
        ages.put("Bob", 30);
        ages.put("Charlie", 28);

        // Get value by key
        int aliceAge = ages.get("Alice"); // 25

        // Check if key exists
        if (ages.containsKey("Bob")) {
            System.out.println("Bob's age: " + ages.get("Bob"));
        }

        // Size
        System.out.println("Number of people: " + ages.size());
    }
}

HashMap Operations

Map<String, Integer> ages = new HashMap<>();
ages.put("Alice", 25);
ages.put("Bob", 30);

// Update existing key (overwrites old value)
ages.put("Alice", 26);

// Remove entry
ages.remove("Bob");

// Get with default value
int charlieAge = ages.getOrDefault("Charlie", 0); // 0 (not found)

// Check if value exists
boolean has25 = ages.containsValue(25);

// Iterate over entries
for (Map.Entry<String, Integer> entry : ages.entrySet()) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}

// Iterate over keys only
for (String name : ages.keySet()) {
    System.out.println(name);
}

Practical Example: Bank Accounts Collection

class BankAccount {
    private String accountNumber;
    private String owner;
    private double balance;

    public BankAccount(String accountNumber, String owner, double balance) {
        this.accountNumber = accountNumber;
        this.owner = owner;
        this.balance = balance;
    }
    // Getters and setters...
}

public class Bank {
    private List<BankAccount> accounts = new ArrayList<>();

    public void addAccount(BankAccount account) {
        accounts.add(account);
    }

    public BankAccount findByAccountNumber(String accountNumber) {
        for (BankAccount account : accounts) {
            if (account.getAccountNumber().equals(accountNumber)) {
                return account;
            }
        }
        return null;
    }
}

Exercise 1: Shape Collection

Create a geometry management system:
  • Create a Shape abstract class with calculateArea() method
  • Create Circle and Rectangle subclasses
  • Create a ShapeManager class with a List<Shape>
  • Add methods to:
    • Add shapes
    • Calculate total area of all shapes
    • Find the largest shape by area

Part 2: Exception Handling

What is an exception?
  • An exception is an event that disrupts the normal flow of the program
  • Examples: file not found, division by zero, null reference, array index out of bounds
  • Without handling, exceptions crash the program
We need to handle exceptions gracefully!

Try-Catch Block

The basic structure for handling exceptions:
public class ExceptionExample {
    public static void main(String[] args) {
        try {
            // Code that might throw an exception
            int result = 10 / 0; // ArithmeticException!
            System.out.println("Result: " + result);
        } catch (ArithmeticException e) {
            // Handle the exception
            System.out.println("Error: Cannot divide by zero!");
            System.out.println("Message: " + e.getMessage());
        }

        System.out.println("Program continues...");
    }
}
Output: "Error: Cannot divide by zero!" and then "Program continues..."

Multiple Catch Blocks

You can catch different exception types:
public class MultiCatchExample {
    public static void main(String[] args) {
        String[] array = {"10", "20", "abc"};

        for (String str : array) {
            try {
                int number = Integer.parseInt(str);
                int result = 100 / number;
                System.out.println("Result: " + result);
            } catch (NumberFormatException e) {
                System.out.println("Invalid number: " + str);
            } catch (ArithmeticException e) {
                System.out.println("Cannot divide by zero");
            }
        }
    }
}

Finally Block

The finally block always executes (even if exception occurs):
public class FinallyExample {
    public static void main(String[] args) {
        try {
            System.out.println("Opening resource...");
            int result = 10 / 0;
            System.out.println("Result: " + result);
        } catch (ArithmeticException e) {
            System.out.println("Error occurred!");
        } finally {
            // This ALWAYS executes
            System.out.println("Closing resource...");
        }
    }
}

// Output:
// Opening resource...
// Error occurred!
// Closing resource...

Common Exceptions

  • NullPointerException - Accessing methods/fields on null object
    String str = null;
    str.length(); // NullPointerException!
  • ArrayIndexOutOfBoundsException - Invalid array index
    int[] numbers = {1, 2, 3};
    int x = numbers[5]; // ArrayIndexOutOfBoundsException!
  • NumberFormatException - Invalid number conversion
    int num = Integer.parseInt("abc"); // NumberFormatException!
  • IOException - File/network operations (we'll see this next!)

Part 3: File I/O

Why do we need to work with files?
  • Persist data between program runs
  • Read configuration files
  • Export/import data
  • Log information for debugging
File I/O operations can throw IOException - we must handle it!

Writing to a File

Using FileWriter and BufferedWriter:
import java.io.FileWriter;
import java.io.BufferedWriter;
import java.io.IOException;

public class FileWriteExample {
    public static void main(String[] args) {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {
            writer.write("Hello, World!");
            writer.newLine();
            writer.write("This is a second line.");
            writer.newLine();
            System.out.println("File written successfully!");
        } catch (IOException e) {
            System.out.println("Error writing file: " + e.getMessage());
        }
    }
}
Note: try-with-resources automatically closes the file!

Reading from a File

Using FileReader and BufferedReader:
import java.io.FileReader;
import java.io.BufferedReader;
import java.io.IOException;

public class FileReadExample {
    public static void main(String[] args) {
        try (BufferedReader reader = new BufferedReader(new FileReader("input.txt"))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            System.out.println("Error reading file: " + e.getMessage());
        }
    }
}

File Operations with Scanner

Scanner is another way to read files:
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class ScannerFileExample {
    public static void main(String[] args) {
        try {
            File file = new File("data.txt");
            Scanner scanner = new Scanner(file);

            while (scanner.hasNextLine()) {
                String line = scanner.nextLine();
                System.out.println(line);
            }

            scanner.close();
        } catch (FileNotFoundException e) {
            System.out.println("File not found: " + e.getMessage());
        }
    }
}

PrintWriter - Simple File Writing

PrintWriter provides convenient methods like println():
import java.io.PrintWriter;
import java.io.IOException;

public class PrintWriterExample {
    public static void main(String[] args) {
        try (PrintWriter writer = new PrintWriter("numbers.txt")) {
            for (int i = 1; i <= 10; i++) {
                writer.println("Number: " + i);
            }
            System.out.println("File created successfully!");
        } catch (IOException e) {
            System.out.println("Error: " + e.getMessage());
        }
    }
}

Practical Example: Saving Bank Accounts

import java.io.*;
import java.util.*;

public class BankAccountPersistence {
    public static void saveAccounts(List<BankAccount> accounts, String filename) {
        try (PrintWriter writer = new PrintWriter(filename)) {
            for (BankAccount account : accounts) {
                writer.println(account.getAccountNumber() + "," +
                             account.getOwner() + "," +
                             account.getBalance());
            }
        } catch (IOException e) {
            System.out.println("Error saving accounts: " + e.getMessage());
        }
    }

    public static List<BankAccount> loadAccounts(String filename) {
        List<BankAccount> accounts = new ArrayList<>();
        try (Scanner scanner = new Scanner(new File(filename))) {
            while (scanner.hasNextLine()) {
                String[] parts = scanner.nextLine().split(",");
                accounts.add(new BankAccount(parts[0], parts[1],
                            Double.parseDouble(parts[2])));
            }
        } catch (FileNotFoundException e) {
            System.out.println("File not found: " + e.getMessage());
        }
        return accounts;
    }
}

Exercise 2: Student Grade Persistence

Create a grade management system:
  • Create a Student class with name and grade fields
  • Create a List<Student> and add several students
  • Write a method saveToFile() that saves all students to "students.txt"
    • Format: "Name,Grade" (one student per line)
  • Write a method loadFromFile() that reads students from the file
  • Test by saving, restarting program, and loading

Exercise 3: Log File System

Create a simple logging system:
  • Create a Logger class with method log(String message)
  • Each log entry should include:
    • Timestamp (use LocalDateTime from Lecture 7)
    • Message
  • Write log entries to "application.log" file (append mode)
  • Hint: Use new FileWriter("application.log", true) for append mode

Best Practices

  • Always use try-with-resources for file operations (auto-closes)
    try (BufferedWriter writer = new BufferedWriter(new FileWriter("file.txt"))) {
        // Use writer
    } // Automatically closed
  • Handle exceptions appropriately - don't just catch and ignore!
  • Use BufferedReader/BufferedWriter for better performance
  • Use specific exception types instead of generic Exception
  • Validate file paths before operations
  • Choose the right collection:
    • Need order and duplicates? → ArrayList
    • Need unique elements? → HashSet
    • Need key-value lookups? → HashMap

Summary

Today we learned:
  • Collections Framework
    • ArrayList for dynamic lists
    • HashSet for unique elements
    • HashMap for key-value pairs
  • Exception Handling
    • try-catch-finally blocks
    • Common exception types
  • File I/O
    • Reading and writing text files
    • Persisting object data
You now have the tools to build real, practical applications!

Project Exercise: Complete Bank System

Apply everything you learned to build a complete banking system:
  • Use List<BankAccount> to store multiple accounts
  • Use Map<String, BankAccount> for quick lookup by account number
  • Handle exceptions (invalid amounts, account not found, etc.)
  • Save accounts to file when program exits
  • Load accounts from file when program starts
  • Provide menu: Create Account, Deposit, Withdraw, Transfer, List All, Exit
Practice: Apply these concepts by building real-world applications combining Collections, File I/O, and Exception Handling

Slide Overview