Java 컬렉션
자바 프로그램을 작성하는 데 중요한 도구이다
자바 JDK는 프로그램 개발에 필요한 기초적인 자료구조들을 거의 대부분 컬렉션으로 만들어서 제공한다
배열은 여러 데이터들을 관리하는데 매우 편리한 자료구조지만 삽입 삭제가 빈번하고
데이터 크기를 예측할 수 없는 응용 프로그램에 사용하기엔 불편한 부분이 있다
또한 고정 크기 이상의 객체를 관리하기 어렵고 배열 중간에 객체가 삭제되면
그 응용프로그램에서 자리를 옮겨야하는 불편함이 있다
그런 반면 컬렉션은 가변 크기로서 객체 개수를 염려할 필요가 없고 컬렉션 내 한 객체가 삭제되면
컬렉션이 자동으로 자리를 옮겨준다
이러한 컬렉션은 제네릭이라는 기법으로 구현되어 있기 때문에 제네릭에 대한 이해도 필요하다
제네릭(Generic)
클래스 내부에서 사용할 데이터 타입을 "외부에서" 매개변수 형태로 지정하면서 데이터 타입을 일반화한다
제네릭 없이 객체에 여러 자료형을 받을 수 있도록 만들려고 하면
int, double, string 등등 모든 자료형에 대해 클래스를 하나씩 만들어야 한다
제네릭을 사용한다면 가상의 자료형을 정의 한 후, 객체 정의할 때 타입 매개변수를 선언해서 사용할 수 있다
제네릭은 클래스, 인터페이스 이름 뒤에 <> 안 타입 파라미터를 넣어 작성한다
public class 클래스명 <타입 매개변수>{ ... }
public interface 인터페이스명 <타입 매개변수> { ... }
자주 사용하는 타입 매개변수는 정해진 규칙이 없지만 일반적으로 대문자 알파벳 한 글자로 표현한다
타입인자
<T> Type
<E> Element
<K> Key
<N> Number
<V> Value
<R> Result
제네릭은 왜 필요할까 ?
한 클래스에 여러 타입의 코드를 작성하게 되면 수동 타입 변환도 많이 발생해서 코드가 복잡해지고
잘못된 변환을 시도하면 에러(Class Cast Exception) 도 발생하기 때문에 제네릭이 추가되었다
타입체크와 형변환을 생략해서 간결한 코드 작성이 가능한 부분이 있고 (컴파일 할 때 체크해준다 에러뜨는지 안뜨는지)
클래스, 메소드 내부에서 사용되는 객체 타입 안정성을 제공한다
좀 더 쉽게 말해서 기존에 타입을 지정안하면 Object 타입으로 지정되기 때문에 컴파일 상 문제는 없다고 뜨지만
이걸 실행했을 때는 ClassCastException 형변환 에러가 뜬다는 말이다
그걸 제네릭으로 정의했을 때 컴파일 부터 에러를 잡아줄 수 있는, 타입 체크, 형변환 체크를 해준다는 말이다
제네릭 클래스 정의
여러 참조 자료형을 사용해야 하는 경우, Object가 아닌 하나의 문자로 표현한다
접근제어자 class 클래스명<T>{ // 제네릭 타입 매개변수가 1개일 때
// 타입 T를 사용한 코드
}
접근제어자 class 클래스명<K,V>{ // 제네릭 타입 매개변수가 2개일 때
// 타입 K, V를 사용한 코드
}
// 제네릭 타입의 클래스 선언
public class Basket<T> { }
// 제네릭 타입의 인터페이스 선언
public interface Basket<T> { }
제네릭 타입을 설정해주고 필드와 메소드 리턴 값을 T로 작성
// 제네릭 타입의 Basket 클래스
public class Basket<T> {
private T t;
public T get() { return t; }
public void set(T t) { this.t = t; }
}
이제 Basket의 객체를 생성할 때 어떤 타입의 값을 넣을 것인지 정해준다
public class BasketMain {
public static void main(String[] args) {
Basket<String> basket1 = new Basket<>();
// Basket<String> basket1 = new Basket<String>();
// Explicit type argument Integer can be replaced with <> 경고가 뜨는데
// 코드를 간단하게 만들기 위한 것이 목적으로 왼쪽에 정보가 다 있기 때문에
// 오른쪽은 중복이라 볼 수 있어서 오른쪽 다이아몬드 오퍼레이터만 남겨두는게 좋다
basket1.set("jphwany");
String str = basket1.get();
Basket<Integer> basket2 = new Basket<>(); //위와 같은 이유로 오른쪽 <>만 남겨두었다
basket2.set(7);
int value = basket2.get();
System.out.println(str);
System.out.println(value);
System.out.println(basket1.get());
System.out.println(basket2.get());
}
}
// output
jphwany
7
jphwany
7
제네릭 클래스를 생성할 때 생성자에 자료형을 명시하지 않을 수 있다
생성자 타입 매개변수를 컴파일러가 문맥을 통해 추론하는데 <>를 다이아몬드 오퍼레이터(연산자) 라고 한다
와일드 카드
? 기호를 사용해서 제한을 두지 않는 기호로서 사용한다
<?> // 타입 매개변수에 모든 타입 사용
<? extends T> // T타입과 T타입 상속받는 하위 클래스 타입만 사용
<? super T> // T 타입과 T타입을 상속받은 상위 클래스 타입만 사용
제네릭 메소드
클래스 전체를 제네릭으로 선언할 수도 있지만
클래스 내부 특정 메소드만 제네릭 선언이 가능하다
제네릭 클래스가 객체를 생성하는 시점에 실제 타입을 지정하는 것과는 다르게
제네릭 메소드는 호출되는 시점에 실제 제네릭 타입을 지정한다
제네릭 클래스 타입이 전역 변수 처럼 사용된다면 제네릭 메소드 타입은 해당 메소드 안에서만 사용할 수 있는
지역 변수처럼 사용되는 것
public class POGMain {
public static void main(String[] args) {
PartOfGeneric partOfGeneric = new PartOfGeneric();
int num1 = partOfGeneric.<Integer>accept(123);
int num2 = partOfGeneric.<Integer>accept(321);
// 입력 매개변수 값으로 제네릭 타입을 유추할 수 있다면 제네릭 타입 지정 생략 가능
System.out.println(num1);
System.out.println(num2);
// partOfGeneric.<Integer,String>getPrint(77, "jp");
// Explicit type arguments can be inferred 경고가 뜬다
// getPrint 메소드가 타입 매개변수와 함께 선언되었기 때문에
// 다이아몬드 오퍼레이터 <>로 다시 만들어주지 않아도 됨. 생략 가능
partOfGeneric.getPrint(77, "jp");
partOfGeneric.getPrint("hwany",3);
}
}
제네릭 메소드는 메소드 호출되는 시점에서 제네릭 타입이 결정된다
제네릭 메소드를 정의하는 시점에서는 실제로 어떤 타입이 입력되는지 알 수 없기 때문에
length() 같은 String클래스 메소드는 정의하는 시점에서 사용할 수 없다
그렇지만 자바 클래스 최상위 클래스 Object 클래스 메소드는 사용이 가능한 부분
(위의 코드에선 getPrint() )
이러한 Object 클래스 메소드는 이런 것들이 있다
제어자 및 타입 메소드 | 설명 |
protected Object clone() | 해당 객체의 복제본을 생성하여 반환 |
boolean equals(Object obj) | 해당 객체와 전달받은 객체가 같은지 여부를 반환 |
protected void finalize() | 해당 객체를 더는 아무도 참조하지 않아 가비지 컬렉터가 객체의 리소스를 정리하기 위해 호출 |
Class getClass() | 해당 객체의 클래스 타입을 반환 |
int hashCode() | 해당 객체의 해시 코드값을 반환 |
void notify() | 해당 객체의 대기(wait)하고 있는 하나의 스레드를 다시 실행할 때 호출 |
void notifyAll() | 해당 객체의 대기(wait)하고 있는 모든 스레드를 다시 실행할 때 호출 |
String toString() | 해당 객체의 정보를 문자열로 반환 |
void wait() | 해당 객체의 다른 스레드가 notify()나 notifyAll() 메소드를 실행할 때까지 현재 스레드를 일시적으로 대기(wait)시킬 때 호출 |
void wait(long timeout) | 해당 객체의 다른 스레드가 notify()나 notifyAll() 메소드를 실행하거나 전달받은 시간이 지날 때까지 현재 스레드를 일시적으로 대기(wait)시킬 때 호출 |
void wait (long timeout, int nanos) |
해당 객체의 다른 스레드가 notify()나 notifyAll() 메소드를 실행하거나 전달받은 시간이 지나거나 다른 스레드가 현재 스레드를 인터럽트(interrupt) 할 때까지 현재 스레드를 일시적으로 대기(wait)시킬 때 호출 |
'Java' 카테고리의 다른 글
[Java 컬렉션] 내부 클래스 (Inner Class) (0) | 2022.06.01 |
---|---|
[Java 컬렉션] 컬렉션 프레임워크(Collection Framework) (0) | 2022.06.01 |
[Java OOP 심화] 다형성(Polymorphism) (0) | 2022.05.29 |
[Java OOP 심화] 추상화 (Abstract) (0) | 2022.05.29 |
[Java OOP 심화] 캡슐화 (Encapsulation) (0) | 2022.05.25 |
댓글