본문 바로가기
프로그래밍 언어/JAVA

[Java 예제] ArrayList 로 구현하는 연락처 관리 프로그램

by 끼미226 2025. 6. 23.

이 글은 단순한 예제 프로그램 완성을 넘어, 그 구현 과정을 상세히 기록하며 학습하고자 작성한 글입니다.

자바를 활용하여 콘솔 기반의 간단한 연락처 관리 시스템을 만들어보겠습니다.


📚 과제 내용 및 목표

만들어야 할 프로그램은 다음과 같은 기능을 수행합니다.

  • 연락처 추가 (add): 이름과 전화번호를 입력받아 새로운 연락처를 저장합니다.
  • 연락처 삭제 (remove): 이름을 기준으로 특정 연락처를 목록에서 제거합니다.
  • 연락처 검색 (search): 이름을 기준으로 연락처를 찾아 그 정보를 출력합니다. (정렬, 이름 오름차순)
  • 연락처 목록 출력 (list): 현재 저장된 모든 연락처를 이름 오름차순으로 정렬하여 보여줍니다.
  • 프로그램 종료 (exit)

핵심 조건: 데이터 저장을 위해 ArrayList를 사용합니다.

 

출력 예시:

 


💡 Step 1: 프로그램의 뼈대 잡기

객체 지향 프로그래밍의 원칙을 적용하여, 프로그램의 역할을 명확히 분리하는 것으로 시작합니다.


Contact 클래스 :  연락처 정보 정의

 

가장 먼저 할 일은 하나의 연락처가 어떤 정보를 가질 것인지 정의하는 것입니다.

각 연락처는 이름과 전화번호를 가지므로, 이 두 가지를 필드로 포함하는 Contact 클래스를 만듭니다.

// Contact.java
class Contact {
    private String name;        // 연락처 이름. 캡슐화를 위해 private로 선언
    private String phoneNumber; // 연락처 전화번호. 캡슐화를 위해 private로 선언

    // 생성자: 이름과 전화번호를 초기화합니다
    public Contact(String name, String phoneNumber) {
        this.name = name;
        this.phoneNumber = phoneNumber;
    }
}

 


ContactApp : 프로그램의 시작점 (main 메소드)

 

ContactApp 클래스는 프로그램의 main 메소드를 포함하며, 사용자로부터 명령을 입력받고 적절한 기능을 호출하는 프로그램의 진입점(Entry Point) 역할을 합니다.

// ContactApp.java
import java.util.Scanner;   // 사용자 입력을 위한 Scanner 클래스 임포트

public class ContactApp {
    public static void main(String[] args) {
        // 1. 연락처 관리를 담당할 ContactManager 객체를 생성합니다.
        ContactManager manager = new ContactManager();
        // 2. 사용자 입력을 받기 위한 Scanner 객체를 생성합니다.
        Scanner scanner = new Scanner(System.in);

        // 3. 무한 루프를 통해 사용자의 명령을 계속해서 받습니다.
        while (true) {
            System.out.println("\n\uD83D\uDCDE 연락처 관리 시스템 (add, remove, list, search, exit)");
            System.out.print("명령 입력: ");
            String command = scanner.nextLine().trim().toLowerCase();

            // 4. 입력된 명령에 따라 적절한 기능을 수행합니다.
            switch (command) {
                case "add":
                    System.out.print("이름: ");
                    String name = scanner.nextLine();
                    System.out.print("전화번호: ");
                    String phone = scanner.nextLine();
                    manager.addContact(name, phone);   // ContactManager의 addContact 호출
                    break;
                case "remove":
                    System.out.print("삭제할 연락처 이름: ");
                    String removeName = scanner.nextLine();
                    manager.removeContact(removeName);   // ContactManager의 removeContact 호출
                    break;
                case "search":
                    System.out.print("검색할 이름: ");
                    String searchName = scanner.nextLine();
                    manager.searchContact(searchName);   // ContactManager의 searchContact 호출
                    break;
                case "list":
                    manager.listContacts();   // ContactManager의 listContacts 호출
                    break;
                case "exit":
                    System.out.println("\uD83D\uDEAA 종료합니다.");
                    scanner.close(); // Scanner 자원 해제
                    return; // main 메소드를 종료하여 프로그램 종료
                default:
                    System.out.println("⚠️ 올바른 명령을 입력하세요. (add, remove, list, search, exit)");
                    break;
            }
        }
    }
}
  • Scanner와 ContactManager 객체 생성 위치: while 루프 바깥에서 한 번만 생성하여 프로그램이 실행되는 동안 상태(연락처 목록)를 유지하도록 했습니다. 만약 루프 안에서 생성하면 매번 초기화되어 연락처가 저장되지 않을 것입니다.
  • trim().toLowerCase(): 사용자 입력의 앞뒤 공백을 제거하고(trim), 모든 문자를 소문자로 변환하여(toLowerCase) 대소문자나 불필요한 공백에 관계없이 명령어를 정확히 인식하도록 했습니다.
  • switch 문과 break: 각 case 끝에 break를 사용하여 해당 명령 처리 후 switch 문을 빠져나오도록 합니다. break가 없으면 다음 case로 실행이 계속 이어지는 'fall-through' 현상이 발생합니다.
  • exit와 return: exit 명령 시 Scanner 자원을 닫고 return을 사용하여 main 메소드를 종료, 결과적으로 프로그램이 완전히 종료됩니다.

ContactManager 클래스 : 핵심 기능 관리

 

ContactManager 클래스는 실제 연락처 목록(ArrayList)을 관리하고, 연락처 추가/삭제/검색/출력 등 모든 핵심 비즈니스 로직을 담당합니다. 처음에는 각 메소드의 뼈대만 만들고, 다음 단계에서 기능을 구현해 나갑니다.

 

 

// ContactManager.java
import java.util.ArrayList;   // ArrayList 사용을 위한 임포트

class ContactManager {
    // 연락처들을 저장할 ArrayList 필드를 선언합니다.
    // private로 선언하여 외부에서 직접 접근을 막고 캡슐화를 유지합니다.
    private ArrayList<Contact> contacts;

    // 생성자: ContactManager 객체 생성 시 contacts ArrayList를 초기화합니다.
    public ContactManager() {
        this.contacts = new ArrayList<>();
    }

    // 아래 메소드들은 각 기능이 구현될 자리입니다.
    public void addContact(String name, String phoneNumber) {
        System.out.println("🚧 add 기능 구현 중...");
    }

    public void listContacts() {
        System.out.println("🚧 list 기능 구현 중...");
    }

    public void searchContact(String name) {
        System.out.println("🚧 search 기능 구현 중...");
    }

    public void removeContact(String name) {
        System.out.println("🚧 remove 기능 구현 중...");
    }
}
  • ArrayList 필드 선언 및 초기화: contacts는 ContactManager의 핵심 데이터이므로 private으로 선언하고, 생성자에서 new ArrayList<>()를 통해 반드시 초기화해줍니다. 이렇게 해야 NullPointerException 없이 add 등의 메소드를 사용할 수 있습니다.
  • 역할 분리: ContactManager는 ContactApp으로부터 명령을 받아 실제 데이터(contacts)를 조작하는 역할을 수행합니다.

💡 Step 2: 핵심 기능 구현

이제 ContactManager 클래스에 정의된 메소드들을 하나씩 구현하여 프로그램에 실제 기능을 추가해봅니다.


2.1) 연락처 추가 (addContact)

새로운 연락처 정보를 받아 Contact 객체를 생성하고, 이를 ArrayList에 저장하는 기능입니다.

  • contacts.add(new Contact(name, phoneNumber));: addContact 메소드가 호출될 때마다, 전달받은 이름과 전화번호로 새로운 Contact 객체를 만들고, 이를 ContactManager의 contacts ArrayList에 추가합니다.
// ContactManager.java (addContact 메소드 수정)
// ...
	public void addContact(String name, String phoneNumber) {
        // 새로운 Contact 객체를 생성하고, contacts ArrayList에 추가합니다.
        contacts.add(new Contact(name, phoneNumber));
        System.out.println("✅ 연락처가 추가되었습니다.");
    }
// ...
}

2.2) 연락처 목록 출력 (listContacts)

현재 ArrayList에 저장된 모든 연락처 정보를 사용자에게 보여주는 기능입니다. 연락처가 없을 경우와 있을 경우를 구분하여 처리합니다.

이 기능을 구현하기에 앞서, Contact 객체를 System.out.println()으로 출력할 때 이름과 전화번호가 원하는 형식으로 예쁘게 나오도록 Contact 클래스에 toString() 메소드를 오버라이드하고, 이름 접근을 위한 getName() 메소드를 추가해 줍니다.

 

 

// Contact.java (수정 및 추가)
class Contact {
    //... 필드, 생성자

    // 연락처 이름을 반환하는 getName 메소드 추가 (캡슐화)
    public String getName() {
        return name;
    }

    // 객체를 문자열로 표현할 때 사용되는 toString() 메소드 오버라이드
    // System.out.println(contact) 호출 시 자동으로 이 메소드가 실행됩니다.
    @Override
    public String toString() {
        return name + " - " + phoneNumber;
    }  // 출력 예시 : 홍길동 - 010-1234-5678
}

 

이제 ContactManager의 listContacts 메소드를 구현합니다.

// ContactManager.java (listContacts 메소드 수정)
class ContactManager {
    // ...
    public void listContacts() {
        if (contacts.isEmpty()) {   // contacts 리스트가 비어있는지 확인
            System.out.println("등록된 연락처가 없습니다.");
        } else {
            // 향상된 for 루프 (for-each)를 사용하여 contacts 리스트의 각 Contact 객체를 순회합니다.
            for (Contact contact : contacts) {
                System.out.println(contact); // Contact 클래스의 toString() 메소드가 자동 호출됩니다.
            }
        }
    }
    // ...
}
  • contacts.isEmpty(): ArrayList가 비어있는지 확인하는 메소드입니다.
  • 향상된 for 루프: for (Contact contact : contacts)는 contacts 리스트의 각 Contact 객체를 contact 변수에 할당하며 반복합니다. 코드가 간결하고 읽기 쉽습니다.
  • System.out.println(contact): Contact 클래스에서 오버라이드한 toString() 메소드 덕분에, 객체 자체를 출력하면 우리가 원하는 "이름 - 전화번호" 형식으로 나타납니다.

2.3) 연락처 검색 (searchContact)

이름을 기준으로 연락처를 찾아 그 정보를 출력하는 기능입니다. 동일한 이름의 연락처가 여러 개 있을 경우 모두 출력하고, 찾지 못했을 때는 사용자에게 알려줍니다.

 

 

// ContactManager.java (searchContact 메소드 수정)
class ContactManager {
    // ...
    public void searchContact(String name) {
        boolean found = false;   // 연락처를 찾았는지 여부를 나타내는 플래그

        for (Contact contact : contacts) {
            if (contact.getName().equalsIgnoreCase(name)) {
                System.out.println(contact);   // 찾은 연락처 출력
                found = true;   // 찾으면 플래그를 true로 설정
            }
        }
        if (!found) {    // 연락처를 못 찾았으면 (found 플래그가 false이면)
            System.out.println("❌ 연락처를 찾을 수 없습니다: " + name);
        }
    }
    // ...
}
  • boolean found = false;: 메소드 시작 시 found라는 플래그 변수를 false로 초기화합니다. 이 변수는 "검색된 연락처가 하나라도 있었는가?"를 추적하는 역할을 합니다.
  • for (Contact contact : contacts): 모든 연락처를 순회하며 하나씩 확인합니다.
  • if (contact.getName().equalsIgnoreCase(name)): 각 Contact 객체의 getName() 메소드를 통해 이름을 가져와, 검색하려는 name과 대소문자 구분 없이(equalsIgnoreCase) 비교합니다.
  • found = true;: 만약 일치하는 연락처를 찾으면, found 값을 true로 변경합니다.
  • if (!found): for 루프가 완전히 끝난 후, found가 여전히 false라면 (즉, 한 번도 일치하는 연락처를 찾지 못했다면) "연락처를 찾을 수 없습니다" 메시지를 출력합니다. 이렇게 하면 모든 연락처를 확인한 후에 최종적으로 결과를 알릴 수 있습니다.

2.4) 연락처 삭제 (removeContact)

이름을 기준으로 연락처 목록에서 특정 연락처를 삭제하는 기능입니다. 동일한 이름의 연락처가 여러 개 있다면, 해당 이름을 가진 모든 연락처를 삭제합니다.

// ContactManager.java (removeContact 메소드 수정)
class ContactManager {
    // ...
    public void removeContact(String name) {
        System.out.println("--- 연락처 삭제 ---");
        
        // 1. `removeIf()` 메소드를 사용하여 조건을 만족하는 모든 요소를 안전하게 제거합니다.
        boolean removed = contacts.removeIf(contact -> contact.getName().equalsIgnoreCase(name));

        // 2. `removeIf()`의 반환값을 통해 삭제 성공 여부를 판단하고 메시지를 출력합니다.
        if (removed) {
            System.out.println("✅ 연락처가 삭제되었습니다.");
        } else {
            System.out.println("❌ 삭제할 연락처를 찾을 수 없습니다: " + name);
        }
    }
}

 

 

  • removeIf()는 Collection 인터페이스에 정의된 메소드로, ArrayList와 같은 컬렉션에서 특정 조건을 만족하는 모든 요소를 효율적이고 안전하게 제거합니다.
  • contact -> contact.getName().equalsIgnoreCase(name): 이 부분은 람다 표현식(Lambda Expression)으로 작성된 Predicate입니다. removeIf()는 이 조건을 contacts 리스트의 각 Contact 객체에 대해 검사합니다. 만약 현재 contact의 이름이 삭제하려는 name과 대소문자 구분 없이 일치하면(equalsIgnoreCase), 해당 Contact 객체는 리스트에서 제거됩니다.
  • 반환값: removeIf()는 하나 이상의 요소가 제거되었다면 true를, 아무것도 제거되지 않았다면 false를 반환합니다. 이를 boolean removed 변수에 저장하여 삭제 성공 여부를 판별합니다.
  • 이전에는 Iterator 를 사용하여 컬렉션을 순회하며 요소를 제거했지만, Java 8 이후로는 removeIf() 로 그 과정을 더욱 간결하게 처리할 수 있습니다.

최종 코드

// Contact.java
class Contact {
    private String name;
    private String phoneNumber;

    Contact(String name, String phoneNumber) {
        this.name = name;
        this.phoneNumber = phoneNumber;
    }

    @Override
    public String toString() {
        return name + " - " + phoneNumber;
    }

    public String getName() {
        return name;
    }
}
// ContactManager.java
import java.util.ArrayList;

class ContactManager {
    private ArrayList<Contact> contacts;
    
    public ContactManager() {
        this.contacts = new ArrayList<>();  // contacts ArrayList 초기화
    }

    public void addContact(String name, String phoneNumber) {
        contacts.add(new Contact(name, phoneNumber));
        System.out.println("✅ 연락처가 추가되었습니다.");
    }

    public void listContacts() {
        if (contacts.isEmpty()) {
            System.out.println("❌ 등록된 연락처가 없습니다.");
        } else {
            for (Contact contact : contacts) {
                System.out.println(contact);
            }
        }
    }

    public void searchContact(String name) {
        boolean found = false;

        for (Contact contact : contacts) {
            if (contact.getName().equalsIgnoreCase(name)) {
                System.out.println(contact);
                found = true;
            }
        }

        if (!found) {
            System.out.println("❌ 연락처를 찾을 수 없습니다: " + name");
        }
    }

    public void removeContact(String name) {
        boolean removed = contacts.removeIf(contact -> contact.getName().equalsIgnoreCase(name));
        if (removed) {
            System.out.println("✅ 삭제 완료!");
        } else {
            System.out.println("❌ 연락처를 찾을 수 없습니다: " + name");
        }
    }
}
// ContactApp.java
import java.util.Scanner;

public class ContactApp {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        ContactManager manager = new ContactManager();

        while (true) {
            System.out.println("\uD83D\uDCDE 연락처 관리 시스템 (add, remove, list, search, exit)");
            System.out.print("명령 입력: ");
            String command = scanner.nextLine().trim().toLowerCase();

            switch (command) {
                case "add":
                    System.out.print("이름: ");
                    String name = scanner.nextLine();
                    System.out.print("전화번호: ");
                    String phoneNumber = scanner.nextLine();
                    manager.addContact(name, phoneNumber);
                    break;
                case "remove":
                    System.out.print("삭제할 연락처 이름: ");
                    String nameToRemove = scanner.nextLine();
                    manager.removeContact(nameToRemove);
                    break;
                case "search":
                    System.out.print("검색할 이름: ");
                    String nameToSearch = scanner.nextLine();
                    manager.searchContact(nameToSearch);
                    break;
                case "list":
                    manager.listContacts();
                    break;
                case "exit":
                    System.out.println("\uD83D\uDEAA 종료합니다.");
                    scanner.close();
                    return;
                default:
                    System.out.println("⚠ 알 수 없는 명령입니다. 다시 시도하세요.");
                    break;
            }
        }
    }
}

 

개선할 수 있는 부분:

  • 연락처 정렬 기능: 현재 list 기능은 단순히 추가된 순서대로 출력됩니다. 이름 오름차순으로 정렬하여 출력하도록 Comparator와 Collections.sort() 또는 스트림의 sorted()를 활용해볼 수 있습니다.
  • 중복 연락처 처리: 동일한 이름의 연락처가 추가되는 것을 방지하거나, 추가 시 사용자에게 경고하는 기능을 추가할 수 있습니다.

 

감사합니다 :)