JSTAcademy
0 XP
Dashboard
Crash Courses
Capstone: OOP Contact Manager with Full CRUD
25 min
PhD+200 XP
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());
    }
}
0%