개요
정확히 같은 작업을 수행하는 클래스가 타입만 다르다면, 다음 코드에서와 같이 불필요한 코드 중복이 생기게된다.
더보기
Main
public static void main(String[] args) {
IntegerBox integerBox = new IntegerBox();
integerBox.setValue(10);
System.out.println("integerBox = " + integerBox.getValue());
DoubleBox doubleBox = new DoubleBox();
doubleBox.setValue(10.2);
System.out.println("doubleBox = " + doubleBox.getValue());
}
DoubleBox
public class DoubleBox {
private Double value;
public void setValue(Double value) {
this.value = value;
}
public Double getValue() {
return value;
}
}
IntegerBox
public class IntegerBox {
private Integer value;
public void setValue(Integer value) {
this.value = value;
}
public Integer getValue() {
return value;
}
}
이러한 코드 중복을 막기 위해 제네릭을 사용한다. 그렇다면, 레네릭이 무엇일까?
목차
1. Object 타입
2. 제네릭 클래스
- 제네릭 특징
- 제네릭 클래스 기본구조
- 제네릭 클래스 사용 예제
- 제네릭 클래스 활용 예제
- 제네릭 클래스 타입 제한
3. 제네릭 메서드
- 제네릭 메서드 특징
- 제네릭 메서드 기본 구조
- 제네릭 메서드 사용 예제
Object 타입
모든 타입의 부모 클래스인 Object를 사용하면 되지 않을까?
더보기
Main
public class ObjectMain {
public static void main(String[] args) {
ObjectBox doublebox = new ObjectBox();
doublebox.setValue("10.2");
System.out.println("doubleBox = " + doublebox.getValue());
}
}
ObjectBox
public class ObjectBox {
private Object value;
public void setValue(Object value) {
this.value = value;
}
public Object getValue() {
return value;
}
}
Object를 사용하면, 다음과 같은 문제점이 있다.
- 다음과 같이 Object 타입으로 클래스를 만든 경우, 의도치 않게 다른 타입으로 넣을수 도 있다. (타입 안정성 ❌)
- 이전 개요에서 보여준 코드의 경우는 타입 안정성이 좋다.
- 항상 반환 타입이 Object이므로, 내가 원하는 타입으로 다운 캐스팅해야 한다.
다형성을 이용하여, 여러 타입을 받을 수 있지만, 타입 안정성이 떨어지는 문제가 발생하였다.
제네릭 클래스
제네릭 클래스는 객체가 생성될 때 타입을 결정하는 클래스이다..
1. 제네릭 클래스 특징
- 코드 중복을 줄일 수 있다.
- 타입 안정성을 높일 수 있다. (컴파일 시점에 타입을 체크한다.)
- 코드 가독성과 유지보수가 좋다.
2. 제네릭 클래스 기본 구조
class Box<T> {
private T item;
public void set(T item) {
this.item = item;
}
public T get() {
return item;
}
}
- `T` : 타입 매개변수 (Type Parameter)
- 객체 선언 및 생성할 때, 실제 타입을 넣어주어야 한다. → ex.`Box <Interger> box = new Box<Integer>();`
3. 제네릭 클래스 사용 예제
더보기
Main
public class GenericMain {
public static void main(String[] args) {
GenericBox<Integer> integerBox = new GenericBox<>();
//.setValue("10");
System.out.println("integerBox = " + integerBox.getValue());
GenericBox<String> stringBox = new GenericBox<>();
stringBox.setValue("안녕하세요.");
System.out.println("stringBox = " + stringBox.getValue());
}
}
GenericBox
public class GenericBox <T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
- 컴파일러가 객체 생성 시점에, 타입 매개변수(<T>) 정보를 기반으로 타입을 결정한다.
- 타입 안정성 ✅
- 여러가지 값을 받을 수 있다. (재사용성 ✅)
4. 제네릭 클래스 활용 예제
다형성을 활용한 예제이다.
더보기
Main
public class CarMain {
public static void main(String[] args) {
CarInfo<Car> carInfo = new CarInfo<>();
carInfo.setCar(new Kia("기아"));
System.out.println("carInfo = " + carInfo.getCar());
carInfo.setCar(new Tesla("테슬라"));
System.out.println("carInfo = " + carInfo.getCar());
}
}
CarInfo
public class CarInfo <T> {
T car;
public void setCar(T car) {
this.car = car;
}
public T getCar() {
return car;
}
}
Car
public abstract class Car {
private String name;
public Car(String name) {
this.name = name;
}
public void move() {
System.out.println("차가 움직입니다.");
}
@Override
public String toString() {
return "Car{" +
"car=" + name +
'}';
}
}
Kia
public class Kia extends Car{
public Kia(String name) {
super(name);
}
}
Tesla
public class Tesla extends Car{
public Tesla(String name) {
super(name);
}
}
- 제네릭 인자로 부모 클래스를 넘겨줌으로써, CarInfo 클래스에 받을 수 있는 타입은 Car 클래스를 상속받고 있는 Kia와 Tesla이다.
- 하지만 제네릭 인자로 여러가지 객체를 받을 수 있다는 단점이 존재한다. 즉, 제네릭 인자의 타입을 제한해야 한다.
더보기
CarInfo<Integer> carInfo = new CarInfo<>();
바로 위 코드에서와 같이 제네릭 인자로 Car 객체 타입이 아닌 다른 객체 타입으로 넣을 수 있다는 것이다. 즉, 어떤 타입이든 매개 인자로 넘길 수 있다.
5. 제네릭 클래스 타입 제한
제네릭 인자의 타입을 다음과 같이 `<T extends 객체>`로 제한할 수 있다.
public class CarInfo <T extends Car> {
T car;
public void setCar(T car) {
this.car = car;
}
public T getCar() {
return car;
}
}
- 전체(Object 객체)의 범위를 Car 객체로 제한하였다. 따라서 이전 코드에서처럼 Interger라는 객체는 받을 수 없고 Car를 상속받은 Kia 객체와 Tesla 객체만 제네릭 인자로 넘길 수 있게 되었다.
제네릭 메서드
제네릭 메서드는 메서드가 호출될 때 타입을 결정하는 메서드이다.
1. 제네릭 메서드 특징
- 클래스 전체가 제네릭이 아니더라도, 메서드 하나만 제네릭으로 사용할 수 있다.
- 제네릭 클래스와 제네릭 메서드는 별개이다.
- 제네릭 메서드도 제네릭 클래스와 마찬가지로 타입을 제한 할 수 있다. → `<S extends Number>`
2. 제네릭 메서드의 기본 구조
public static <S> S genericMethod(S item) {
System.out.println("item = " + item);
return item;
}
- `S` : 타입 매개변수
- <>는 반환 타입 앞에 넣어준다.
- 제네릭 메서드를 호출할 때, 실제 타입을 넣어주어야 한다. → ex.`클래스명.<Integer>genericMethod(10);`
- 제네릭 메서드의 매개변수와 반환 타입을 통해 제네릭 메서드의 타입을 유추할 수 있게 되어 <Integer>를 생략해도 오류가 생기지 않는다. → ex.`클래스명.genericMethod(10);`
3. 제네릭 메서드 사용 예제
더보기
Main
public class GenericMethodMain {
public static void main(String[] args) {
String[] strArray = {"apple", "banana"};
Integer[] intArray = {1, 2, 3};
String firstStr = Util.getFirst(strArray); // T는 String
Integer firstInt = Util.getFirst(intArray); // T는 Integer
}
}
Util
class Util {
public static <T> T getFirst(T[] array) {
return array[0];
}
}
- 서로 다른 객체 배열이지만, 같은 메서드를 사용할 수 있다.
- 꼭 제네릭 메서드는 static으로 만들어질 필요는 없다. 다만, 그렇게 되면 Util 객체를 생성한 후에 메서드를 호출해야 한다.
'JAVA' 카테고리의 다른 글
[Java] Lv_1 도서관 프로젝트 (0) | 2025.05.01 |
---|---|
[Java] 도전 과제 Lv_2 키오스크 (0) | 2025.04.28 |
[Java] 도전 과제 Lv_1 키오스크 (0) | 2025.04.26 |
[Java] Lv_4 & 5 키오스크 문제 (0) | 2025.04.25 |
[Java] Lv_3 키오스크 (0) | 2025.04.24 |