Generics 특징
업데이트:
Generics를 사용하는 이유을 알아보고자 합니다.
제네릭은 자바의 가장 늦게 도입 된 기능 중 하나 입니다. 이 기능이 도입되기까지 많은 고민과 필요성이 있었을 것입니다. 제네릭을 사용하는 이유 및 방법에 대해 알아 보겠습니다.
Usage
제네릭이란?
제네릭(Generic)은 클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법을 의미합니다. 코드로 살펴보겠습니다.
class Person<T> {
public T info;
}
public class GenericDemo {
public static void main(String[] args) {
Person<String> p1 = new Person<String>();
Person<StringBuilder> p2 = new Person<StringBuilder>();
}
}
p1.info와 p2.info의 데이터 타입은 아래와 같습니다. 이것은 인스턴스를 생성할 때 <> 사이에 어떤 데이터 타입을 사용했는지에 달려 있게 됩니다.
- p1.info : String
- p2.info : StringBuilder
제네릭을 사용하는 이유
- 타입 안정성을 보장하기 위해 사용 합니다.
(e.g. Person Class는 StudentInfo, EmployeeInfo Class를 모두 담을 수 있습니다. 중복을 제거 하기 위해 사용 한 것 입니다. StudentInfo, EmployeeInfo를 모두 처리 할 수 있게, 생성자에 상위 클래스인 Object를 사용한다고 가정 합니다. 만약 “부장”이라는 String 값을 넣을 때는 에러가 발생 하지 않지만, 꺼내 쓸때에는 Type Casting 에러가 발생하게 됩니다.)
class StudentInfo { public int grade; StudentInfo(int grade){ this.grade = grade; } } class EmployeeInfo { public int rank; EmployeeInfo(int rank){ this.rank = rank; } } class Person { public Object info; Person(Object info){ this.info = info; } } public class GenericDemo { public static void main(String[] args) { Person p1 = new Person("부장"); EmployeeInfo ei = (EmployeeInfo)p1.info; System.out.println(ei.rank); } }
위의 코드는 컴파일은 되지만, 아래와 같은 runtime error가 발생합니다. Type Casting 에러가 쉽게 발생 할 수 있는 것 입니다.
java.lang.ClassCastException: java.lang.String cannot be cast to com.example.bagic.EmployeeInfo
제네릭의 제한
- extends를 사용하여, generics으로 생성 할 객체를 제한 한다.
interface class Info { int getLevel(); } class EmployeeInfo implements Info { public int rank; EmployeeInfo(int rank){ this.rank = rank; } public int getLevel() { return this.rank; } } class Person<T extends Info> { public T info; Person(T info){ this.info = info; } } public class GenericDemo { public static void main(String[] args) { Person p1 = new Person(new EmployeeInfo(1)); Person<String> p2 = new Person<String>("부장"); // error 발생 } }
위 코드의 Generics 형태의 Person Class는 “부장”이라는 String 파라미터 값의 인스턴스를 생성할 수 없습니다. String 타입의 Person을 생성할때, T 는 info 타입인지를 확인하여, info의 자식으로 제한 하기 때문입니다. ___
제네릭 특징
class EmployeeInfo {
public int rank;
EmployeeInfo(int rank){ this.rank = rank; }
}
class Person<T, S> {
public T info;
public S id;
Person(T info, S id) {
this.info = info;
this.id = id;
}
public <U> void printInfo(U info) {
if (info instanceof EmployeeInfo) {
System.out.println("EmployeeInfo : " + ((EmployeeInfo) info).rank); // EmployeeInfo : 1
} else if (info instanceof StudentInfo) {
System.out.println("StudentInfo : " + ((StudentInfo) info).grade); // StudentInfo : 99
}
}
}
public class GenericDemo {
public static void main(String[] args) {
EmployeeInfo e = new EmployeeInfo(1);
Integer i = new Integer(10);
/* EmployeeInfo 의 id 값을 integer 로 변환하여, generics 형태인 Person 객체 생성 */
Person<EmployeeInfo, Integer> p1 = new Person<EmployeeInfo, Integer>(e, i);
System.out.println("p1.info.rank : " + p1.info.rank); // rank : 1
System.out.println("p1.id.intValue() : " + p1.id.intValue()); // id : 10
/* EmployeeInfo 형태의 Person 생성하여, print */
Person p2 = new Person(e, i);
p2.<EmployeeInfo>printInfo(e);
/* StudentInfo 형태의 Person 생성하여, print */
Person p3 = new Person(new StudentInfo(99), new Integer(11));
p3.printInfo(new StudentInfo(99));
}
}
위의 코드에서 Person의 두번째 parameter 값을 Interger의 형태로 변환하여야 한다.
- 제네릭은 primitive type(e.g. int, char…)을 넣을 수 없다.
- 제네릭은 생략 가능하다. 1.Person p2 = new Person(e, i); 2.Person<EmployeeInfo, Integer> p1 = new Person<EmployeeInfo, Integer>(e, i)
- 메서드 형태로 사용 가능하다.
와일드 카드(Wild Card)
- 전체가 아닌 일부분으로 범위를 제한 할 때 사용.
public class GenericDemo {
public static void main(String[] args) {
List<Integer> lists = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
System.out.println("Sum : " + sumOfList(lists)); // Sum : 55
}
double sumOfList(List<? extends Number> lists) {
double number = 0.0;
for (Number n : lists) {
number += n.doubleValue();
}
return number;
}
}
위의 코드에서, <? extends Number> 과 같이 sumOfList의 parameter 값을 정하였습니다. 그렇다면, List
Generic를 사용한 copyOf
generics은 framework 소스를 보면, 쉽게 찾아 볼수 있습니다. 예를 들어 copyOf에서 어떻게 사용 한것인지 살펴 보겠습니다.
String[] movies = {"킹덤", "하우스오브카드","종이의집"};
String[] copyList = Arrays.copyOf(movies, 2);
for (String movie : copyList) {
System.out.println("movie : " + movie); // movie : 킹덤, 하우스오브카드
}
위의 코드에서는 “킹덤”, “하우스오브카드”,”종이의집” 3개의 String이 담긴 배열을 생성하였고, 2개만 복사 하였습니다.
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
return은 타입이 T[]인 배열로 하게 됩니다. 오버로딩 되어 있는, copyOf에 parameter을 전달 할 때에는, original 값을 U[] 타입인 String[] 을 넣었습니다. 또한 newType 값을 T[] 으로 하여, original.getClass() 을 넣었습니다. 클래스 내부에서 사용할 데이터 타입을 외부에서 지정한 것입니다. 이렇게 한 이유는 copyOf의 값으로 string, int, double 배열을 전달하여도, 내부에서 U[], T[]로 정의 되어 있기때문에, string, int, double에 각각 맞는 copyOf 메서드를 만들지 않아도 되기 때문입니다. 3개의 메서드가 1개로 줄어 든 것입니다.
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
T[] copy = ((Object)newType == (Object)Object[].class) ?
(T[]) new Object[newLength] : (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
return copy; // {"킹덤", "하우스오브카드"}
}
- original : {“킹덤”, “하우스오브카드”,”종이의집”}
- newLength : 2
- newType : class[Ljava.lang.String];
System.arraycopy 을 통해, copy에는 String[2] 값이 담기게 됩니다. ___
Reference
- https://opentutorials.org/module/516/6237
- https://movefast.tistory.com/74
___
Source
- 전체 소스 : GitHub
- branch : generics