본문 바로가기
JAVA

CH07. 제네릭, 열거형, 컬렉션

by MINNI_ 2021. 8. 4.

[ CH07. 제네릭, 열거형, 컬렉션 ]

 

1. 제네릭

1.1 제네릭 개념

  • 모든 종류의 타입으로 변신할 수 있도록 클래스나 메소드를 일반화시키는 기법
  • 컴파일시 타입 체크
  • 타입 매개 변수 : 요소 타입을 일반화한 타입 ex) <E>, <K>, <V>

1.2 제네릭 용어

  • Box<T> : 제네릭 클래스
  • T : 타입 변수, 타입 매개변수
  • Box : 원시 타입

1.3 제네릭 클래스 선언

  • 클래스나 인터페이스 선언부에 일반화된 타입 추가

  • 제네릭 클래스 레퍼런스 변수 선언

1.4 제네릭 클래스의 객체 생성과 사용

  • Box<T>의 객체 생성 → 참조변수와 생성자에 대입된 타입 일치
Box<Apple> appleBox = new Box<Apple>();	// OK
Box<Apple> appleBox = new Box<Grape>();	// ERROR: 대입된 타입이 다름
Box<Fruit> appleBox = new Box<Apple>();	// ERROR: 대입된 타입이 다름
  • 두 제네릭 클래스가 상속관계이고, 대입된 타입이 일치하는 것 OK
Box<Apple> appleBox = new FruitBox<Apple>();	// OK: 다형성
Box<Apple> appleBox = new Box<>();	// OK: 생략가능
  • 대입된 타입과 다른 타입의 객체 추가 X
Box<Apple> appleBox = new Box<Apple>();
appleBox.add(new Apple());	// OK
appleBox.add(new Grape());	// ERROR: Box<Apple>에는 Apple객체만 추가 가능
  • 구체화
    - 제네릭 타입의 클래스에 구체적인 타입 대입하여 객체 생성 by 컴파일러

MyClass<String> s = new MyClass<String>(); // 제네릭 타입 T에 String 지정
s.set("hello");
System.out.println(s.get()); // "hello" 출력

MyClass<Integer> n = new MyClass<Integer>(); // 제네릭 타입 T에 Integer 지정
n.set(5);
System.out.println(n.get()); // 숫자 5 출력
  • 구체화 오류
    - 타입 매개 변수에 기본 타입 사용 X
    Vector<int> vi = new Vector<int>();	// ERROR: 기본타입 int 사용 불가
    
    Vector<Integer> vi = new Vector<Integer>();	// OK​
  • 실제 타입 지정 ⇒ 형변환 생략 O

1.5 제네릭의 제한

  • static멤버에서 타입 매개변수 T 사용 X

  • 제네릭 클래스 또는 인터페이스의 배열 허용 X

  • 제네릭 타입의 배열 허용 X
    - 다른 타입으로 배열 생성 후 실제 사용 시 타입 캐스팅 ex) return (T)arr[n];

  • 타입 매개변수의 배열에 레퍼런스는 허용 O

1.6 제한된 제네릭 클래스

  • 제네릭 타입에  'extends' 사용하면, 특정 타입의 자손들만 대입하도록 제한 O

  • add()의 매개변수의 타입 T도 특정 타입과 특정 타입의 자식 타입이 됨

  • 인터페이스도 'implements'가 아닌 'extends'를 사용

1.7 와일드 카드 '?'

  • 제네릭 타입에 와일드 카드 사용 → 여러 타입 대입 O
  • 와일드 카드에는 <? extends T & E >와 같이 '&' 사용 X
< ? extends T >  와일드 카드의 상한 제한, T와 그 자식들만 가능
< ? super T > 와일드 카드의 하한 제한, T와 그 부모들만 가능
< ? > 제한 X, 모든 타입이 가능 와 동일
static Juice makeJuice(FruitBox<? extends Fruit> box) {
	String tmp = "";
    for(Fruit f : box.getList()) tmp += f + " ";
    return new Juice(tmp);
}

Juices.makeJuice(fruitBox));	// OK: FruitBOx<Fruit> fruitBox → 자기자신 Fruit 타입 가능
Juices.makeJuice(appleBox));	// OK: FruitBOx<Apple> appleBox → Fruit의 자손  Apple 가능

 

1.8 제네릭 메서드

  • 반환타입 앞에 제네릭 타입 선언
  • 클래스의 타입 매개변수 <T>와 메서드 타입 매개변수 <T>는 별개
class GenericMethodEx<T> {
	static <T> void toStack(T[] a, GStack<T> gs) {
		for (int i = 0; i < a.length; i++) {
			gs.push(a[i]);
		}
	}
}
  • 컴파일러가 메소드의 인자를 통해 이미 타입 알 때 → 타입 생략 O
Object[] oArray = new Object[100];
GStack<Object> objectStack = new GStack<Object>();

GenericMethodEx.<Object>toStack(oArray, objectStack); 
GenericMethodEx.toStack(oArray, objectStack);	// 타입 매개변수 T를 Object로 유추함, 대입된 타입 생략 O
					 	// 클래스 이름은 생략 X

1.9 제네릭 타입의 형변환

  • 제네릭 타입과 원시 타입간 형변환 O
    대입된 타입이 다른 제네릭 타입 간 형변환 X
// 1. 제네릭과 원시 타입간의 형변환
Box box = null;
Box<Object> objBox = null;
box = (Box)objBox;		// OK: 제네릭 타입 → 원시 타입 // 경고
objBox = (Box<Object>)box;	// OK: 원시 타입 → 제네릭 타입 // 경고

// 2. 대입된 타입이 다른 제네릭 타입 간 형변환
Box<Object> objBox = null;
Box<String> strBox = null;
objBox = (Box<Object>)strBox;	// ERROR: Box<String> → Box<Objet>
strBox = (Box<String>)objbox;	// ERROR: Box<Object> → Box<String>
  • 와일드 카드가 사용된 제네릭 타입으로는 형변환 O
// 와일드 카드가 사용된 제네릭 타입으로는 형변환 O
Box<? extends Object> wBox = new Box<String>();
FruitBox<? extends Fruit> box = null;
FruitBox<Apple> appleBox = (FruitBox<Apple>)box;	// OK: 미확인 타입으로 형변환 경고

// 와일드 카드가 사용된 제네릭타입끼리 형변환 O
FruitBox<? extends Object> objBox = null;
FruitBox<? extends String> strBox = null;
objBox = (FruitBox<? extends Object>)strBox;	// OK: 미확인 타입으로 형변환 경고
strBox = (FruitBox<? extends String>)objbox;	// OK: 미확인 타입으로 형변환 경고
  • <? extends Object>를 줄여서 <?> 로 쓸 수 있음
Optional<?> EMPTY = new Optional<?>();	// ERROR: 미확인 타입의 객체는 생성 X
Optional<?> EMPTY = new Optional<Object>();	// OK
Optional<?> EMPTY = new Optional<>();	// OK:  위문장과 동일

1.10 제네릭 타입의 제거

  • 컴파일러는 제네릭 타입을 제거하고, 필요한 곳에 형변환 함

2. 열거형

2.1 열거형 개념

  • 관련된 상수들을 같이 묶어 놓은 것
// 열거형 전
class Card {
    static final int CLOVER = 0;
    static final int HEART = 1;
    static final int DIAMOND = 2;
    static final int SPADE = 3;
    
    static final int TWO = 0;
    static final int THREE = 1;
    static final int FOUR = 2;
    
    static int kind;
    static int num;  
}

// 열거형 후
class Card {
    enum Kind { CLOVER, HEART, DIAMOND, SPADE }	// 열겨형 Kind를 정의
    enum Value { TWO, THREE, FOUR }	// 열겨형 Value를 정의
    
    final Kind kin;	// 타입: Kind
    final Value value;
}

2.2 열거형 정의 및 사용

  • 정의
enum 열거형이름 { 상수명1, 상수명2, ... }
  • 변수 선언 및 사용
    - 상수 사용 : '열거형이름.상수명'
enum Direction { EAST. SOUTH. WEST. NORTH };

class Unit{
	int x, y;	// 유닛의 위치
    Direction dir;	// 열거형을 인스턴수 변수로 선언
    
    void init(){
    	dir = Direction.EAST;	// 유닛의 방향을 EAST로 초기화
    }
}
  • 열거형 상수의 비교
    - '==', compareTo() 사용
if(dir==Direction.EAST) {
	x++;
} else if (dir > Direction.WEST) {		// ERROR: 열거형 상수에 비교연산자 사용 X
} else if (dir.compareTo(Direction.WEST) > 0) {	// compareTo() 가능
}
  • switch문 조건식에서의 열거형
    - case문에 열거형의 이름 적지 않고, 상수의 이름만 적어야 함
void move(){
	switch(dir) {
    	case EAST: x++;	// Direction.EAST라고 쓰면 X
        	break;
        case WEST: x--;
        	break;
        case SOUTH: y++;
        	break;
        case NORTH: x--;
        	break;
    }
}

2.3 모든 열거형의 조상 : java.lang.Enum

  • 모든 열거형은 Enum의 자손, 아래 메서드 상속

2.4 열거형에 멤버 추가

  • 불연속적인 열거형 상수 → 원하는 값 괄호()안에 적음
enum Direction { EAST(1), SOUTH(5), WEST(-1), NORTH(10) }
  • 괄호()를 사용 → 인스턴스 변수와 생성자를 새로 추가
enum Direction {
    EAST(1), SOUTH(5), WEST(-1), NORTH(10);
    
    private final int value;	// 정수를 저장할 필도(인스턴스 변수)를 추가
    Direction(int value) { this.value = value; }	// 생성자 추가
    
    public int getValue() { return value; }
}
  • 열거형의 생성자는 묵시적으로 private ⇒ 외부에서 객체 생성 X
Direction d = new Direction(1);	// ERROR: 열거형의 생성자는 외부에서 호출 X

2.4 열거형 이해

enum Direction { EAST, SOUTH, WEST, NORTH }
// 위와 아래가 같다
class Direction {
	static final Direction EAST = new Direction("EAST");
    static final Direction SOUTH = new Direction("SOUTH");
    static final Direction WEST = new Direction("WEST");
    static final Direction NORTH = new Direction("NORTH");
    
    private String name;
    
    private Direction(String name) {
    	this.name = name;
    }
}

4. 컬렉션

1.1 컬랙션 개념

  • 요소 객체들의 저장소
    - 객체들의 컨테이너
    - 요소의 개수에 따라 크기 자동 조절
    - 요소의 삽입, 삭제에 따른 요소의 위치 자동 이동
  • 고정 크기의 배열을 다루는 어려움 해소
  • 다양한 객체들의 삽입, 삭제, 검색 등의 관리 용이
  • 제네릭(generics)기법으로 구현
  • 컬렉션의 요소는 객체만 가능
    - 기본 타입(int, char, double 등) 사용 불가
       - JDK 1.5부터 자동 박싱/언박싱으로 기본 타입 값을 객체로 자동 변환

1.2 컬렉션의 핵심 인터페이스

  • List
    - 순서가 있는 데이터의 집합
    - 데이터의 중복 O
    - 예제 : ArrayList, LinkedList, Stack, Vector
  • Set
    - 순서를 유지하지 않은 데이터의 집합
    - 데이터의 중복 X
    - 예제 : HashSet, TreeSet 
  • Map
    - 키(key)와 값(value)의 쌍(pair)으로 이루어진 데이터의 집합
    - 순서 유지 X
    - 키는 중복 X , 값은 중복 O
    - 예제 : HashMap, TreeMap, Hashtable, Properties 

1.4 컬렉션 인터페이스의 메서드

1.5 List 인터페이스의 메서드

  • 순서 O, 중복 O
  • 위의 컬렉션 인터페이스의 메서드 가능, 아래는 위의 없는 추가적인 메서드

1.6 Set 인터페이스 메서드

  • 순서 X, 중복X
  • Collection 인터페이스 메서드와 동일

1.7 Map 인터페이스의 메서드

  • 순서 X, 중복(키 X, 값 O)


2. Vector

2.1 Vector<E> 특성

  • java.util.Vector
    - <E>에서 E 대신 요소로 사용할 특정 타입으로 구체화
    - 타입 매개 변수를 사용해야 함
  • 여러 객체들을 삽입, 삭제, 검색하는 컨테이너 클래스
    - 자동으로 길이 조절 -> 배열의 길이 제한 극복
  • Vector에 삽입 가능한 것
    - 객체, null
    - 기본 타입은 Wrapper 객체로 만들어 저장
  • Vector에 객체 삽입
    - 벡터의 맨 뒤에 객체 추가
    - 벡토 중간에 객체 삽입
  • Vector에서 객체 삭제
    - 임의의 위치에서 객체 삭제 가능 -> 삭제 후 자동으로 자리 이동

3.2 Vector<E> 클래스의 주요 메소드


3. ArrayList


3.1 ArrayList 개념

  • 기존의 Vector를 개선
    - Vector는 자체적으로 동기화 처리 O
    - ArrayList는 동기화 처리 X
  • List 인터페이스 구현 -> 순서 O, 중복 O
  • 데이터의 저장공간으로 배열 사용
public class Vector extends AbstractList implements List, RandomAccess, Clonable, java.io.Serializable
{
	protect Object[] elementData; // 객체 담기 위한 배열
    ...
}

3.2 ArrayList 예제

package ch08_컬렉션;
import java.util.*;

class ArrayListEx1{
	public static void main(String[] args) {
		ArrayList list1 = new ArrayList(10);
		list1.add(new Integer(5));
		list1.add(new Integer(4));
		list1.add(new Integer(2));
		list1.add(new Integer(0));
		list1.add(new Integer(1));
		list1.add(new Integer(3));

		ArrayList list2 = new ArrayList(list1.subList(1,4)); 
		print(list1, list2);

		Collections.sort(list1);	// list1과 list2를 정렬한다.
		Collections.sort(list2);	// Collections.sort(List l)
		print(list1, list2);

		// list2가 list1 일부이므로 true
		System.out.println("list1.containsAll(list2):" + list1.containsAll(list2));

		list2.add("B");
		list2.add("C");
		list2.add(3, "A");
		print(list1, list2);

		list2.set(3, "AA");
		print(list1, list2);
		
		// list1에서 list2와 겹치는 부분만 남기고 나머지는 삭제한다.
		System.out.println("list1.retainAll(list2):" + list1.retainAll(list2));	
		print(list1, list2);
		
		//  list2에서 list1에 포함된 객체들을 삭제한다.
		for(int i= list2.size()-1; i >= 0; i--) {
			if(list1.contains(list2.get(i)))
				list2.remove(i);
		}
		print(list1, list2);
	} // main의 끝

	static void print(ArrayList list1, ArrayList list2) {
		System.out.println("list1:"+list1);
		System.out.println("list2:"+list2);
		System.out.println();		
	}
}

3.3 ArrayList에 저장된 객체 삭제과정

  • 배열 복사를 이용한 ArrayList 삭제 원리

  • remove()메소드를 이용한 ArrayList 삭제 과정


[ 참조 ]

1. Java의 정석 3판 (지은이 : 남궁성)

2. 명품 JAVA Programming 개정 3판 (지은이 : 황기태, 김효수)

 

댓글