티스토리 뷰

Java/문법

11. 제네릭스 : Generics

알 수 없는 사용자 2018. 11. 1. 01:52


이번 포스팅에서는

제네릭이라는 것에 대해서

다루겠습니다.

제네릭은 날이 갈수록 사용하는 경우가 많아지고 있고

java API 문서조차 제네릭을 모르면

 해석하기 어려울 정도로 많이 사용되고 있습니다.


==================================================================


제네릭스 : Generics





1. Generics : What?


 제네릭이란 사용하고자 하는 데이터의 타입을 컴파일 시에 결정지을 수 있게 가변 처리해주는 기능입니다. 

정말 쉽고 간단하게 말하자면 타입을 제한하는 것이다 라고 할 수 있습니다.


 중 고등학생 때 많이 다루던 수학 문제에서 f(x,y) = x + y 라는 식을 예로 들겠습니다.

f(x,y)에서 x, y는 각 값에 들어갈 숫자를 결정하게 되며 f(2,3) 이라면 x=2, y=3 이 대입되어 x+y = 5라는 결과를 얻게 되죠.


제너릭도 비슷합니다.

방정식에서 어떤 것이 들어갈지를 모르는 변수 x를 만들어놓고 사용자의 입맛에 따라 x의 값을 정할 수 있습니다.

제네릭에서도 마찬가지로 T, E, K, V 와 같은 타입으로 설정해두고 객체를 생성할 때에 그 타입을 결정해줍니다.

 



위 사진의 왼쪽 Box처럼 Box 안에 one이라는 변수와 other이라는 변수, 그리고 setData()가 존재한다할 때 이 변수들과 메소드에 전달되는 인자로 어떠한 타입이 올 지 정해두지는 않고 T라는 임의의 참조형 타입을 만들어 Box 객체의 제네릭 타입에 따라 안에 Integer형 변수도 올 수 있고, String형 변수도 올 수 있게 됩니다.


이처럼 제네릭은 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입 체크를 해주는 기능입니다. 





2. Generics : How?


 

제네릭의 사용은 다음과 같이 할 수 있습니다.


class Box<T>{
T one;
T other;

public boolean setData(T t) {
if(one == null) {
one = t;
return true;
} else if(other == null) {
other = t;
return true;
} else {
return false;
}
}

public String toString() {
return "one =" + one + ", other = " + other;
}
}


제네릭을 사용하는 방법은 어렵지 않습니다. (물론 제한된 제네릭을 사용하려면 복잡해집니다 ...)


제네릭 클래스를 이용하기 위해서는 <> 기호를 사용하면 됩니다.

Box를 원시 타입이라 하고, 타입 변수 T를 사용하여 만들어진 Box<T>를 제네릭 클래스라고 부릅니다.

일반 클래스를 설계하는 것과 같지만 단지 원시 타입 명에 <>를 붙여 해당 클래스 안에 같은 타입 변수를 가진 타입들을 객체 생성 시 결정한 타입으로 모두 치환해줍니다.



제네릭 클래스 객체를 생성하는 방법은 다음과 같습니다.

Box<Integer> b = new Box<Integer>();

일반 클래스의 객체를 생성할 때와 같이 생성해주되, 타입 변수의 형을 결정해줍니다.

주의할 점은 타입변수는 무조건 객체 타입이어야 합니다. 만약 타입 변수로 기본 데이터(int, double, char...) 형을 사용하고 싶다면 위와 같이 래퍼 클래스로 생성되는 각 형에 따른 클래스를 사용할 수 있습니다.



또한 객체로 생성하는 타입이 변수의 타입과 같다면 new 연산자의 타입 변수는 다음과 같이 생략할 수 있습니다.

Box<Integer> b = new Box<>();



제네릭 타입 변수는 한 개만 올 수 있는 것이 아닙니다.

class Box<T, E>{
T one;
E other;

public boolean setData(T t, E e) {

one = t;

other = e;

}

public String toString() {
return "one =" + one + ", other = " + other;
}
}

다음과 같이 제네릭 타입 변수를 다중으로 설정하여 사용할 수도 있습니다.



만약 위와 같이 Integer로 객체를 생성한다면 변수 b에 저장된 Box는 다음과 같은 형태로 객체가 저장될 것입니다.

class Box<Integer>{
Integer one;
Integer other;

public boolean setData(Integer t) {
if(one == null) {
one = t;
return true;
} else if(other == null) {
other = t;
return true;
} else {
return false;
}
}

public String toString() {
return "one =" + one + ", other = " + other;
}
}






3. Generics : limted Generics?


 제네릭에 사용되는 타입 변수는 모든 객체가 들어갈 수 있다고 했습니다. 즉, 내부적으로 타입 변수를 Object로 간주한다는 것입니다.

하지만 만약 타입 변수로 올 수 있는 객체를 제한하고 싶다면 어떻게 해야 할까요?


답은 'extends' 키워드에 있습니다.

extends 키워드를 사용하면 특정 타입의 자손들만 타입 변수로 사용할 수 있게 제한할 수 있는 것입니다.


만약 Fruit란 클래스를 Grape 클래스가 상속하고 있고, 또 다른 Apple 클래스가 Fruit를 상속하고 있다고 가정하고, FruitBox라는 클래스를 따로 정의했습니다.

FruitBox에는 과일(Fruit 객체)만 담을 수 있어야 하고 채소나 고기와 같은 다른 종류의 객체는 담으면 안 됩니다.

이런 경우 extends 키워드를 사용하여 다음과 같이 타입 변수의 형을 제한할 수 있습니다.


class FruitBox<T extends Fruit> {
ArrayList<T> list = new ArrayList<>();
...
}


위에서는 클래스를 제한하기 때문에 extends를 사용했고, 인터페이스를 제한할 경우 Implements를 사용한다고 생각할 수 있지만, 인터페이스를 제한할 경우에도 extends 키워드를 사용한다는 것을 주의해야 합니다. 



 또한 만약 먹을 수 있는 과일만 구분하기 위해 Eatable 인터페이스를 동시에 구현해야 한다면 '&' 기호를 통해 연결해 줄 수 있습니다.

class FruitBox<T extends Fruit & Eatable> {
ArrayList<T> list = new ArrayList<>();
...
}






4. Generics : Generics Method?


 제네릭 클래스를 생성하면서 제네릭 메소드를 사용할 수 있었는데 제네릭 클래스가 없이도 제네릭 메소드를 사용할 수 있습니다.


제네릭 메소드는 메서드의 선언부에 서 메소드의 수식자(public,static)와 반환형(T) 사이에 위치합니다.


class FruitBox<T> {
...
static <T> void sort(List<T> list, Comparable<? extends T> c) {
...
}
}


만약 위와 같이 제레릭 클래스와 제네릭 메소드가 동시에 사용된다 해도 제네릭 클래스 명에 선언된 타입변수 T와 제네릭 메서드(sort())에 선언된 T는 문자만 같을 뿐 서로 다른 것입니다.


또한 static은 메모리에 적재되는 시기가 객체가 생성되기 이전이기 때문에 static 맴버변수나 static 메소드에는 타입 매개변수를 사용할 수 없지만 위와 같이 static 메소드에서 따로 선언된 제네릭 타입은 선언하고 사용하는 것이 가능합니다.


마치 메소드에 선언된 지역변수는 메소드 내에서만 사용 가능 한 것처럼 static 제네렉 메소드의 타입 변수는 메소드 내에서만 지역적으로 사용된다고 생각하면 됩니다.

 

만약 위와 같이 구현된 sort 메소드를 호출한다면 다음과 같이 호출하면 됩니다.

Collections.<Grape>sort(appleBox.getList(), new AppleComp());

제네릭 메소드에서의 타입 변수는 호출하는 메소드 앞에서 선언해주면 됩니다.



==========================================================================================================


언뜻 보기에는 객체에 들어갈 타입을 제한하는 것으로 불편한 것이 아닌가 생각할 수도 있지만 오히려 의도하지 않은 타입이 객체 안에 저장되는 것을 막아 타입의 안정성을 높일 수 있고, 타입의 캐스팅에 있어서 잘못 캐스팅되는 오류를 줄여줄 수도 있습니다. 또한 타입 체크와 형 변환을 생략할 수 있기 때문에 코드가 간결해집니다.




공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
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
글 보관함