본문 바로가기

JAVA의 정석

ArrayList, HashSet, HashMap

 

1. ArrayList

 

ArrayListsms List인터페이스를 구현하기 때문에 데이터의 저장순서가 유지되고 중복을 허용한다.

 

기존의 Vector를 개선한 것으로 원리와 기능적인 측면은 동일하다.

 

배열에 더 이상 저장할 공간이 없으면 보다 큰 새로운 배열을 생성해서 기존의 배열에 저장된 내용을

 

새로운 배열로 복사한 다음에 저장한다.

메서드 설명 메서드 설명
ArrayList() 크기가 10인 ArrayList를 생성 ArrayList(Collection c) 주어진 컬렉션이 저장된 ArrayList를 생성
ArrayList(int initialCapa) 지정된 초기용량을 갖는 ArrayList를 생성 bolean add(Object o) ArrayList의 마지막에 객체를 추가. 성공하면 true
void add(int indexl, Object element) 지정된 위치(index)에 객체를 저장 boolean addAll(int index, Collection c) 지정된 위치부터 주어직 컬렉션의 모든 객체를 저장한다.
void clear() ArrayList를 완전히 비운다. Object clone() ArrayList를 복제한다.
boolean contains (Object o) 지정된 객체가 ArrayList에 포함되어 있는지 확인 void ensureCapacity (int min Capacity) ArrayList의 용량이 최소한 최소한 minCapacity가 되도록 한다.
Object get(int index) 지정된 위치에 저장된 객체를 반환한다.    

 

 

메서드 설명 메서드 설명
int indexOf(object o) 지정된 객체가 저장된 위치를 찾아 반환한다. boolean isEmpty() ArrayList가 비어있는지 확인한다.
Iterator iterator() ArrayList의 Iterator 객체를 반환 int lastIndexOf(Object o) 객체가 저장된 위치를 끝에서부터 역방향으로 검색해서 반환
ListIterator listIterator() ArrayList의 ListIterator를 반환 ListIterator listIterator(int inex) ArrayList의 지정된 위치부터 시작하는 ListIterator를 반환
Object remove(int index) 지정된 위치에 있는 객체 제거 boolean remove(Object o) 지정된 객체를 제거한다.
boolean removeAll(Collection c) 지정한 컬레셕에 저장된 것과 동일한 객체들을 제거한다. boolean retainAll(Collection c) ArrayList에 저장된 객체 중에서 주어진 컬렉션과 공토왼 것만을 남기고 나머지는 삭제한다.
Object set(int index, Object element) 주어진 객체를 지정된 위치에 저장한다. int size() ArrayList에 지정된 객체의 수를 반환한다.
void sort(Comparator c) 지정된 정렬기준(c)으로 ArrayList를 정렬 List subList(int fromIndex, int toInex) fromindex부터 toindex사이에 저장된 객체반환한다.
Object[] toArray() ArrayList에 저장된 모든 객체들을 객체배열로 반환한다. Object[] toArray(Object[] a) ArrayList에 저장된 모든 객체들객체배열 a에 담아 반환한다.
void trimToSize() 용량을 크기에 맞게 줄인다. (비)    

 

 

예제1)

package Java2;

import java.util.ArrayList;
import java.util.Collections;

public class ArrayListEx1 {
    public static void main(String[] args) {
        ArrayList list1 = new ArrayList(10);
        list1.add(Integer.valueOf(5));
        list1.add(Integer.valueOf(4));
        list1.add(Integer.valueOf(2));
        list1.add(Integer.valueOf(0));
        list1.add(Integer.valueOf(1));
        list1.add(Integer.valueOf(3));

        ArrayList list2 = new ArrayList(list1.subList(1, 4));
        print(list1, list2);

        Collections.sort(list1); // list1과 List2를 정렬한다.
        Collections.sort(list2); //
        print(list1, list2);

        System.out.println("list1.containsAll(list2): " + list1.containsAll(list2));

        list2.add("B");
        list2.add("C");
        list2.add(3,"A");
        print(list1, list2);

        list2.set(3,"AA");
        print(list1, list2);

        //list1에서 list2와 겹치는 부분만 남기고 삭제하기.
        System.out.println("list1.retainAll(list2): " + list1.retainAll(list2));

        print(list1,list2);

        // list2에서 list1에 포함된 객체들 삭제하기.
        for(int i = list2.size()-1; i >= 0 ; i--){
            if(list1.contains(list2.get(i)))
                list2.remove(i);
        }
        print(list1, list2);
    }

    static void print(ArrayList<Integer> list1, ArrayList<Integer> list2) {
        System.out.println("list1: " + list1);
        System.out.println("list2: " + list2);
        System.out.println();
    }
}

 

 

 

위의 예제는 ArrayList의 기본적인 메서드를 이용해서 객체를 다루는 방법을 보여준다. 그리고 Collection 클래스의 sort 메서드를

 

이용해서 ArrayList에 저장된 객체들을 정렬하였는데 Collection 클래스에 대한 내용과 정렬하는 방법에 대해서는 나중에 

 

알아보자.

 

2. HashSet

 

HashSet은 Set인터페이스를 구현한 가장 대표적인 컬렉션이며, Set 인터페이스의 특징대로 중복된 요소를 저장하지 않는다.

 

HashSet에 요소를 추천할 때는 add나 addAll 메서드를 사용하고 , 이미 중복된 요소를 추가하자고 한다면 false를 반환한다.

 

이러한 HashSet의 특징을 이용하면 컬렉션 내의 중복 요소들을 쉽게 제거할 수 있다.

 

ArrayList와 같이 List인터페이스를 구현한 컬렉션과 달리 HashSet은 저장순서를 유지하지 않으므로 저장순서를 

 

유지하고자 한다면 LinkedHashSet을 사용해야한다.

 

 

생성자 또는 메서드 설명
HashSet() HashSet객체를 생성한다.
HashSet(Collection c) 주어진 컬렉션을 포함하는 Hashset을 생성한다
HashSet(int initialCapacity) 주어진 값을 초기용량으로 하는 HashSet객체를 생성한다.
HashSet(int initialCapa, float loadFactor) 초기용량과 load facor를 지정하는 생성자
boolean add(Object o) 새로운 객체를 저장한다
boolean addAll(Collection c) 주어진 컬렉션에 저장된 모든 객체들을 추가한다 (합집합)
void clear() 객체 모두 삭제
Object clone() HashSet을 복제해서 반환한다.
boolean contains(Object o) 지정된 객체가 포함되어 있는지 확인한다.

 

생성자 또는 메서드 설명
boolean containsAll(Collection c) 주어진 컬렉션에 저장된 모든 객체들을 포함하고 있는지 알려준다
boolean isEmpty() 비어있는지 알려준다
Iterator iterator() Iterator를 반환한다
boolean remove(Object o) 지정된 객체를 HashSet에서 삭제한다. 성공하면 true 
boolean removeAll(Collection c) 주어진 컬렉션에 저장된 모든 객체와 동일한 것들을 HashSet에서 모두 제거한다.(차집합)
booloean retainAll(Collection c) 주어진 컬렉션에 저장된 객체와 동일한 것만 남기고 삭제한다 (교집합)
int size() 지정된 객체의 수를 반환한다.
Object[] toArray() 저장된 객체들을 객체배열의 형태로 반환한다.
Object[] toArray(Object[] a) 지정된 객체들을 주어진 객체배열(a)에 담는다.

 

 

예제 1)

package Java2;

import java.util.HashSet;
import java.util.Set;

public class HashSetEx1 {
    public static void main(String[] args) {
        Object[] objArr = {"1","2","3","4",4,5,6,6,6,6};
        Set set = new HashSet();

        for (int i = 0 ; i < objArr.length; i++){
            set.add(objArr[i]);
        }

        System.out.println(set);
    }
}

 

 

결과 :

 

 

 

 

결과에서 알 수 있듯 중복된 값은 저장되지 않는다. add메서드는 객체를 추가할 때 hashset에 같은 객체가 있으면 중복으로 간주하고

 

저장하지 않기 때문이다. 4가 두번 출력되었는데 하나는 String이고 하나는 integer라는 다른 객체여서 중복으로 간주하지 않는다.

 

Set을 구현한 컬렉션 클래스는 List를 구현한 컬렉션 클래스와 달리 순서를 유지하기 때문에 저장한 순서와 다를 수 있다.

 

 

예제 2)

package Java2;

import java.util.HashSet;

public class HashSetEx3 {
    public static void main(String[] args) {
        HashSet set = new HashSet();

        set.add("abc");
        set.add("abc");
        set.add(new Person("David",10));
        set.add(new Person("David",10));


        System.out.println(set);
    }

}

class Person {
    String name;
    int age;

    Person(String name, int age){
        this.name = name;
        this.age = age;
    }

    public String toString() {
        return name + ":" + age;
    }


}

 

 

결과 :

 

 

 

두 인스턴스의 namer과 age 값이 같음에도 불구하고 서로 다른 것으로 인식하여 David 10이 두번 출력되는 모습을 볼 수 있다.

 

클래스의 작성의도대로 두 인스턴스를 같게 인식하려면 어떻게 해야할까?

 

 

package Java2;

import java.util.HashSet;

public class HashSetEx3 {
    public static void main(String[] args) {
        HashSet set = new HashSet();

        set.add("abc");
        set.add("abc");
        set.add(new Person("David",10));
        set.add(new Person("David",10));


        System.out.println(set);
    }

}

class Person {
    String name;
    int age;

    Person(String name, int age){
        this.name = name;
        this.age = age;
    }

    public boolean equals(Object obj){
        if(obj instanceof Person) {
            Person tmp = (Person)obj;
            return name.equals(tmp.name) && age == tmp.age;
        }
        return false;
    }

    public int hashCode(){
        return (name+age).hashCode();
    }

    public String toString() {
        return name + ":" + age;
    }

}

 

 

결과 :

 

 

 

 

Hashset의 add메서드는 새로운 요소를 추가하기전에 기존에 저장된 요소와 같은 것인지 판별하기 위해 추가하려는 요소의 

 

equals()와 hashCode()를 호출하기 때문에 equals()와 hashCode()를 목적에 맞게 오버라이딩해야 한다.

 

그래서 String클래스에서 같은 내용의 문자열에 대한 equals()의 호출결과가 true인 것과 같이 , Person 클래스에서도

 

두 인스턴스의 name과 age가 서로 같으면 true를 반환하도록 equals()를 오버라이딩 했다.

 

그리고 hashCode()는 String클래스의 hashCode()를 이용해서 구현했다. String 클래스의 hashCode()는 

 

잘 구현되어 있기 떄문에 이를 활용하면 간단히 처리할 수 있다. 

 

public int hashCode() {
                  return (name+age).hashCode();
}

 

 

위의 코드를 JDK 1.8 부터 추가된 java.utill.Objects 클래스의 hash()를 이용해서 작성하면 아래와 같다. 이 메서드의 괄호 안에

 

클래스의 인스턴스 변수들을 넣으면 된다. 이전의 코드와 별반 다르지 않지만, 가능하면 아래의 코드를 쓰자

 

public int hashCode(){
                   return Objects.hash(name, age); // int hash(Object ... values)
}

 

 

 

 

오버라이딩을 통해서 작성된 hashCode()는 다음의 세 가지 조건을 만족 시켜야 한다. 

 

 

1. 실행중인 애플리케이션 내의 동일한 객체에 대해서 여러 번 hashCdoe()를 호출해도 동일한 Int 값을 반환해야한다.

 

하지만, 실행시마다 동일한 Int값을 반환할 필요는 없다.(단,equals메서드의 구현에 사용된 멤버변수의 값이 바뀌지 않을 때)

 

예를 들어 Person2 클래스의 equals메서드에 사용된 멤버변수 name과 age의 값이 바뀌지 않는 한, 

 

하나의 Person 인스턴스에 대해 hashCode()를 여러 번 호출했을 때 항항 같은 int 값을 얻어야 한다. 

 

Person2 p = new Person2("David", 10); 

int hashCode1 = p.hashCode();
int hashCode2 = p.hashCode();

p.age = 20; 
int hashCode3 = p.hashCode();

 

위의 코드에서 hashCode1의 값과 hashCode2의 값은 항상 일치해야하지만 이 두값이 매번 실행할때 마다 반드시 같은 값일

 

필요는 없다. hashCode3은 equals 메서드에 사용된 멤버변수 age를 변경한 후에 hashCode 메서드를 호출한 결과이므로

 

hashCdoe1이나 hashCode2와 달라도 된다.

 


 

참고. String 클래스는 문자열의 내용으로 해시코드를 만들어 내기 때문에 내용이 같은 문자열에 대한 hashCode()호출은

 

항상 동일한 해시코드를 반환한다. 반면에 Object클래스는 객체의 주소로 해시코드를 만들어 내기 때문에 실행할 때마다

 

해시코드 값이 달라질 수 있다. 


 

2. equals 메서드를 이용한 비교에 의해서 true를 얻은 두 객체에 대해 각각 hashCdoe()를 호출애서 얻은 결과는 반드시

 

같아야한다. 인스턴스 p1과 p2에 대해서 equals메서드를 이용한 비교의 결과인 변수 b의 값이 true라면

 

hashCode1과 hashCode2의 값은 같아야 한다는 뜻이다.

 

Person2 p1 = new Person2("David", 10); 
Person2 p2 = new Person2("David", 10); 

boolean b = p1.equals(p2);

int hashCode1 = p1.hashCode();
int hashCode2 = p2.hashCode();

 

 

3. equals메서드를 호출했을 때 false를 반환하는 두 객체는 hashCode()호출에 대해 같은 Int 값을 반환하는 경우가 있어도 괜찮지만,

 

해싱(hashing)을 사용하는 컬렉션의 선ㅇ응을 향상시키기 위해서는 다른 int값을 반환하는 것이 좋다. 

 

위의 코드에서 변수 b값이 false일지라도 hashCode1과 haschCode2의 값이 같은 경우가 발생하는 것을 허용한다. 하지만,

 

해시코드를 사용하는 Hashtable이나 HashMap과 같은 컬렉션의 성능을 높이기 위해서는 가능한 한 서로 다른 값을 반환하도록

 

hashCode()를 잘 작성해야한다는 뜻이다.

 

서로 다른 객체에 대해서 해시코드값 hasCode()를 호출한 결과가 중복되는 경우가 많아질 수록 해싱을 사용하는 Hashtable,

 

HashMap과 같은 컬렉션의 검색속도가 떨어진다.

 

두 객체에 대해 equals메서드를 호출한 결과가 true이면, 두 객체의 해시코드는 반드시 같아야 하지만, 두 객체의 해시코드가

 

같다고 해서 equals메서드의 호출결과가 반드시 true이어야 하는 것은 아니다. 

 

사용자정의 클래스를 작성할 때 equals메서드를 오버라이딩해야 한다면 hashCode()도 클래스의 작성의도에 맞게

 

오버라이딩하는 것이 원칙이지만, 경우에 따라 위의 에제에서와 같이 간단히 구현하거나 생략해도 별 문제가 

 

되지 않으므로 hashCode()를 구현하는데 너무 부담가지 않았으면 한다. 

 

 

예제2)

 

package Java2;

import java.util.HashSet;
import java.util.Iterator;

public class HashSetEx5 {
    public static void main(String[] args) {
        HashSet setA = new HashSet();
        HashSet setB = new HashSet();
        HashSet setHab = new HashSet();
        HashSet setKyo = new HashSet();
        HashSet setCha = new HashSet();

        setA.add("1"); setA.add("2"); setA.add("3");
        setA.add("4"); setA.add("5");
        System.out.println("A = " + setA);

        setB.add("4"); setB.add("5"); setB.add("6");
        setB.add("7"); setB.add("8");
        System.out.println("B = " + setB);

        Iterator it = setB.iterator();
        while (it.hasNext()){
            Object tmp = it.next();
            if(setA.contains(tmp))
                setKyo.add(tmp);
        }

        it = setA.iterator();
        while(it.hasNext()){
            Object tmp = it.next();
            if(!setB.contains(tmp))
                setCha.add(tmp);
        }

        it = setA.iterator();
        while(it.hasNext())
            setHab.add(it.next());

        it = setB.iterator();
        while(it.hasNext())
            setHab.add(it.next());

        System.out.println("A ∩ B = " + setKyo);
        System.out.println("A ∪ B = " + setHab);
        System.out.println("A - B = " + setCha);

    }
}

 

 

결과 :

 

 

 

이 예제는 두개의 HashSet에 저장된 객체들을 비교해 합집합, 교집합, 차집합을 구하는 방법을 보여준다.

 

사실 Set은 중복을 허용하지 않으므로 HashSet의 메서드를 호출하는 것만으로도 간단하게 합집합,교집합,차집합을

 

구현할 수 있다. contains()와 add()만을 이용한 간단한 것이다.

 

3. HashMap

 

Hashtable과 HashMap의 관계는 Vector와 ArrayList의 관계와 같아서 Hashtable보다는 새로운 버전인

 

HashMap을 사용할 것을 권한다. HashMap은 Entry라는 내부 클래스를 정의하고, 다시 Entry타입의 배열을 선언하고 

 

있다. 

 

HashMap은 키와 값을 각각 Object타입으로 저장한다. 즉(Object,Object)의 형태로 저장하기 때문에 어떠한 객체도

 

저장할 수 있지만 주로 String을 대문자 또는 소문자로 통일해서 사용하곤 한다.

 

생성자 / 메서드 설명
HashMap() MashMap객체 생성
HashMap(int initialCapa) 지정된 값을 초기용량으로 하는 HashMap객체 생성
HashMap(int initialCapa, float loadFactor) 지정된 초기용량과 load factor의 HashMap객체 생성
HashMap(Map m) 지정된 Map의 모든 요소를 포함하는 HashMap을 생성
void clear() 모든 객체 제거
Object clone() 현재 HashMap을 복제해서 반환
boolean containsKey(Object key) HashMap에 지정된 Key가 있는지 확인
boolean containsValue(Object value) HashMap에 지정된 vaule가 있는지 확인
Set entrySet() HashMap에 저장된 키와 값을 엔트리의 형태로 Set에 저장해서 반환

 

생성자 / 메서드 설명
Object get(Object key) 지정된 키의 값을 반환, 못찾으면 null 반환
Object getOrDefault(Object key, Object defaultValue) 지정된 키의 값을 반환한다. 못찾으면 기본값으로 지정된 객체 반환
void putAll(Map m) Map에 저장된 모든 요소를 HashMap에 저장
Object remove(Object key) HashMap에 지정된 키로 저장된 값을 제거
Object replace(Object key, Object value) 지정된 키의 값을 객체로 대체
boolean replace(Object key, Object oldValue, Object newValue) 지정된 키와 객체가 모두 일치하는 경우만 새로운 객체로 대체
int size() HashMap 저장된 요소의 개수를 반환
Collection values() HashMap에 저장된 모든 값을 컬렉션의 형태로 변환

 

 

 

예제1)

 

package Java2;

import java.util.HashMap;
import java.util.Scanner;

public class HashMapEx1 {
    public static void main(String[] args) {
        HashMap map = new HashMap();
        map.put("myId", "1234");
        map.put("asdf", "1111");
        map.put("asdf", "1234");

        Scanner s = new Scanner(System.in);

        while (true) {
            System.out.println("ID와 PW를 입력해주세요.");
            System.out.print("ID : ");
            String id = s.nextLine().trim();

            System.out.print("Password : ");
            String password = s.nextLine().trim();


            if (!map.containsKey(id)) {
                System.out.println("입력하신 id는 존재하지 않습니다. 다시 입력해주세요.");
                continue;
            }

            if (!(map.get(id)).equals(password)) {
                System.out.println("비밀번호가 일치하지 않습니다. 다시 입력해주세요.");
            } else {
                System.out.println("id와 비밀번호가 일치합니다.");
                break;
            }
        }

    }
}

 

 

 

 

 

위 코드는 3개의 데이터 쌍을 저장했지만 실제로는 2개 밖에 저장되지 않은 이유는 중복된 key가 있기 때문이다.

 

세 번째로 저장한 데이터의 키인 'asdf'는 이미 존재하기 때문에 새로 추가되는 대신 기존의 값을 덮어썼다.

 

그래서 키 'asdf'에 연결된 값은 '1234'가 된다.

 

 

예제2)

package Java2;

import java.util.*;

public class HashMapEx2 {
    public static void main(String[] args) {
        HashMap map = new HashMap();
        map.put("김자바", 100);
        map.put("이자바", 100);
        map.put("강자바", 80);
        map.put("안자바", 90);

        Set set = map.entrySet();  // 엔트리셋을 집합형태로... ?
        Iterator it = set.iterator(); // 엔트리 셋을 이터레이터로하는 변수

        while (it.hasNext()){
            Map.Entry e = (Map.Entry)it.next();
            System.out.println("이름 : " + e.getKey() + ", 점수 : " + e.getValue());
        }

        set = map.keySet();
        System.out.println("참가자 명단 : " + set);

        Collection values = map.values(); // HashMap에 저장된 모든 값을 컬렉션 형태로 반환
        it = values.iterator();

        int total = 0;

        while(it.hasNext()){
            Integer i = (Integer) it.next();
            total += i.intValue();
        }

        System.out.println("총점 : " + total);
        System.out.println("평균 : " + (float)total/set.size());
        System.out.println("최고 점수 : " + Collections.max(values));
        System.out.println("최저 점수 : " + Collections.min(values));
    }
}

 

 

 

예제3)

 

package Java2;

import java.util.*;

public class HashMapEx3 {
    static HashMap phoneBook = new HashMap(); // 그룹을 키로하고 벨류는 또 다른 해시맵

    public static void main(String[] args) {
        addPhoneNo("친구", "이자바", "010-111-1111");
        addPhoneNo("친구", "김자바", "010-222-2222");
        addPhoneNo("친구", "김자바", "010-333-3333");
        addPhoneNo("회사", "김대리", "010-444-4444");
        addPhoneNo("회사", "김대리", "010-555-5555");
        addPhoneNo("회사", "박대리", "010-666-6666");
        addPhoneNo("회사", "이과장", "010-777-7777");
        addPhoneNo("세탁", "010-888-8888");

        printList();
    } // main


    // 그룹에 전화번호를 추가하는 메서드
    static void addPhoneNo(String groupName, String name, String tel) {
        addGroup(groupName);
        HashMap group = (HashMap) phoneBook.get(groupName); // 지정된 키의 값을 반환 즉 , 추가로 하나의 map을 반환한다.
        group.put(tel, name);// 이름은 중복될 수 있으니 전화번호를 key로 저장한다.
    }

    // 그룹을 추가하는 메서드
    static void addGroup(String groupName) {
        if (!phoneBook.containsKey(groupName))
            phoneBook.put(groupName, new HashMap());
    }



    static void addPhoneNo(String name, String tel) {
        addPhoneNo("기타", name, tel);
    }

    static void printList() {
        Set set = phoneBook.entrySet();
        Iterator it = set.iterator();

        while (it.hasNext()) {
            Map.Entry e = (Map.Entry) it.next();

            Set subSet = ((HashMap) e.getValue()).entrySet();
            Iterator subIt = subSet.iterator();

            System.out.println(" * " + e.getKey() + "[" + subSet.size() + "]");

            while (subIt.hasNext()) {
                Map.Entry subE = (Map.Entry) subIt.next();
                String telNo = (String) subE.getKey();
                String name = (String) subE.getValue();
                System.out.println(name + " " + telNo);
            }

            System.out.println();
        }
    } // printList()
} // class


 

 

 

 

Hashmap은 데이터를 키와 값을 모두 Object로 저장하기 때문에 HashMap의 값으로 HashMap을 다시 저장할 수 있다.

 

이로써 하나의 키에 다시 복수의 데이터를 저장할 수 있다.

 

먼저 전화번호를 저장할 그룹을 만들고 그룹 안에 다시 이름과 전화번호를 저장하도록 했다. 이 때 이름 대신 전화번호를 키로 

 

사용한 이유는 동명이인은 있을 수 있지만 전화번호는 유일하기 때문이었다.