ㅁ 제네릭은 자바 버전 5.0에서 새로 추가된 문법이다.
제네릭은 자바 컬렉션 프레임워크를 이해하기 위한 필수 개념이기도 하다.
ㅁ 제네릭 클래스의 이해와 설계
- 제네릭은 '일반화'한다는 뜻을 담고 있다. 그리고 그 일반화의 대상은 자료형이다.
- 어떠한 자료형을 기반으로도 인스턴스의 생성이 가능하도록, 자료형에 일반적인 클래스를 정의하는 문법이다.
- 자바에서 제네릭(Generics)은 클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법을 의미한다.
객체별로 다른 타입의 자료가 저장될 수 있도록 한다.
ㅁ AppleBox와 OrangeBox 클래스의 설계
- 사과를 의미하는 Apple 클래스와 오렌지를 의미하는 Orange 클래스는 이미 정의되어 있다고 가정한다.
class AppleBox {
Apple item;
public void store(Apple item) { this.item = item; }
public Apple pullOut() { return item; }
}
class OrangeBox {
Orange item;
public void store(Orange item) { this.item = item; }
public Orange pullOut() { return item; }
}
- 이렇게 일일이 정의하지 않고, Object 클래스를 기반으로 하나만 정의하면?
class FruitBox {
Object item;
public void store(Object item) { this.item = item; }
public Object pullOut() { return item; }
}
이렇게 하면 Apple과 Orange뿐 아니라, Lemon과 Banana도 저장할 수 있다.
그런데 이렇게 Object를 기반으로 정의하지 않는 이유가 있다.
- OrangeBaseFruitBox.java
class Orange {
int sugarContent;
public Orange(int sugar) { sugarContent = sugar; }
public void showSugarContent() { System.out.println("당도 " + sugarContent); }
}
class FruitBox {
Object item;
public void store(Object item) { this.item = item; }
public Object pullOut() { return item; }
}
class ObjectBaseFruitBox {
public static void main(String[] args) {
FruitBox fBox1 = new FruitBox();
fBox1.store(new Orange(10) );
Orange org1 = (Orange) fBox1.pullOut();
org1.showSugarContent();
FruitBox fBox2 = new FruitBox();
fBox2.store("오렌지"); // (1)
Orange org2 = (Orange) fBox2.pullOut(); // (2)
org2.showSugarContent();
}
}
[실행 결과]
당도 10
Exception in thread "main" java.lang.ClassCastException: class java.lang.String cannot be cast to class com.br.run.Orange (java.lang.String is in module java.base of loader 'bootstrap'; com.br.run.Orange is in unnamed module of loader 'app')
at com.br.run.ObjectBaseFruitBox.main(OrangeBaseFruitBox.java:51)
- (1) 문법적으로는 문제가 없지만, FruitBox는 과일을 담기 위해 정의한 클래스지, 문자열을 담기 위해 정의된 클래스가 아니다. 그런데도 이 부분에서 컴파일 오류가 발생하지 않는다.
컴파일 오류가 발생했다면 이 문제를 쉽게 알 수 있었다.
- (2) 여기서 예외 발생. 다음 줄은 실행되지 않는다.
- 실행 결과를 보면 컴파일 과정에서 발견하지 못했던 오류로 인해서 예외가 발생했다.
ㅁ OrangeBox를 기반으로 예제 변경
class Orange {
int sugarContent;
public Orange(int sugar) { sugarContent = sugar; }
public void showSugarContent() { System.out.println("당도 " + sugarContent); }
}
class OrangeBox {
Orange item;
public void store(Orange item) { this.item = item; }
public Orange pullOut() { return item; }
}
class ObjectBaseOrangeBox {
public static void main(String[] args) {
OrangeBox fBox1 = new OrangeBox();
fBox1.store(new Orange(10) );
Orange org1 = fBox1.pullOut();
org1.showSugarContent();
OrangeBox fBox2 = new OrangeBox ();
fBox2.store("오렌지"); // (1)
Orange org2 = fBox2.pullOut(); // (2)
org2.showSugarContent();
}
}
[컴파일 결과] javac OrangeBaseFruitBox.java -encoding UTF-8
OrangeBaseOrangeBox.java:45: error: incompatible types: String cannot be converted to Orange
fBox2.store("오렌지");
^
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error
- 이렇듯 컴파일 과정에서 발견되는 오류는 매우 쉽게 해결이 가능하다.
그러나 앞서 보인 예제와 같이 실행 과정에서 발생하는 오류는 찾기 쉽지 않다.
별 차이 없어보이지만, 프로그램의 규모가 크면 클수록 이 둘의 차이는 매우 극명하게 드러난다.
따라서 실행과정에서 발견되는 오류를 컴파일 과정에서 발견되도록 코드를 작성하는 것은 매우 의미 있는 일이다.
- OrangeBox 클래스를 기반으로 작성된, 위 예제의 장점은 자료형에 대한 안정성이 보장된다는 것이다.
자료형의 불일치로 발생하는 문제가 컴파일 과정에서 발견되기 때문에 자료형에 대한 안정성이 보장된다고 표현한다.
- 그러나 단점은 저장할 대상이 Orange, Apple, Banana, Lemon인 상황에서 자료형에 대한 안정성을 보장받으려면 OrangeBox, AppleBox, BananBox, LemonBox를 각각 정의해야 한다.
- 이러한 단점을 제거하기 위해 정의된 문법적 요소가 바로 제네릭 Generics이다.
- 첫 번째 예제에서는 FruitBox가 모든 종류의 객체를 다룰 수 있기 때문에 컴파일 오류가 발생하지 않았습니다.
- 두 번째 예제에서는 OrangeBox가 Orange 객체만 다룰 수 있기 때문에 String 객체를 저장하려고 하면 컴파일러가 오류를 발생시킵니다.
ㅁ 제네릭 클래스의 설계
-
class FruitBox {
Object item;
public void store(Object item) { this.item = item; }
public Object pullOut() { return item; }
}
회색 음영처리된 부분을 저장의 대상이 Apple이면 Apple로, Orange면 Orange로 프로그램 실행 중에 변경할 수 있다면, 자료형에 안전한 클래스로 다양한 상황에서 사용할 수 있다.
class FruitBox <T> {
T item;
public void store(T item) { this.item = item; }
public T pullOut() { return item; }
}
T라는 이름은 type의 약자를 대문자로 표현한 것인데, 이 이름이 무엇이 되든 상관은 없다.
- class FruitBox <T>
이 클래스의 인스턴스를 생성하려면 자료형 정보를 인자로 전달해야 한다.
전달되는 인자는 클래스 내에 존재하는 T를 대체해서 인스턴스가 생성된다.
- 인스턴스 생성과정에서 자료형 정보의 전달방법
FruitBox <Orange> orBox = new FuritBox <Orange> ();
FruitBox <Apple> apBox = new FuritBox <Apple> ();
- GenericBaseFruitBox.java
class Orange {
int sugarContent;
public Orange(int sugar) { sugarContent = sugar; }
public void showSugarContent() { System.out.println("당도 " + sugarContent); }
}
class Apple {
int weight;
public Apple(int weight) { this.weight = weight; }
public void showAppleWeight() { System.out.println("무게 " + weight); }
}
class FruitBox <T> {
T item;
public void store(T item) { this.item = item; }
public T pullOut() { return item; }
}
class GenericBaseFruitBox {
public static void main(String[] args) {
FruitBox<Orange> orBox = new FruitBox<Orange> ();
orBox.store(new Orange(10) );
Orange org = orBox.pullOut();
org.showSugarContent();
FruitBox<Apple> apBox = new FruitBox<Apple> ();
apBox.store(new Apple(20) );
Apple app = apBox.pullOut();
app.showAppleWeight();
}
}
[실행 결과]
당도 10
무게 20
- 제네릭은 하나의 클래스 정의로 둘 이상의 클래스를 정의한 효과가 있다.
앞서 말한 단점(자료형이 일치하지 않아도 컴파일 과정에서 오류가 발생하지 않는 단점)도 해결이 되었다.
다른 형태의 참조값이 전달되면 컴파일 과정에서 에러가 발생한다.
- 변형. FruitBox <T> 클래스에 생성자를 추가.
public FruitBox(Object obj) {
item = (T) obj;
}
FruitBox<Orange> orBox = new FruitBox<Orange> (new Orange(10));
Orange org = orBox.pullOut();
org.showSugarContent();
FruitBox<Apple> apBox = new FruitBox<Apple> (new Apple(20));
Apple app = apBox.pullOut();
app.showAppleWeight();
- 위는 내가 한 거고, 아래는 답. 이게 더 취지에 맞는 듯.
public FruitBox(T item) {
this.item = item;
}
ㅁ
- 제네릭을 기반으로 클래스를 정의해서 실무에 직접 활용해 볼 확률은 생각보다 매우 적다.
제네릭을 고려한 클래스의 설계에는 몇 배의 시간과 노력이 필요하고 이는 곧 비용이기 때문.
- 제네릭을 공부하는 또다른 이유는 자바에서 제공하는 라이브러리(프로그래머가 쉽게 가져다 쓸 수 있는 클래스, 메소드의 묶음) 성격의 클래스를 활용하기 위함이다. 라이브러리에 제네릭 기반으로 이루어진 클래스와 인터페이스가 많다.
ㅁ 제네릭 메소드의 정의와 호출
- IntroGenricMethod.java
class AAA {
public String toString() { return "Class AAA"; }
}
class BBB {
public String toString() { return "Class BBB"; }
}
class InstanceTypeShower {
int showCnt = 0;
public <T> void showInstType(T inst) {
System.out.println(inst);
showCnt++;
}
void showPrintCnt() { System.out.println("Show count : " + showCnt); }
}
class IntroGenericMethod {
public static void main(String[] args) {
AAA aaa = new AAA();
BBB bbb = new BBB();
InstanceTypeShower shower = new InstanceTypeShower();
shower.<AAA>showInstType(aaa);
shower.<BBB>showInstType(bbb);
shower.showPrintCnt();
}
}
[실행 결과]
Class AAA
Class BBB
Show count : 2
- 제네릭 메소드 간단히 호출 가능.
shower.<AAA>showInstType(aaa); 대신 shower.showInstType(aaa); 가능하고 이것이 일반적인 호출방식.
shower.<BBB>showInstType(bbb); 대신 shower.showInstType(bbb); 가능하고 이것이 일반적인 호출방식.
ㅁ 제네릭 메소드에 둘 이상의 자료형 매개변수를 사용하는 것도 가능하다.
- IntroGenricMethod2.java
class AAA {
public String toString() { return "Class AAA"; }
}
class BBB {
public String toString() { return "Class BBB"; }
}
Class InstanceTypeShower2 {
public <T, U> void showInstType(T inst1, U inst2) {
System.out.println(inst1);
System.out.println(inst2);
}
}
class IntroGenericMethod2 {
public static void main(String[] args) {
AAA aaa = new AAA();
BBB bbb = new BBB();
InstanceTypeShower2 shower = new InstanceTypeShower2();
shower.<AAA, BBB> showInstType(aaa, bbb);
shower.showInstType(aaa, bbb);
}
}
ㅁ 제네릭 메소드뿐 아니라, 제네릭 클래스에도 둘 이상의 자료형 매개변수를 사용할 수 있다.
class GenericTwoParam<T, U> {
T item1;
U item2;
public void setItem1(T item) {
item1 = item;
}
public ovid setItem2(U item) {
item2 = item;
}
}
ㅁ 문제
- 아래와 같이 정의된 클래스는 컴파일 시 에러가 발생한다. 이유는?
class MyClass {
public <T> void simpleMethod(T param) {
param.showData();
Systme.out.println(param);
}
}
- 제네릭 클래스, 제네릭 메소드는 어떤 자료형이든지 실행이 가능하도록 정의되어야 컴파일 에러가 발생하지 않는다.
- 위 클래스에서는 매개변수 param이 참조하는 인스턴스에 showData라는 메소드가 존재해야만 실행이 가능하다.
즉 모든 자료형을 기반으로 실행 가능한 문장이 아니기 때문에 컴파일 에러가 발생한다.
- 반면 Systme.out.println(param);는 컴파일이 가능하다.
(1) println 메소드는 Object형 참조변수를 인자로 전달받는다.
모든 클래스는 Object 클래스를 상속하기 때문에 println 메소드로의 인자 전달은 언제나 성립한다.
※ 기본 자료형(primitive types) 값들은 Object로 바로 전달할 수 없기 때문에, Java에서는 이를 자동으로 해당 기본 자료형에 대응하는 Wrapper 클래스로 변환합니다. 예를 들어, int 값은 Integer로, double 값은 Double로 변환됩니다. 이 과정을 자바에서는 "오토박싱(autoboxing)"이라고 합니다.
(2) println 메소드는 인스턴스의 toString 메소드를 호출해서 반환되는 문자열을 출력한다.
toString 메소드 역시 Object 클래스에 정의되어 있으므로 언제나 호출 가능하다.
ㅁ 매개변수 자료형 제한
- 기본적으로 제네릭 메소드 내에서는 제네릭으로 선언된 참조변수를 통해서 Object 클래스에 정의된 메소드만 호출이 가능하다.
이는 모든 자료형을 기반으로 실행이 가능하도록 하기 위함인데, 때로는 이러한 제한이 불편하게 느껴질 수 있다.
(모든 자료형 클래스들은 Object 클래스를 상속받기 때문에 Object 클래스의 메소드는 다 사용 가능)
- BoundedTypeParam.java
interface SimpleInterface {
public void showYourName();
}
class UpperClass {
public void showYourAncestor() {
System.out.println("UpperClass");
}
}
class AAA extends UpperClass implements SimpleInterface {
public void showYourName() {
System.out.println("Class AAA");
}
}
class BBB extends UpperClass implements SimpleInterface {
public void showYourName() {
System.out.println("Class BBB");
}
}
class BoundedTypeParam {
public static <T> void showInstanceAncestor(T param) {
( (SimpleInterface)param ).showYourName();
}
public static <T> void showInstanceName(T param) {
( (UpperClass)param ).showYourAncestor();
}
public static void main(String[] args) {
AAA aaa = new AAA();
BBB bbb = new BBB();
showInstanceAncestor(aaa);
showInstanceName(aaa);
showInstanceAncestor(bbb);
showInstanceName(bbb);
}
}
- 위 예제에서
( (SimpleInterface)param ).showYourName(); 대신 param.showYourName();
( (UpperClass)param ).showYourAncestor(); 대신 param.showYourAncestor();
를 쓰면 The method showYourAncestor() is undefined for the type T라는 예외가 발생함.
제네릭 메서드는 제네릭 타입 T를 받아와서 param.showYourName()과 param.showYourAncestor()를 호출하고 있지만, 컴파일러는 T가 어떤 타입인지 알 수 없기 때문에 showYourAncestor() 메서드를 찾을 수 없다는 오류가 발생합니다.
제네릭 타입 T가 UpperClass를 상속받는 클래스임을 보장할 수 없기 때문에 이러한 오류가 발생하는 것입니다.
- 위 예제에서는 매개변수 param을 강제 형변환하고 있다.
제네릭 매개변수로는 Object 클래스에 정의된 메소드만 호출 가능하기 때문이다.
(모든 자료형을 기반으로 실행이 가능해야 하는데 showYourXXX() 메소드들이 없을 수도 있기 때문에 오류 발생)
- 위 예제에 SimpleInterface 인터페이스를 구현하지 않은 클래스의 인스턴스나 UpperClass를 상속하지 않은 클래스의 인스턴스의 참조값이 메소드에 전달될 때 컴파일은 되지만, 실행 시점에서 ClassCastException이 발생한다. 이는 해당 객체가 메소드에서 요구하는 인터페이스를 구현하지 않았거나 상속 관계가 없어서 형변환이 실패하기 때문입니다.
ㅁ 그래서 자바는 제네릭 매개변수의 자료형에 제한을 둘 수 있는 문법적 요소를 제공한다. (타입 파라미터 제한)
- BoundedTypeParam2.java
interface SimpleInterface {
public void showYourName();
}
class UpperClass {
public void showYourAncestor() {
System.out.println("UpperClass");
}
}
class AAA extends UpperClass implements SimpleInterface {
public void showYourName() {
System.out.println("Class AAA");
}
}
class BBB extends UpperClass implements SimpleInterface {
public void showYourName() {
System.out.println("Class BBB");
}
}
class BoundedTypeParam2 {
public static <T extends SimpleInterface> void showInstanceAncestor(T param) {
param.showYourName();
}
public static <T extends UpperClass> void showInstanceName(T param) {
param.showYourAncestor();
}
public static void main(String[] args) {
AAA aaa = new AAA();
BBB bbb = new BBB();
showInstanceAncestor(aaa);
showInstanceName(aaa);
showInstanceAncestor(bbb);
showInstanceName(bbb);
}
}
- 클래스의 상속에는 extends를, 인터페이스의 구현에는 implements를 사용하지만 제네릭의 자료형 제한에는 클래스와 인터페이스를 구분하지 않고 두 경우 모두 키워드 extends를 사용한다.
- <T> 대신 <T extends SimpleInterface>가 삽입되었다. 이는 T가 SimpleInterface를 상속(SimpleInterface가 클래스인 경우) 또는 구현(SimpleInterface가 인터페이스인 경우)하는 클래스의 자료형이 되어야 함을 명시하는 문법이다.
- T를 SimpleInterface를 구현하는 클래스로 제한했기 때문에 이 인터페이스에 정의되어 있는 메소드의 호출이 가능하게 되었다.
- 실행결과는 전 예제와 동일.
- showInstanceAncestor 메소드의 인자로 SimpleInterface를 구현하지 않는 인스턴스의 참조값이 전달되거나,
showInstanceName 메소드의 인자로 UpperClass를 상속하지 않는 인스턴스의 참조값이 전달되면 컴파일 에러가 발생하기 때문에, 자료형에 안전한 구조는 유지된다.
ㅁ 제네릭 메소드와 배열
- 배열도 인스턴스이므로 제네릭 매개변수에 전달이 가능하다.
그러나 배열의 경우 다음 예제에서 보이는 방식으로 처리를 해야 배열의 특성을 적극 활용할 수 있다.
- IntroGenericArray.java
class IntroGenericArray {
public static <T> void showArrayData( T[] arr ) {
for(int i=0; i<arr.length; i++) {
System.out.println(arr[i]);
}
}
public static void main(String[] args) {
String[] stArr = new String[] {
"Hi!",
"I'm so happy",
"Java Generic Progamming"
};
showArrayData(stArr);
}
}
- 매개변수 선언이 ( T[] arr )이므로 배열의 참조값이 전달된다.
매개변수로 전달되는 대상을 배열 인스턴스로 제한한 것이다.
- showArrayData()에서 배열의 인스턴스 변수 length에 접근하고, 배열에 저장된 데이터의 참조를 위한 []연산이 진행되고 있다. 이것이 가능한 이유는 매개변수로 전달되는 대상을 배열 인스턴스로 제한했기 때문이다.
- 배열의 제네릭 매개변수 선언을 T[] arr로 한 것만으로도 매개변수에 전달되는 대상을 배열의 인스턴스로 제한할 수 있다.
이렇게 함으로써 참조변수 arr을 통한 인스턴스 멤버 length의 접근 및 [] 연산이 가능해진다.
ㅁ 예상과는 다른 제네릭 변수의 참조와 상속의 관계
- public void hiMethod(Apple param) { ... }
이 메소드의 정의를 보고 매개변수로 전달될 수 있는 대상의 범위를 생각해 보자.
매개변수의 자료형이 Apple이니, Apple의 인스턴스 또는 Apple을 상속하는 인스턴스의 참조값이 매개변수에 전달될 수 있다.
- public void ohMethod(FruitBox<Fruit> param) { ... }
매개변수 선언에서 보이는 그대로 FruitBox<Fruit> 인스턴스의 참조값이 전달 대상이 될 수 있다.
그렇다면 Apple이 Fruit을 상속하는 관계라면?
Apple 클래스가 Fruit 클래스를 상속하는 경우에, FruitBox<Apple> 인스턴스의 참조값이 위의 ohMethod의 매개변수에 전달될 수 있는가? 답은 NO.
※ 참고 - (제네릭 클래스) 인스턴스 생성과정에서 자료형 정보의 전달방법
class Fruit { }
class Apple extends Fruit { }
class FruitBox <T> { }
FruitBox <Fruit> fruBox = new FuritBox <Fruit> ();
FruitBox <Apple> apBox = new FuritBox <Apple> ();
Fruit과 Apple이 상속관계에 놓여있다고 해서 FruitBox<Fruit>과 FruitBox<Apple>이 상속관계에 놓이는 것은 아니다.
상속관계에 놓이려면 클래스가 정의되는 과정에서 키워드 extends를 통해서 상속됨이 명시되어야 한다.
ㅁ 와일드카드와 제네릭 변수의 선언
- Apple 클래스가 Fruit 클래스를 상속하는 관계에서, FruitBox<Fruit> 인스턴스의 참조값도, FruitBox<Apple> 인스턴스의 참조값도 인자로 전달받을 수 있는 매개변수의 선언은 와일드카드를 이용해 자료형을 명시하면 된다.
- 참고로 와일드카드는 이름 또는 문자열에 제한을 가하지 않음을 명시하는 용도로 사용되는 특별한 기호를 말한다.
예를 들어 명령 프롬포트상에서 다음과 같이 명령을 내리면 확장자가 .class로 끝나는 모든 파일의 이름이 출력된다.
C:\MyCode>dir *.class
여기서는 기호 *가 파일의 이름을 명시하는데 있어서 와일드카드로 사용되었다.
- 자바는 클래스 이름을 명시하는데 있어서 와일드카드로 사용되는 기호 ?를 정의하고 있다.
그리고 이를 기반으로 다음과 같이 변수 또는 매개변수가 선언될 수 있도록 하고 있다.
FruitBox <? extends Fruit> box1 = new FruitBox<Fruit> ();
FruitBox <? extends Fruit> box2 = new FruitBox<Apple> ();
여기서 <? extneds Fruit>가 의미하는 바는 즉 자료형을 결정짓는 제네릭 매개변수 T에
"Fruit을 상속하는 모든 클래스"이다. Fruit 클래스를 포함하여, Fruit을 상속하는 클래스면 무엇이든 올 수 있다.
따라서 참조변수 box1과 box2는 다음의 형태로 생성되는 인스턴스면 무엇이든 참조 가능하다.
new FruitBox <'Fruit 클래스, 또는 Fruit을 상속하는 클래스의 이름'> ()
- IntroWildCard.java
class Fruit {
public void showYou() {
System.out.println("난 과일입니다.");
}
class Apple extends Fruit {
public void showYou() {
super.showYou();
System.out.println("난 붉은 과일입니다.");
}
}
class FruitBox <T> {
T item;
public void store(T item) { this.item = item; }
public T pullOut() { return item; }
}
class IntroWildCard {
public static void openAndShowFruitBox(FruitBox <? extends Fruit> box) {
Fruit fruit = box.pullOut();
fruit.showYou();
}
public static void main(String[] args) {
FruitBox <Fruit> box1 = new FruitBox <Fruit> ();
box1.store(new Fruit());
FruitBox <Apple> box2 = new FruitBox <Apple> ();
box2.store(new Apple());
openAndShowFruitBox(box1);
openAndShowFruitBox(box2);
}
}
[실행 결과]
난 과일입니다.
난 과일입니다.
난 붉은 과일입니다.
- 참고로 전달되는 자료형에 상관없이 FruitBox <T>의 인스턴스를 참조하려면 다음과 같이 참조변수를 선언하면 된다.
FruitBox <?> box;
그리고 모든 인스턴스는 Object 클래스를 상속하니 위의 선언을 대신해서 다음과 같이 선언해도 된다.
(이 두 선언은 차이가 없는 동일한 선언이다.)
FruitBox <? extends Object> box;
ㅁ 하위 클래스를 제한하는 용도의 와일드카드
- 참조변수 선언 시 와일드카드를 기반으로 다음과 같은 선언도 가능하다.
FruitBox <? super Apple> boundedBox;
- extends는 "~을 상속하는 클래스라면 무엇이든지"
- super는 반대. "~이 상속하는 클래스라면 무엇이든지"
- 즉 위에 선언된 참조변수 boundedBox는 FruitBox <T>의 인스턴스를 참조하되, T가 Apple 클래스 또는 Apple 클래스가 직간접적으로 상속하는 클래스인 경우에만 참조할 수 있다.
( class FruitBox <T>가 이미 있는 듯?)
- 예를 들어 Apple 클래스가 Fruit 클래스를 상속한다면, 위의 boundedBox가 참조할 수 있는 인스턴스의 자료형은 FruitBox<Object>, FruitBox<Fruit>, FruitBox<Apple> 세 가지 이다.
- 이건 이정도만. 나중에 API 문서를 보다가 이와 관련된 내용을 만났을 때 API 문서에서 말하는 바를 이해할 수 있는 정도면 충분하다.
ㅁ 제네릭 클래스를 상속하는 다양한 방법
- 이번에 설명하는 내용과 다음에 설명하는 "제네릭 인터페이스를 구현하는 두 가지 방법"에 대해서는 지금 당장 공부하지 않아도 좋다. 나중에 궁금해지는 때가 오면 그 때 공부해도 된다.
- 제네릭으로 정의된 클래스의 상속에 대한 설명.
class AAA <T> {
T itemAAA;
}
이 때 이를 상속하는 BBB 클래스는 다음과 같이 정의하면 된다.
class BBB <T> extends AAA <T> {
T itemBBB;
}
이렇게 상속이 되면, 하나의 자료형 정보로 인해서 AAA의 자료형과 BBB의 자료형이 모두 결정된다.
즉 다음과 같이 문장을 구성하면, T가 각각 String과 Integer로 대체되어 인스턴스가 생성된다.
BBB <String> myString = new BBB <String> ();
BBB <Integer> myInteger = new BBB <Integer> ();
- 반면 AAA <T> 클래스의 T를 지정해서 상속할 수도 있다. 즉 다음과 같이 상속하는 것도 가능하다.
class BBB extends AAA <String> {
int itemBBB;
}
물론 위의 BBB 클래스는 제네릭으로 정의될 수도 있다.
그러나 제네릭이 아닌 일반 클래스로 정의할 수도 있다.
ㅁ 제네릭 인터페이스를 구현하는 두 가지 방법
- 클래스뿐 아니라 인터페이스도 제네릭으로 정의할 수 있다.
interface MyInterface <T> {
public T myFunc(T item);
}
그리고 이 인터페이스를 구현하여 클래스를 정의하는 방식에도 두 가지가 있다.
첫째는 T를 그대로 유지하는 방식이다.
class MyImplement <T> implements MyInterface <T> {
public T myFunc(T item) {
return item;
}
}
두번째는 아래와 같이 T의 자료형을 결정하는 방식이다.
class MyImplement implements MyInterface <String> {
public String myFunc(String item) {
return item;
}
}
주의해야할 사항은 위의 클래스 정의와 같이 T의 자료형이 String으로 결정되면, MyInterface <T>의 메소드 myFunc를 구현할 때도 T가 아닌 String으로 명시해야 한다는 점이다.
ㅁ 기본자료형의 이름은 제네릭 클래스의 인스턴스 생성에 사용될 수 없다.
- FruitBox <int> fb1 = new FruitBox <int> ();
- FruitBox <double> fb1 = new FruitBox <double> ();
- 이러한 제한으로 인해서 고민해야할 문제를 다음 장에서 논의하게 되므로, 반드시 기억.
'난 정말 JAVA를 공부한적이 없다구요' 카테고리의 다른 글
23. 쓰레드(Thread)와 동기화 (0) | 2024.07.03 |
---|---|
22. 컬렉션 프레임워크(Collection Framework) (0) | 2024.06.27 |
20. 자바의 다양한 기본 클래스 (0) | 2024.06.21 |
19. 자바의 메모리 모델과 Object 클래스 (0) | 2024.06.20 |
18. 예외처리 Exception Handling (0) | 2024.06.19 |