[TIL] 코드스테이츠 SEB BE Day 15
💡 Today I Will Learn
- Generic
- Collection Framework
✏️ Summary
제네릭
제네릭은 클래스 내부에서 사용할 타입을 외부에서 파라미터로 지정하여 타입을 ‘일반화’한다.
public class 클래스명<타입 매개변수>{...}
// 인터페이스도 마찬가지로 선언 가능
타입 | 의미 |
---|---|
<T> | Type |
<E> | Element |
<K> | Key |
<V> | Value |
<N> | Number |
<R> | Result |
제네릭의 필요성
public class Test (extends Object){
private Object obj;
public void set(Object obj){
this.obj = obj;
}
public void get(){
return this.obj;
}
}
// 생성자 매개변수를 Object 타입으로 지정하여 obj 참조변수에 다양한 타입을 넣을 수 있다.
public static void main(String[] args){
Test t = new Test("String");
String a = (String) basket.get();
Integer a = (Integer) basket.get(); //error
}
Test 클래스에서 여러 타입을 이용하려 최상위 클래스인 Object로 선언하였지만, 다운캐스팅을 위해 캐스팅 연산자를 많이 사용하여 코드가 지저분해고, 오류가 생길 여지가 있다.
제네릭 클래스 정의
위와 같이 Object 타입으로 이용하는 대신! 규칙을 정한 하나의 문자로 표현한다.
Access Modifier class 클래스명<T>{
private T test;
public T get() {return test;}
}
Access Modifier class 클래스명<K, V>{
private K key;
private V value;
public 클래스명(K key, V value){
this.key = key;
this.value = value;
}
}
클래스에 제네릭을 정의했다면, 객체를 생성할 때 타입을 지정하여 넘겨주면 된다.
public class Main{
public static void main(String[] args){
클래스명<Integer> t1 = new 클래스명<>();
클래스명<String> t2 = new 클래스명<>();
}
}
타입을 지정해주었다면 컴파일 후에 해당 문자(타입 매개변수)에 지정한 타입으로 교체되어 사용된다.
class Test<T extends Class & Interface>{...}
클래스와 인터페이스를 동시에 상속받는 것을 구현해야 한다면 ‘&’ 기호를 사용한다.
타입 매개변수
<> 안에 변수명을 타입 매개변수라고 한다!
- 다이아몬드 연산자 <>
자바 7부터 제네릭 클래스의 생성자에는 타입을 명시하지 않아도 된다.
List<String> list = new ArrayList<>();
// '<>' 다이아몬드 연산자
- 와일드카드
?
기호로 제한을 두지 않는다는 의미이다.
<?> // 타입 매개변수에 모든 타입
<? extends T> // T타입을 포함한 하위 클래스 타입
<? super T> // T타입을 포함한 상위 클래스 타입
- 사용예제
public static void crying(AnimalList<? extends LandAnimal> al){
// LandAnimal 을 포함한 하위 모든 클래스 허용
LandAnimal la = al.get(0);
}
클래스 메서드의 매개변수 타입을 와일드 카드로 제한하는 예 이다.
제네릭 메서드
클래스 내부에 특정 메서드만 선언할 수 있다. 제네릭 클래스는 객체 생성 시점에 타입을 지정하지만, 제네릭 메서드는 호출되는 시점에 타입을 지정한다.
class Test{
public <T> T get(T t){
return t;
}
public <K, V> void print(K k, V v){
System.out.println(k+""+v);
}
}
public class Main{
public static void main(String[] args){
Test t = new Test();
t.<Integer>get(100);
t.get("String");
// 넘겨지는 인자의 타입이 유추 가능하면 생략 가능 (리터럴?)
t.<String, Integer>print("String", 100);
t.print("String", 100);
}
}
제네릭 메서드는 호출되는 시점에 타입이 결정되기 때문에, 특정 타입에서 사용하는 메서드는 사용할 수 없다.
length(), String 클래스의 메서드 등.
단! 최상위 클래스인 Object 클래스의 메서드는 가능하다.
Java 컬렉션 프레임워크 구조
https://techvidvan.com/tutorials/java-collection-framework
List
, Set
인터페이스는 추가, 삭제, 검색 등의 공통적인 동작이 있으므로 Collection 인터페이스로 묶어 정의한다.
Map
은 Key-Value 쌍으로 묶어 관리하므로, Collection과 독립된 Map 인터페이스로 분리되어있다.
인터페이스 | 특징 | 구현 클래스 |
---|---|---|
List | 순서 유지, 중복저장 가능 | ArrayList, Vector, Stack, LinkedList 등 |
Set | 순서 없음, 중복저장 불가 | HashSet, TreeSet 등 |
Map | 순서 없음, Key 중복저장 불가 | HashMap, Hashtable, TreeMap, Properties 등 |
자바에 Collections 클래스는 컬렉션에서의 동작, 반환하는 클래스(static) 메서드만으로 구성된 클래스이다.
Collections는 클래스! / Collection은 인터페이스!
List
객체의 삽입, 삭제 검색 등이 Array보다 유용한 구조이다.
- 주요 메서드
메서드 | 설명 |
---|---|
void add(int idx, Object e) | idx에 객체를 추가 |
boolean addAll(int idx, Collection c | idx에 컬렉션을 추가 |
Object set(int idx, Object e | idx에 객체를 저장 |
Object get(int idx) | idx의 객체를 반환 |
int indexOf(Object o) int lastIndexOf(Object o) | 순방향으로 탐색하여 idx 반환 역방향으로 탐색하여 idx 반환 |
ListIterator listIterator() ListIterator listIterator(int idx) | List 탐색 Iterator 반환 idx부터 탐색 Iterator 반환 |
List subList(int from, int to) | from ~ to 의 객체 반환 |
Object remove(int idx) | idx 객체를 삭제하고 반환 |
boolean remove(Object o) | 주어진 객체를 삭제 |
void sort(Comparator c) | Comparator로 List를 정렬 |
- ArrayList
List 인터페이스를 ‘구현한’ 클래스이며 Vectoer 클래스의 개선버전이다.
배열은 고정길이인 반면, ArrayList는 저장용량이 동적으로 늘어난다.
List<Type> 객체명 = new ArrayList<>(저장용량); //선언하지 않을 시 용량은 10개
- LinkedList
https://www.programiz.com/java-programming/linkedlist
LinkedList 컬렉션은 데이터의 추가,삭제,변경을 효율적으로 이용하기 위해 사용한다.
각 요소는 연결된 요소의 주소값과 해당 데이터로 구성되어있다.
즉, ArrayList처럼 데이터가 삭제되어 부분 전체를 복사하여 이동할 필요 없이 링크를 끊어 연결해주는 방식으로 동작하여 효율적이다.
하지만, 데이터 검색에서 ArrayList에 비해 첫 노드부터 순회해야하는 LinkedList는 불리하다.
- Iterator
컬렉션에 저장된 요소를 읽는 방법의 표준이다.
메서드 | 설명 |
---|---|
boolean hasNext() | 가져올 객체의 유무를 boolean 타입으로 반환 |
Object next() | 하나의 객체를 반환 |
void remove() | 컬렉션에서 객체를 제거 |
Set
중복을 허용하지 않으며, 순서도 정해져있지 않는 컬렉션이다.
- HashSet
Set 인터페이스를 구현한 구현체이다.
Set<String> s = new HashSet<>(String);
s.add("a");
s.add("a");
// set에는 a요소 하나만 들어있다.
- TreeSet
Binary Search Tree의 형태로 저장된다.
- 하나의 부모 노드에 최대 두개의 자식 노드
- 부모노드를 기준으로 왼쪽은 작은 값을 가진 노드, 오른쪽은 큰 값을 가진 노드
String의 경우 사전순(default: 오름차순)으로 정렬된다.
TreeSet, TreeMap은 요소가 삽입될 때 자동으로 정렬된다.
- Comparator & Comparable
컬렉션 정렬을 위한 인터페이스로 Comparable은 비교 대상(매개변수)과 자기 자신을 비교하고, Comparator는 매개 변수인 두 객체를 비교한다.
class Comp implements Comparable<Comp>{
// Comparable을 사용한 예
int id;
public Comp(int id) {
this.id = id;
}
@Override
public int compareTo(Comp o) {
// 비교대상과 자기자신을 비교
if (id > o.id) {
return 1;
} else if (id == o.id) {
return 0;
} else {
return -1;
}
}
}
class Comparat {
public static void main(String[] args) {
List<Comp> list = new ArrayList<>();
list.add(new Comp(100));
list.add(new Comp(50));
list.add(new Comp(300));
for (Comp comp : list) {
System.out.println(comp.id);
}
Collections.sort(list, new Comparator<Comp>() {
@Override
public int compare(Comp o1, Comp o2) {
return o2.id-o1.id;
}
});
for (Comp comp : list) {
System.out.println(comp.id);
}
}
}
Comparable은 클래스 내에서 sort를 지정한다면, Comparator는 외부에서 sort 방법을 재정의하고 싶을 때 사용한다.
Map
Key-Value 쌍으로 구성된 Entry 객체를 저장한다.
Key는 중복될 수 없지만, Value는 중복이 가능하다. (Key가 다르다면 다른 Entry로 취급)
메서드 | 설명 |
---|---|
Object put(Object key, Object value) | key-value 쌍으로 저장 존재할 경우 대체하고 이전 값, 반대는 null |
boolean containsKey(Object key) | key 존재 유무 |
boolean containsValue(Object value) | value 존재 유무 |
Set entrySet() | Map.Entry 객체를 Set에 담아 반환 |
Object get(Object Key) | key에 해당하는 value 반환 |
boolean isEmpty() | 엔트리 존재 유무 |
Set keySet() | key를 Set에 담아 반환 |
int size() | 엔트리의 총 개수 |
Collection values() | value를 Collection에 담아 반환 |
void clear() | 모든 엔트리 삭제 |
Object remove(Object key) | key에 해당하는 엔트리 삭제하고 그 값을 반환 |
- HashMap
Map 인터페이스를 구현한 구현체이다.
Hash 함수를 통해 Key와 Value의 저장위치를 결정하므로 순서와 위치가 관계가 없으며 사용자도 알 수 없다.
HashMap의 Key는 hashCode()
, equals()
메서드로 동일한 객체인지 정한다. 이 조건이 HashSet에서 Key를 구별하는 조건이 된다.
즉 Hashing을 사용하기에 많은 양의 데이터 검색에서 효율적이다.
String 타입은 hasCode()와 equals()가 재정의 되어있으므로 Key로 많이 사용된다.
Map<Key, Value> map = new HashMap<>();
📌 정리
개발하면서 수도 없이 쓸 컬렉션 프레임워크에 대해 학습하였다. 물론 지금도 중요하지만 다음주 알고리즘 진도를 나갈 때를 대비해 지금 확실히 개념을 잡아놓아야 한다.
알고리즘 문제풀 때 멘탈 안나가려면 머리속에 쏙쏙 집어넣자고 ❗❗❗
👿 Problem
static 제네릭 메서드는 가능하지만, static 제네릭 클래스는 불가능하다.
compare() 로 비교하여 return -1, 0, 1 로 자리바꿈하는 sort는 어떻게 동작하는가?
HashMap의 getOrDefault() 메서드
👼 Solution
제네릭은 정해진 타입을 컴파일 후에 적용한다. 하지만 static 클래스는 인스턴스화 되기전에 메모리에 올라가 타입 매개변수 자체가 결정되지 않기에 사용할 수 없다.
// file: "generic Method" static <T> T genericMethod(T id){ ... } // <> 다이아몬드 연산자가 있는 메서드
하지만 제네릭 메서드는 static이 가능하다. 이유는 “제네릭 메서드”의 경우 호출 시 타입 매개변수를 결정하기 때문이다.
sort는 내부적으로 Quick, Merge Sort를 사용하는 것으로 대략 확인하였다. 즉, 끝 단에서는 comparator, comparable에서 두 값을 비교하여 나온 값을 통한 자리바꿈(1일 경우)으로 정렬하는 것이다.
Key값이 존재하지 않을 경우, 참조하는 것 만으로 오류를 발생시킬 수 있다. 이를 해결하기 위해
getOrDefault(key, defaultValue)
를 통해 Key가 존재하지않을 경우 defaultValue 인자로 반환하는 메서드를 이용한다.// file: "HashMap.java" hashMap.put(key, hashMap.getOrDefault(key, defaultValue));
🎯 Tomorrow
- Inner Class
Back to [TIL] 코드스테이츠 SEB BE Day 14