제네릭의 필요성
다음 코드는 종족 클래스에 종족별 유닛을 생산해서 저장하고 가져오는 것인데, 종족을 표현하는 클래스인 HumanCamp1 클래스와 MachineCamp1 클래스 구조가 똑같음 → 코드가 중복된다는 단점이 있음
class Npc1 {
public String toString() {
return "This is a Npc1.";
}
}
class HumanCamp1 {
private Npc1 unit;
public void set(Npc1 unit) {
this.unit = unit;
}
public Npc1 get() {
return unit;
}
}
class Tank1 {
public String toString() {
return "This is a Tank1.";
}
}
class MachineCamp1 {
private Tank1 unit;
public void set(Tank1 unit) {
this.unit = unit;
}
public Tank1 get() {
return unit;
}
}
public class Ex01_MyGame1
{
public static void main(String[] args)
{
// 게임 종족 생성
HumanCamp1 human = new HumanCamp1();
MachineCamp1 machine = new MachineCamp1();
// 게임 종족에 유닛을 생성해 담기
human.set(new Npc1());
machine.set(new Tank1());
// 게임 종족에서 유닛을 가져오기
Npc1 hUnit = human.get();
Tank1 mUnit = machine.get();
System.out.println(hUnit);
System.out.println(mUnit);
}
}
HumanCamp1 클래스와 MachineCamp1 클래스 구조가 똑같으므로 Camp2로 합치기로 하고 어떤 자식 클래스라도 받아들일 수 있게 매개변수의 자료형을 Object로 만듦
- 그로 인해 객체를 꺼내올 때 형변환이 필요하게 됨
- 꺼낼 때 약간 불편하지만 코드는 잘 동작함
- 하지만 이는 컴파일러의 오류 발견 가능성을 낮추는 결과로 이어짐
class Npc2 {
public String toString() {
return "This is a Npc2.";
}
}
class Tank2 {
public String toString() {
return "This is a Tank2.";
}
}
class Camp2 {
private Object unit;
public void set(Object unit) {
this.unit = unit;
}
public Object get() {
return unit;
}
}
public class Ex02_MyGame2
{
public static void main(String[] args)
{
// 게임 종족 생성
Camp2 human = new Camp2();
Camp2 machine = new Camp2();
// 게임 종족에 유닛을 생성해 담기
// 자식객체를 부모타입의 변수에 대입
human.set(new Npc2());
machine.set(new Tank2());
// 게임 종족에서 유닛을 가져오기
// 꺼낼 때 형변환이 필요함
Npc2 hUnit = (Npc2)human.get();
Tank2 mUnit = (Tank2)machine.get();
System.out.println(hUnit);
System.out.println(mUnit);
}
}
class Npc3 {
public String toString() {
return "This is a Npc3.";
}
}
class Tank3 {
public String toString() {
return "This is a Tank3.";
}
}
class Camp3 {
private Object unit;
public void set(Object unit) {
this.unit = unit;
}
public Object get() {
return unit;
}
}
public class Ex03_MyGame3
{
public static void main(String[] args)
{
// 게임 종족 생성
Camp3 human = new Camp3();
Camp3 machine = new Camp3();
// 게임 종족에 유닛을 생성해 담기
// 우리가 만든 유닛을 넣어야 하는데....
human.set("난 공룡"); // <-- human.set(new String("난 공룡");
machine.set("난 우주인");
// 게임 종족에서 유닛을 가져오기
// 꺼낼 때 당연히 게임 유닛을 기대하는데....
Npc3 hUnit = (Npc3)human.get();
Tank3 mUnit = (Tank3)machine.get();
System.out.println(hUnit);
System.out.println(mUnit);
}
}
우리가 만든 유닛을 생성해서 넣어줘야 하는데 스트링 객체를 생성해서 넣음
- 매개변수가 Object 타입이고, 우리도 객체를 생성해 넣어준 것이기 때문에 문법적으로 오류는 나지 않음
- 하지만 꺼낼 때 에러가 생김
- 꺼내 쓰는 입장에선 당연히 게임 유닛이 들어 있을 거라고 생각하기 때문에 형변환을 하게 되고, 그 때 에러가 나게 됨
class Npc4 {
public String toString() {
return "This is a Npc4.";
}
}
class Tank4 {
public String toString() {
return "This is a Tank4.";
}
}
class Camp4 {
private Object unit;
public void set(Object unit) {
this.unit = unit;
}
public Object get() {
return unit;
}
}
public class Ex04_MyGame4
{
public static void main(String[] args)
{
// 게임 종족 생성
Camp4 human = new Camp4();
Camp4 machine = new Camp4();
// 게임 종족에 유닛을 생성해 담기
// 우리가 만든 유닛을 넣어야 하는데....
human.set("난 공룡");
machine.set("난 우주인");
System.out.println(human.get());
System.out.println(machine.get());
}
}
에러는 발생하지 않았지만 원하는 결과가 아님
이처럼 실행을 할 때 에러가 발생하지 않으면 프로그래머는 코드에 이상이 없다고 생각할 수 있음
이처럼 제네릭을 적용하기 이전의 코드는 객체를 돌려받을 때 형변환을 잊지 말고 해야 한다는 불편함이 있고, 코드 진행상 프로그래머가 실수를 해도 그 실수가 드러나지 않을 수도 있다는 잠재적 위험이 존재함
제네릭 기반의 클래스 정의하기
제네릭은 클래스, 메서드에서 사용할 자료형을 나중에 확정하는 기법
클래스나 메서드를 선언할 때가 아닌 사용할 때, 즉 객체를 생성할 때나 메서드를 호출할 때 정한다는 의미
객체 생성 시 결정이 되는 자료형의 정보를 T로 대체
다이아몬드 연산자 <>를 통해 자료형을 전달함
// 제네릭을 사용하지 않는 코드
class Camp {
private Object unit;
public void set(Object unit) {
this.unit = unit;
}
public Object get() {
return unit;
}
}
// 제네릭을 사용하는 코드
class Camp<T> {
private T unit;
public void set (T unit) {
this.unit = unit;
}
public T get() {
return unit;
}
}
Camp<Npc> human = new Camp<Npc>();
Camp<Npc> human = new Camp<>(); // 뒤쪽은 추론 가능하므로 자바 7부터 생략
T를 Npc로 결정하여 인스턴스 생성
따라서 Npc 또는 Npc를 상속하는 하위 클래스의 인스턴스를 저장할 수 있음
Camp<Tank> machine = new Camp<>();
T를 Tank로 결정하여 인스턴스 생성
따라서 Tank 또는 Tank를 상속하는 하위 클래스의 인스턴스를 저장할 수 있음
▼제네릭 관련 변수 용어
용어 | 대상 |
타입 매개변수(type parameter) | Camp<T>에서 T |
타입 인수(type argument) | Camp<Npc>에서 Npc |
매개변수화 타입(parameterized type) | Camp<Npc> |
타입 매개변수 이름 규칙
▼일반적인 관례
- 보통 한 문자
- 대문자
▼보편적인 선택
E | Element |
K | Key |
N | Number |
T | Type |
V | Value |
제네릭 기반의 코드로 개선한 결과
class Npc5 {
public String toString() {
return "This is a Npc4.";
}
}
class Tank5 {
public String toString() {
return "This is a Tank4.";
}
}
class Camp5<T> {
private T unit;
public void set(T unit) {
this.unit = unit;
}
public T get() {
return unit;
}
}
public class Ex05_MyGameGeneric1
{
public static void main(String[] args)
{
// 게임 종족 생성
Camp5<Npc5> human = new Camp5<>();
Camp5<Tank5> machine = new Camp5<>();
// 게임 종족에 유닛을 생성해 담기
human.set(new Npc5());
machine.set(new Tank5());
// 게임 종족에서 유닛을 가져오기
Npc5 hUnit = human.get();
Tank5 mUnit = machine.get();
System.out.println(hUnit);
System.out.println(mUnit);
}
}
class Npc6 {
public String toString() {
return "This is a Npc4.";
}
}
class Tank6 {
public String toString() {
return "This is a Tank4.";
}
}
class Camp6<T> {
private T unit;
public void set(T unit) {
this.unit = unit;
}
public T get() {
return unit;
}
}
public class Ex06_MyGameGeneric2
{
public static void main(String[] args)
{
// 게임 종족 생성
Camp6<Npc6> human = new Camp6<>();
Camp6<Tank6> machine = new Camp6<>();
// 게임 종족에 유닛을 생성해 담기
human.set(new Npc6());
machine.set("난 공룡");
// 게임 종족에서 유닛을 가져오기
Npc6 hUnit = human.get();
Tank6 mUnit = machine.get();
System.out.println(hUnit);
System.out.println(mUnit);
}
}
이제 타입 인수로 지정한 클래스형 외에 다른 형의 객체는 대입할 수 없음
제네릭의 장점
- 중복된 코드의 결합 & 간소화
- 데이터를 가져올 때 형변환 없이 가져올 수 있음
- 데이터 대입 시 다른 자료형이 대입되는 것 방지 → 강한 자료형 체크
매개변수가 여러 개일 때 제네릭 클래스의 정의
class Camp7<T1, T2>
{
private T1 param1;
private T2 param2;
public void set(T1 o1, T2 o2)
{
param1 = o1;
param2 = o2;
}
public String toString()
{
return param1 + " & " + param2;
}
}
public class Ex07_MultiParameter
{
public static void main(String[] args)
{
Camp7<String, Integer> camp = new Camp7<>();
camp.set("Apple", 25);
System.out.println(camp);
}
}
제네릭 클래스의 매개변수 타입 제한하기
상속 관계를 표시하여 매개변수의 타입을 제한할 수 있음
class Box<T extends Number> {...}
인스턴스 생성 시 타입 인수로 Number 또는 이를 상속하는 클래스만 올 수 있게 설정한 것
이렇게 하면 Number에서 상속받은 메서드를 안전하게 사용할 수 있음
// 매개변수 타입을 제한하지 않은 경우
class Camp<T> {
private T ob;
......
public int toIntValue() {
return ob.intValue(); // Error!
}
}
// 매개변수 타입을 제한하는 경우
class Camp<T extends Number> {
private T ob;
......
public int toIntValue() {
return ob.intValue(); // Ok
}
}
위의 코드는 아무 자료형이나 들어올 수 있기에 래퍼 클래스의 메서드를 호출하면 에러가 발생함
그러나 아래처럼 제네릭에 지정할 수 있는 자료형을 Number를 상속받은 래퍼 타입만으로 한정한다면 intValue() 메서드를 사용할 때 에러 걱정을 할 필요가 없게 됨
class Camp8<T extends Number>
{
private T ob;
public void set(T o) {
ob = o;
}
public T get() {
return ob;
}
}
public class Ex08_BoundedCamp
{
public static void main(String[] args)
{
Camp8<Integer> iBox = new Camp8<>();
iBox.set(24);
Camp8<Double> dBox = new Camp8<>();
dBox.set(5.97);
System.out.println(iBox.get());
System.out.println(dBox.get());
}
}
제네릭 메서드의 정의
클래스 전부가 아닌 메서드 하나에 대해서도 제네릭으로 정의할 수 있음
class MyData
{
public static <T> T showData(T data)
{
if (data instanceof String)
System.out.println("String");
else if (data instanceof Integer)
System.out.println("Integer);
else if (data instanceof Double)
System.out.println("Double");
return data;
}
}
제네릭 메서드의 T는 메서드 호출 시점에 결정됨
MyData.<String>showData("Hello World!");
다음과 같이 타입 인수 생략이 가능함
생략된 인수는 매개변수로 들어온 데이터의 자료형으로 추론하게 됨
MyData.showData(1);
class MyData
{
public static <T> T showData(T data)
{
if (data instanceof String)
System.out.println("String");
else if (data instanceof Integer)
System.out.println("Integer");
else if (data instanceof Double)
System.out.println("Double");
return data;
}
}
public class Ex09_GenericMethod
{
public static void main(String[] args)
{
MyData.<String>showData("Hello World");
MyData.showData(1); // <Integer> 생략
MyData.showData(1.0); // <Double> 생략
}
}
'JAVA > 이재환의 자바 프로그래밍 입문' 카테고리의 다른 글
[Java] Ch.22 내부 클래스, 람다식 (0) | 2023.10.25 |
---|---|
[Java] Ch.21 컬렉션 프레임워크 (0) | 2023.10.25 |
[Java] Ch.19 정렬 알고리즘 만들기 (0) | 2023.10.23 |
[Java] Ch.18 열거형, 가변 인수, 어노테이션 (1) | 2023.10.23 |
[Java] Ch.17 자바의 기본 클래스 (0) | 2023.10.23 |