컬렉션 프레임워크

  • 프로그램을 개발 할 때 사용하는 자료를 관리하는 방법론이 자료구조다.
  • 자료구조는 프로그램 실행 중 메모리에 자료를 유지, 관리 하기 위해 사용한다.
  • Java에서는 필요한 자료구조를 미리 구현하여 java.util 패키지에 제공하고 있고, 이것을 컬렉션 프레임워크 라고 한다.

DAO

  • 컬렉션 예제에 사용할 DAO 코드
  • memberIdmemberName로 이루어진 getter/setter를 가지는 간단한 DAO다.

Collection 인터페이스

Collection 주요 메소드

  • boolean add(E e) : Collection에 객체를 추가한다.
  • void clear() : Collection의 모든 객체를 제거한다.
  • Iterator<E> iterator : Collection에 이터에이터를 반환한다.
  • boolean remove(Object o) : Collection의 인자에 해당하는 인스턴스가 존재하면 제거한다.
  • int size() : Collection의 요소의 갯수를 반환한다.

List 인터페이스

ArrayList 클래스

ArrayList와 Vector 클래스

  • Vector역시 ArrayList와 같이 배열을 구현한 클래스다. (since Java 2)
  • 둘의 가장 큰 차이는 동기화 (synchronized) 지원 여부다. 두개 이사으이 thread가 동시에 Vector를 사용할 때 오류가 나지 않도록 동기성을 보장한다.
  • multi-thread 환경이 아닐때는 ArrayList사용을 권장한다. 동기화를 구현하기 위해 동시에 작업이 이루어지는 자원에 대해 lock을 수행하기 때문이다.
  • 즉, 메소드를 호출할 때 배열 객체에 잠금을 하고, 메소드 수행이 끝나면 잠금을 해제한다. Vector는 모든 호출에 대해서 잠금과 해제가 알어나 ArrayList보다 비용이 크다.
  • ArrayList로 구현한 코드를 변경하지 않고 동기화가 필요하다면 생성 코드를 다음과 같이 바꾸면 된다.
Collections.synchronizedList(new ArrayList<String>());

다만 이 역시 자료형이 아닌 내부 구현된 메소드 (add, remove 등)에 한해서만 동기성을 보장할 뿐이다. 이에 대해서는 멀티스레드를 다루면서 다시 돌아오겠다.

LinkedList 클래스

  • ArrayList와 비교해 배열은 처음 생성시 정적 크기로 선언하고, 물리적 순서와 눈리적 순서가 동일하다. 다만 중간에 자료를 삽입하거나 삭제할 때 나머지 자료를 이동시키거나 배열이 작은 경우 새로운 배열을 만들어 복사한다. 여기서 많은 비용이 발생한다.
  • 이런점을 개선한 것이 LinkedList다.
    a3e6725b35e66da72fb41feeac2f30a1.png
  • Linked list는 각 객체에 data부분과 다음 인스턴스를 가리키는 주소를 위한 변수가 있다.
  • 논리적으로는 순서가 같으나 물리적인 순서는 메모리 주소에 의해 결정된다.
  • 배열에 비해 중간에 자료를 삽입하고 추출하는데 적은 시간이 소요된다.

ArrayList(Array) vs LinkedList

  • Array는 자료의 위치가 메모리의 물리적 위치와 같기 때문에 index로 바로 current를 이동 할 수 있지만 LinkedList는 물리적 위치가 같지 않기 때문에 모든 노드를 digging해서 찾아가야 한다.
  • LinkedList는 자료를 삽입하거나 추출할 때 새로 생성한 노드 인스턴스를 원하는 위치의 노드와 주소 교환만 하면 되지만 Array는 추출시 뒤에 있는 원소들을 모두 앞으로 이동해야하고 삽입시 배열의 크기가 모자라다면 새롭게 배열을 할당해 기존의 원소들까지 복사해야한다.
  • 자료의 변동이 많은 경우는 LinkedList, 변동이 없고 조회가 잦은 경우 Array를 시용하는 것이 효율적이다.

Collection을 순회하는 Iterator

  • 순서가 없는 Set을 포함하여 모든 collection은 미리 정의되어 있는 이터레이터를 사용할 수 있다.
Iterator iterator = memberService.iterator();

Iterator 메소드

  • boolean hasNext() : 다음 요소(노드)가 있는지 여부를 반환한다
  • E next() : 다음 요소(노드)를 반환한다

Iterator로 구현한 removeMember()

public boolean removeMember(int memberId) {
    Iterator<Member> iterator = list.iterator();
    while (iterator.hasNext()) {
        Member member = iterator.next();
        int id = member.getMemberId();
        if (id == memberId) {
            list.remove(member);
            return true;
        }
    }
    System.out.println("Invalid " + memberId);
    return false;
}

더 최적화 된 것은 스트림 할 때 다시 다루자.

Set 인터페이스

  • 중복된 요소를 허용하지 않는 자료구조다.
  • 순서가 없는 것이 특징이다.

HashSet

HashSet을 활용한 회원관리

public class MemberTest {
    public static void main(String[] args) {
        MemberService memberService = new MemberService();

        Member member1 = new Member(500, "강민철");
        Member member2 = new Member(501, "김민철");
        Member member3 = new Member(502, "이민철");
        Member member3_1 = new Member(502, "아니요");
        Member member4 = new Member(503, "최민철");

        memberService.addMember(member1);
        memberService.addMember(member2);
        memberService.addMember(member3);
        memberService.addMember(member3_1);
        memberService.addMember(member4);

        memberService.showAllMember();

    }
}
  • member3_1을 보면 502로 중복 회원을 추가한다. 아래는 실행결과다
강민철Member ID is 500.
최민철Member ID is 503.
김민철Member ID is 501.
이민철Member ID is 502.
아니요Member ID is 502.
===End of Member===
  • 보면 memberId가 중복인 회원이 추가된 것을 알 수 있다.

인스턴스의 판단을 위한 hashCode()와 equals()

  • 인스턴스가 동일한지 판단하기 위한 기준을 memberId로 만들면 해결할 수 있다.
  • hashCode()equals()를 오버라이딩 하면된다.
//Member.java
    @Override
    public int hashCode() {
        return memberId;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Member){
            Member member = (Member)obj;
            return this.memberId == member.memberId;
        }
        return true;
    }
  • 아래는 Member수정 뒤 출력화면이다.
강민철Member ID is 500.
김민철Member ID is 501.
이민철Member ID is 502.
최민철Member ID is 503.
  • 그러고 나면 중복 회원이 추가되지 않는것을 확인할 수 있다.

TreeSet

  • Java의 collection 인터페이스나 Map 인터페이스를 구현한 클래스 중 Tree로 시작하는 클래스는 데이터를 추가후 출력하면 값이 정렬되어있다.
  • Java에서는 정렬을 구현하기 위해 binary-tree를 사용한다.
  • TreeSet은 데이터를 추가 할때 어떤 기준으로 노드를 비교하여 트리를 형성해야 하는지 정해야 한다.

HashSet과의 차이에 대해서는 자료가 보강되는대로 업데이트 하겠다.

Comparable

  • 이것은 Comparable 인터페이스를 DAO에 구현함으로써 기준을 정하면 된다.
  • 그리고 compareTo를 구현함으로써 id를 비교하면 된다.
//MemberComparable
public class MemberComparable implements Comparable<MemberComparable> {
    private int memberId;
    private String memberName;
    ...
    @Override
    public int compareTo(MemberComparable member) {
        return (this.memberId - member.memberId);
    }
  • 다음과 같이 뒤죽박죽 넣더라도
memberService.addMember(member1);
memberService.addMember(member3);
memberService.addMember(member2);
memberService.addMember(member3_1);
memberService.addMember(member4);
  • 아래와 같이 memberId를 기준으로 정렬되는 것을 확인할 수 있다.
강민철Member ID is 500.
김민철Member ID is 501.
이민철Member ID is 502.
최민철Member ID is 503.
===End of Member===
  • 오름차순에서 내림차순으로 바꿀려면 compareTo의 반환을 음수로 하면 된다.
//MemberComparable
@Override
public int compareTo(MemberComparable member) {
    return (this.memberId - member.memberId);
}

Comparator

  • Comparator는 두개의 인자를 받아 정렬 순위를 결정하는 인터페이스다
  • compare()를 구현해 인자를 비교하는 기능을 만든다.
class Compare implements Comparator<String> {
    @Override
    public int compare(String arg1, String arg2) {
        return arg1.compareTo(arg2);
    }

}

public class ComparatorTest {
    public static void main(String[] args) {
        Set<String> set = new TreeSet<String>(new Compare());
        set.add("esens");
        set.add("ximya");
        set.add("mastawu");

        System.out.println(set);

    }
}
  • 아래 실행 결과에서 정렬이 되는것을 확인할 수 있다.
[esens, mastawu, ximya]
  • 일반적으로 Comparable을 더 많이 선호한다.
  • Comparable이 이미 구현되어 있는 경우 정렬 방식을 정의할 때 Comparator을 사용할 수 있다.

멀티스레드 환경에서 동시성이나 메소드 단위의 언급은 없었는데 멀티스레드를 하면서 다시 다루겠다.