java [7] Collections-2

lsy·2022년 10월 22일
0

자바

목록 보기
9/14

Arrays

Arrays클래스에는 배열을 다루는데 유용한 메서드가 정의되어 있다.

배열의 복사 - copyOf(), copyOfRange()
배열 채우기 - fill(), setAll()
배열의 정렬과 탐색 - sort(), binarySearch()
배열의 비교와 출력 - equals(), toString()
배열을 List로 변환 - asList(Object... a)

Comparator와 Comparable

ComparatorComparable은 모두 인터페이스로 컬렉션을 정렬하는데 필요한 메서드를 정의하고 있다. 예를 들어, Arrays.sort()에서는 Character클래스의 Comparable의 구현에 의해 정렬된다.

Comparable을 구현하고 있는 클래스들은 같은 타입의 인스턴스끼리 서로 비교할 수 있는 클래스들, 주로 Integer와 같은 wrapper클래스와 String, Date, File과 같은 것들이며, 기본적으로 오름차순으로 정렬되도록 구현되어 있다. 그래서 Comaparable을 구현한 클래스는 정렬이 가능하다는 것을 의미 한다.

public interface Comparator {
    int compare(Object o1, Object o2);
    boolean equals(Object obj);
}

public interface Comparable {
    public int compareTo(Object o);
}

compare()과 compareTo()는 선언형태와 이름이 다를 뿐 두 객체를 비교한다는 같은 기능을 목적으로 고안된 것이다.

두 객체가 같으면 0, 비교하는 값보다 작으면 음수, 크면 양수를 반환하도록 구현해야 한다.

Comparable을 구현한 클래스들이 기본적으로 오름차순으로 정렬되어 있지만, 내림차순으로 정렬한다던가 다른 기준으로 정렬되게 하고 싶을 때는 Comparator를 구현해서 정렬기준으로 제공할 수 있다.

Comparable - 기본 정렬기준을 구현하는데 사용
Comparator - 기본 정렬기준 외에 다른 기준으로 정렬하고자할 때 사용

import java.util.*;

class ComparatorEx {
	public static void main(String[] args) {
		String[] strArr = {"cat", "Dog", "lion", "tiger"};

		Arrays.sort(strArr); // String의 Comparable구현에 의한 정렬
		System.out.println("strArr=" + Arrays.toString(strArr));

		Arrays.sort(strArr, String.CASE_INSENSITIVE_ORDER); // 대소문자 구분안함
		System.out.println("strArr=" + Arrays.toString(strArr));

		Arrays.sort(strArr, new Descending()); // 역순 정렬
		System.out.println("strArr=" + Arrays.toString(strArr));
	}
}

class Descending implements Comparator { 
	public int compare(Object o1, Object o2){
		if( o1 instanceof Comparable && o2 instanceof Comparable) {
			Comparable c1 = (Comparable)o1;
			Comparable c2 = (Comparable)o2;
			return c1.compareTo(c2) * -1 ; // -1을 곱해서 기본 정렬방식의 역으로 변경한다.
					// 또는 c2.compareTo(c1)와 같이 순서를 바꿔도 된다.

		}
		return -1;
	} 
}

HashSet

HashSet은 Set인터페이스를 구현한 가장 대표적인 컬렉션이다. HashMap을 이용하여 만들어졌으며, hashing을 이용하여 구현했다.

HashSet에서 add()나 addAll()을 사용할때 이미 저장되어 있는 요소와 중복된 요소를 추가하고자 한다면, 이 메서드들은 false를 반환함으로써 중복된 요소이기 때문에 추가에 실패했다는 것을 알린다.

또한 HashSet은 저장순서를 유지하지 않으므로, 저장순서를 유지하고자 한다면 LinkedHashSet을 이용해야 한다.

import java.util.*;

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;
	}
}
[abc, David:10, David:10]

평상시와 다르게, 위 코드에서는 같은 name과 age를 가진 Person 객체를 HashSet에 넣었지만, 서로 다른 것으로 인식한다. 이를 해결하기 위해서는 equals()와 hashCode()를 목적에 맞게 오버라이딩해야 한다.

import java.util.*;

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

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

		System.out.println(set);
	}
}

class Person2 {
	String name;
	int age;

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

	public boolean equals(Object obj) {
		if(obj instanceof Person2) {
			Person2 tmp = (Person2)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()를 오버라이딩해줘야 한다.

hashCode()에서는 JDK1.8부터 추가된 java.util.Objects클래스의 hash()를 사용할 수 있다.

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

TreeSet

TreeSet은 binary search tree형태로 데이터를 저장하는 컬렉션 클래스다. 그 중에도 Red-Black tree로 구현되어 있다. 그리고 중복된 데이터의 저장을 허용하지 않으며, 정렬된 위치에 저장하므로 저장순서를 유지하지도 않는다.

TreeSet에 객체를 저장할 때는 Comparable이 구현되어 있는 객체를 저장하거나 아님 직접 구현, 또는 TreeSet에게 Comparator를 제공해서 두 객체를 비교할 방법을 알려줘야 한다. 그렇지 않으면 데이터를 저장할 때 예외가 발생한다.

트리는 데이터를 순차적으로 저장하는 것이 아니라 저장위치를 찾아서 저장해야하고, 삭제하는 경우 트리의 일부를 재구성해야하므로 링크드 리스트보다 데이터의 추가/삭제 시간은 더 걸린다. 대신 배열이나 링크드 리스트에 비해 검색과 정렬 기능이 더 뛰어나다.

import java.util.*;

class TreeSetLotto {
	public static void main(String[] args) {
		Set set = new TreeSet();
		
		for (int i = 0; set.size() < 6 ; i++) {
			int num = (int)(Math.random()*45) + 1;
			set.add(num);  // set.add(new Integer(num));
		}

		System.out.println(set);
	}
}
[5, 12, 24, 26, 33, 45]

정렬되어 출력되는 모습을 볼 수 있다.

HashMap과 Hashtable

HashTableHashMap와 관계는 Vector와 ArrayList의 관계와 같아서 새로운 버전인 HashMap을 사용할 것을 권장한다.

HashMap은 Map인터페이스를 구현했으므로 key와 value를 묶어서 하나의 데이터로 저장한다는 특징을 가진다. 그리고 hashing을 사용하기 때문에 많은 양의 데이터를 검색하는데 있어서 뛰어난 성능을 보인다.

또한 HashMap은 key와 value를 멤버로 가지는 Entry라는 내부 클래스를 정의한다음, Entry 배열을 선언하여 사용한다. key와 value는 별개의 값이 아니라 서로 관련된 값이기 때문에 각각의 배열로 선언하기 보다는, 하나의 클래스로 정의해서 하나의 배열로 다루는 것이 데이터의 무결성 측면에서 더 바람직하기 때문이다.

key와 value는 각각 Object타입으로 저장한다. 즉 어떤 객체도 저장할 수 있지만 key는 주로 String을 대문자 또는 소문자로 통일하여 사용한다.

import java.util.*;

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와 password를 입력해주세요.");
			System.out.print("id :");
			String id = s.nextLine().trim();

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

			if(!map.containsKey(id)) {
				System.out.println("입력하신 id는 존재하지 않습니다. 다시 입력해주세요.");
				continue;
			} else {
				if(!(map.get(id)).equals(password)) {
					System.out.println("비밀번호가 일치하지 않습니다. 다시 입력해주세요.");
				} else {
					System.out.println("id와 비밀번호가 일치합니다.");						
					break;
				}
			}
		} // while
	} // main의 끝
}

해싱과 해시함수

hashing(해싱)이란 hash 함수를 이용해서 데이터를 hash table에 저장하고 검색하는 기법을 말한다. hash 함수는 데이터가 저장되어 있는 곳을 알려주기 때문에 다량의 데이터 중에서도 원하는 데이터를 빠르게 찾을 수 있다.

hashing에서 사용하는 자료구조는 위와 같이 배열과 링크드 리스트의 조합으로 되어있다.

저장할 데이터의 key를 hash 함수에 넣으면 결과로 나오는 hash code에 해당하는 배열의 index을 얻게 되고, 그 자리에 데이터를 저장하는데, 여기에 데이터를 저장하는 방법은 알고리즘마다 다른데, 자바에서는 분리연결법(separate chaining)이라는 알고리즘을 사용한다.

위 그림처럼, 중복된 hash code가 있다면, 데이터를 연결해서 저장한다.

그러나 데이터가 많아질 수록, 데이터 저장에 링크드 리스트는 효율적이지 않다. JDK1.8부터 데이터가 많아지면 데이터를 Red-Black-tree 구조로 저장한다.

실제로 hashing을 구현한 클래스에서는 Object클래스에 정의된 hashCode()를 hash 함수로 사용한다. Object클래스에 정의된 hashCode()는 객체의 주소를 이용하는 알고리즘으로 hash code를 만들어 내기 때문에 모든 객체에 대해 hashCode()를 호출한 결과가 서로 유일한 좋은 방법이다.

String클래스에서는 Object로부터 상속받은 hashCode()를 오버라이딩해서 문자열의 내용으로 hash code를 만들어낸다. 그래서 서로 다른 String인스턴스라도 같은 내용의 문자열을 가졌다면, hashCode()를 호출하면 같은 hash code를 얻는다.

TreeMap

TreeMap은 binary search tree형태로 key와 value의 쌍으로 이루어진 데이터를 저장한다. 검색에 관한 대부분의 경우에는 HashMap이 TreeMap보다 뛰어나므로 범위검색이나 정렬이 필요한 경우 TreeMap을 사용하는게 좋다.

Properties

Peroperties는 Hashtable을 상속받아 구현한 것으로 key와 value를 (String, String)의 형태로 저장하는 단순화된 클래스다. 주로 애플리케이션의 환경설정과 관련된 속성을 저장하는데 사용되며 데이터를 파일로부터 읽고 쓰는 편리한 기능을 제공한다.

Collections

Arrays가 배열과 관련된 메서드를 제공하는 것처럼 Collections는 컬렉션과 관련된 메서드를 제공한다. fill(), copy(), sort(), binarySearch() 등의 메서드는 두 클래스에 모두 포함되어 있으며 같은 기능을 한다.

멀티쓰레딩 프로그램에서는 객체의 동기화가 필요하다. Vector와 Hashtable과 같은 구버젼의 클래스들은 자체적으로 동기화 처리가 되어 있는데, 멀티쓰레드 프로그래밍이 아닌 경우에는 불필요한 기능이 되어 성능을 떨어트리는 요인이 된다. 그래서 새로 추가된 ArrayList와 HashMap과 같은 컬렉션은 필요한 경우에만 java.util.Collections클래스의 동기화 메서드를 이용해서 동기화 처리가 가능하도록 변경되었다.

static Collection synchronizedCollection(Collection c)
static List synchronizedList(List list)
static Set synchronizedSet(Set s)
static Map synchronizedMap(Map s)
...

다음과 같이 사용한다.

List list = Collections.synchronizedList(new ArrayList());

컬렉션에 저장된 데이터를 보호하기 위해서 컬렉션을 변경할 수 없게 읽기전용으로 만들어야할 때가 있다. 주로 멀티 쓰레드 프로그래밍에서 여러 쓰레드가 하나의 컬렉션을 공유하다보면 데이터가 손상될 수 있는데, 이때 사용한다.

static Collection unmodifiableCollection(Collection c)
static List unmodifiableList(List list)
static Set unmodifiabledSet(Set s)
static Map unmodifiableMap(Map s)
...

단 하나만의 객체를 저장하는 컬렉션은 다음과 같이 만든다.

static List singletonList(Object o)
static Set singleton(Object o)
static Map singletonMap(Object key, Object value)

매개변수에 저장할 요소를 지정하면 해당 요소를 저장하는 컬렉션을 반환한다. 그리고 반환된 컬렉션은 변경할 수 없다.

컬렉션 클래스 정리

지금까지 배운 클래스들을 정리하면 다음 그림과 같다.

profile
server를 공부하고 있습니다.

0개의 댓글