package genegic;
//슷자를 하나 넣고 뺄 수 있는 값
public class IntegerBox {
private Integer value;
public void set(Integer value) {
this.value = value;
}
public Integer get() {
return value;
}
}
set 메서드로 value값을 받아온다.
package genegic;
public class BoxMain {
public static void main(String[] args) {
IntegerBox integerBox = new IntegerBox();
integerBox.set(10); //오토박싱
Integer integer = integerBox.get();
System.out.println(integer);
StringBox stringBox = new StringBox();
stringBox.setvalue("Hello");
String str = stringBox.get();
System.out.println(stringBox.get());
System.out.println(str);
}
}
10
Hello
Hello
이렇게 사용하다가, 다음에 Boolean, Double 타입의 박스가 필요하면 각각의 타입별로 이 클래스들을 새로 만들어야 한다.
이 문제를 어떻게 해결할까?
다형성을 통한 중복 해결 시도
Object는 모든 타입의 부모이다. 따라서 다형성을 사용하여 문제를 해결할 수 있다.
package genegic;
public class ObjectBox {
private Object value;
public void set(Object object) {
this.value = object;
}
public Object get() {
return value;
}
}
package genegic;
public class BoxMain {
public static void main(String[] args) {
//ObjectBox사용해서 하기
ObjectBox objectBox = new ObjectBox();
objectBox.set(10);
Integer integer = (Integer) objectBox.get();
System.out.println(integer);
}
}
Object 에서 String 으로 변환하여 사용하는 것
**잘못된 타입의 인수전달 문제 **
set(Object)메서드는 모든 타입의 부모인 Object를 매개변수로 받기 때문에 어떤 데이터도 입력받을 수 있다. 따라서 이렇게 원하던 문자열을 입력해도 문제가 되지 않는다.
하지만, 잘못된 타입의 값을 전달하면 값을 꺼낼 때 문제가 발생한다.
integer.ser("Hello");
Integer result = (Integer) integerBox.get();
Integer result = (Integer) "Hello";
Integer result = (Integer) "Hello"; //예외 발생 String을 Integer로 캐스팅할 수 없다.
숫자를 기대했는데 실수로 문자열을 넣어서 원하지 않는 타입을 반환받을 수 없게 된다.
따라서 이러한 방식은 타입 안전성이 떨어진다.
** 반환 타입이 맞지 않는 문제 **
하지만 이렇게 하면 문제가 있는게, int값을 넣어야하는 것을 String 값으로 넣는 것이다.
Object.Box.set("안녕하세요") 같이 넣었을 때는 컴파일 오류가 발생할 수 있다.
Object는 부모이기 때문에 Object 는 Integer 타입으로 직접 다운 캐스팅해야 한다. String 도 마찬가지이다.
Object obj =integerBox.get();
//Integer안에 Object를 넣는 것은 불가능하다
따라서, (Integer)타입 캐스팅 코드를 넣어서 Object 타입을 Integer 타입으로 직접 다운 캐스팅해야한다.
Integer integer = (Integer) integerBox.get()
//get 하여 object value 가 나온다
Integer integer = (Integer) (Object)value
Integer integer = (Integer) value
//다운캐스팅하여 Object타입을 Integer로 다운캐스팅한다
이러한 두 문제점을 해결하기 위해서 제네릭을 사용한다.
제네릭 사용
<>를 사용한 클래스를 제네릭 클래스라고 한다. <> 기호를 보통 다이아몬드라고 한다.
제네릭 클래스를 사용할 때는 Integer,String 같은 타입을 미리 결정하지 않는다.
대신에 클래스명 오른쪽에 <T>와 같이 선언하면 제네릭 클래스가 된다.
여기서 T를 타입 매개변수라고 한다. 이 타입 매개변수는 이후에 Integer, String 으로 변할 수 있다.
그리고 클래스 내부에 T 타입이 필요한 곳에 T value와 같이 타입 매개변수를 적어두면 된다.
package genegic.ex1;
public class GenericBox<T> {
private T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
꼭 T를 사용하지 않아도 된다.
package genegic.ex1;
public class GenericMain {
public static void main(String[] args) {
GenericBox<Integer> integerBox = new GenericBox<Integer>(); //<> 통해 T의 타입 결정
integerBox.set(10);
//Integer 타입만 허용, 컴파일 오류
integerBox.set("안녕하세요");
}
}
이전에 하나하나 객체 만들어서 오토박싱했을 때랑 비교해보면
package genegic;
public class BoxMain {
public static void main(String[] args) {
IntegerBox integerBox = new IntegerBox();
integerBox.set(10); //오토박싱
Integer integer = integerBox.get();
System.out.println(integer);
StringBox stringBox = new StringBox();
stringBox.setvalue("Hello");
String str = stringBox.get();
System.out.println(stringBox.get());
System.out.println(str);
}
}
코드가 좀 줄어든걸로 보인다
package genegic.ex1;
public class GenericMain {
public static void main(String[] args) {
GenericBox<Integer> integerBox = new GenericBox<Integer>(); //<> 통해 T의 타입 결정
integerBox.set(10);
//Integer 타입만 허용, 컴파일 오류
// integerBox.set("안녕하세요");
//원하는 모든 타입 사용 가능
GenericBox<Double> doubleBox = new GenericBox<Double>();
doubleBox.set(3.14);
Double d = doubleBox.get();
System.out.println(d);
}
}
두번 적는걸을 생략하는 것도 가능하다 //이름 타입 추론이라고 한다
GenericBox<Double> doubleBox = new GenericBox<>();
당연하게 double일 것이라고 생각하여 뒤에는 생략해도 된다.
이로 인해 코드 재사용과 타입 안정성이라는 두 이점을 잡을 수 있다.
제네릭 활용 사례
public class ResultResponse<T> {
private boolean success;
private String code;
private String message;
private T data;
public static <T> ResultResponse<T> ok(T data) { return new ResultResponse<>(true, “200”, null, data); }
public static <T> ResultResponse<T> fail(String message) {
return new ResultResponse<>(false, “500”, message, null);
}
}
public ResultResponse<AbcDto> search() {
return ResultRespon
- ResultResponse<T>: 제네릭 클래스로, T라는 타입을 사용합니다.
T는 실제 사용될 때 지정되며, 다양한 데이터 타입을 저장할 수 있습니다. - success: 요청이 성공했는지 여부 (true 또는 false)
- code: 상태 코드 (200, 500 등)
- message: 오류 메시지 등을 저장할 문자열
- data: 응답 데이터 (T 타입)
1. 응답을 만드는 메서드
public static <T> ResultResponse<T> ok(T data) {
return new ResultResponse<>(true, "200", null, data);
}
- 성공(success = true)한 응답을 생성합니다.
- HTTP 응답 코드 "200" (성공)을 설정합니다.
- data(응답 데이터)를 받아서 저장합니다.
public static <T> ResultResponse<T> fail(String message) {
return new ResultResponse<>(false, "500", message, null);
}
- 실패(success = false)한 응답을 생성합니다.
- HTTP 응답 코드 "500" (서버 오류)을 설정합니다.
- 실패 원인을 설명하는 message를 저장하고, data는 null로 설정합니다.
제네릭을 사용하지 않을 경우에는 어떻게 구성하게 될까?
public class AbcResponse {
private boolean success;
private String code;
private String message;
private AbcDto data;
}
public class XyzResponse {
private boolean success;
private String code;
private String message;
private XyzDto data;
}
⚠️ 문제점
- 데이터 타입이 달라질 때마다 클래스를 새로 만들어야 해서 중복 코드가 많아짐
- 관리가 어렵고 유지보수가 힘듦
✅ 제네릭을 사용하면
- T 타입을 활용하여 하나의 클래스로 여러 데이터를 처리할 수 있음
- 코드가 짧고 재사용 가능
'JAVA' 카테고리의 다른 글
[Java] 지역 클래스 (0) | 2025.02.15 |
---|---|
[Java] Thread (2) | 2025.02.11 |
[Java] 중첩 클래스, 내부 클래스 (0) | 2025.02.09 |
[Java] 문자열과 타입 안전성 (0) | 2025.02.08 |
[Java] 다시 기본 복습(1) (0) | 2025.01.26 |