만약에 우리가 어떤 자료구조를 만들어 공유하려고 하는데 String 타입도 지원하고 싶고 Integer타입도 지원하고 싶고 많은 타입을 지원하고 싶으면 어떻게 해야 할까요??
많은 타입을 지원하기 위해서는 원하는 타입에 따른 String에 대한 클래스, Integer에 대한 클래스 등 하나하나 타입에 따라 만들어야합니다.
하지만 이 방법은 너무 비효율적이고 수정, 관리하기에 어렵기 때문에 이러한 문제를 해결하기 위해 우리는 제네릭이라는 것을 사용합니다.
이렇듯 제네릭(Generic)은 한마디로 특정 타입을 미리 지정해주는 것이 아닌 필요에 의해 지정할 수 있도록 하는 일반 타입이라고 할 수 있습니다.
제네릭을 사용하면 잘못된 타입이 들어올 수 있는 것을 컴파일 단계에서 방지할 수 있습니다.
클래스 외부에서 타입을 지정해주기 때문에 따로 타입을 체크하고 변환해줄 필요가 없습니다.
비슷한 기능을 지원하는 경우 코드의 재사용성이 높아집니다.
public class _01_Generics_Method {
public static void main(String[] args) {
Integer[] intArray = {1,2,3,4,5};
Double[] doubleArray = {5.5, 4.4, 3.3, 2.2, 1.1};
Character[] chrArray = {'H', 'E', 'L', 'L', 'O'};
String[] stringArray = {"B", "Y", "E"};
displayArray(intArray);
displayArray(doubleArray);
displayArray(chrArray);
displayArray(stringArray);
}
}
위의 코드를 실행시키위해서 Overloading을 사용하여 아래와 같은 함수들을 만들어야 합니다.
public static void displayArray (int[] array) {
for (Integer x : array) {
System.out.print(x + " ");
}
System.out.println();
}
public static void displayArray (Double[] array) {
for (Double x : array) {
System.out.print(x + " ");
}
System.out.println();
}
public static void displayArray (Character[] array) {
for (Character x : array) {
System.out.print(x + " ");
}
System.out.println();
}
public static void displayArray (String[] array) {
for (String x : array) {
System.out.print(x + " ");
}
System.out.println();
}
하지만 여기서 제네릭을 사용한다면 아래와 같이 코드를 간소화할 수 있습니다.
public static <Thing> void displayArray (Thing[] array) {
for (Thing x : array) {
System.out.print(x + " ");
}
System.out.println();
}
public class _02_Generics_Class {
public static void main(String[] args) {
MyIntegerClass myInt = new MyIntegerClass(1);
MyDoubleClass myDouble = new MyDoubleClass(3.14);
MyCharacterClass myChar = new MyCharacterClass('@');
MyStringClass myStr = new MyStringClass("Hello");
System.out.println(myInt.getValue());
System.out.println(myDouble.getValue());
System.out.println(myChar.getValue());
System.out.println(myStr.getValue());
}
}
위의 코드를 실행하려면 아래와 같은 클래스를 4개를 만들어야합니다.
public class MyIntegerClass <int> {
int x;
MyIntegerClass (int x) {
this.x = x;
}
public int getValue () {
return x;
}
}
하지만 제네릭을 이용해서 아래와 같이 클래스를 만들면 하나의 클래스로 모든 클래스를 표현할 수 있습니다.
public class MyGenericClass <Thing> {
Thing x;
MyGenericClass (Thing x) {
this.x = x;
}
public Thing getValue () {
return x;
}
}
public class _02_Generics_Class {
public static void main(String[] args) {
// 하나의 클래스로 다양한 자료형을 가진 객체를 만들어낼 수 있다.
MyGenericClass<Integer> myInt = new MyGenericClass<>(1);
MyGenericClass<Double> myDouble = new MyGenericClass<>(3.14);
MyGenericClass<Character> myChar = new MyGenericClass<>('@');
MyGenericClass<String> myStr = new MyGenericClass<>("Hello");
System.out.println(myInt.getValue());
System.out.println(myDouble.getValue());
System.out.println(myChar.getValue());
System.out.println(myStr.getValue());
}
}
public class MyGenericClass2 <Thing, Thing2> {
Thing x;
Thing2 y;
MyGenericClass2 (Thing x, Thing2 y) {
this.x = x;
this.y = y;
}
public Thing2 getValue () {
return y;
}
}
위와 같이 Thing
, Thing2
, ... 처럼 변수를 표현해서 여러 개의 변수들을 사용할 수 있습니다.
그리고 Thing에서도 자료형에 제한을 줄 수 있는데요.
매개변수명, extends, 상위 타입 경계
public class MyGenericClass2 <Thing extends Number, Thing2> {
......
}
위의 코드에서는 Thing
변수는 Number
에 속하는 Integer
, Double
만을 사용할 수 있습니다.
하나의 경계가 아니라 여러 개의 경계를 가질 경우, & 기호로 구분합니다.
이때, 타입 매개변수 Thing
는 경계로 나열된 모든 타입의 하위 타입입니다.
public class MyGenericClass2 <Thing extends A & B & C, Thing2> {
......
}
예시 코드)
static void displayArray1(List<?> list) {
list.add(list.get(1)); // 컴파일 실패
}
와일드 카드는 list
에 담긴 원소에는 전혀 관심이 없기 때문에 원소와 관련된 add
메소드를 사용할 수 없어서 컴파일 오류가 발생합니다. (단, null
은 들어갈 수 있습니다.)
static <Thing> void displayArray2(List<Thing> list) {
list.add(list.get(1)); // 컴파일 성공
}
제네릭은 ```list```에 담긴 원소에 관심을 갖기 때문에 원소와 관련된 add
메소드를 사용할 수 있습니다. (당연히 null
도 들어갈 수 있습니다.)
출처
https://st-lab.tistory.com/153
https://nauni.tistory.com/143
YouTube : BroCode