[Java Programming] 11.1 제네릭
제네릭 & 컬렉션 프레임워크
- 자바 1.5부터 추가됨 (15년 전)
- 유연한 데이터 처리를 가능케 하는 Java의 강력한 기능 중 하나
- 자바로 개발을 할 때 안쓰면 안됨. 반드시 알고 있어야됨
- 완전히 중복된 코드인데, 타입만 다를경우 유용하게 사용할수있음
제네릭
자료구조의 형태만 제시하고 실제로 어떤 타입의 데이터가 관리될 것인지는 나중에 결정하는 기술
컬렉션
제네릭을 이용해 Array를 조금 더 다양한 형태로 간편하게 사용할 수 있도록 제공하는 프레임워크
제네릭
- 유연한 자료구조를 만들어낼 수 있음
- 클래스 / 인터페이스 / 메소드 정의할 때 type 을 parameter로 사용할 수 있도록 함
- 사용할 타입을 나중에 정하도록 하는 제네릭을 사용하면 훨씬 더 편리해짐
제네릭 타입
- 타입을 파라미터로 가지는 클래스와 인터페이스 <> 사이에 타입파라미터 위치함
public class 클래스명<T> {...}
public interface 인터페이스명 <T> {...}
1. 제네릭 클래스와 제네릭 인터페이스 정의하기
- 제네릭은 클래스명 옆에 부등호( < > )를 이용해 정의.
<제네릭 타입변수명(들) 선언> , < T > = 어떤타입인지 일단은 모르지만, 데이터가 들어오면 그 타입으로 T를 싹 바꿔준다
- 일반적으로 영문 대문자 한글자를 사용한다
제네릭 타입변수 | 의미 |
T | 타입 |
K | 키 |
V | 값 |
N | 숫자 |
E | 원소 |
2. 제네릭 클래스의 객체생성
- 일반 클래스의 객체생성과정과 비슷하지만, 클래스명 다음에 <실제 제네릭 타입> 삽입한다는점에서 차이가있음
= > 객체 생성시 제네릭 타입 변수에 실제 타입을 대입함
클래스명 <실제 제네릭 타입> 참조변수명 = new 클래스명<실제 제네릭 타입>();
or
클래스명 <실제 제네릭 타입> 참조변수명 = new 클래스명<>();
3. 제네릭 메서드의 정의와 호출
- 일반 클래스 내부의 특정 메서드만 제네릭으로 선언하는것
- 리턴타입 or 입력매개변수의 타입을 제네릭타입 변수로 선언함
- 제네릭 메서드는 호출되는 시점에 실제 제네릭 타입을 지정함 (제네릭 클래스 : 객체 생성하는 시점에 실제 타입 지정)
제네릭메서드 문법구조
제네릭타입 변수명이 1개
접근지정자 <T> T 메서드명 (T t){
}
제네릭타입 변수명 2개
접근지정자 <T, V> T 메서드명 (T t, V v){
}
매개변수에만 제네릭 사용됐을때
접근지정자 <T> void 메서드명 (T t){
}
리턴타입에만 제네릭 사용
접근지정자<T> T 메서드명 (int a){
}
제네릭메서드 호출의 문법구조
참조객체.<실제 제네릭 타입> 메서드명(입력매개변수);
598
제네릭을 사용하지 않은 ScoreList
public class DynamicArray {
public static void main(String[] args) {
int[] scoreArray = new int[10];
scoreArray[0] = 100;
scoreArray[1] = 90;
scoreArray[2] = 80;
scoreArray[3] = 70;
scoreArray[4] = 60;
scoreArray[5] = 50;
scoreArray[6] = 40;
scoreArray[7] = 30;
scoreArray[8] = 20;
scoreArray[9] = 10;
//scoreArray에 값 하나를 더 넣으려면 어떻게 해야 하나?
// 학급의 학생 : 10명, 선생은 학생 10명의 학생점수를 관리하고있었다
// int[] scoreArray = new int[10]; 이라고 썼을것
// 학기가 종료되기 전에 1명의 학생이 전학을 왔다.
// -> 11명의 학생점수를 관리해야한다 -> 어떻게 해야할까?
//방법1. 실패
int[] scoreArray2 = new int[scoreArray.length + 1];
// scoreArray2 = scoreArray;
/*
* 배열에 데이터 추가하는 방법
*/
// System.arraycopy (원본배열, 원본배열에서 몇번째부터 복사할건지, 원본배열을 받을 새로운 배열,
// 새로운배열의 몇번째부터 복사할것인가, 원본배열에서 어디까지 복사할것인지)
System.arraycopy(scoreArray, 0, scoreArray2, 0, scoreArray.length); //아래 3줄을 한줄로 처리
for(int i = 0; i < scoreArray.length; i++) {
scoreArray2[i] = scoreArray[i];
}
for(int score : scoreArray2) {
System.out.println(score);
}
System.out.println(scoreArray2.length); // 1
// 배열은 레퍼런스타입, 래퍼런스타입은 메모리를 들고다니므로 메모리 자체를 복사한것
// 즉, 메모리인덱스 한개를 socreArray2에 전달해준것 (int[] scoreArray2 = new int[2];)
//방법2. 성공
scoreArray2[0] = scoreArray[0];
System.out.println(scoreArray2.length); //2
System.out.println(scoreArray[0]); //100
System.out.println(scoreArray2[0]); //100
}
제네릭을 사용한 ScoreList
public class ScoreList<T> {
// 여기에 T가아닌 object 쓰는이유 : 여기에 T 쓰면 3줄밑의 코드에서도 new T[2]라고 해야되는데, 생성자는 생성할때 타입 명시해줘야되기 때문
private Object[] scoreArray; //object : 어떤타입이나 들어갈 수 있음
private int size;
public ScoreList() {
scoreArray = new Object[2]; //생성자는생성할때 타입 명시해줘야하므로(<-T라고 쓰면 안되는 이유!)
}
/**
* scoreArray 배열에 값을 추가합니다.
* @param score
*/
public void add(T score) {
if ( size >= scoreArray.length ) {
Object[] tempScoreArray = new Object[scoreArray.length + 2];
… 생략 …
}
… 생략 …
}
/**
* scoreArray 배열에서 index의 값을 반환합니다.
* @param index
* @return
*/
public T get(int index) {
if (index >= size) {
throw new IndexOutOfBoundsException(index);
}
return (T) scoreArray[index]; // object타입을 integer로 형변환시켜주기 위함
}
… 생략 …}
- 제네릭에는 레퍼런스타입, 즉 클래스 데이터만 쓸 수 있음 (int타입 쓰고싶다면 <Integer> 이라고 써줘야됨)
- 자바 1.7부터 문법 변경되어 앞쪽 타입에만 Integer 적어주면 됨
ScoreList<Integer> score = new ScoreList<>();
<T> : 제네릭은 보통 한 글자 단위로 작성
public class ScoreList<T>
- 의미는 부여하기 나름으로 T는 Type의 앞 글자만 사용한것. 다른 타입을 사용해도 문제는 없음
public void add(T score)
- 파라미터의 T는 클래스에 정의한 T와 동일
- 만약, 클래스 제네릭에 String이 전달되었다면 add 메소드의 T 또한 String이 됨
public T get(int index)
- 리턴타입의 T는 클래스에 정의한 T와 동일
- 만약, 클래스 제네릭에 String이 전달되었다면 get 메소드의 T 또한 String이 됨
인터페이스부터 제네릭 만들어나가야함