본문 바로가기
Develop

[Concept] JAVA (제네릭스, 컬렉션 프레임워크, Set, Map, Stack, Queue)

by 빈급 2022. 7. 4.

1. 제네릭스

 매개변수화된 자료형으로 클래스, 인터페이스, 메소드에도 적용할 수 있다. 재네릭스를 이용하면 매개변수에 다양한 자료형의 데이터를 넘길 수 있으므로 코드 재사용을 높여준다.

void set(T x)     // 어떤 자료형이든 T에 넣을 수 있다.
{   System.out.println(x);  }

1.1 제네릭 클래스

(내부)
클래스명<매개변수 타입 리스트) { }
(외부)
클래스명<타입> 객체명 = new 클래스명<타입> ();  // 두번재 클래스명은 생성자

이때 타입은 기본형이 아니라 클래스 타입(Integer, String, Double 등)을 사용하거나 레퍼런스 형을 사용해야 한다. 
 *관습적으로 제내릭스 타입은 대문자 1개로 선언함.

재네릭스 클래스 사용 예시

 재네릭스 타입이 변환되는 과정은 다음과 같다. 19번째 줄을 통해 Data의 생성자가 호출되고 매개변수를 받으면서 생성자의 T가 Integer로 바뀐다. 그 후 매개변수로 받은 값이 필드에 저장되면서 필드의 T가 Integer로 바뀐다. 마지막으로 Data 클래스에 존재하는 모든 멤버에 대한 사용권한을 받으면서 나머지 T가 Integer로 바뀐다. String으로 바뀌는 과정도 같다.
 
1.2 제한된 제네릭스 타입

<T extends V>

 위와 같은 경우 제네릭 T 자리에는 클래스 타입이 V이거나 V 클래스의 하위 클래스 타입만 올 수 있다는 뜻이다.

제한된 제네릭스 타입 예시

  위 예시를 보면 T는 extends Number의 형태를 가지고 있다. 때문에 T의 타입은 Number이거나 Number의 하위 클래스(Integer, Double 등)이 올 수 있다. String의 경우 Number의 하위 클래스가 아니기 때문에 사용할 수 없다.
 
1.3 와일드카드 인수

<?>

 와일드 카드 자리에는 어떤 클래스 타입도 올 수 있다는 의미이다.

와일드카드 사용 예시

 23번째 if절 구문에서 현재 a 주체를 이용했기 때문에 T가 Integer인 상황에서 WithWild<?> x에서 Double 타입의 b 주체를 받고있는 것을 알 수 있다. 29번째 if절 구문에서도 a 주체를 이용해 T가 Integer이지만 와일드카드 인수를 사용해 매개변수로 Long 타입의 c 주체를 받은 걸 볼 수 있다. 
 어떠한 인수든 받을 수 있다고 설명했지만 여기서 함정이 있다. WithWild의 제네릭스는 Number를 Extend 하고 있다. 때문에 어떠한 인수를 받을 수 있다고 했지만 Number이거나 Number의 하위 클래스만을 받을 수 있다. 아래와 같이 String 타입은 적용할 수 없다.

와일드 카드에 String을 받았을 때 오류

 

2. 컬렉션 프레임워크

 자료구조를 사용해서 객체들을 효율적으로 관리할 수 있도록 인터페이스와 구현 클래스를 java.util 패키지에서 제공한다. 

2.1 List 컬렉션
객체를 인덱스로 관리하는 자료구조로 인덱스마다 객체 번지를 참조하고 있다. 추가, 삭제 검색을 위한 메소드들이 제공된다.

List 컬렉션
List 컬렉션 메소드 종류

2.1.1 ArrayList

 List<String> list = new ArrayList<String>();
List<String> list = new ArrayList<>();

 List와 ArrayList의 가장 큰 차이점은 객체를 제거하는 경우 바로 뒤 인덱스부터 마지막 인덱스까지 모두 앞으로 1씩 당겨진 다는 것이다. 
 

ListArray 사용 예시

 처음에는 (0)Java, (1)JDBC, (2)Database, (3)Servlet/JSP, (4)iBATIS 차례로 존재했지만 30번째 줄에서처럼 인덱스 2를 지우고 나면  (0)Java, (1)JDBC, (2)Servlet/JSP, (3)iBATIS가 되고 다시 한 번 인덱스 2를 지우면 (0)Java, (1)JDBC, (2)iBATIS가 된다. 그 후 iBATIS값을 가진 객체를 제거하면서 (0)Java, (1)JDBC가 된다.
 
 2.1.2 LinkedList
 ArrayList와 사용 방법은 같으나 내부 구조가 다르다. 인접 참조를 링크하여 체인처럼 객체를 관리하며 ArrayList보다 속도가 빠르다.

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

LinkedList 구조

 LinkedList는 다음 노드를 참조하는 값이 저장되어 서로 Link 되어있다. 때문에 중간 연결이 끊어져 버리면 그 뒤에 노드도 찾을 수 없다는 단점이 있다. 때문에 다음과 같은 과정을 거쳐야한다. A->B->C 노드가 순서대로 있다고 할 때, B 노드를 삭제하려면 삭제하기 전에 A노드가 C노드를 가리키는 작업을 우선적으로 해야한다.
 
2.2 Set 컬렉션
 저장 순서가 유지되지 않으며, 객체가 중복되어 저장되지 않는 자료구조이다.

Set 컬렉션 메소드 종류

 Set 컬렉션의 경우 순서가 없기 때문에 인덱스로 객체를 검색해서 가져오는 메소드가 없다.  대신, 전체 객체를 대상으로 한 번씩 반복해서 가져오는 iterator(반복자)를 제공한다. iterator는 아래와 같이 호출하며 제공하는 메소드는 다음과 같다.

Set<String> set = ...; // set 선언
Iterator<String> iterator = set.iterator(); // set이 String이기 때문에 String으로 호출

iterator 메소드 종류

 
2.2.1 HashSet
객체를 순서 없이 저장하며 동일한 객체를 중복 저장하지 않는다. 객체를 생성하고 객체 저장 전 객체의 hashCode() 메소드 호출하여 해시코드를 얻어내고 이미 저장된 객체의 해시코드와 비교한다. 그후 해시코드가 같다면 equals 메소드를 호출하여 리턴 값이 true라면 동등객체로 인식하여 저장 하지 않고, 리턴 값이 false라면 다른 객체로 인식하여 저장한다.

Set<E> set new HashSet<E>();
Set<E> set new HashSet<>();

HashSet 사용 예시

 add() 메소드를 이용해 객체를 추가한다. 이때 중복된 객체 중 어느 것이 것이 저장될 지는 OS에 따라 다르다. 이후 객체를 출력하기 위해 18번째 줄처럼 iterator를 얻는다. 이때 iterator는 set 자체를 가리키고 있다. 그 후 hasNext()가 set이 있는지 확인하고 있다면 while 구문이 실행된다. 다음 next() 메소드를 통해 iterator 안에 객체를 가져오게 된다.
 
2.3 Map 컬렉션
 키와 값으로 구성된 Map.Entry 객체를 저장하는 구조이다. 키는 중복 저장될 수 없으나 값은 중복 저장이 가능하다. 이때 기존 저장된 키와 동일한 키로 값을 저장하면 기존 값이 없어지로 새로운 값으로 대체된다.

Map 컬렉션 구조
Map 컬렉션 메소드

 Map 컬렉션은 키 타입이 String, 값 타입이 Integer로 생성할 수 있으며 방법은 다음과 같다.

Map<String, Integer> map = new ...<String, Integer>();

 저장된 전체 객체를 대상으로 하나씩 얻고 싶은 경우에는 두 가지 방법이 있다.
1) 

Map<K, V> map = ...;
Set<K> keySet = map.keySet();    // keySet() 메소드로 모든 키를 Set 컬렉션으로 얻음
Iterator<K> keyIterator = keySet.iterator();     //반복자 얻음
while(keyIterator.hasNext()) { 
   K key = keyIterator.next();    // 반복자를 통해 key를 얻음 (하나씩)
   V value = map.get(key);   //get(key)로 키가 가지고 있는 값을 얻음
}

2)

Set<Map.Entry<K, V>> entrySet = map.entrySet(); //entrySet() 메소드를 이용해 모든 Map.Entry를 Set으로 얻음
Iterator<Map.Entry<K, V>> entryIterator = entrySet.iterator();   //반복자를 얻음
while(entryIterator.hasNext()) {
    Map.Entry<K, V> entry = entryIterator.next();   // 반복자를 통해 Map.Entry를 하나씩 얻음
    K key = entry.getKey();   //얻은 Map.Entry에서 getKey 메소드를 이용해 키를 얻음
    V value = entry.getValue();   //얻은 Map.Entry에서 getValue 메소드를 이용해 값을 얻음
}

 
2.3.1 HashMap
 대표적인 Map 컬렉션으로 HashMap의 키로 사용할 객체는 hashCode()와 equals() 메소드를 재정의하여 동등 객체가 될 조건을 정해야한다.

HashMap 객체 중복 확인 메커니즘
HashMap 사용 예시

 9번째 줄까지의 과정을 통해 HashMap 컬렉션에 Map.Entry 객체가 하나 생긴다. 그 후 10번째 줄에서 Student객체와 95를 가진 객체가 생성된다. 그 후 객체가 저장되는 과정에서 hashCode() 메소드가 호출되어 해시코드 값을 비교한다. 그 결과 같음을 확인하고 바로 equals() 메서드가 호출된다. 이미 저장되어 있는 객체를 매개값으로 받아 둘의 sno와 name이 같은지를 확인하고 같기 때문에 true를 리턴한다. 그 결과 객체는 저장되지 않고 값 변경만 이뤄진다.
 

3. Stack (stack도 컬렉션 프레임워크)

 후입선출(List In First Out) 자료구조로 나중에 넣은 객체가 먼저 빠져나가는 구조이다. 아래는 stack 객체를 생성하기 위한 선언 방법과 사용 메소드 종류이다.

Stack<E> stack = new Stack<E>();
Stack<E> stack = new Stack<>();

stack 메소드 종류
stack 사용 예시

 7번째 줄과 같이 Coin 객체 타입을 저장하는 Stack을 선언한다. 그 후 9~12번째 줄처럼 Coin 객체의 생성자를 호출해 객체를 생성하고 push() 메소드를 이용해 Stack에 객체를 채워넣는다. 마지막으로 isEnpty() 메소드를 이용해 Stack이 비어있는를 확인한다. 이 경우 객체가 채워져 있기 때문에 false를 반환한다. 그리고 !를 만나 true가 되어 while문 안으로 들어가게 된다. 그 후 pop() 메소드를 이용해 객체를 하나씩 꺼내온다. (stack의 구조 사진은 이해를 돕기 위한 것일뿐 사실과 다름)
이때 객체가 꺼내지는 순서는 10 -> 500 -> 50 -> 100 으로 가장 나중에 들어온 객체가 가장 먼저 빠져나가는 것을 알 수 있다. 
 

4. Queue (queue도 컬렉션 프레임워크)

 선입선출(First In First Out) 자료구조로 먼저 넣은 객체가 먼저 빠져나가는 구조이다. 아래는 queue 객체를 생성하기 위한 선언 반법 및 사용 메소드 종류이다.

Queue<E> queue = new LinkedList<E>();
Queue<E> queue = new LinkedList<E>();

 *큐는 LinkedList로 구성해야 한다.

queue 사용 예시

  7번째 줄과 같이 LinkedList 생성자를 호출하여 Message 객체를 가지는 Queue를 선언한다. 그 후 9~11번째 줄과 같이 Message 객체를 생성후 offer() 메소드를 사용해 큐에 저장한다. 그 후 isEmpty() 메소드를 사용하여 queue에 객체가 있는지 확인을 진행한다. isEmpty()는 객체가 비어있으면 true를 반환하고 들어있으면 false를 반환한다. 위 예시의 경우 isEmpty의 결과로 false가 반환되지만 !를 만나 true가 되어 while 구문으로 들어간다. 14번째 줄에서 poll() 메소드를 이용해 queue에서 객체를 꺼내오고, 15번째 줄과 같이 객체의 command 값을 읽어와 해당하는 부분으로 분기한다. 해당 예시의 결과 객체가 반환되는 순서는 홍길동에게 메일을 보냅니다. -> 신동현에게 SMS을 보냅니다. -> 홍두께에게 카카오톡을 보냅니다. 순서로 가장 먼저 들어간 객체가 가장 먼저 나가는 것을 알 수 있다.