지네릭스 <>
Collection 클래스의 타입체크를 해주는 기능을 가지고 있다.
아래의 예시처럼 지네릭스를 써주지 않으면 컴파일러가 arr.get(0)의 인덱스 자리의 값을 확인할 수 없기 때문에 일일이 형변환을 해주어야 컴파일오류가 나지 않는다.
ArrayList arr = new ArrayList();
arr.add(1);
arr.add(1);
arr.add(1);
int i = (Integer) arr.get(0);
지네릭스를 아래와 같이 지정해주면 컴파일러에게 새로운 기준을 넣어준 것이기 때문에 형변환을 하지 않아도 컴파일오류가 발생되지 않는다.
단 지정타입을 제외한 타입을 저장하게 될 경우 컴파일 오류 발생
ArrayList<Integer> arr = new ArrayList<Integer>();
arr.add(1);
arr.add(1);
arr.add(1);
int i = arr.get(0);
// Generic을 쓰지않으면 형변환을 해줘야 하지만
// Generic으로 타입을 지정하면 형변환을 하지 않아도 컴파일러가 자동으로 오토박싱을 해줌
이와 같이 지네릭스를 사용하여 타입을 지정해주면 아래와 같은 장점이 있다.
- 타입을 미리 지정함으로써 타입의 안정성을 준다.
- 형변환을 할 필요가 없으므로 코드를 간결화 시켜준다.
타입 변수
타입 변수는 'E'로 선언한다 - Element의 약자
Object를 선언하는대신 E로 선언하여 Object를 대체한다.
ArrayList<E> arr = new ArrayList<E>();
*Good to Know*
E 와 T의 차이
지네릭스를 선언할 때 Object로 선언하는 대신 'E' 혹은 'T'로 대신 선언
E는 element, 즉 요소라는 의미인데 배열 클래스(ArrayList, LinkedList 등)들이 요소를 배열로 저장하기 때문에 배열 기반의 클래스들에는 'E'로 선언하는 것이 좋고, 그 외에는 'T'로 선언하는 것이 좋다.
둘다 혼용하여 사용해도 컴파일에는 문제가 없음.
타입 변수에 클래스타입을 대입할 때 지네릭스에 들어가는 타입이 동일해야한다.
ArrayList<TV> arr = new ArrayList<TV>();
// TV 클래스를 지네릭스에 넣어줌 = 일치
*Good to Know*
지네릭스 용어
Parameterized Type (매개변수화된 타입 = 대입된 타입)
ArrayList<TV> arr = new ArrayList<TV>();
// 지네릭스를 선언한 Collection 클래스를 부를 때 '대입된 타입'이라 부른다
지네릭타입의 다형성
지네릭안의 객체 타입 변수는 무조건 같아야 하지만, 객체클래스의 다형성은 가능하다.
List<Test> list = new ArrayList<Test>();
// 가능!
또한 대입된 타입의 자손객체가 존재하면, 지네릭스 타입과 일치하지 않더라도 다형성에 의해 add(), 즉 대입이 가능하다
- TV클래스를 상속하는 sound, display의 객체 데이터를 저장 가능
주의할 점: 처음 선언하는 지네릭스의 대입 타입 변수는 무조건 같아야한다!!
List<Tv> list = new ArrayList<Tv>();
list.add(new sound());
list.add(new display());
// 대입된 타입은 동일해야 하지만, 추가될 객체 데이터는 다형성이 가능하다
}
}
class Tv{
}
class sound extends Tv{
}
class display extends Tv{
}
iterator의 지네릭타입
iterator의 지네릭안에 타입변수를 대입하여 형변환을 생략할 수 있다.
- 만약 값만 출력할 목적이라면 평범하게 출력하면 되지만, 만약 값을 저장하여 재 사용 할 목적이라면 새로운 Tv변수에 저장을 하여 사용하기 때문에 형변환이 필요했다
- 하지만 지네릭스에 미리 대입 타입을 지정해주면 형변환을 생략하여도 컴파일러가 오토박싱 기능을 수행하여 형변환을 생략할 수 있도록 해준다.
Iterator<Tv> it = list.iterator();
// 지네릭스에 Tv추가!
while(it.hasNext()) {
Tv tv = it.next();
// Tv tv = (Tv)it.next();
// 지네릭스를 사용하면 Tv로 형변환을 하지 않아도 컴파일 오류가 발생하지 않는다
System.out.println(tv);
// System.out.println(it.next());
// 평범한 출력이 목적이라면 그냥 출력하면 되지만,
// 출력값을 저장하여 재 사용 하려면 위와 같이 실행한다.
HashMap의 지네릭 타입
iterator의 지네릭안에 Key(키)와 Value(값)을 지정하여 선언할 수 있다.
각각 다른 객체를 선언하여 키와 값을 넣어줄 수 있으며 키에 해당되는 값을 불러올 시 HashMap.get('값')을 선언하여 값에 해당되는 값을 불러온다.
아래의 코드의 출력문은 '중요도90'이다
Map<lecture, importance> map= new HashMap<lecture, importance>();
lecture l = new lecture("자바");
map.put(l, new importance(90));
map.put(new lecture("spring"), new importance(100));
map.put(new lecture("html"), new importance(98));
importance i = map.get(l);
System.out.println(i);
}
}
class lecture{
String name;
public lecture(String name) {
this.name = name;
}
public String toString() {
return "수업 이름" + name;
}
}
class importance{
int n;
public importance(int n) {
this.n = n;
}
public String toString() {
return "중요도" + n;
}
}
지네릭스의 제한
클래스를 선언하고 상속을 받을 때 지네릭스를 사용하여 상속받을 타입을 제한할 수 있다.
지네릭스에 <E extends Obect>의 형식으로 상속받을 클래스를 제한한다.
아래의 예시는 basil, rosemari의 클래스에 plant클래스를 부모클래스로 상속받고 그 plant를 상속받은 클래스에 한해서 plantbox 클래스에 상속할 수 있게 지네릭스로 제한을 두었다.
여기서 주목할점!
- 지네릭스 format 중, 여러개의 클래스&인터페이스를 제한을 둘 때 '&'을 하나만 사용하여 선언
- eatable이라는 인터페이스의 상속을 기본 제한으로 두었는데 이 조건을 충족시키기위해 아래 클래스 전부다 implements로 상속을 시켜버리면 basil클래스에 rosemari객체 데이터를 저장할 수 있게 되어버린다.
- 따라서 plant라는 큰 틀의 클래스에 interface를 상속시켜 조건부 제한을 두어야 한다.
plantbox<basil> s = new plantbox<basil>();
s.add(new basil());
s.check();
// s.add(new rose());
// basil의 클래스만 저장하도록 제네릭스를 넣었기 때문에 다른 클래스를 넣으면 컴파일 오류가 발생한다.
// save<rose> s = new save<rose>();
// 여기서 eatable과 plant를 상속받지 않은 rose를 입력하게 되면 컴파일 오류가 발생한다.
}
}
class plantbox<E extends plant & eatable> extends control{
public String toString() {
return "save []";
}
}
class control{
ArrayList arr = new ArrayList();
void add(Object obj) {
arr.add(obj);
}
void check() {
System.out.println(arr);
}
}
class plant implements eatable{}
interface eatable{};
class basil extends plant{
public String toString() {return "basil";}}
class pensil {
public String toString() {return "pensil";}}
class rosemari extends plant{
public String toString() {return "rosemari";}}
지네릭스의 제약
지네릭스를 사용할 때 제약이 두가지 있다
1. 지네릭스의 타입 변수에 static은 사용 불가능하다.
- 지네릭스를 선언 시 지정한 타입 변수, 인스턴스 별로 다르게 대입하여야 하는데 static을 사용하면 인스턴스를 공통의 객체로 만들어버리기 때문에 컴파일 오류가 실행된다.
2. 배열을 생성할 때 타입 변수는 사용이 불가능하다
- new 다음에 T를 선언하지 못하는데, 그 이유는 T의 타입 변수는 어떤 변수가 대입될지 확정이 되어야하는데 어떤 객체가 들어올지 모르니 컴파일 오류가 실행된다.
- 단, 타입 변수 배열 선언은 가능하다.
class Gclass<T>{
T[] Tarr;
// 배열 선언은 가능
public Gclass(T t) {
// Tarr = new T[initial];
// 배열 생성 시 대입 변수 사용 불가
// T를 new 다음에 선언 불가능하다 - 어떤 객체가 들어올 지 모르기 때문
}
}
'Java > 자바의정석 기초편' 카테고리의 다른 글
자바의 정석 12장 (31일차) - 열거형 (enum) (0) | 2022.02.22 |
---|---|
자바의 정석 12장 (31일차) - 와일드카드 <?> & 지네릭스 형변환 (0) | 2022.02.22 |
자바의 정석 11장 (30일차) - Collections 클래스 (0) | 2022.02.21 |
자바의 정석 11장 (30일차) - HashMap (0) | 2022.02.21 |
자바의 정석 11장 (29일차) - TreSet (0) | 2022.02.20 |