본문 바로가기
java_study

명품 JAVA 프로그래밍: 7장 제네릭과 컬렉션

by 질서정연_ 2020. 12. 3.

컬렉션이란?

컬렉션은 배열이 가진 고정 크기의 단점을 극복하기 위해 객체들을 쉽게 삽입, 삭제, 검색할 수 있는 가변 크기의 컨테이너이다. 컬렉션 클래스는 개발자가 바로 사용할 수 있는 것들로서, Vector<E>와 ArrayList<E>는 가변 크기의 배열을 구현하며, LinkedList<E>는 노드들이 링크로 연결되는 리스트를 구현한다. Stack<E>는 스택을 구현하며 HashSet<E>는 집합을 구현한다. 이들은 모두 Collection<E>를 상속받고, 단일 클래스의 객체만을 요소로 다루는 공통점이 있다. HashMap<K,V>는 키(K)와 값(V)의 쌍으로 이루어지는 데이터를 저장하고, 키로 쉽게 검색하도록 만든 컬렉션이다. 

 

컬렉션의 특징

첫째, 컬렉션은 제네릭 이라는 기법으로 만들어져있다.

컬렉션 클래스의 이름에는 <E>,<K>,<V>등이 항상 포함된다. 이들은 타입 매개 변수 라고 하며 Vector<E>에서 E 대신 Integer와 같이 구체적인 타입을 지정하면 Vector<Integer>는 정수 값만 저장하는 벡터로, Vector<String>문자열만 저장하는 벡터로 사용할 수 있다. 특정 타입만 다루지 않고 여러 종류의 타입으로 변신할 수 있도록 컬렉션을 일반화 시키기 위해 <E>를 사용하는 것이다. 그러므로 E를 일반화 시킨 타입 혹은 제네릭 타입(generic type)이라고 부른다. 컬렉션은 여러 타입의 값을 다룰 수 있도록 변신이 가능한 자료 구조 이지만, 컬렉션을 사용할 때는 지정된 특정 타입의 값만 저장 가능하다.

둘째, 컬렉션의 요소는 객체들만 가능하다. int , char, double등의 기본타입의 데이터는 원칙적으로 컬렉션의 요소로 들어가지 못한다. 

Vector<int> v = new Vector<int>();  // int로 생성 불가능
Vector<Integer> v = new Vector<Integer>(); // 정상적으로 생성 

하지만 기본 타입의 값이 삽입되면 자동 박싱에 의해 Wrapper 클래스 타입으로 변환되어 객체로 저장한다. 

 

제네릭의 기본 개념

제네릭은 모든 종류의 타입을 다룰 수 있도록, 클래스나 메소드를 타입 매개변수 를 이용하여 선언하는 기법이다. 자바의 제네릭은 클래스 코드를 찍어내듯이 생산할 수 있도록 일반화(generic)시키는 도구이다. 

Stack<E>클래스의 예를 들어보자. Stack<E>클래스는 제네릭 타입 <E>를 가진 제네릭 클래스이다. Stack<E>에서 E에 구체적인 타입을 지정하면, 지정된 타입만 다룰 수 있는 구체화된 스택이 된다. 예를 들어, Stack<Integer>는 Integer 타입만 다루는 스택이 되고, Stack<Point>는 Point타입의 객체만 Push하고 pop할 수 있는 스택이 된다. 

 

제네릭 타입 매개변수

컬렉션 클래스에서 타입 매개변수로 사용하는 문자는 다른 변수와 혼동을 피하기 위해 일반적으로 하나의 대문자를 사용한다. 

E :  Element를 의미하며 컬렉션에서 요소임을 나타냄.

T :  Type을 의미

V:   Value를 의미

K :  Key를 의미 

 

제네릭 컬렉션 활용

Vector<E>는 배열을 가변 크기로 다룰 수 있게 하고, 객체의 삽입, 삭제, 이동이 쉽도록 구성한 컬렉션 클래스이다. 백터는 삽입되는 요소의 개수에 따라 자동으로 크기를 조절하고, 요소의 삽입과 삭제에 따라 자동으로 요소들의 자리를 이동한다. 벡터에는 null도 삽입할 수 있다.      

대부분 컬렉션에서 삽입: add

요소 검색: get(int index)

크기와 용량: size() : int가 반환됨.(크기 반환), capacity(): int (반환용량 반환)

삭제 : remove( int index)

보통 이 정도로 사용한다. 컬렉션에 따라서 조금 다른 부분도 있고 더 추가되는 부분도 있다. 

 

제네릭 만들기 

제네릭 클래스를 작성하는 방법은 기존의 클래스 작성방법과 유사한데 클래스 이름 다음에 generic type의 매개변수를 <>사이에 추가한다는 것에 차이가 있다. 

 

클래스의 일부 메소드만 제네릭으로 구현할 수도 있다. 

 

Static <T> void toStack(T[] a, GStack<T> gs){} 이렇게 생긴 메소드를 사용할 때,

 

String[] sArray = new String[100]

GStackt<String> stringStack = new GStack<String>();

GenericMethod.toStack( sArray , stringStack ) // 타입 매개변수 T를 String으로 유추한다. 

 

T 매개변수가 Object로 유추된다. Object가 String의 슈퍼클래스 이기 때문에 컴파일러는 Object 타입으로 유추한다. 

 

 

음 그럼 T나 D같은 애로 만들어 주는게 어떤 타입이던 들어올 수 있게 해주려고 그런가보다. 

 

제네릭의 장점

- 동적으로 타입이 결정되지 않고 컴파일 시에 타입이 결정되므로 보다 안전한 프로그래밍 가능

- 런타임 타입 충돌 문제 방지

- 개발 시 타입 캐스팅 절차 불필요

- ClassCaseException 방지. 

 

댓글