본문 바로가기
Java

자바 제네릭 / java generic

by 자유코딩 2017. 9. 23.

이번 글에서는 자바의 제네릭에 대해서 알아보도록 하겠습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Fruits <T>{
    public T info;
    
}
public class Generic{
    public static void main(String[] args) {
        Fruits<String> f1 = new Fruits<String>();
        Fruits<StringBuilder> f2 = new Fruits<StringBuilder>();
    }
    /*(Fruits 클래스 내부=예를들면 T info)에서 사용할 데이터 타입을
     * Fruits클래스의 외부인 Generic 클래스에서 인스턴스를 생성하며 지정한다.
     * 이렇게 클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법을 제네릭이라고 한다.
     */
}
cs

 

이렇게 클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법을 제네릭이라고 합니다.

 

이런 기능은 왜 쓰이게 되었을까요?

 

먼저 제네릭의 도입 배경부터 이야기를 시작하겠습니다.

 

제네릭의 도입 배경

 

여기 다음과 같은 코드가 있습니다.

 

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
class StudentInfo{
    public int grade;
    StudentInfo(int grade){ this.grade = grade; }
}
class StudentPerson{
    public StudentInfo info;
    StudentPerson(StudentInfo info){ this.info = info; }
}
class EmployeeInfo{
    public int rank;
    EmployeeInfo(int rank){ this.rank = rank; }
}
class EmployeePerson{
    public EmployeeInfo info;
    EmployeePerson(EmployeeInfo info){ this.info = info; }
}
public class GenericDemo {
    public static void main(String[] args) {
        StudentInfo si = new StudentInfo(2);//StudentInfo의 grade에 1을 전달
        StudentPerson sp = new StudentPerson(si);//StudentInfo info에 si를 전달
        System.out.println(sp.info.grade); // 2
        EmployeeInfo ei = new EmployeeInfo(1);
        EmployeePerson ep = new EmployeePerson(ei);
        System.out.println(ep.info.rank); // 1
    }
}
cs

 

 

여기서 EmployeePerson 과 StudentPerson은 사실상 같은 기능을 한다.

두 클래스를 모두 대표할 수 있는 클래스가 하나 있다면 소스코드의 중복이 줄어들 것이다.

 

그럼 이 코드를 한 번 Person 클래스로 대표해서 바꿔보자.

 

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
class StudentInfo{
    public int grade;
    StudentInfo(int grade){ this.grade = grade; }
}
class EmployeeInfo{
    public int rank;
    EmployeeInfo(int rank){ this.rank = rank; }
}
class Person{ //Person이라는 EmployeePerson과 StudentPerson 클래스를 대표하는 클래스를 만들었다.
    public Object info;
    Person(Object info){ this.info = info; }
}
public class GenericDemo {
    public static void main(String[] args) {
        Person p1 = new Person("부장");//Person 클래스 안의 Object 타입의 info변수에 "부장" 값을 전달한다.
        EmployeeInfo ei = (EmployeeInfo)p1.info;//p1.info에는 지금 문자열이 들어있다.
//Object 타입인 p1.info를 형변환 해서 EmployeeInfo타입 변수 ei에 저장한다.
//그런데 EmployeeInfo 안의 변수 rank는 int타입이다.
//클래스 형변환 에러가 발생한다.
//EmployeeInfo클래스 안의 rank는 타입이 int이다.
//Person p1 = new Person(여기에 int타입 값) 이렇게 작성 되어야 EmployeeInfo 클래스의 설계 취지에 맞는다.
//이렇게 "부장"이라는 문자열을 전달하는 것은 자바의 타입 안전성에 문제가 된다.
        System.out.println(ei.rank);
    }
}
cs

 

코드를 변경했습니다.

 

그렇지만 이렇게 바뀐 코드에서는 Person 안의 변수가 Object가 되기 때문에 어떤 값이든 타입에 상관없이 Person으로 전달됩니다.

 

이럴경우 타입 안전성에 문제가 생깁니다.

 

그래서 자바에서는 코드의 간소화와 타입 안전성을 모두 지키려고 "제네릭"이라는 기능을 추가하게 되었습니다.

 

●제네릭의 사용

 

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
class StudentInfo{
    public int grade;
    StudentInfo(int grade){ this.grade = grade; }
}
class EmployeeInfo{
    public int rank;
    EmployeeInfo(int rank){ this.rank = rank; }
}
class Person<T>{//제네릭을 사용
    public T info;
    Person(T info){ this.info = info; }
}
public class GenericDemo {
    public static void main(String[] args) {
        Person<EmployeeInfo> p1 = new Person<EmployeeInfo>(new EmployeeInfo(1));
        //EmployeeInfo로 Person 클래스의 타입을 지정해줍니다.
        EmployeeInfo ei1 = p1.info;//EmployeeInfo클래스 안의 rank에 15번줄의 값1을 전달한다.
        //EmployeeInfo가 int형 속성을 가지고 있어서 가능하다.
        System.out.println(ei1.rank); // 성공
        
        Person<String> p2 = new Person<String>("부장");
        //p2.info는 "부장" 값으로 넣었고 , <String>을 사용했기에 String이다.
        String ei2 = p2.info;//갑자기 String 타입의 ei2 변수를 선언함.
        //여기서 한가지. String은 자바 책들을 참고하면 기본형 변수 타입을 정리한 표에 없다.
        //EmployeeInfo처럼 클래스,객체이다.
        System.out.println(ei2.rank); // 컴파일 실패
        //String 객체는 rank속성이 없어서 컴파일 오류가 발생한다.
    }
}
cs

 

여러 개의 제네릭을 선언 할 수도 있습니다.

 

 

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
class EmployeeInfo{
    public int rank;
    EmployeeInfo(int rank){ this.rank = rank; }
}
class Person<T, S>{//2개의 제네릭을 선언합니다.
    public T info;
    public S id;
    Person(T info, S id){ 
        this.info = info; 
        this.id = id;
    }
}
public class GenericDemo {
    public static void main(String[] args) {
        Person<EmployeeInfo, int> p1 = new Person<EmployeeInfo, int>(new EmployeeInfo(1), 1);
        //Person <EmployeeInfo, int>로 코드를 작성하면
        //Person의 T에는 Employee가 전달된다.
        //S에는 int가 전달된다.
        //그런데 int는 기본데이터 타입이라서 이 코드는 에러가 발생한다.
        //int를 객체타입으로 바꿔줘야한다.
        //이렇게 바꿔주는 클래스를 자바에서 제공한다. 이런 바꿈을 해주는 클래스들을
        //래퍼 클래스라고 부른다.(Wrapper class)
        //결국 15번 줄은 이렇게 바꿀 수 있다.
        Person<EmployeeInfo, Integer> p1 = new Person<EmployeeInfo, Integer>(new EmployeeInfo(1), 1);
    }
}
cs

 

그럼 위의 코드를 래퍼 클래스를 이용해서 다시 한 번 바꿔보겠습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 class GenericDemo {
    public static void main(String[] args) {
        EmployeeInfo e = new EmployeeInfo(1);//EmployeeInfo의 필드에 1을 전달합니다.
        Integer i = new Integer(10);//Integer타입 변수 i를 선언하고 10을 전달합니다.
        Person<EmployeeInfo, Integer> p1 = new Person<EmployeeInfo, Integer>(e, i);
        //Person p1 = new Person(e,i);로 바꿀 수 있다.
        //16번 줄의 i를 17번 줄의 맨 뒤에 담습니다.
        System.out.println(p1.id.intValue());
        //intValue는 Integer타입 안에 들어있는 기본 int타입 값을 가져오는 메소드입니다.
    }
}
 
cs

 

그런데 제네릭을 정의하는 한 줄의 코드가 너무 길다고 생각되지 않으시나요?

 

제네릭은 생략이 가능합니다.

 

위의 코드를 바꿔 보겠습니다.

 

자바는 생성자의 매개변수(e,i)를 통해서 타입을 추측할 수 있기 때문에 EmployeeInfo , Integer를 생략할 수 있습니다. 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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 class GenericDemo {
    public static void main(String[] args) {
        EmployeeInfo e = new EmployeeInfo(1);//EmployeeInfo의 필드에 1을 전달합니다.
        Integer i = new Integer(10);//Integer타입 변수 i를 선언하고 10을 전달합니다.
 
        Person p1 = new Person(e, i);//이렇게 코드를 생략 할 수 있습니다.
        //Person p1 = new Person(e,i);로 바꿀 수 있다.
        System.out.println(p1.id.intValue());
    }
}
cs

 

Person p1 = new Person(e,i); 으로 코드를 생략 할 수 있습니다.

 

 Person<EmployeeInfo, Integer> p1 = new Person<EmployeeInfo, Integer>(e, i);

 생략 전

 Person p1 = new Person(e, i);

 생략 후

 

 

제네릭에 대해서 알아보았습니다. 설명이 부족하진 않으셨나요? jswoo030@gmail.com 으로 질문하시면 빠른 답변을 받으 실 수 있습니다.

댓글