Java

제네릭

제네릭이란?

제네릭(Generic)은 클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법을 의미한다. 말이 어렵다. 아래 그림을 보자.

위의 그림은 아래의 코드를 간략화한 것이다.

package org.opentutorials.javatutorials.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

그것은 각각의 인스턴스를 생성할 때 사용한 <> 사이에 어떤 데이터 타입을 사용했느냐에 달려있다. 

클래스 선언부를 보자.

public T info;

클래스 Person의 필드 info의 데이터 타입은 T로 되어 있다. 그런데 T라는 데이터 타입은 존재하지 않는다. 이 값은 아래 코드의 T에서 정해진다.

class Person<T>{

위 코드의 T는 아래 코드의 <> 안에 지정된 데이터 타입에 의해서 결정된다. 

Person<String> p1 = new Person<String>();

위의 코드를 나눠보자. 아래 코드는 변수 p1의 데이터 타입을 정의하고 있다.

Person<String> p1

아래 코드는 인스턴스를 생성하고 있다. 

new Person<String>();

즉 클래스를 정의 할 때는 info의 데이터 타입을 확정하지 않고 인스턴스를 생성할 때 데이터 타입을 지정하는 기능의 제네릭이다. 

제네릭이 무엇인가를 알았으니까 이제 제네릭을 사용하는 이유를 알아보자. 

제네릭을 사용하는 이유

타입 안전성

아래 코드를 보자.

package org.opentutorials.javatutorials.generic;
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);
		StudentPerson sp = new StudentPerson(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
	}
}

그리고 아래 코드를 보자. 위의 코드는 StudentPerson과 EmployeeInfo가 사실상 같은 구조를 가지고 있다. 중복이 발생하고 있는 것이다. 중복을 제거해보자.

package org.opentutorials.javatutorials.generic;
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);
	}
}

위의 코드는 성공적으로 컴파일된다. 하지만 실행을 하면 아래와 같은 오류가 발생한다.

Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to org.opentutorials.javatutorials.generic.EmployeeInfo
    at org.opentutorials.javatutorials.generic.GenericDemo.main(GenericDemo.java:17)

아래 코드를 보자.

Person p1 = new Person("부장");

클래스 Person의 생성자는 매개변수 info의 데이터 타입이 Object이다. 따라서 모든 객체가 될 수 있다. 그렇기 때문에 위와 EmployeeInfo의 객체가 아니라 String이 와도 컴파일 에러가 발생하지 않는다. 대신 런타임 에러가 발생한다. 컴파일 언어의 기본은 모든 에러는 컴파일에 발생할 수 있도록 유도해야 한다는 것이다. 런타임은 실제로 애플리케이션이 동작하고 있는 상황이기 때문에 런타임에 발생하는 에러는 항상 심각한 문제를 초래할 수 있기 때문이다. 

위와 같은 에러를 타입에 안전하지 않다고 한다. 즉 모든 타입이 올 수 있기 때문에 타입을 엄격하게 제한 할 수 없게 되는 것이다. 

제네릭화

이것을 제네릭으로 바꿔보자.

package org.opentutorials.javatutorials.generic;
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 ei1 = p1.info;
		System.out.println(ei1.rank); // 성공
		
		Person<String> p2 = new Person<String>("부장");
		String ei2 = p2.info;
		System.out.println(ei2.rank); // 컴파일 실패
	}
}

p1은 잘 동작할 것이다. 중요한 것은 p2다. p2는 컴파일 오류가 발생하는데 p2.info가 String이고 String은 rank 필드가 없는데 이것을 호출하고 있기 때문이다. 여기서 중요한 것은 아래와 같이 정리할 수 있다.

  • 컴파일 단계에서 오류가 검출된다.
  • 중복의 제거와 타입 안전성을 동시에 추구할 수 있게 되었다.

제네릭의 특성

복수의 제네릭

클래스 내에서 여러개의 제네릭을 필요로 하는 경우가 있을 것이다. 예제를 보자.

package org.opentutorials.javatutorials.generic;
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) {
		Person<EmployeeInfo, int> p1 = new Person<EmployeeInfo, int>(new EmployeeInfo(1), 1);
	}
}

위의 코드는 예외를 발생시키지만 문제는 다음 예제에서 처리하고 형식만 보자. 

즉, 복수의 제네릭을 사용할 때는 <T, S>와 같은 형식을 사용한다. 여기서 T와 S 대신 어떠한 문자를 사용해도 된다. 하지만 묵시적인 약속(convention)이 있기는 하다. 그럼 예제의 오류를 해결하자.

기본 데이터 타입과 제네릭

제네릭은 참조 데이터 타입에 대해서만 사용할 수 있다. 기본 데이터 타입에서는 사용할 수 없다. 따라서 아래와 같이 코드를 변경한다.

package org.opentutorials.javatutorials.generic;
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);
		Integer i = new Integer(10);
		Person<EmployeeInfo, Integer> p1 = new Person<EmployeeInfo, Integer>(e, i);
		System.out.println(p1.id.intValue());
	}
}

new Integer는 기본 데이터 타입인 int를 참조 데이터 타입으로 변환해주는 역할을 한다. 이러한 클래스를 래퍼(wrapper) 클래스라고 한다. 덕분에 기본 데이터 타입을 사용할 수 없는 제네릭에서 int를 사용할 수 있다.

제네릭의 생략

제네릭은 생략 가능하다. 아래 두 개의 코드가 있다. 이 코드들은 정확히 동일하게 동작한다. e와 i의 데이터 타입을 알고 있기 때문이다.

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

메소드에 적용

제네릭은 메소드에 적용할 수도 있다. 

package org.opentutorials.javatutorials.generic;
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){
		System.out.println(info);
	}
}
public class GenericDemo {
	public static void main(String[] args) {
		EmployeeInfo e = new EmployeeInfo(1);
		Integer i = new Integer(10);
		Person<EmployeeInfo, Integer> p1 = new Person<EmployeeInfo, Integer>(e, i);
		p1.<EmployeeInfo>printInfo(e);
		p1.printInfo(e);
	}
}

제네릭의 제한

extends

제네릭으로 올 수 있는 데이터 타입을 특정 클래스의 자식으로 제한할 수 있다.

package org.opentutorials.javatutorials.generic;
abstract class Info{
    public abstract int getLevel();
}
class EmployeeInfo extends 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>("부장");
	}
}

위의 코드에서 중요한 부분은 다음과 같다.

class Person<T extends Info>{

즉 Person의 T는 Info 클래스나 그 자식 외에는 올 수 없다.

extends는 상속(extends)뿐 아니라 구현(implements)의 관계에서도 사용할 수 있다.

package org.opentutorials.javatutorials.generic;
interface 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>("부장");
	}
}

이상으로 제네릭의 기본적인 사용법을 알아봤다. 

댓글

댓글 본문
작성자
비밀번호
  1. 헛둘헛둘
    진짜 너무 좋네요 ㅠㅠ 동영상까지 올려주시고 감사합니다.
  2. 자바바
    갑자기 너무 어렵다;;
  3. Weaver
    강의 감사합니다~
  4. SK Kim
    Generic 계념은 이해가 되는데, 설명 예제들의 구조가 오히려 햇갈리네요.
    클래스 내부에 클래스가 있고 하니..
    초보가 사용하기에는 고급 내용인듯하니 다음 강의로 넘어갑니다.
  5. JustStudy
    고맙습니다
  6. 감사합니당ㅇ
  7. 김트라슈
    감사합니다~
  8. 보람차게
    IDE 인텔리제이 쓰고 있는데 제네릭 생략하면 Person(T,S)의 요청이 확인되지 않는다는 경고문구가 뜨네요. 그리고 p2.info.rank가 해결되지 않는다고 에러가 발생하는데 왜 이런걸까요?
    p2.info
    p2.id
    는 잘 되는데 말이죠...
  9. 보람차게
    Java5 이상부턴 new Integer(1)로 박싱 안 하고 그냥 1로 보내도 된다는군요
  10. 오빠는다르다
    감사합니다!!!!!!
  11. ㅇㅇ
    작년에 처음들었을때는 도저히 이해가 안갔었는데
    이번에 두번째로 들으니깐 보이긴 보이네요
    막상 나중에 쓸려고 하면 또 어려울듯 ㅋㅋ
  12. 박첩구드
    제네릭은 정말 유용할 것 같아요~!!
  13. flys0m@naver.com
    자바 넘나 어려운것
  14. eagles70
    좀 어려워 지네요^^;;
  15. scs87
    안녕하세요 이고잉 강사님 !

    두 번째 강의에서 StudentPerson 과 EmployeePerson 로직의 형태가 중복되고 있는데요~.

    EmployeePerson 대신 EmployeeInfo로 잘못 메모 되어 있는 것 같아서요

    수정해주시면 더 좋은 강의가 될 것 같아서요 !

    무튼 감사합니다. 아 그리고 지금까지 강의 전부 만족하는데요 ^^

    enum강의만 꼭 좀 보완 해주시면 감사해요

    노트에는 나와있는데 설명을 안해주시는 부분도 있더라구요

    그럼 수고하세요 파이팅 ^^
  16. 방문자
    String Integer 둘이 다르기 때문이 아닐까여
    대화보기
    • dkiekkf@gmail.com
      클래스에서는 T S 를 사용하셨고 메서드에서는 U를 사용하셨는데 각각의 T S U가 용도가 틀린가요 아니면 전부다 generic을 의미하는 대문자 같은건가요?
    • cocohodu
      좋은강의 감사합니다
    • 나무늘보
      유튜브 오른쪽 하단에
      HD라고 뜬 톱니바퀴모양
      속도 <<<<<<<<<<
      대화보기
      • 배속
        배속으로 보는법좀요
        대화보기
        • case 어쩌구가 아니라
          ClassCastException인듯 하네요
          다운캐스팅이 불가할때 발생하는 예외입니다
          대화보기
          • GunLoc
            처음으로 질문 남깁니다.,
            class Person<T extends Info>{
            public T info;
            Person(T info){ this.info = info; }
            }
            이 클래스 안에다가
            info..getLevel();이 가능 한 이유를 알고 싶습니다.
            여기서 info는 EmployeeInfo이므로 Info의 자식이기때문에
            Info의 기능을 쓸수있다는건 알겠는데.
            여기서 "info."는 참조변수로 봐야하는건가요? (즉, 가리킨다고 봐야하나요?)
            접근을 "info."로 하는 이유가 궁금해서요 ㅎ
          • 이리지기
            감사합니다 잘보고 갑니다.
            하나 궁금한게 있는데요.

            public <T> T back(Object data){
            return (T) data;
            }
            위의 형태로 메서드를 작성했을 때

            int temp = a.<Integer>back("12");
            위와같이 호출을 하면, 반환값에서 왜 case Exception이 나는건가요?
          • ps.won
            감사합니다 잘보고 갑니다~~
          • 안졸려고노력하는이
            졸릴땐 1.5배속 혹은 2배속으로 보세요.
            대화보기
            • 지나가는이
              와 고맙습니다. 머리에 쏙쏙 들어오는데요
            • 개발원교육센터
              <너><무><어><렵><네><요>
            • 경영기술
              <졸><립><네><요>
            • 지나가는 인
              뒷부분은 많이 복잡하게 느껴지네요.
              하나짜리는 좀 봐서 쓸만한데 다수를 동시에 하면 복자도가 올라가고 어려울 것 같네요.

              그래도 덕분에 좋은 리뷰가 되었네요.
              감사합니다.
            • coolperson<>
              강의는 너무 고마운데, 말투가 너무 너무 졸리워요..
            • jeyul
              제가 아는 범위내에서 두 가지 경우로 나눠서 적어 볼께요.

              1) 제네릭을 생략하지 않은 경우에는 그냥 변수의 값을 출력하시면 됩니다.
              p1.info는 EmployeeInfo 타입입니다.
              p1.info.rank는 int 타입입니다.
              p1.id는 Integer 타입입니다.
              p1.id.intValue()의 리턴값은 int 타입입니다.

              2) 제네릭을 생략한 경우에는 형변환이 필요합니다. 마우스 포인터를 변수 p2.info와 p2.id 위에
              올려놓아보면 Object 타입임을 확인할 수 있습니다. 맴버 필드의 값을 참조하기위해 Object 타입을
              해당 클래스 타입으로 형변환해줘야 합니다.
              p2.info는 Object 타입입니다.
              ((EmployeeInfo)p2.info).rank는 int 타입입니다.
              p2.id는 Object 타입입니다.
              ((Integer)p2.id).intValue()의 리턴값은 int 타입입니다.

              제네릭 생략시 Person 클래스의 맴버 필드 info와 id가 Object 클래스 타입으로 인식되는 것만
              주의하시고 변수들의 타입을 잘 확인하고 콘솔에 값을 출력해보시면 됩니다.
              질문1답) intValue()메소드를 호출해도 되고 안 해도 됩니다. 결과는 똑 같이 출력됩니다.
              질문2답) 멤버 필드의 접근 지시자가 public이므로 당연히 접근이 가능합니다.
              대화보기
              • Haewon Lee
                제네릭을 생략화 시키니까 3번째 강의에서 구현했던
                System.out.println(p1.id.intValue()); 이 부분에서 오류가 나네요??
                제네릭 생략화를 했을 때 위 구문을 실행시키려면 어떻게 바꿔줘야하나요??
                -> 아 System.out.println(p1.id); 로 바꿔주면 되는군요. 그런데 제네릭 생략화 하기전에
                System.out.println(p1.id); 이렇게 실행해도 아무 오류가 없네요...
                질문1. 흠.... 그렇다면 id의 상수값을 호출하기 위해서 꼭 .intValue()를 꼭 붙여줘야하나요?? (단순히 화면 출력할 떄는 .intValue가 구지 필요한건 아닌건가요..?)

                제네릭 생략화가 되어있는 상태에서
                System.out.println(p1.info.rank); 이 구문을 제가 시험삼아 적어봤는데,
                오류가 나네요. 이건 어떻게 바꿔줘야하나요?(물론, 제네릭 생략화를 안했을때는 정상 실행이 됬습니다.)
                -> 아 class Person<T,S>를 class Person<T extends EmployeeInfo, S> 이렇게 바꿔주니 실행이 되네요. 불특정한 Object의 변수 중에는 rank라는 변수가 없어서 오류가 났다고 이해했습니다.

                질문2. 그렇다면 class Person<T,S>로 선언되어 있고 제네릭을 생략화 시키지 않았을때 메인 메소드에서 System.out.println(p1.info.rank); 는 왜 실행이 되는거죠?
              • egoing
                알겠습니다. 제네릭 부분 수업 다시 만드는 것도 고려해볼께요. 지금 밀려있는 일들 정리 되는데로요..
                대화보기
                • Lewis6
                  Hyug Yoon 님이 말씀하신 대로 '제네릭화' 부분의 예제 설명이 빠진 듯 합니다.
                  제네릭화 하여 p1은 성공, p2는 컴파일 실패를 확인해 보는 예제 부분이요 ^^

                  그래서 인지 .. 갑자기 '복수의 제네릭' 사용법이 나와 이해가 좀 어려웠네요 ;;
                  대화보기
                  • 도로시
                    사소한 오타가 있네요. 아마 보시는 분들에게 큰 문제는 없었을 테지만..

                    타입 안전성의 첫 예제 아래에 있는 텍스트에서
                    "위의 코드는 StudentPerson과 EmployeeInfo가 사실상 같은 구조를 가지고 있다"
                    --> "위의 코드는 StudentPerson과 EmployeePerson이 사실상 같은 구조를 가지고 있다"
                    이렇게 바뀌는 게 맞을 것 같아요 ^^
                  • Hyug Yoon
                    동영상을 다시 다 보았는데, 제 생각에는
                    2/5 영상과 3/5 영상 사이에서 예제 소스를 '제네릭화' 하는 내용이 빠진 것 같네요.
                    3/5 영상이 시작되면 "자바라는 언어에 새로운 기능이 추가도입되면서 벌어지는 상황"에 대해 잠시 얘기해주신 뒤에,
                    바로 '복수의 제네릭' 사용법에 대해서 강의해 주고 계시거든요.

                    시간 되실 때 2/5 영상 끝과, 3/5 영상 앞부분을 체크해보시면 좋을 것 같습니다.
                    지금 보니까 페이지 내에서 예제 소스 블록에 대해서 [예제 1], [예제 2], 이런식으로 붙어 있으면
                    더 소통하기 좋지 않을까 하는 생각도 드네요.

                    좋은 강의 감사드립니다.
                    대화보기
                    • egoing
                      음 제가 동영상을 전부 다시 리뷰해보지는 못했는데요. 아마 제네릭을 사용하는 방법 자체는 첫번째 동영상에서 설명한 것이 아닐까 싶습니다. 혹시 제가 빠뜨린게 있으면 알려주세요. 감사합니다.
                      대화보기
                      • Hyug Yoon
                        훈훈한 목소리의 egoing님 강의 잘 보고 있습니다. :D
                        2/5 영상 끝부분에서 '타입안정성'에 대해서 설명해주신 후에,
                        "'제네릭화' 시키는 부분은 다음 동영상에서 살펴본다"고 하셨는데,
                        3/5 영상을 보면 바로 '제네릭의 특성 > 복수의 제네릭' 내용 부터 시작하는 것 같네요.
                        혹시 중간에 누락된 게 아닌지요?
                      • egoing
                        그것은 후에 안드로이드 강좌 등에서 진행 할 예정입니다.
                        대화보기
                        • mnb9139
                          GUI자바프로그래밍 강좌는 없나용?
                        • Jeong
                          친절한 설명 감사드려요~
                        • 김씨밥세끼드셨나요
                          제네릭에 대한 친절한 설명 감사합니다

                          제네릭을 처음 배울때 <Member>등을 사용하였는데 왜 이걸 쓰는지 이해를 못하고 그냥 무작정 사용 했었습니다

                          책을 찾아봐도 아리송했는데 이해가 되네요
                        • 김씨밥세끼드셨나요
                          제네릭에 대한 친절한 설명 감사합니다

                          제네릭을 처음 배울때 <Member>등을 사용하였는데 왜 이걸 쓰는지 이해를 못하고 그냥 무작정 사용 했었습니다

                          책을 찾아봐도 아리송했는데 이해가 되네요
                        버전 관리
                        egoing
                        현재 버전
                        선택 버전
                        graphittie 자세히 보기