✍🏻 배열 복습 ✍🏻
한 번 생성된 배열은 길이를 바꿀 수 없다.
같은 타입의 데이터만 저장 가능하다.
생성 방법 : ① { 1, 2, 3, ... }로 원소를 주어서 생성하거나 ② new int[3]처럼 크기를 주고 기본값으로 초기화하여 생성한다.
원소에 접근 방법 : 대괄호에 인덱스를 넣어 접근
크기 접근 방법 : 배열 이름에 .length 붙이기
Intro. 배열의 한계
배열에도 객체를 저장할 순 있지만, 생성 후에는 길이를 변경할 수 없고
항목을 저장, 삭제, 추가하는 메소드가 없으므로 직접 인덱스를 사용해야 한다는 단점이 있다.
(직접 인덱스를 사용하면, 인덱스 사이에 값을 추가할 수 없으며, IndexOutOfBoundsExcpetion 등의 에러가 발생할 수도 있다.)
따라서 객체를 저장하며, 객체를 관리하기 위해 다양한 메소드를 제공하는 자료구조가 필요하게 되었다.
13-1 컬렉션 프레임워크
자바는 객체들을 효율적으로 추가, 삭제, 검색할 수 있는 컬렉션 프레임워크를 java.util 패키지에서 제공한다.
컬렉션은 객체의 저장을 뜻하고, 프레임 워크란 사용 방법을 정해놓은 라이브러리를 말한다.
➡️ 컬렉션 프레임워크 : 객체를 잘 저장하고 관리하기 위해 자바에서 제공하는 라이브러리
cf. 컬렉션 프레임워크들이 java.util의 하위에 있기 때문에 import java.util.* ;를 해줘야 한다.
cf. 컬렉션 프레임워크에는 '객체'를 담아야 한다. 즉, 기본형은 담을 수 없다.
기본형을 담기 위해서는 Wrapper class (ex. Integer, Boolean) 등으로 박싱을 해줘야 한다.
컬렉션 프레임워크는 인터페이스와 클래스로 구성되어있다.
컬렉션 프레임워크의 주요 인터페이스로는 List, Set, Map, Queue가 있다.
Map 인터페이스는 구조상 특징이 달라 List, Queue, Set 과 달리 Collection를 상속받지 않는다.
▶ List 인터페이스
- 객체를 인덱스로 관리한다. (but, 배열처럼 [inx] 형식이 아니라, 메소드의 매개변수로 idx를 넘기는 방식)
- 저장 용량이 자동으로 증가한다. (=동적 배열)
- 객체를 저장할 때 인덱스가 자동으로 부여된다.
- 객체를 저장하는 것이 아니라, 객체의 주소를 저장(참조)한다.
List의 구현 클래스
※ 구현 클래스의 객체를 생성할 때, 대부분 List<E> list = new 구현클래스<E>( ); 로 생성한다.
이는 구현 객체를 List 타입으로 자동 형변환하여 List에서 공통적으로 제공하는 메소드를 이용해 다형성을 확보하기 위함이다.
이때 우변의 <E>에서 E를 생략하면 좌변의 <E>를 따라간다.
cf. 여기서 E는 List에 저장할 객체의 타입
List 컬렉션에서 제공하는 메소드는 아래와 같다.
주의할 것은, 배열과 달리 [idx]로 접근하는게 아니라, 메소드의 매개값으로 인덱스를 넣어 접근(get), 삭제(remove) 한다는 것이다.
또 배열은 .length로 길이를 가져왔으나, List 컬렉션은 .size( )로 길이를 가져온다.
List 컬렉션은 배열과 마찬가지로 항상된 for문을 사용할 수 있다.
cf. 크기를 가져오는 함수에 대해 배열만 .length이고 나머지 컬렉션 프레임워크는 .size( ) 라고 외워두자!
// Ex. ArrayList 객체 생성 방법
List<String> list = new ArrayList<String>( );
List<String> list = new ArrayList<>( );
▷ ArrayList
ArrayList 객체를 생성하면 기본적으로 내부에 10개의 객체를 저장할 수 있다.
저장되는 객체수가 늘어나면 용량이 자동으로 증가한다.
ArrayList에 객체를 추가하면 추가한 인덱스 뒤의 인덱스가 하나씩 뒤로 밀려나고
객체를 삭제하면, 삭제된 인덱스 뒤부터 인덱스가 하나씩 당겨진다.
이는 시간 복잡도를 생각하면 굉장히 비효율적이다. (0번 인덱스를 삭제할 경우, 모든 인덱스를 하나씩 줄여야 함)
따라서 객체를 추가하거나 삭제하는 일이 빈번하다면, ArrayList보다 LinkedList를 사용하는 것이 좋다.
▷ Vector
Vector는 ArrayList와 같은 내부 구조(동적 배열)를 가지고 있다.
하지만, 동기화된(synchronized) 메소드로 구성되어있어
멀티 스레드 환경에서 하나의 Vector에 두 스레드가 접근해 동시에 메소드를 실행할 수 없다.
이것을 스레드에 안전(thread safe)하다고 표현한다.
▷ LinkedList
LinkedList는 ArrayList와 완전히 다른 내부 구조를 가지고 있다.
ArrayList는 기본적으로 배열이되, 크기가 변하는 구조이고
LinkedList는 자료구조에서 배운 더블리 링크드 리스트를 생각하면 된다.
LinkedList는 앞뒤 인덱스의 주소를 가지고 있으므로 특정 객체를 삽입, 삭제할 때 앞뒤 링크만 변경되고 나머지 링크는 변경되지 않는다.
따라서 끝에서부터 삽입, 삭제 하는 경우 ArrayList가 빠르지만, 중간에서 삽입, 삭제 하는 경우 LinkedList가 훨씬 빠르다.
또한 인덱스로 검색하는 get의 경우 인덱스 정보를 저장하는 ArrayList는 빠르지만,
인덱스 정보 없이 링크를 따라 n번 이동해야 하는 LinkedList는 상대적으로 느리다.
수행할 연산에 따라 소요되는 시간(정확히는 시간복잡도)을 고려하여 알맞은 컬렉션을 선택하면 된다.
▶ Set 인터페이스
Set 인터페이스는 수학에서의 '집합' 개념을 생각하면 쉽다.
집합은 원소를 저장할 때, 순서대로 저장하지 않으며, 동일한 원소는 같은 것으로 취급한다.
마찬가지로 Set에 저장되는 객체는 순서가 없으며, 중복된 객체를 저장할 수도 없다. (따라서 null도 하나만 저장할 수 있음) '순서가 없다는 것'은 즉, 인덱스가 없다는 것이다. 따라서 인덱스로 객체를 검색해서 가져오는 메소드가 존재하지 않는다.
대신 전체 객체를 대상으로 한 번씩 가져오는 반복자 Iterator를 제공한다.
Set 객체에 .iterator( )메소드를 호출하면, Iterator 인터페이스를 구현한 객체가 리턴된다.
⭐ Iterator 선언 문법 : Iterator<set원소타입> iter = set.iterator( ); ⭐
Iterator 객체에 아래 메소드를 호출하여 전체 원소를 가져오게 할 수 있다.
또는 향상된 for문을 이용해도 전체 객체를 대상으로 반복할수 있다.
리턴 타입 | 메소드 | 설명 | |
boolean | hasNext( ) | 가져올 객체가 있으면 true를 리턴하고 없으면 false를 리턴한다. 특히 while 문의 조건절에 들어가 '전체 객체를 한번씩 반복'하게 한다. |
|
E | next( ) | 컬렉션에서 하나의 객체를 리턴한다. | |
void | remove(Object o) | Set에서는 인덱스로 객체를 지우지 못하므로, 직접 객체를 주어 삭제할 수 있다. 해당 객체가 Set에 있으면 객체를 지우고 true를, 없으면 false를 리턴한다. |
▷ HashSet
- Set 인터페이스의 대표적인 구현 객체로 객체를 순서 없이 저장하고 중복 저장하지 않는다.
- 중복 저장에서 동일한 객체임을 판단하는 기준 :
저장할 객체에 hashCode( ) 메소드를 호출하여 기존 객체들의 hashCode와 동일한지 확인하고
equlas( )메소드로 두 객체를 비교하여 true이면 저장하지 않는다.
※ 이 과정에서 hashCode( )와 equals( ) 메소드의 오버로딩이 필요하다! (Object의 메소드는 구리므로)
만약 자바에서 제공하는 클래스를 사용하는 경우, 이미 오버로딩이 되어있겠지만
직접 만드는 클래스를 HashSet에 저장하는 경우 hashCode( )와 equals( ) 메소드의 오버로딩은 필수적이다!!
▶ Map 인터페이스
Map 컬렉션은 키와 값으로 구성된 Map.Entry 객체를 저장하는 구조로 되어있다.
Entry는 Map 인터페이스 내부에 선언된 중첩 인터페이스이다.
Entry는 <키, 값>을 담을 수 있는 구조인데, 여기서 키와 값은 모두 객체이다.
Map 컬렉션에서 키는 중복될 수 없지만, 값은 중복될 수 있다.
만약 기존에 저장된 키와 동일한 값을 저장하려 한다면, 기존 키에 저장된 값이 없어지고 새로운 값으로 대체된다.
Map 인터페이스에는 HashMap, HashTable 등의 구현 클래스가 있다.
우리는 지금까지 <E> 안에 객체의 타입을 넣어 컬렉션 객체를 생성했지만,
Map 객체를 만들기 위해선 선언 부분에 <K,V> 처럼 키와 값의 타입을 동시에 주어야 한다.
또한, 앞의 컬렉션에서는 add(element)로 객체를 추가했지만, Map에서는 put(key, value)로 값을 추가한다.
cf. 아마 이런 차이들 때문에 Map만 Collection을 상속하지 않는 것 같다.
cf. 자동 박싱 복습))
Map<String, Integer> map = new HashMap<>(); 로 선언하고 map.put("영서", 22); 로 객체를 넣어줬다면,
22는 기본 타입에서 Integer 객체로 자동 박싱된다.
저장된 전체 객체를 하나씩 얻고 싶은 경우, 두가지 방법을 사용할 수 있다.
① Map 객체에 keySet( ) 메소드를 호출하여 존재하는 모든 key를 Set 객체로 얻은 다음,
Set에 Iterator( ) 메소드를 호출하여 Iterator 객체를 얻고 (Iterator 객체 타입은 키의 타입)
while문 안에서 반복자에 next( )를 적용하여 키를 하나씩 얻고
Map 객체에 get(key)를 호출하여 값을 얻는 방법
② Map 객체에 entrySet( ) 메소드로 모든 Entry를 Set 객체로 얻고,
Set 객체에 Iterator( ) 메소드를 호출하여 Iterator 객체를 얻은 다음 (Iterator 객체 타입은 Map.Entry<K,V>의 타입)
반복자에 next( )를 적용하여 Entry를 하나씩 얻고
엔트리 객체에 getKey( ), getValue( ) 호출하여 키와 값을 얻는 방법
※ iterator은 Map이 아니라 Set 컬렉션에 iterator( ) 메소드를 호출해야 나오는 것이므로 keySet( )이나 entrySet( )을 먼저 해야 한다.
※ entrySet( )가 리턴하는 것은 Map.Entry<K,V>이므로 Set<Map.Entry<K,V>> 로 map.entrySet( )을 받아야 한다.
▷ HashMap
- 대표적인 Map 구현 클래스
- key 중복을 허용하지 않는 Map의 성질을 그대로 받아 같은 key를 갖는 엔트리는 가장 최신 것만 저장한다.
같은 key인지 판단하는 기준 : hashCode( ) 같은지 → equals( )가 true인지
따라서 직접 만든 클래스를 HashMap에 저장할것이라면,
HashSet과 마찬가지로 hashCode( )와 equals( ) 메소드의 오버로딩을 필수적으로 해줘야 한다.
public class HashMapExample {
public static void main(String[] args) {
// Map 컬렉션 생성
Map<String, Integer> map = new HashMap<>();
// 객체 저장 - put(K,V)
map.put("신용권", 85);
map.put("홍길동", 90);
map.put("동장군", 80);
map.put("홍길동", 95); // 같은 키인 엔트리를 저장하면, 마지막 저장한 것으로 값이 대체됨
System.out.println("총 Entry 수 : "+map.size());
// 객체 찾기 - get(K)
System.out.println("\t홍길동:"+map.get("홍길동"));
// 객체를 하나씩 처리 - keySet()
Set<String> set1 = map.keySet();
Iterator<String> iter1 = set1.iterator();
while(iter1.hasNext()) {
String key = iter1.next();
Integer value = map.get(key);
System.out.println("\t"+key+":"+value);
}
// 객체 삭제 - remove(K)
map.remove("홍길동");
System.out.println("총 Entry 수 : "+map.size());
// 객체를 하나씩 처리 - entrySet()
Set<Map.Entry<String, Integer>> set2 = map.entrySet();
Iterator<Map.Entry<String, Integer>> iter2 = set2.iterator();
while(iter2.hasNext()) {
Map.Entry<String, Integer> entry = iter2.next();
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println("\t"+key+":"+value);
}
// 객체 전체 삭제 - clear(K)
map.clear();
System.out.println("총 Entry 수 : "+map.size());
}
}
▷ Hashtable
- HashMap 과 동일한 내부구조를 가졌다. (직접 만든 클래스를 저장하려면 hashCode와 equals를 오버라이딩해야 하는 것도 동일!)
- 하지만, Hashtable은 동기화된 메소드로 구성되어있으므로,
멀티스레드 환경에서 하나의 Hashtable에 두 스레드가 접근해 동시에 메소드를 실행할 수 없다.
따라서 Hashtable은 스레드에 안전(thread safe)한 컬렉션이다. (ArrayList 와 Vector관계와 같음)
'JAVA > 혼공자' 카테고리의 다른 글
[혼공자] Chapter 14-1 입출력 스트림 (2) | 2022.08.21 |
---|---|
[혼공자] Chapter 13-2 LIFO와 FIFO 컬렉션 (1) | 2022.08.20 |
[혼공자] Chapter 12-2 스레드 제어 (1) | 2022.08.17 |
[혼공자] Chapter 12-1 멀티 스레드 (1) | 2022.08.15 |
[혼공자] Chapter 11-2 java.util 패키지 (0) | 2022.08.15 |