[ 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 출력
- 구체화 오류
- 타입 매개 변수에 기본 타입 사용 XVector<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판 (지은이 : 황기태, 김효수)
'JAVA' 카테고리의 다른 글
Stream(스트림) (0) | 2021.08.09 |
---|---|
Optional 클래스 (0) | 2021.08.04 |
CH06. java.lang 패키지와 유용한 클래스 (0) | 2021.05.10 |
[Tip] static 메서드의 오버라이딩 불가 (0) | 2021.05.07 |
CH05. 상속, 오버라이딩, package, 제어자, 다형성, 추상클래스, 인터페이스 (0) | 2021.05.03 |
댓글