Crash Courses · PhD
Capstone: OOP Contact Manager with Full CRUD
Build a complete in-memory contact manager with add/find/update/delete, sorting, searching, and file persistence — applying every concept from this course.
25 min read+200 XP on completionCert: Java / Kotlin Crash Course
Tap any word in the text below to start reading from there.
Capstone: OOP Contact Manager with Full CRUD
Architecture Overview
Contact (abstract)
├── PersonContact (firstName, lastName, phone, email)
└── BusinessContact (companyName, contactPerson, phone, email)
ContactBook
├── add(Contact)
├── findById(String) → Optional<Contact>
├── findByName(String) → List<Contact>
├── update(String id, Contact updated)
├── delete(String id) → boolean
├── listAll() → List<Contact>
├── listSorted(Comparator) → List<Contact>
└── exportToString() / importFromString()---
The Contact Hierarchy
import java.time.LocalDateTime;
import java.util.UUID;
public abstract class Contact {
protected final String id;
protected String phone;
protected String email;
protected final LocalDateTime createdAt;
protected Contact(String phone, String email) {
this.id = UUID.randomUUID().toString().substring(0, 8);
this.phone = phone;
this.email = email;
this.createdAt = LocalDateTime.now();
}
// For reconstruction from storage (known id)
protected Contact(String id, String phone, String email) {
this.id = id;
this.phone = phone;
this.email = email;
this.createdAt = LocalDateTime.now();
}
public String getId() { return id; }
public String getPhone() { return phone; }
public String getEmail() { return email; }
public void setPhone(String phone) {
if (phone == null || phone.isBlank())
throw new IllegalArgumentException("Phone cannot be blank");
this.phone = phone;
}
public void setEmail(String email) { this.email = email; }
public abstract String getDisplayName();
public abstract String serialize(); // for file export
@Override
public String toString() {
return String.format("[%s] %s | %s | %s",
id, getDisplayName(), phone, email != null ? email : "no email");
}
}
public class PersonContact extends Contact {
private String firstName;
private String lastName;
public PersonContact(String firstName, String lastName, String phone, String email) {
super(phone, email);
this.firstName = firstName.trim();
this.lastName = lastName.trim();
}
@Override public String getDisplayName() { return firstName + " " + lastName; }
public String getFirstName() { return firstName; }
public String getLastName() { return lastName; }
public void setFirstName(String fn) { this.firstName = fn.trim(); }
public void setLastName(String ln) { this.lastName = ln.trim(); }
@Override
public String serialize() {
return String.join("|", "PERSON", id, firstName, lastName,
phone, email != null ? email : "");
}
}
public class BusinessContact extends Contact {
private String companyName;
private String contactPerson;
public BusinessContact(String companyName, String contactPerson, String phone, String email) {
super(phone, email);
this.companyName = companyName.trim();
this.contactPerson = contactPerson.trim();
}
@Override public String getDisplayName() { return companyName + " (" + contactPerson + ")"; }
public String getCompanyName() { return companyName; }
public String getContactPerson() { return contactPerson; }
@Override
public String serialize() {
return String.join("|", "BUSINESS", id, companyName, contactPerson,
phone, email != null ? email : "");
}
}---
ContactBook: Full CRUD Implementation
import java.util.*;
import java.util.stream.*;
public class ContactBook {
private final Map<String, Contact> contacts = new LinkedHashMap<>();
// CREATE
public void add(Contact c) {
if (contacts.containsKey(c.getId()))
throw new IllegalStateException("Contact id already exists: " + c.getId());
contacts.put(c.getId(), c);
}
// READ — single
public Optional<Contact> findById(String id) {
return Optional.ofNullable(contacts.get(id));
}
// READ — search by name (case-insensitive)
public List<Contact> findByName(String query) {
String q = query.toLowerCase();
return contacts.values().stream()
.filter(c -> c.getDisplayName().toLowerCase().contains(q))
.collect(Collectors.toList());
}
// READ — all contacts (defensive copy)
public List<Contact> listAll() {
return new ArrayList<>(contacts.values());
}
// READ — sorted
public List<Contact> listSorted(Comparator<Contact> comparator) {
return contacts.values().stream()
.sorted(comparator)
.collect(Collectors.toList());
}
// UPDATE
public boolean update(String id, String phone, String email) {
Contact c = contacts.get(id);
if (c == null) return false;
if (phone != null && !phone.isBlank()) c.setPhone(phone);
if (email != null) c.setEmail(email);
return true;
}
// DELETE
public boolean delete(String id) {
return contacts.remove(id) != null;
}
public int size() { return contacts.size(); }
// EXPORT — pipe-delimited text
public String exportToString() {
return contacts.values().stream()
.map(Contact::serialize)
.collect(Collectors.joining("\n"));
}
// STATISTICS
public record Stats(int total, int persons, int businesses) {}
public Stats getStats() {
long persons = contacts.values().stream()
.filter(c -> c instanceof PersonContact).count();
return new Stats(contacts.size(), (int) persons,
(int)(contacts.size() - persons));
}
}---
Main Program: Putting It All Together
public class Main {
public static void main(String[] args) {
ContactBook book = new ContactBook();
// Add contacts
book.add(new PersonContact("Jordan", "Morris", "876-555-0100", "jordan@jst.com"));
book.add(new PersonContact("Alice", "Johnson", "876-555-0101", "alice@email.com"));
book.add(new PersonContact("Bob", "Smith", "876-555-0102", null));
book.add(new BusinessContact("J Supreme Tech", "Jordan Morris", "876-555-0103", "info@jst.com"));
book.add(new BusinessContact("Solid Trust", "Owen Ferguson", "876-555-0104", "info@st.com"));
System.out.println("All contacts (" + book.size() + "):");
book.listAll().forEach(System.out::println);
System.out.println("\n--- Search 'jordan' ---");
book.findByName("jordan").forEach(System.out::println);
System.out.println("\n--- Sorted by display name ---");
book.listSorted(Comparator.comparing(Contact::getDisplayName))
.forEach(System.out::println);
System.out.println("\n--- Stats ---");
var stats = book.getStats();
System.out.printf("Total: %d | Persons: %d | Businesses: %d%n",
stats.total(), stats.persons(), stats.businesses());
System.out.println("\n--- Export ---");
System.out.println(book.exportToString());
}
}Reading progress
0% read
In this module
CRUD
UUID
Optional<T>
Comparator
0%