티스토리 뷰
제네릭(Generic)
제네릭(Generic)은 '일반적인'이라는 뜻을 지니고 있다.
자바에서 제네릭이란 타입을 내부에서 지정하는 것이 아닌 외부에서 사용자가 지정하는 것을 말한다.
필요에 의해 지정할 수 있는 일반(Generic) 타입을 사용한다는 의미이다.
예를 들면, 아래 Basket 클래스는 오직 String 타입만을 저장할 수 있다.
따라서 여러 타입의 데이터를 저장할 수 있는 객체를 만들려면, 그 수만큼 클래스를 생성해야 한다.
class Basket {
private String item;
Basket(String item) {
this.item = item;
}
public String getItem() {
return item;
}
public void setItem(String item) {
this.item = item;
}
}
이런 상황에서 필요한 것이 제네릭이다. 제네릭을 사용하면
하나의 클래스로 모든 타입의 데이터를 저장할 수 있는 객체를 만들 수 있다.
class Basket<T> {
private T item;
public Basket(T item) {
this.item = item;
}
public T getItem() {
return item;
}
public void setItem(T item) {
this.item = item;
}
}
위 코드를 비교해서 살펴보면, 우선 클래스 이름 옆에 <T>가 붙었으며
데이터의 타입을 가리키는 키워드들이 T로 치환된 것을 확인할 수 있다.
또한 위 클래스는 다음과 같이 인스턴스화 할 수 있다.
Basket<String> basket1 = new Basket<String>("기타줄");
데이터 타입을 String이 아닌 정수형으로 바꾸고 싶으면 아래와 같이 하면 된다.
Basket<Integer> basket2 = new Basket<Integer>(42);
여기서 타입을 나타내는 키워드로 int 대신 Integer를 사용했는데,
이는 제네릭 문법의 파라미터로 명시할 수 있는 것이 참조타입밖에 없기 때문이다.
따라서 int, double과 같은 기본타입들은 래퍼 클래스(Wrapper Class)인 Integer, Double과 같이 사용해야 한다.
제네릭 클래스(Generic Class)
제네릭 클래스는 제네릭이 사용된 클래스를 말한다.
위에서 살펴본 Basket 클래스가 그 쉬운 예가 될 것이다.
여기서 <T>를 타입 매개변수라고 하며, 한 클래스에서 여러 개의 타입 매개변수를 사용할 수도 있다.
class Basket<T, V> { ... }
클래스에서 타입 매개변수를 선언해주고 나면,
위의 예에서 볼 수 있듯이 그 매개변수를 원하는 타입으로 사용할 수 있게 된다.
여기서 주의할 한 가지는, 클래스 변수에는 타입 매개변수를 사용할 수 없다는 점인데,
class Basket<T> {
private T item1; // O
static T item2; // X
}
이는 클래스 변수가 모든 인스턴스에 공통으로 적용되는 변수이기 때문이다.
제네릭 클래스를 인스턴스화 하는 과정에서 원하는 타입을 지정해주어야 하는데,
그 방법을 다시 보자면 다음과 같다.
Basket<String> basket1 = new Basket<String>("기타줄");
Basket<Integer> basket2 = new Basket<Integer>(42);
Basket<Boolean> basket3 = new Basket<Boolean>(true);
Basket<Double> basket4 = new Basket<>(2.71); // 생략 가능
원하는 타입을 <>안에 적었으며, new Basket<> 내부의 타입은 생략이 가능함을 볼 수 있다.
또한, 제네릭 클래스에도 다형성을 적용할 수 있는데, 그 예는 다음과 같다.
class Flower {}
class Rose extends Flower {}
class RosePasta {}
class Basket<T> {
private T item;
public T getItem() {
return item;
}
public void setItem(T item) {
this.item = item;
}
}
public class Main {
public static void main(String[] args) {
Basket<Flower> flowerBasket = new Basket<>();
flowerBasket.setItem(new Rose()); // 다형성 적용
flowerBasket.setItem(new RosePasta()); // 에러
짧게 복습하자면 다형성이란 상위 클래스 타입의 참조변수를 통해 하위 클래스의 객체를 참조할 수 있도록 허용된 것이다.
위 예에서 Basket<Flower>를 통해 생성된 인스턴스 flowerBasket의 item의 타입은 Flower이며,
이를 상속받은 Rose 클래스는 item에 할당될 수 있으나 RosePasta 클래스는 그렇지 않아 에러가 나는 것을 볼 수 있다.
계속해서 타입 변수를 제한하는 방법에 대해 알아보자.
class Flower {}
class Rose extends Flower {}
class RosePasta {}
class Basket<T extends Flower> {
private T item;
public T getItem() {
return item;
}
public void setItem(T item) {
this.item = item;
}
}
public class Main {
public static void main(String[] args) {
Basket<Flower> flowerBasket = new Basket<>();
Basket<Rose> roseBasket = new Basket<>();
Basket<RosePasta> rosePastaBasket = new Basket<>(); // 에러
위 예를 살펴보면 Basket의 타입 변수 자리에 <T extends Flower>가 들어온 것을 볼 수 있다.
이는 Basket 클래스를 인스턴스화 할 때 타입으로 Flower의 하위 클래스만 올 수 있게 지정하는 것이다.
때문에 마지막 라인의 Basket<RosePasta> 타입의 객체를 생성하려고 시도할 때 에러가 나는 것이다.
interface Plant {}
class Flower implements Plant{}
class Rose extends Flower implements Plant {}
class Basket<T extends Plant> {
private T item;
public T getItem() {
return item;
}
public void setItem(T item) {
this.item = item;
}
}
public class Main {
public static void main(String[] args) {
Basket<Flower> flowerBasket = new Basket<>();
Basket<Rose> roseBasket = new Basket<>();
이번에는 특정 클래스가 아닌 특정 인터페이스를 구현한 클래스만 타입으로 받는 예이다.
특이한 점은 타입 변수에 <T implements Plant>가 아닌 <T extends Plant>가 쓰인다는 점이다.
interface Plant {}
class Flower implements Plant{}
class Rose extends Flower implements Plant {}
class RosePasta {}
class Basket<T extends Flower & Plant> {
private T item;
public T getItem() {
return item;
}
public void setItem(T item) {
this.item = item;
}
}
public class Main {
public static void main(String[] args) {
Basket<Flower> flowerBasket = new Basket<>();
Basket<Rose> roseBasket = new Basket<>();
마지막으로, 특정 클래스를 상속 받으면서 동시에 특정 인터페이스를 구현한 클래스 타입으로
타입 변수를 지정하고 싶은 경우, &를 사용하면 된다.
제네릭 메서드(Generic Method)
클래스 전체가 아닌, 특정 메서드만 제네릭으로 선언할 수도 있다.
이를 제네릭 메서드라고 부르며, 선언 방법은 다음과 같다.
class Basket {
...
public <T> void add(T element) {
...
}
}
제네릭 메서드의 타입 선언은 반환타입 앞에서 이루어지며,
여기에 붙어있는 <T>는 제네릭 클래스에 붙는 <T>와는 별개의 존재이다.
class Basket<T> { // 1 : 여기에서 선언한 타입 매개변수 T와
...
public <T> void add(T element) { // 2 : 여기에서 선언한 타입 매개변수 T는 서로 다른 것
...
}
}
이는 외부에서 타입을 지정하는 타이밍이 다르기 때문인데,
제네릭 클래스명에 붙은 타입 매개변수는 클래스가 인스턴스화 될 때,
제네릭 메서드의 타입 지정은 메서드가 호출될 때 이루어진다.
Basket<String> basket = new Bakset<>(); // 위 예제의 1의 T가 String으로 지정
basket.<Integer>add(10); // 위 예제의 2의 T가 Integer로 지정
basket.add(10); // 타입 지정 생략 가능
또한 클래스 타입 매개변수와 달리, 메서드의 경우는 static 메서드 앞에도 사용될 수 있다.
class Basket {
...
static <T> int setPrice(T element) {
...
}
}
마지막으로 제네릭 메서드 안에서는 length()와 같은 String 클래스의 메서드들을 사용할 수 없는데,
이는 당연하게도 호출되기 전까지 메서드의 제네릭 타입이 정해지지 않기 때문이다.
class Basket {
public <T> void print(T item) {
System.out.println(item.length()); // 불가
}
}
하지만 equals(), toString() 등 Object 클래스의 메서드는 사용할 수 있으며,
이는 모든 클래스가 공통으로 Object를 상속받기 때문이다.
class Basket {
public <T> void getPrint(T item) {
System.out.println(item.equals("Kim coding")); // 가능
}
}
'Java+Spring > Java' 카테고리의 다른 글
[Java]Iterator (0) | 2022.07.15 |
---|---|
[Java]List<E>, ArrayList<E>, LinkedList<E> (0) | 2022.07.15 |
[Java]컬렉션 프레임워크(Collection Framework) (0) | 2022.07.15 |
[Java]인터페이스의 활용 예제 (0) | 2022.07.15 |
[Java]final 키워드, 인터페이스(Interface) (0) | 2022.07.15 |
[Java]추상화, abstract 제어자, 추상 클래스 (0) | 2022.07.15 |
- Total
- Today
- Yesterday
- 면접 준비
- 스프링
- 유럽
- 중남미
- 맛집
- 야경
- 동적계획법
- 자바
- spring
- 지지
- Backjoon
- 칼이사
- 스트림
- 파이썬
- RX100M5
- BOJ
- 기술면접
- Python
- Algorithm
- 리스트
- 여행
- 알고리즘
- 세계일주
- java
- a6000
- 백준
- 세계여행
- 세모
- 남미
- 유럽여행
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |