컬렉션

Eunjeon_g·2023년 3월 11일
0

Collection

: 객체들을 효율적으로 추가, 삭제, 검색할 수 있도록 관련된 인터페이스와 클래스들이 java.util 패키지에 포함되어 있다. 이들을 컬렉션 프레임워크라고 한다.

  • List
  • Set
  • Map

1. List

: 순서를 유지하고, 중복 저장이 가능하다. (index로 관리)

.add(값) : 값 저장하기

- ArrayList

: 기본으로 10개 생성한하며, 객체를 제한없이 추가할 수 있다.
인덱스에는 객체의 주소가 저장되는데 (null 가능), 중복 저장할 경우 같은 주소값을 가리키게 된다.

특정 인덱스의 객체를 제거하면 1개씩 앞으로 당겨지고, 삽입하면 1개씩 뒤로 밀려난다. 그러므로 객체의 삽입과 삭제가 빈번히 발생하는 곳에는 사용하지 않는 것이 좋다. (LinkedList 사용하는 것이 좋다.)

class ArrayListHouse{
	String name;
	int age;
    
	public ArrayListHouse(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	
}

public class ArrayListEx {
	
	public static void main(String[] args) {
		List<ArrayListHouse> list = new ArrayList<>();

		ArrayListHouse house1 = new ArrayListHouse("kim", 10);
		ArrayListHouse house2 = new ArrayListHouse("kim", 10);

		list.add(house1);
		list.add(house2);
	}
}

- Vector

: ArrayList와 동일한 구조를 가지고 있으며, 차이점은 동기화된 메소드로 구성되어 있어 멀티 스레드가 Vector()메소드를 동시에 실행할 수 없다는 것이다. (스레드 환경에서 안전하게 객체를 추가, 삽입할 수 있다.)

List<E> list = new Vector<E>();

- LinkedList

: ArrayList와 사용 방법은 동일하지만 구조가 다르다. 내부 배열에 객체를 저장하는 ArrayList와 달리 LinkedList는 인접 객체를 연결한다.
특정 위치에 객체를 삽입하거나 삭제하면 앞뒤 링크만 변경하면 되므로 객체의 삽입과 변경이 빈번하게 일어나는 곳에서 ArrayList보다 성능이 좋다.

List<E> list = new LinkedList<E>();

2. Set

: 순서를 유지하지 않고, 중복 저장 불가능하다. (그러므로 null은 하나만 저장 가능하다)

.add(값) : 값 저장하기

- HashSet

: 동일한 객체는 중복 저장하지 않는다. 이때 동일한 객체는 동등 객체를 말하며, 동등객체란 hashCode( )가 같고 equals( )가 true를 리턴하는 것을 말한다.

다음과 같이 구현되었을 때 총 객체의 수는 몇개일까?

public class HashSetEx {
	public static void main(String[] args) {
		Set<Member> set = new HashSet<>();
		
		set.add(new Member("이태민1", 20));
		set.add(new Member("이태민2", 40));
		set.add(new Member("이태민3", 30));
		set.add(new Member("이태민2", 40));	//값 중복
		
		System.out.println("총 객체 수 : " + set.size());

		
		for(Member data:set) {
			System.out.println(data);
		}	
	}
}

두번째, 세번째 객체 내용이 같으므로, 총 객체의 수는 3이 되어야 한다.
그러나 결과는 다음과 같다.

총 객체 수 : 4
Member [name=이태민1, age=20]
Code :1282811396
Member [name=이태민2, age=40]
Code :641853239
Member [name=이태민3, age=30]
Code :1920467934
Member [name=이태민2, age=40]
Code :1883840933

값이 중복되어 저장되는 이유는 hashCode의 값이 다르기 때문이다. (객체는 new 할 때마다 hashCode가 다르게 생성된다.)

new Member("이태민2", 40)

위의 값이 중복 저장되지 않게하기 위해서는 같은 hashCode를 가지고, equals가 true를 return하도록 재정의해야 한다.

class Member{
	String name;
	int age;
	public Member2(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	@Override
	public int hashCode() {
		return Objects.hash(age, name);
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Member2 other = (Member2) obj;
		return age == other.age && Objects.equals(name, other.name);
	}
}

Set 컬렉션은 인덱스가 없기 때문에 일반적인 반복문은 사용할 수 없다.

//기본 반복문 - 사용불가 : Set은 순서가 없음
for(int i=0; i<set.size; i++) {
			System.out.println(set[i]);
		}	

대신 위에서 작성했던 것과 같이 향상된 반복문을 사용하거나 Set 컬렉션의 iterator를 사용하면 된다.

//향상된 반복문
for(Member data:set) {
			System.out.println(data);
		}	
//iterator
Iterator iterator = set.iterator();
	while(iterator.hasNext()) {
		System.out.println(iterator.next());
	}
}
.iterator()는 메소드로 반복자를 얻어 객체를 하나씩 가져온다.
.hasNext() : 다음에 가져올 객체가 있는지에 따라 boolean을 리턴한다.
.next() : 컬렉션에서 하나의 객체를 가져온다.

- TreeSet

: 검색 기능을 강화시킨 Set으로 검색 관련 메소드가 정의되어 있고, 이진 트리를 기반으로 한다. 부모 노드의 객체와 비교해 낮은것은 왼쪽 자식 노드, 높은 것은 오른쪽 자식 노드에 저장한다. (자동 ascending 정렬)

public class TreeSetEx {
	public static void main(String[] args) {
		TreeSet<Integer> tree = new TreeSet<>();
		
		tree.add(400);
		tree.add(300);
		tree.add(100);
		tree.add(600);
		tree.add(500);
		tree.add(200);
		tree.add(200);	//중복저장 안됨
		
		for(Integer i: tree) {
			System.out.println(i);
		}
		/* 결과 : 자동 정렬됨
		 	100
			200
			300
			400
			500
			600
		 */
    }
}

Set에 저장해도 되지만, TreeSet에 저장하는 이유는 검색 메소드는 TreeSet이 가지고 있기 때문이다.

다음은 검색 메소드를 사용한 코드다.

System.out.println(tree.first());
System.out.println(tree.last());
System.out.println(tree.lower(300));	//주어진 객체 바로 이전 값 리턴
System.out.println(tree.higher(300));	//주어진 객체 바로 다음 값 리턴
System.out.println(tree.floor(300));	//주어진 객체 || 바로 이전 값 리턴
System.out.println(tree.ceiling(300));	//주어진 객체 || 바로 다음 값 리턴
결과
100
600
200
400
300
300 
 //NavigableSet<Integer> rangeSet = tree.tailSet(300, true);	//300 이상
 //NavigableSet<Integer> rangeSet = tree.tailSet(300, false);	//300 초과
 //NavigableSet<Integer> rangeSet = tree.headSet(300, true);	//300 이하
 NavigableSet<Integer> rangeSet = tree.headSet(300, false);	//300 미만
 for(Integer i: rangeSet) {
 System.out.println(i);
 }
결과
100
200
rangeSet = tree.subSet(300, true, 500, false);	//300 이상, 500 미만
	for(Integer i: rangeSet) {
		System.out.println(i);
	}		
}

기본으로 ascending(오름차순) 정렬이 된다. 만약 내림차순으로 정렬하고 싶다면 descendingSet()을 사용하면 된다.

 NavigableSet<Integer> descending = tree.descendingSet();
	 for(Integer i: descending) {
		 System.out.println(i);
	 }

3. Map

: key-value로 구성된 엔트리로 저장되며, key는 중복 저장 불가능하다. 기존에 저장되어 있는 key와 동일한 key로 value를 저장하면 새로운 value가 저장된다. (덮어쓴다.)

.put(값) : 값 저장하기

- HashMap

: key는 hashCode의 리턴값이 같고, equals가 true를 리턴할경우 동일한 키로 본다. (Set 과 같음)

public class MapPractice {
	public static void main(String[] args) {
		//Map 컬렉션 생성
		Map<String, Integer> map = new HashMap<>();
		
        map.put("김기범", 33);
		map.put("김이범", 50);
		map.put("김이범", 60);	//Key 중복
		map.put("김비범", 40);

		System.out.println("총 엔트리 수 :"+ map.size()); //결과 : 3
		
		int key = map.get("김이범");
		System.out.println(key); //결과 : 60 //Key값이 중복되어 value를 덮어씀
		
		//키 Set 컬렉션을 얻음
		Set<String> keySet = map.keySet();
		Iterator<String> keyIt = keySet.iterator();
		while(keyIt.hasNext()) {
			String k = keyIt.next();
			Integer v = map.get(k);
			System.out.println(k +":"+v);
            //결과
			//김비범:40
			//김기범:33
			//김이범:60
		}

	}
}
.keySet()은 key값을 모두 가져온다.
.get(key)는 key의 value를 가지고 온다.
.remove(key)는 (key에 해당하는) 엔트리를 삭제한다.

위의 코드에 다음을 추가해 보면 다음과 같다.

		map.remove("김비범");
		keyIt = keySet.iterator();
		while(keyIt.hasNext()) {
			String k = keyIt.next();
			Integer v = map.get(k);
			System.out.println(k +":"+v);
            //결과
            //김기범:33
			//김이범:60
		}

iterator를 통해 키의 값들을 한 번 더 가져오는 이유는 이미 사용을 완료했기 때문이다.

- Hashtable

: HashMap과 동일한 내부 구조를 가지고 있다. 동기화된 메소드로 구성되어 있기 때문에 멀티 스레드가 동시에 Hashtable에 접근할 수 없다. 즉, 스레드 환경에서도 안전하게 객체를 삽입/삭제 할 수 있다.

- TreeMap

: TreeSet과의 차이점이 있다면 엔트리로 값을 저장한다는 것이다. key를 기준으로 자동 정렬이 된다.

public class TreeMapEx {
	public static void main(String[] args) {
		TreeMap<String, Integer> tree = new TreeMap<>();
		
		tree.put("이진기", 30);
		tree.put("가진기", 40);
		tree.put("남진기", 50);
		tree.put("한진기", 30);
		tree.put("이진기", 20);	//key 중복, value 덮어씀
		
		//entrySet() 엔트리 가져오기
		Set<Entry<String, Integer>> entrySet = tree.entrySet();
		for(Entry<String, Integer> n:entrySet) {
			System.out.println(n.getKey() + "-" + n.getValue());
		}
	}
}
결과
가진기-40
남진기-50
이진기-20
한진기-30

TreeSet과 마찬가지로 Map에 저장해도 되지만 검색기능을 TreeMap이 가지고 있어 TreeMap에 저장한다.

//특정 키에 대한 값 가져오기 
 System.out.println(tree.firstEntry());	//첫번째 엔트리 가져오기
 System.out.println(tree.lastEntry());	//마지막 엔트리 가져오기

 Entry<String, Integer> entry = tree.firstEntry();	
 System.out.println(entry.getKey() + "-" + entry.getValue());
결과
가진기=40
한진기=30
가진기-40

TreeSet과 동일하게 자동 ascending 정렬이 되고, descending 정렬을 원하면 다음과 같이 하면 된다.

 NavigableMap desc = tree.descendingMap();	//내림 차순 정렬
 Set<Entry<String, Integer>> descSet = desc.entrySet();
 for(Entry<String, Integer> n:descSet) {
 	System.out.println(n.getKey() + "-" + n.getValue());
 }
결과
한진기-30
이진기-20
남진기-50
가진기-40

특정 범위에 대한 값을 찾고 싶을 경우 subMap을 사용하면 된다.

//key 값을 '나'부터 '하'전까지 추출하기
 NavigableMap<String, Integer> rangeMap = tree.subMap("나", true, "하", false);
 for(Entry<String, Integer> n: rangeMap.entrySet()) {
 	System.out.println(n.getKey() + "-" + n.getValue());
 }

- Properties

: Hashtable의 자식 클래스로 key, value의 타입을 String으로 제한한 컬렉션이다. 주로 확장자가 .properties인 파일을 읽을 때 사용한다.

🙇🏻‍♀️

이것이 자바다 - 한빛미디어

0개의 댓글