생활코딩

Coding Everybody

코스 전체목록

닫기

제네릭

제네릭이란?

제네릭(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과 EmployeePerson가 사실상 같은 구조를 가지고 있다. 중복이 발생하고 있는 것이다. 중복을 제거해보자.

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. brucehan
    2024/06/15 수강완료
  2. labis98
    20230204 수강완료.
  3. wwwqiqi
    몰랐던것들을 많이 알게됐습니다 완료
  4. Alan Turing
    22/10/11
  5. PassionOfStudy
    복습 8일차!
  6. PassionOfStudy
    제네릭!
  7. 코드파괴자
    22.05.09 Form Ride. Generic..

    (이론만 ok, 제네릭 활용 문법 연습 필요)
  8. aesop0207
    22.03.08. Tue.
    다시 봐야할 듯
  9. 모찌말랑카우
    22.03.02
  10. 드림보이
    2021.12.29. 제네릭 파트 수강완료
  11. syh712
    2021-12-15
    <제네릭>
    1. 제네릭이란?
    - 클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법
    - 클래스를 정의 할 때는 info의 데이터 타입을 확정하지 않고 인스턴스를 생성할 때 데이터 타입을 지정하는 기능이 제네릭

    2. 제네릭을 사용하는 이유
    컴파일 단계에서 오류가 검출된다.
    중복의 제거와 타입 안전성을 동시에 추구

    3. 제네릭의 특성
    - 복수의 제네릭: 복수의 제네릭을 사용할 때는 <T, S>와 같은 형식을 사용한다. 여기서 T와 S 대신 어떠한 문자를 사용해도 된다. 하지만 묵시적인 약속(convention)이 있기는 하다.- 기본 데이터 타입과 제네릭: 제네릭은 참조 데이터 타입에 대해서만 사용할 수 있다. 기본 데이터 타입에서는 사용할 수 없다. new Integer는 기본 데이터 타입인 int를 참조 데이터 타입으로 변환해주는 역할을 한다. 이러한 클래스를 래퍼(wrapper) 클래스라고 한다.

    4. 제네릭의 생략
    5. 메소드에 적용
    6. 제네릭의 제한
    - extends
    제네릭으로 올 수 있는 데이터 타입을 특정 클래스의 자식으로 제한할 수 있다.
  12. H4PPY
    1122
  13. IaaS
    2021.11.04 수강완료

    제네릭은 몇번씩 들어봐야겠네요
  14. 노오오오옹
    많이 어렵네요.. 충분히 이해될 때까지 몇번 더 봐야겠습니다!
  15. 김수빈
    p1.info 데이터타입이 오브젝트라서 그렇습니다.
    오브젝트는 모든 클래스의 부모격이라 명시적 형변환이 가능한 것 같습니다.
    대화보기
    • 악어수장
      2021-05-11 수강완료 1회독
    • ㅈㅇㅅㅇ
      14 public class GenericDemo {
      15 public static void main(String[] args) {
      16 Person p1 = new Person("부장");
      17 EmployeeInfo ei = (EmployeeInfo)p1.info;
      18 }
      19}

      에서 ei는 new를 통해 생성된 적이 없는데 어떻게 바로 형변환이 가능한건가요??
      가능한 자세히 설명 부탁드리겠습니다...ㅠㅠㅠ
    • 김태현
      3회완료
      1, <> 미리 정하지 않은 데이터 타잎을 받아서 쓴다.
      2, Person<EmployeeInfo, int> p1 = new Person<EmployeeInfo, int>(new EmployeeInfo(1), 1);
      ~ 는

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

      int를 데이터 타잎으로 쓸려면 Integer로 Integer i = new Integer(10) 객체를 만들어 쓴다. ;
      new Integer는 기본 데이터 타입인 int를 참조 데이터 타입으로 변환해주는 역할을 한다. 이러한 클래스를 래퍼(wrapper) 클래스라고 한다. 덕분에 기본 데이터 타입을 사용할 수 없는 제네릭에서 int를 사용할 수 있다

      즉 Person의 T는 Info 클래스나 그 자식 외에는 올 수 없다.
      extends는 상속(extends)뿐 아니라 구현(implements)의 관계에서도 사용할 수 있다.

      extends는 제네릭에서 부모 클래스에 있는 데이터 타잎이 자식클래스에 들어 오면 ok 다르면 에러발생시킨다

      ~ 여기까지만 우선 암기
    • 행복코딩
      오늘도 복습 잘하고 갑니다~
      감사합니다 :)
    • 김요한
      제네릭 복습 완료!
      *5번은 개념적 이해는 완료가 되었으나, 사용법에 대해서는 약간 헷갈리니 다시 봐야할 듯.
    • 애욷
      처음 접하는데 너무 어렵게 느껴지네요,,ㅠ
      이해될 때 까지 보겠습니다.!!!
    • 김승민
      2020-05-14
      감사합니다.
    • 흐무
      실전 Spring에 비하면 넘나 쉬운 기초인 제네릭...
    • silver94
      감사합니다
    • 허공
      감사합니다!
    • 홍주호
      20191005 완료
    • doevery
      수강완료
    • 아롱범
      데이터 타입의 안전성을 중시하는 java에서는 편의성과 안정성 두 마리 토끼를 잡기 위한 제네릭 기법을 제공하는군요.
    • 라또마니
      와~ 어렵네요!! 그래도 마지막까지 강의 듣고 다시 처음부터 해야겠어요~
    • ㅁㅇㄹ
      이게무슨 기본적인 사용법이야 ㅈㄴ어려운데;;
    • 전민희
      제네릭 4,5 다시보기!
    • 조서호
      제네릭은 다시봐야대
    • 이태호
      7/17
    • 뀨우
      멘붕했네요.. 나중에 필요하게 되면 다시 보러 올게요 ㅜㅜ
      좋은 강의 감사합니다.
    • 김진홍
      감사합니다.
    • 곧고수
      질문입니다..2번째 동영상에서
      17번과 그리고 특히 18번 어떻게 static도 아닌 인스턴스맴버를 클래스맴버처럼 ei.rank 로 바로 사용할수있는건가요??
      인스턴스화 시킨후에만 저렇게 사용가능한것이 아닌가요??ㅠㅠㅜ밑에 댓글중에 new person("부장")설명과는 상관없는 것 같은데요....
      아님 제가 무언가 착각하고있는거같은데 알려주세요~

      14. public class GenericDemo {
      15. public static void main(String[] args) {
      16. Person p1 = new Person("부장");
      17. EmployeeInfo ei = (EmployeeInfo)p1.info;
      18. System.out.println(ei.rank);
      19. }
      20. }


      아..! 그리고 17번 라인에서 object라는 부모클래스의 형이 employeeInfo라는 자식클래스의 형으로 변환할 수 있다는것도 이해가 안되네요....ㅠㅠ자식클래스만이 부모의 형태로 변환될수 있는 것이 아닌가요??..ㅠㅠㅠㅠㅠ
    • 하면된다하자
      제네릭 어렵네요.;;
    • 돌침대에서덤블링
      16 Person p1 = new Person("부장");

      여기서 Person 생성자 안에서 p1.info 가 인스턴스화 되잖아요.
      대화보기
      • 코딩천재
        훌륭한 강의 감사합니다
      • LazoYoung
        좋은 팁 감사합니다.
        대화보기
        • darkflame
          궁금한게 있습니다. 두 번째 동영상, 두 번째 코드 중,

          14 public class GenericDemo {
          15 public static void main(String[] args) {
          16 Person p1 = new Person("부장");
          17 EmployeeInfo ei = (EmployeeInfo)p1.info;
          18 System.out.println(ei.rank);

          17번 줄인, EmployeeInfo ei = (EmployeeInfo)p1.info; 에서 어떻게 인스턴스화 하지 않은 EmployeeInfo형 변수인 ei에 형변환된 값을 입력할수 있는가요?
          인스턴스변수는 인스턴스해야만 사용할 수 있는 것이 아닌가요?
        • popinbompin
          잘봤습니다
        • CalvinJeong
          아.. 마지막 Generic의 제한에서 의문점이 풀렸네요.
          Object 클래스를 부모로 사용했을때, class의 parameter로 개나소나 오만가지 다 올 수 있다는 문제점이 있다는 이유로 Generic을 사용하기 시작했는데,
          사실상 Generic의 선언 단계에서도 Generic 타입으로 오만가지가 다 올 수 있어서 .. 뭐가 의미가 있나 생각했는데
          Generic의 제한으로 이 문제를 해결할 수 있었던 거군요.

          egoing님의 강좌로 JAVA 정주행 완료해갑니다. 너무 감사해요
        • 효근
          나중에 다시 봐야겠습니다.
          자바는 협업이 아주 중요한 것 같네요
        • 민갱
          와.. 진짜 제네릭은 역대급이다.. 라고 느껴지는군요.. 이상하게 제네릭이 다른 파트보다 더 어렵게 느껴지는 1인입니다
        • 1234
          명강의 감사합니다
          강의 듣다가 궁금증이 든게
          Person클래스에서 제네릭을 사용안하고 생성자함수 인자타입을 인터페이스Info로주면 제네릭을 사용안하고도 똑같은 효과를 낼수있는건가 싶어서요 그렇다면 제네릭을 꼭 사용해야되나 하는 궁금증이드네요
        • GoldPenguin
          감사합니다.
        • 궁금이
          아래코드에 왜 에러가 발생하는지 잘 모르겠어요... 생략을 하면 왜 오류가 나는거죠?

          https://ideone.com/cpUGe7

          class EmployeeInfo4{
          public int rank;
          EmployeeInfo4(int rank){ this.rank = rank; }
          }
          class Person4<T, S>{ //generic으로 원시 기본 data type 을 사용할 수 없다. 사용하고 싶다면 wrapper class 을 사용해야한다. 원시 기본 data type을 객체화 시키는것
          public T info;
          public S id;
          Person4(T info, S id){
          this.info = info;
          this.id = id;
          }
          public <U> void printInfo (U info) {
          System.out.println(info);
          }
          }
          public class GenericDemo4 {
          public static void main(String[] args) { //Integer => int 에 대한 wrapping class

          System.out.println("");
          System.out.println("Multiple Generic Demo\n");

          EmployeeInfo4 e = new EmployeeInfo4(1);
          Integer i = new Integer(10);
          Person4<EmployeeInfo4, /*int*/Integer> p1 = new Person4<EmployeeInfo4, /*int*/Integer>(e, i);
          Person4 p2 = new Person4(e, i);
          //Person4 p1 = new Person4(new EmployeeInfo4(1), new Integer(10));

          //p1
          System.out.println(p1.info.rank);
          System.out.println(p1.id);
          System.out.println(p1.id.intValue());
          System.out.println("end\n");

          p1.<Integer>printInfo(e.rank);
          p1.printInfo(e.rank);
          p1.<Integer>printInfo(i);
          p1.printInfo(i);
          p1.<Integer>printInfo(i.intValue());
          p1.printInfo(i.intValue());
          System.out.println("end\n");

          //p2
          System.out.println(p2.info.rank); //=>오류 why?
          System.out.println(p2.id);
          System.out.println(p2.id.intValue()); //=>오류 why?
          System.out.println("end\n");

          p2.<Integer>printInfo(e.rank);
          p2.printInfo(e.rank);
          p2.<Integer>printInfo(i);
          p2.printInfo(i);
          p2.<Integer>printInfo(i.intValue());
          p2.printInfo(i.intValue());
          System.out.println("end\n");

          }
          }
        • 오징돌
          temp를 Integer로 수정하고 data의 타입을 오브젝트말고 T 로 바꿔보세요
          대화보기
          • 오징돌
            person<String> A = person<>은
            person클래스한테 스트링의객채만 전달할수있다고 제한하는 것입니다 그러니까 A는 person클래스의 객채인거죠
            대화보기
            버전 관리
            egoing
            현재 버전
            선택 버전
            graphittie 자세히 보기