개발하는 삶
[Java] 제네릭스 본문
제네릭스
- 컴파일 시 타입을 체크해주는 기능
- 잘못된 타입이 사용되는 문제를 줄여줌
- 후에 형변환 할 필요없음. 코드가 간결해진다.
- 타입을 <> 로 가지는 클래스, 인터페이스
- public class 클래스명<T>{...}
- public interface 인터페이스명<T>{...}
- ArrayList<Object> list = new ArrayList<Object>();
- list에 Object 타입만 넣을 것이라는 뜻
import java.util.ArrayList;
public class GenericTest {
public static void main(String[] args) {
//ArrayList list = new Arraylist(); //~jdk1.5(지네릭스 도입이전)
ArrayList<Object> list = new ArrayList<Object>(); //지네릭스 도입이후, 미리 타입체크
list.add(10);
list.add("30"); //문자열 타입
String i = (String)list.get(2);
}
}
타입 변수
- 아무 이름으로 저장해도 컴파일에 문제가 없음
- 클래스를 작성할 때, Object타입 대신 타입변수(E)를 선언해서 사용
- 일반클래스 → 지네릭 클래스
public class ArrayList<E> extends AbstractList<E> {
private transient E[] elementData;
public boolean add(E o) {/..}
public E get(int index) {/..}
...
}
- 객체를 생성 시, 타입변수(E) 대신 실제 타입(ex. Tv)를 대입
public class ArrayList extends AbstractList {
private transient Tv[] elementData;
public boolean add(Tv o) {/..}
public Tv get(int index) {/..}
...
}
- 타입변수 대신 실제 타입이 지정되면, 형변환 생략 가능
//before
ArrayList tvList = new ArrayList();
tvList.add(new Tv());
Tv t = (Tv)tvList.get(0); //형변환O 필수
//after
ArrayList<Tv> tvList = new ArrayList<Tv>();
tvList.add(new Tv());
Tv t = tvList.get(0); //형변환X
지네릭스 용어
- Box<T> : 지네릭 클래스
- T : 타입변수 / 타입매개변수
- Box : 원시타입 (기존의 원래 타입)
- 지네릭 클래스 선언 : class Box<T> {}
- Box <String> b = new Box<String>();
- String = 대입된 타입
지네릭 타입과 다형성
- 참조변수 == 생성자의 대입된 타입
- 조상-자손 관계여도 안됨 (대입된 타입이 달라서)
class Tv extends Product {}
//일때,
ArrayList<Product> list = new ArrayList<Tv>(); //에러
지네릭 클래스간의 다형성은 성립 (대입된 타입은 일치해야함)
// ArrayList= 자손, List= 조상
List<Tv> list = new ArrayList<Tv>();
// LinkedList= 자손, List= 조상
List<Tv> list = new LinkedList<Tv>();
매개변수의 다형성도 성립
class Tv extends Product {}
//일때,
ArrayList<Product> list = newArrayList<Product>();
list.add(new Product());
list.add(new Tv()); //Product 자손도 OK
list.add(new Audio()); //Product 자손도 OK
Iterator<E>
- 클래스 작성시, Object타입 대신 타입변수를 사용
Iterator<Student> it = list.iterator();
while(it.hasNext()) {
Student s = it.next(); //형변환 필요X
...
}
HashMap<K,V>
- 여러 개의 타입변수가 필요할 때
- HashMap<key값,Value값> 예시로 들 때,
class Ex2 {
public static void main(String[] args) {
HashMap<String,Student> map = new HashMap<String, Student>(); //생성
//<>값이 일치하면 JDK1.7부터 타입지정 생략가능
//HashMap<String,Student> map = new HashMap<>(); //와 같음
map.put("자바왕", new Student("자바왕",1,1,100,100,100)); //데이터 저장
Student s = map.get("자바왕");
System.out.println(map);
}
}
class Student {
String name = "";
int ban;
int no;
int kor;
int eng;
int math;
....
}
제한된 지네릭 클래스
- extends로 대입할 수 있는 타입을 제한
class FruitBox<T extends Fruit> { //Fruit의 자손만 타입으로 지정 가능
ArrayList<T> list = new ArrayList<T>();
...
}
FruitBox<Apple> appleBox = new FruitBox<Apple>();
FruitBox<Toy> toyBox = new FruitBox<Toy>(); //에러. Toy는 Fruit의 자손이 아님
- 인터페이스인 경우에도 extends를 사용 (implements 아님!)
- 두가지 묶을때는 &로 묶기
- ex. T extends Fruit & Eatable
interface Eatable{}
class FruitBox<T extends Eatable> {...}
지네릭스의 제약
- 타입 변수에 대입은 인스턴스별로 다르게 가능
- Box<Apple> appleBox = new Box<Apple>(); //appleBox인스턴스 -> Apple객체만 저장가능
- Box<Grape> grapeBox = new Box<Grape>(); //grapeBox인스턴스 -> Grape객체만 저장가능
- static 멤버에 타입변수 사용불가
- class Box<T> {
- static T item; //에러
- static int compare(T t1, T t2){...}; //에러
- ... }
- 배열 생성시, 타입변수 사용 불가. (선언만 가능)
class Box<T> {
T[] itemArr; //배열 선언은 OK
...
T[] toArray() {
T[] tmpArr = new T(itemArr_length); //에러
//new T 이런 형식의 배열 생성이 안됨! new 연산자 안됨.
... }
와일드 카드 <?>
- 하나의 참조변수로 대입된 타입이 다른객체를 참조 가능
- ArrayList<? extends Product> list = new ArrayList<Tv>();
- ArrayList<? extends Product> list = new ArrayList<Audio>();
- ArrayList<Product> list = new ArrayList<Tv>(); //에러. 대입된 타입이 불일치
- <?> 사용법
- 지네릭스 활용 안되는것에 보통 사용됨
- <? extends T> : T와 그 자손들만 가능
- <? super T> : T와 그 조상들만 가능
- <?> : 제한 없음. 모든타입이 가능 == <? extends Object>
- 메서드의 매개변수에 와일드카드를 사용
apple<?> a = new apple<?>(); //에러. 어떤 타입인지 전혀 알수없음
apple<?> a = new apple<object>(); //됨
apple<?> a = new apple<>(); //됨. 위 문장과 같은 문장임 (타입유추가 가능해서)
지네릭 메서드
- 지네릭 타입이 선언된 메서드
//제네릭 메서드 선언하기
public <T> 리턴타입 메서드명(매개변수...){..}
//리턴타입으로 제네릭타입 사용하기 : Box<T>
public <T> Box<T> boxing(T t){...}
- 지네릭 메서드 호출하는 법
//리턴타입 변수 = <구체적타입> 메서드명(매개값);
Box<Integer> box = <Integer>boxing(100);
//리턴타입 변수 = 메서드명(매개값);
//매개값을 보고 <구체적타입>을 추정
Box<Integer> box = boxing(100);
지네릭 타입의 제거
- 컴파일러는 지네릭 타입을 제거하고, 필요한 곳에 형변환을 넣는다
- 지네릭 타입의 경계를 제거
- 하위호완성을 위해 (JDK 1.5 이전버전과 문제없이 사용할수 있게)
class Box<T extends Fruit> {
void add(T t) {
...
}
}
//를, 컴파일 후에
class Box {
void add(Fruit t) { // T -> Object -> Fruit
...
}
}
//로 바꿔짐
지네릭 타입 제거 후, 타입이 불일치하면 형변환을 추가
get(int i) {
return list.get(i);
}
//를, 컴파일 후에 타입이 불일치하면
Fruit get(int i) {
return (Fruit)list.get(i);
}
//형변환을 추가
'Java' 카테고리의 다른 글
[Java] Stream (0) | 2022.06.27 |
---|---|
[Java] 람다식 (0) | 2022.06.26 |
[Java] 예외, 래퍼 클래스 (0) | 2022.06.25 |
[Java] 스레드 (0) | 2022.06.21 |
[Java] Object, String 클래스 (0) | 2022.06.21 |