
교재 Must Have 이재환의 자바 프로그래밍 입문을 학습하며 정리한 내용입니다.
제너릭을 적용하기 이전의 코드는 객체를 돌려받을 때 형변환을 잊지 말고 해야한다는 불편함이 있고, 코드 진행상 개발자가 실수를 해도 실수가 드러나지 않을 수도 있다는 잠재적 위험이 존재한다.
제너릭은 클래스, 메서드에서 사용할 자료형을 나중에 확정하는 기법이다. 클래스나 메서드를 선언할 때가 아닌 사용할 때, 즉 객체를 생성할 때나 메서드를 호출할 때 정한다는 의미다.
객체 생성시 결정이 되는 자료형의 정보를 T로 대체한다. 그리고 다이아몬드 연산자를 통해 자료형을 전달한다.
제너릭을 사용하지 않는 코드
class Camp {
private Object unit;
public void set(Object unit) {
this.unit = unit;
}
public Object get() {
return unit;
}
}
제너릭을 사용하는 코드
class Camp<T>{
private Object unit;
public void set(T unit) {
this.unit = unit;
}
public T get() {
return unit;
}
}
Camp<NPC>(매개변수화타입) human = new Camp<NPC>(타입인수)();
Camp<NPC> human = new Camp<>(); // 뒤쪽은 추론가능하므로 생략
class Camp<T1, T2> {
private T1 param1;
private T2 param2;
}
.
.
public static void main(String[] args) {
Camp<String, Integer> camp = new Camp<>();
.
.
}
상속관계를 표시하여 매개변수의 타입을 제한 할 수도 있다.
class Camp<T> {
private T ob;
.
.
public int toIntValue() {
return ob.intValue(); // ERROR
}
} class Camp<T extends Number> {
private T ob;
.
.
public int toIntValue() {
return ob.intValue(); // OK
}
} 매개변수 타입을 제한하지 않은 경우, 아무 자료형이나 들어올 수 있기에 래퍼 클래스의 메서드를 호출하면 에러가 발생한다. 그러나 매개변수 타입을 제한하는 경우 한정을 했기 때문에 intValue() 메서드를 사용할 때 에러 걱정을 할 필요가 없다.
클래스 전부가 아닌 메서드 하나에 대해서도 제너릭으로 정의할 수 있다.
public static <T> T showData(T data) // 앞의 T의 자료형이 뒤의 매개변수의 자료형
을 결정한다.
// 제너릭 메서드의 T는 메서드 호출 시점에 결정된다.
클래스명.<String>showData("");
// 타입 인수는 생략이 가능하다. 생략된 인수는 매개변수로 들어온 데이터의 자료형으로
추론한다.
클래스명.showData("");
대량의 데이터를 효율적으로 관리하는 메커니즘을 자료구조라고 한다.
자료구조로는 배열, 리스트, 스택, 큐, 트리 등이 있다.
배열은 크기가 고정되어 있어 데이터를 추가하거나 삭제할 수 없다.

리스트는 원소가 원소를 가리켜서 관리하는 자료구조이다. 데이터가 추가되거나 삭제될 때 연결하는 정보만 바꾸면 쉽게 추가, 삭제가 된다.

스택은 한 쪽 끝에서만 자료를 넣거나 뺄 수 있는 선형구조(LIFO, Last In First Out)로 되어있다. 자료를 넣는 것을 '밀어 넣는다'라 하여 푸시(push)라고 하고 반대로 넣어둔 자료를 꺼내는 것을 팝(pop)이라고 한다.

큐(queue)먼저 집어넣는 데이터가 먼저 나오는 FIFO(First In First Out) 구조로 저장하는 자료구조르 ㄹ말한다.

부모 노드 밑에 여러 자식 노드가 연결되고, 자식 노드 각각에 다시 자식 노드가 연결되는 형태의 자료구조를 트리라고 부른다.
자식노드에서 부모 쪽으로 계속해서 타고 올라가다 보면 결국 부모가 없는 하나의 노드로 이어지게 되는데, 이 노드를 루트 노드라고 부른다. 루트 노드를 중심으로 뻗어나가는 모습이 나무의 구조와 비슷하여 트리라는 이름이 붙었다.

자바에서는 자료구조를 개발자가 편리하게 사용할 수 있도록 컬렉션 프레임워크를 제공한다.

컬렉션 프레임워크에 속하는 인터페이스
| 인터페이스 | 설명 | 구현 클래스 |
|---|---|---|
| List<E,> | 순서가 있는 데이터 집합이다. 추가된 데이터 순서도 유지되며, 데이터 중복도 허용된다. | ArrayList, LinkedList, Vector, Stack |
| Set<E,> | 중복된 데이터가 제거되는 등 추가된 데이터의 순서가 유지되지 않는 데이터 집합이다. 데이터 중복이 허용되지 않는다. | HashSet, TreeSet |
| Map<K,V> | 키(key)와 값(Value)으로 이루어진 데이터들의 집합이다. 키는 중복을 허용하지 않지만 값은 중복될 수 있다. 출석부에 1번 홍길동과 2번 홍길동(동명이인)이 있는 경우를 들 수 있다. | HashMap, TreeMap, Hashtable,Properties |
| Queue<E,> | 순서가 있는 데이터 집합이다. 추가되 ㄴ데이터의 순서도 유지되며, 데이터 중복도 허용된다. | LinkedList |
// 객체 생성
List<String> list = new ArrayList<>();
// 객체 저장 : 순서 있음, 중복 허용
list.add("");
.
.
//객체 참조
for(int i = 0; i<list.size(); i ++) >
System.out.print(list.get(i) + '\t');
// 객체 삭제
list.remove(0);
ArrayList는 배열은 아니지만 배열 기반이라 데이터의 추가, 삭제보다는 참조가 LinkedList보다 빠르다는 장점이 있다. 반면 LinkedList는 리스트 기반이라 데이터 참조 속도보다는 데이터의 추가, 삭제가 ArrayList보다 쉽다는 장점이 있다.
그래서 만드는 시점에 추가될 데이터 성격을 생각해서 두가지 중 하나를 선택하여 만들고 사용하 ㄹ때는 그냥 List로 사용하면 된다.
| 단점 | 장점 | |
|---|---|---|
| ArrayList<E,> | - 객체가 추가될 때 저장 공간을 늘리는 과정에서 시간이 비교적 많이 소요된다.. - 객체의 삭제 과정에서 많은 연산이 필요할 수 있다. 따라서 느릴 수 있다. | 저장된 객체의 참조가 LinkedList보다 빠르다. |
| LinkedList<E,> | 저장된 객체의 참조과정이 배열에 비해 복잡하다. | 저장 공간을 늘리는 과정이 간단하다. |

배열과 비교하여 리스트에서 데이터의 추가는 다음과 같이 연결 정보만 만들어준다.

Iterable 인터페이스를 구현했기 때문에 저장된 인스턴스의 순차적 접근에 향상된 기능의 for문이나 Iterator 반복자를 이용할 수 있다.
//향상된 기능의 for문으로 객체 참조
for(String s : list)
// 반복자 획득
Iterator<String> itr = list.iterator();
//반복자를 이용한 순차적 참조
String str;
while(itr.hasNext()) {
str = itr.next();
.
.
if(str.equals("dd"))
itr.remove();
리스트는 배열처럼 선언과 동시에 초기화가 불가능하다. 그러나 Arrays 클래스의 유틸 메서드를 사용해서 다음과 같이 사용할 수 있다.
List<String,> list = Arrays.asList("","","","");
인수로 전달된 객체들은 저장한 컬렉션 객체를 생성 및 반환하다. 이렇게 생성된 리스트 객체는 객체에 요소를 추가하거나 삭제할 수 없는 객체이다.
List<String,> list = Arrays.asList("","","","");
list = new ArrayList<>(list);
데이터의 성격에 따라 list를 선택하고 사용하고는 List로 정의하는데, 사용중에도 성격을 바꿀 수 있다.
list = new ArrayList<>(list);
list = new LinkedList<>(list);
컬렉션 프레임워크는 제너릭을 사용하여 자료형을 제한한다. 이때 제너릭 부분에 클래스 타입을 지정해주어야한다. 기본 자료형을 직접 적어줄수는 없다.
List<Integer,> list = new LinkedList<>(); // O
List<int,> list new LinkedList<>(); //X
하지만 앞서 배웠던 래퍼 클래스들은 오토 박싱과 오토 언박싱이 되기 때문에 자료형만 래퍼 클래스로 적어줄 뿐 기본 자료형을 사용하는데 제약사항은 없다.
LinkedList<Integer,> list = new LinkedList<>()'
//저장과정에서 오토 박싱
list.add(10);
for(Iterator<Integer,> itr = list.iterator(); itr.hasNext(); ) {
int n = itr.next(); // 오토 언박싱
}
set<String> set = new HashSet<>();
set.add("dd");
// 반복자를 이용한 전체 출력
for(Iterator<String> itr = set.iterator(); itr.hasNext(); )
//향상된 기능의 for문을 이용한 전체 출력
for(String s : set)
HashSet으로 지정하여 객체를 만들고 사용은 set으로 한다. add() 메서드를로 추가한다. Iterator을 이용하여 반복자를 구해올 수 있다. hasNext() 메서드로 다음 반복이 가능한지 알아온다. next() 메서드로 Set 컬렉션 프레임워크안의 요소를 가져올 수 있다. Set 컬렉션 프레임워크는 Iterable을 구현했기 때문에 향상된 기능의 for문을 사용할 수 있다.
Set<E,> 인터페이스를 구현하는 컬렉션 클래스들은 데이터 중복 저장을 허용하지 않는다고 했다. 중복 저장을 방지하려면 데이터가 입력되기 전 이미 있는 데이터인지 검색해보아야한다. 이때 사용하는 알고르지므이 해시이다.
해시는 정보를 저장하거나 검색할 때 사용하는 알고리즘이다.
분류 대상을 정하고, 해시 알고리즘을 적용하면서 분류를 해놓아 탐색속도가 빨라진다.
Object 클래스의 hashCode 메서드는 이렇듯 객체들을 부류하는 역할으르 한다. 그래서 HashSet에서 중복 저장을 막으려면 hashCode() 메서드에서 반환하는 해시 코드값에 해당하는 내부 목록을 찾는다. 그러면 비교해야할 탐색의 대상이 확 줄어든다.
hashCode() 메서드를 제공해준다.
public int hashCode() {
return java.util.Objects.hash(가변 인수);
}
컬렉션 프로그램워크에서 Tree로 시작하는 클래스는 데이터를 추가한 후 결과를 출력하면 결괏값이 정렬된다. 그러므로 TreeSet은 자료의 중복을 허용하지 ㅇ낳으면서 출력값을 정렬하는 클래스이다.
TreeSet<String,> tree = new TreeSet<>();
자바는 TreeSet의 정렬을 구현하기 위해 이진탐색트리를 사용한다.
트리 자료구조에서 각 자료가 들어가는 공간을 노드라고 한다. 그리고 위아래로 연결된 노드의 관계를 부모-자식 노드라고 한다. 이진 탐색 트리는 노드에 저장되는 자료의 중복을 허용하지 않고, 부모가 가지는 자식 노드의 수가 2개 이하이다.

따라서 어떤 특정 값을 찾으려 할 때 한 노드와 비교해 배교한 노드보다 작은 값이면 왼쪽 자식 노드 방향으로, 그렇지 않으면 오른쪽 자식 노드 방향으로 이동한다. 따라서 비교 범위가 평균 2분의 1만큼씩 줄어들어 효과적으로 자료를 거색할 수 있다.
예시

이렇게 만들어진 이진 탐색 트리를 맨 왼쪽 노드부터 시작해서 왼쪽 -> 부모 -> 오른쪽(뭉치) 순으로 순회하면 오름차순이된다. 순회하다가 노드의 끝을 만나면 부모 노드로 올라간다.
어떤 기준으로 비교할지는 프로그래머가 직접 구현을 해야한다.
interface Comparable<T,>
-> int compareTo(T o)
Comparable<T,> 인터페이스의 구현 결과를 근거로 객체의 크기 비교가 이루어진다. 따라서 TreeSet<T,>에 저장할 객체들은 모두 Comparable<T,> 인터페이스를 반드시 구현할 클래스의 객체여야한다. 아니면 예외가 발생한다.
class Student implements Comparable<Student,> {
pulblic int compareTo(Student s) {
return this.age - s. age;
}
Comparator<T,> 역시 정렬을 구현하는데 사용하는 인터페이스로 compare() 메서드를 구현해야한다. 기존 클래스가 Comparable<T,> 인터페이스를 구현하여 이미 정렬 조건이 있다고 하더라도 새로운 정렬 조건을 주고 싶을 때 사용할 수 있다.
String 클래스는 Comparable<E,> 인터페이스를 구현하여 이미 사전순으로 정렬되도록 만들어져 있다. 이 String 클래스의 정렬 조건을 변경하려면
Set<String,> tree = new TreeSet<>(new MyStringComparator());
로 개발자가 만든 클래스 안의 compare() 메서드로 하겠다는 의미이다.
// set의 특성을 이용하여 중복 제거
HashSet<String,> set = new HashSet<>(list)
//다시 set을 list로 반환
list = new ArrayList<>(set);

LinkedList<E,>는 List<E,>와 동시에 Queue<E,>를 구현하는 컬렉션 클래스이다. 따라서 어떠한 타입의 참조 변수로 참조하느냐에 따라 '리스트'로도 '큐'로도 동작할 수 있다.
Queue<String> que = new LinkedList<>();
//데이터 저장
que.offer("A");
.
.
//무엇이 다음에 나올지 확인
System.out.println("next : " +que.peek());
//첫번째 객체 꺼내기
System.out.println(que.poll());
System.out.println(que.size());
.
.
자바에서 Deque를 기준으로 스택을 구현한다.
Deque<String,> deq = new ArrayDeque<>();
Deque<String,> deq = new LinkedList<>();
다만 Deque를 이용하면 메서드를 사용하는 방법에 따라 자료구조를 큐처럼 사용할 수도 있고, 스택처럼 사용할 수도 있다.

//둘다 사용 가능
Deque<String,> deq = new ArrayDeque<>();
//Deque<String,> deq = new LinkedList<>();
// 앞으로 넣고
deq.offerFirst("A");
//앞에서 꺼내기
System.out.println(deq.pollFirst());
//뒤에서 꺼내기
System.out.println(deq.pollLast());
//뒤로 넣기
deq.offerLast("A");
Map 인터페이스에는 Key-Value 방식의 데이터를 관리하는 데 필요한 메서드가 정의되어있다. 객체의 Key값은 유일하며 Value값은 중복될 수 있다.
HashMap<String, String> map = new HashMap<>();
// key-value 기반 데이터 저장
map.put("홍길동", "010-1234-1443");
//데이터 탐색
System.out.println("홍길동 :" + map.get("홍길동"));
//데이터 삭제
map.remove(""); HashMap<K, V> 클래스는 Iterable<T,>인터페이스를 구현하지 않았기에 향상된 기능의 for문을 통해서, 또는 '반복자'를 얻어서 순차적 접근을 할 수 없다.
대신 다음 keyset() 메서드 호출을 통해서 key를 따로 모아놓은 컬렉션 객체를 얻을 수 있다. 그리고 이때 반환된 컬렉션 객체 대상으로 반복자를 얻을 수 있다.
Map<String, Integer> map = new HashMap<>();
// key-value 기반 데이터 저장
map.put("홍길동",20);
//Key만 담고 있는 컬렉션 객체 생성
Set<String> ks = map.keySet();
// 전체 key 출력 (향상된 기능의 for문 기반)
for(String s : ks)
..
// 전체 value 출력(향상된 기능의 for문 기반)
for(String s : ks)
.
.
//전체 Value 출력(반복자 기반)
for(Iterator<String> itr = ks.iterator(); itr.hasNext();)
.
.
List<E,>를 구현한 컬렉션 클래스들은 저장된 개체를 정렬된 상태로 유지하지 않고 입력된 순서대로 유지하고 있다. 그래서 정렬을 해야 한다면 Collections.sort() 메서드를 사용할 수 있다. 이때 객체 크기를 비교해야 정렬할 수 있으므로 객체의 클래스는 Comparable<T,> 인터페이스를 구현한 상태이어야한다.
String 클래스는 Comparable 인터페이스를 구현한 상태이므로 바로 쓸 수 있다.
Collections.sort(list);
해당 객체의 Comparable 인터페이스 구현에 의한 정렬뿐 아니라 다른 정렬 방법을 제공할 수도 있다. 이때도 Comparator 인터페이스를 구현한 객체를 사용할 수 있다. 기본적으로 String 클래스는 사전순으로 정렬이 된다.
// 정렬 : 오름차순
Collections.sort(list);
StringDesc cmp = new StringDesc();
// 정렬 : 내림차순
Collections.sort(list,cmp);
이진 탐색 기능을 이용하여 리스트 안에 데이터가 있는지 확인할 수 있다. 다만 이진 탐색을 이용하려면 데이터가 먼저 정렬되어 있어야 한다.
// 정렬
Collections.sort(list);
//탐색
int idx1 = Collections.binarySearch(list, "");
System.out.println(idx1);
//수정 가능한 리스트 생성
List<String,> dst = new ArrayList<>(src);
// 정렬
Collections.sort(dst);
// src의 데이터를 dst로 복사
Collections.copy(dts,src)