Working with collections, reading/writing files, and handling errors properly
In this lecture, we'll cover three essential topics for practical Java programming:
Primitives (int, double, etc.) are not objects. But sometimes we need objects!
| Primitive | Wrapper Class | Example |
|---|---|---|
int |
Integer |
Integer num = 42; |
double |
Double |
Double price = 19.99; |
boolean |
Boolean |
Boolean flag = true; |
char |
Character |
Character letter = 'A'; |
Java automatically converts between primitives and wrappers:
// Autoboxing: primitive -> wrapper (automatic)
int primitiveInt = 42;
Integer wrapperInt = primitiveInt; // Autoboxing
// Unboxing: wrapper -> primitive (automatic)
Integer wrapperNum = 100;
int primitiveNum = wrapperNum; // Unboxing
// This works seamlessly in collections:
List<Integer> numbers = new ArrayList<>();
numbers.add(10); // Autoboxing: int -> Integer
numbers.add(20);
int sum = numbers.get(0) + numbers.get(1); // Unboxing
null throws NullPointerException!
Generics allow collections to enforce what type they contain:
// Without generics (old style) - NOT type-safe
List list = new ArrayList();
list.add("Hello");
list.add(123); // Allowed, but dangerous!
String str = (String) list.get(1); // ClassCastException at runtime!
// With generics - type-safe
List<String> names = new ArrayList<String>();
names.add("Alice");
names.add("Bob");
// names.add(123); // Compile error! Only Strings allowed
String name = names.get(0); // No cast needed
<Type> specify what the collection will hold
Java 7+ lets you omit the type on the right side (diamond operator):
// Full syntax
List<String> names = new ArrayList<String>();
// Diamond operator (shorter, preferred)
List<String> names = new ArrayList<>();
// Works with any type
List<Integer> numbers = new ArrayList<>();
List<BankAccount> accounts = new ArrayList<>();
Map<String, Integer> ages = new HashMap<>();
Reading generic types:
List<String> = "a List of Strings"Map<String, Integer> = "a Map from Strings to Integers"You can create your own generic classes:
// A generic "box" that can hold any type
public class Box<T> {
private T content;
public void put(T item) {
this.content = item;
}
public T get() {
return this.content;
}
}
// Usage with different types
Box<String> stringBox = new Box<>();
stringBox.put("Hello");
String message = stringBox.get();
Box<Integer> intBox = new Box<>();
intBox.put(42);
int number = intBox.get();
T is a type parameter - a placeholder for the actual type
The problem with null:
// The billion-dollar mistake
public String getCustomerEmail(long id) {
Customer customer = findById(id); // Might return null!
return customer.getEmail(); // NullPointerException if null!
}
// Traditional null check - verbose and easy to forget
public String getCustomerEmail(long id) {
Customer customer = findById(id);
if (customer != null) {
return customer.getEmail();
}
return "unknown";
}
import java.util.Optional;
// Create Optional with a value
Optional<String> name = Optional.of("Alice"); // Must not be null!
// Create Optional that might be null
Optional<String> maybeName = Optional.ofNullable(getName()); // Safe with null
// Create empty Optional
Optional<String> empty = Optional.empty(); // Explicitly no value
// Example usage
public Optional<Customer> findById(long id) {
Customer customer = database.find(id);
return Optional.ofNullable(customer); // Wraps null safely
}
Optional<String> name = Optional.of("Alice");
// Check if value exists
if (name.isPresent()) {
System.out.println("Name: " + name.get());
}
// Better: Execute action if present
name.ifPresent(n -> System.out.println("Name: " + n));
// Check if empty (Java 11+)
if (name.isEmpty()) {
System.out.println("No name provided");
}
// ifPresentOrElse (Java 9+)
name.ifPresentOrElse(
n -> System.out.println("Hello, " + n),
() -> System.out.println("Hello, stranger")
);
Optional<String> name = findName();
// Provide default value
String result = name.orElse("Unknown");
// Provide default via supplier (lazy evaluation)
String result = name.orElseGet(() -> generateDefaultName());
// Throw exception if empty
String result = name.orElseThrow(); // NoSuchElementException
String result = name.orElseThrow(
() -> new CustomerNotFoundException("Name not found")
);
// AVOID: get() without checking - defeats the purpose!
String result = name.get(); // Throws if empty - bad practice!
get() without checking - use orElse() family instead!
Optional<String> name = Optional.of("alice");
// map: Transform the value if present
Optional<String> upperName = name.map(String::toUpperCase);
// Result: Optional["ALICE"]
// map with method chain
Optional<Integer> nameLength = name.map(String::toUpperCase)
.map(String::length);
// Result: Optional[5]
// filter: Keep value only if it matches predicate
Optional<String> longName = name.filter(n -> n.length() > 3);
// Result: Optional["alice"]
Optional<String> shortName = name.filter(n -> n.length() > 10);
// Result: Optional.empty
Use flatMap when your transformation returns an Optional:
class Customer {
private String email; // Might be null!
public Optional<String> getEmail() {
return Optional.ofNullable(email);
}
}
Optional<Customer> customer = findById(42);
// map would give Optional<Optional<String>> - nested!
Optional<Optional<String>> nested = customer.map(c -> c.getEmail());
// flatMap "flattens" to Optional<String>
Optional<String> email = customer.flatMap(Customer::getEmail);
// Full chain
String result = findById(42)
.flatMap(Customer::getEmail)
.map(String::toLowerCase)
.orElse("no-email@example.com");
// Convert Optional to Stream (Java 9+)
Optional<String> name = Optional.of("Alice");
Stream<String> stream = name.stream(); // Stream with 0 or 1 elements
// Filter nulls from a list using Optional
List<String> names = customers.stream()
.map(Customer::getName) // Some might be null
.filter(Objects::nonNull) // Remove nulls
.collect(Collectors.toList());
// With Optional - more explicit
List<String> emails = customers.stream()
.map(Customer::getEmail) // Returns Optional<String>
.flatMap(Optional::stream) // Keep only present values
.collect(Collectors.toList());
Optional from methods that might not find a value (findById, getEmail, etc.)
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
}
}
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();
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());
}
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");
}
}
}
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());
}
}
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);
}
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;
}
}
Shape abstract class with calculateArea() methodCircle and Rectangle subclassesShapeManager class with a List<Shape>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...");
}
}
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");
}
}
}
}
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...
String str = null;
str.length(); // NullPointerException!
int[] numbers = {1, 2, 3};
int x = numbers[5]; // ArrayIndexOutOfBoundsException!
int num = Integer.parseInt("abc"); // NumberFormatException!
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());
}
}
}
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());
}
}
}
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());
}
}
}
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());
}
}
}
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;
}
}
Student class with name and grade fieldsList<Student> and add several studentssaveToFile() that saves all students to "students.txt"
loadFromFile() that reads students from the fileLogger class with method log(String message)new FileWriter("application.log", true) for append modetry (BufferedWriter writer = new BufferedWriter(new FileWriter("file.txt"))) {
// Use writer
} // Automatically closed
List<BankAccount> to store multiple accountsMap<String, BankAccount> for quick lookup by account number