[자바 무료 강의] <제네릭> - 코드라떼
Lesson List button
코스자바로 배우는 프로그래밍
hamburger button
강의<제네릭>최종수정일 2021-09-07
아이콘약 4분

이번 강의는 자바의 제네릭에 대해 알아보는 강의입니다. 제네릭이 무엇인지 배우는 것도 좋지만, 왜 제네릭을 사용하는지가 중요한 부분입니다. 앞으로 자바로 작성된 코드 또는 코틀린으로 작성된 코드를 본다고 하더라도 제네릭으로 작성된 코드를 보게됩니다.

추가 노트

목차


  1. Generic
  2. [심화] Generic 디컴파일?




Generic


GenericActual Type Parameter가 삽입된 경우 정상적인 Type인지 컴파일 단계에서 체크해주는 도구입니다.

class Box <T> {     private Object[] box = new Object[10];     int cursor = 0;     void putData(T chicken) {         box[cursor++] = chicken;     }     T getData() {         T data= (T)box[cursor – 1];         box[cursor - 1] = null;         cursor--;         return chicken;     } }
copy

Generic을 클래스에서 사용하려면 <> 키워드를 이용하여 선언합니다.

class Box <T, E> { // T, E 같은 문자들을 Formal Type Parameter라고 부른다 }
copy

Formal Type Parameter는 특수문자나 키워드를 제외하고는 문자 형식에는 크게 상관없으며 대부분 한 글자로 대문자로 표현합니다. 그리고 콤마를 이용하여 여러 개를 선언할 수 있습니다.


Generic 인스턴스 메서드

그리고 Generic 클래스와 함께 Generic 인스턴스 메서드도 선언할 수 있습니다.

class Box <T, E> { T getData() { // E getData()와 중복 } E getData() { // T getData()와 중복 } }
copy

다만 주의할 점은 T, E는 컴파일 이후에는 내부적으로 T -> java.lang.Object, E -> java.lang.Object로 서술되기 때문에, 메서드 중복 선언과 동일하므로 동일한 이름과 형식의 메서드를 사용할 수 없습니다.

반환값 뿐만 아니라 매개변수도 Generic으로 선언할 수 있습니다.

class Box <T, E> { void setData(T data) { } void setElement(E data) { } }
copy


Generic 정적 메서드

Generic 정적 메서드도 존재합니다. 다만 클래스의 Formal Type Parameter를 따라가는 것이 아니라 정적 메서드에 대해서는 여러번 설명했듯이 별도의 공간에 저장되고 수명주기도 다르기 때문에 다르게 운용됩니다. 클래스의 Formal Type Parameter와 상관없습니다.

class Box <T, E> { public void <A> static calculate(A data) { … } }
copy

Generic 정적 메서드를 선언할 때는 반환형 키워드와 static 사이<> 키워드를 선언합니다. 사실 이렇게만 Generic 정적 메서드를 사용하면 사실상 아무 의미 없습니다. Generic 제한 키워드를 이용하여 사용했을 때 그나마 의미가 존재합니다.

class Box <T, E> { public void <A extends Chicken> static calculate(A data) { … } }
copy
// Chicken을 상속 받는 객체만 가능하다. // 자기자신도 Chicken이므로 가능하다 Box.calculate(new Chicken()); // 컴파일 에러 Box.calculate(new Book());
copy


Generic 제한자

Generic 제한자Actual Type Parameter를 모든 타입이 아니라 한정된 타입만 사용할 수 있도록 Formal Type Parameter를 제한하는 것입니다

제한자에는 extends, super, ?(와일드 카드)가 있습니다

extends - 자기 자신과 자신을 상속하는 Actual Type만 가능
super - 자기 자신과 부모의 Actual Type만 가능
? - Actual Type Parameter에서 T 같이 사용 가능한 와일드 카드

Formal Type Parameter에 사용할 수 있는 방법과
Actual Type Parameter에 사용할 수 있는 방법이 다릅니다.


Formal Type Parameter에서 사용하는 법 (클래스 또는 메서드)

class GrandParent { } class Parent extends GrandParent { } class Children extends Parent { }
copy



extends 제한자

class FamilyTree <T extends Parent> { // 자기 자신(Parent)과 자기 자신(Parent)을 상속하는 Type만 가능 }
copy
FamilyTree<Parent> tree = new FamilyTree<>(); // 가능 FamilyTree<Child> tree = new FamilyTree<>(); // 가능
copy



super 제한자

class FamilyTree <T super Parent> { // 자기 자신(Parent)과 자기 자신(Parent)의 부모만 가능 }
copy
FamilyTree<Parent> tree = new FamilyTree<>(); // 가능 FamilyTree<GrandParent> tree = new FamilyTree<>(); // 가능 FamilyTree<Child> tree = new FamilyTree<>(); // 불가능
copy


Actual type에서 사용하는 법

class GrandParent { } class Parent extends GrandParent { } class Children extends Parent { }
copy
class FamilyTree <T> { public void addFamily(T data) { … } }
copy
FamilyTree<? extend Parent> tree = new FamilyTree<>(); // Parent와 Parent를 상속하는 Type만 가능하다 tree.addFamily(new Child()); // 가능 tree.addFamily(new Parent()); // 가능 tree.addFamily(new GrandParent()); // 불가능 FamilyTree<? super Child> tree = new FamilyTree<>(); // Child나 Child의 부모 Type만 가능하다. tree.addFamily(new Child()); // 가능 tree.addFamily(new Parent()); // 가능 tree.addFamily(new Chicken()); // 불가능
copy




[심화] Generic 디컴파일?


Generic으로 설계된 클래스를 컴파일하여 class 파일로 변경 후 다시 디 컴파일하면 어떤 모습일까요?

컴파일 후 디 컴파일 했을 때 class 파일이 어떤 정보를 가지고 있는지도 확인할 수 있으므로 나름 의미가 있습니다.

*온라인 디컴파일 사이트


원본


public class Wrapper<T> { T data; public Wrapper(T data) { this.data = data; } public T getData() { return data; } }
copy


Procyon


public class Wrapper<T> { T data; public Wrapper(final T data) { this.data = data; } public T getData() { return this.data; } }
copy

Procyon 디 컴파일러를 이용했을 때는 제네릭에 대한 정보를 정상적으로 잘 보여줍니다.

다만 추가적으로 생성자의 매개변수가 final이 작성되어있고, getData() 메서드에 data -> this.data로 변경하여 보여주는 것을 알 수 있습니다.


JAD


public class Wrapper { public Wrapper(Object data) { this.data = data; } public Object getData() { return data; } Object data; }
copy

JAD로 디컴파일 했을 때는 Formal Type Parameter가 Object로 대체된 것을 보입니다.

해당 디컴파일러가 빠른 변환을 위해 제네릭 정보를 생략한 것으로 보입니다.

그러나 Wrapper.class 파일을 실제로 오픈해서 byte code를 분석해보면 Generic에 대한 정보가 존재합니다

image

LocalVariableTable과 함께 LocalVariableTypeTable이 별도로 생성됬습니다.

image

Signature, Constant Pool에서도 Generic 참조자에 대한 정보가 존재합니다.
(#23, #25, #29, #30)

다만 Formal Type Parameter를 Object 자체로 봐도 무방하지 않는 이유는

image

테스트를 위해 별도의 클래스를 만들고 확인해본 결과 Signature를 심볼릭 참조자로 하고 있지만 java.lang.Object로 서술하고 있기 때문입니다.

정리하면 컴파일 했을 때, Wrapper.class 파일에는 Generic에 대한 정보가 존재하나, Formal Type ParameterObject로 판단하는 것으로 보입니다.

image

그리고 재미있는 점은 Generic 클래스를 사용하지 않고 Object 클래스로 일반화된 클래스를 만들었을 때, 사용하던 참조 자료형 형 변환에서 사용되는 opcode 명령어(checkcast)가 Generic 클래스에서도 동일하게 사용되고 있습니다.

// Object getData(); Box box = new Box(); Chicken chicken = (Chicken) box.getData(); // T getData(); - Generic Chicken Box<Chicken> box = new Box<>(); Chicken chicken = box.getData();
copy

box.getData() 메서드를 사용할 때 동일한 byte code가 생성된다는 점이죠.
작동방식은 동일하다고 볼 수 있습니다.

결론적으로 정리하면

  • class 파일에는 Generic 정보가 존재한다
  • 컴파일 후에는 Formal Type Parameter를 Object로 서술한다
  • Generic 클래스는 컴파일 시점에만 타입 체크를 해주는 클래스로 확인할 수 있다
  • Object로 일반화된 클래스나 Generic으로 만든 일반화된 클래스나 작동방식은 다르지 않다
도전자 질문
작성된 질문이 없습니다
이용약관|개인정보취급방침
알유티씨클래스|대표, 개인정보보호책임자 : 이병록
이메일 : cs@codelatte.io
사업자등록번호 : 824-06-01921
통신판매업신고 : 2021-성남분당C-0740
주소 : 경기도 성남시 분당구 대왕판교로645번길 12, 9층 24호
파일
파일파일
Root
파일

Object로 일반화된 클래스와 Generic으로 설계된 클래스를 비교해보세요

Output
root$
Lesson List button
코스자바로 배우는 프로그래밍
hamburger button
강의<제네릭>최종수정일 2021-09-07
아이콘약 4분

이번 강의는 자바의 제네릭에 대해 알아보는 강의입니다. 제네릭이 무엇인지 배우는 것도 좋지만, 왜 제네릭을 사용하는지가 중요한 부분입니다. 앞으로 자바로 작성된 코드 또는 코틀린으로 작성된 코드를 본다고 하더라도 제네릭으로 작성된 코드를 보게됩니다.

추가 노트

목차


  1. Generic
  2. [심화] Generic 디컴파일?




Generic


GenericActual Type Parameter가 삽입된 경우 정상적인 Type인지 컴파일 단계에서 체크해주는 도구입니다.

class Box <T> {     private Object[] box = new Object[10];     int cursor = 0;     void putData(T chicken) {         box[cursor++] = chicken;     }     T getData() {         T data= (T)box[cursor – 1];         box[cursor - 1] = null;         cursor--;         return chicken;     } }
copy

Generic을 클래스에서 사용하려면 <> 키워드를 이용하여 선언합니다.

class Box <T, E> { // T, E 같은 문자들을 Formal Type Parameter라고 부른다 }
copy

Formal Type Parameter는 특수문자나 키워드를 제외하고는 문자 형식에는 크게 상관없으며 대부분 한 글자로 대문자로 표현합니다. 그리고 콤마를 이용하여 여러 개를 선언할 수 있습니다.


Generic 인스턴스 메서드

그리고 Generic 클래스와 함께 Generic 인스턴스 메서드도 선언할 수 있습니다.

class Box <T, E> { T getData() { // E getData()와 중복 } E getData() { // T getData()와 중복 } }
copy

다만 주의할 점은 T, E는 컴파일 이후에는 내부적으로 T -> java.lang.Object, E -> java.lang.Object로 서술되기 때문에, 메서드 중복 선언과 동일하므로 동일한 이름과 형식의 메서드를 사용할 수 없습니다.

반환값 뿐만 아니라 매개변수도 Generic으로 선언할 수 있습니다.

class Box <T, E> { void setData(T data) { } void setElement(E data) { } }
copy


Generic 정적 메서드

Generic 정적 메서드도 존재합니다. 다만 클래스의 Formal Type Parameter를 따라가는 것이 아니라 정적 메서드에 대해서는 여러번 설명했듯이 별도의 공간에 저장되고 수명주기도 다르기 때문에 다르게 운용됩니다. 클래스의 Formal Type Parameter와 상관없습니다.

class Box <T, E> { public void <A> static calculate(A data) { … } }
copy

Generic 정적 메서드를 선언할 때는 반환형 키워드와 static 사이<> 키워드를 선언합니다. 사실 이렇게만 Generic 정적 메서드를 사용하면 사실상 아무 의미 없습니다. Generic 제한 키워드를 이용하여 사용했을 때 그나마 의미가 존재합니다.

class Box <T, E> { public void <A extends Chicken> static calculate(A data) { … } }
copy
// Chicken을 상속 받는 객체만 가능하다. // 자기자신도 Chicken이므로 가능하다 Box.calculate(new Chicken()); // 컴파일 에러 Box.calculate(new Book());
copy


Generic 제한자

Generic 제한자Actual Type Parameter를 모든 타입이 아니라 한정된 타입만 사용할 수 있도록 Formal Type Parameter를 제한하는 것입니다

제한자에는 extends, super, ?(와일드 카드)가 있습니다

extends - 자기 자신과 자신을 상속하는 Actual Type만 가능
super - 자기 자신과 부모의 Actual Type만 가능
? - Actual Type Parameter에서 T 같이 사용 가능한 와일드 카드

Formal Type Parameter에 사용할 수 있는 방법과
Actual Type Parameter에 사용할 수 있는 방법이 다릅니다.


Formal Type Parameter에서 사용하는 법 (클래스 또는 메서드)

class GrandParent { } class Parent extends GrandParent { } class Children extends Parent { }
copy



extends 제한자

class FamilyTree <T extends Parent> { // 자기 자신(Parent)과 자기 자신(Parent)을 상속하는 Type만 가능 }
copy
FamilyTree<Parent> tree = new FamilyTree<>(); // 가능 FamilyTree<Child> tree = new FamilyTree<>(); // 가능
copy



super 제한자

class FamilyTree <T super Parent> { // 자기 자신(Parent)과 자기 자신(Parent)의 부모만 가능 }
copy
FamilyTree<Parent> tree = new FamilyTree<>(); // 가능 FamilyTree<GrandParent> tree = new FamilyTree<>(); // 가능 FamilyTree<Child> tree = new FamilyTree<>(); // 불가능
copy


Actual type에서 사용하는 법

class GrandParent { } class Parent extends GrandParent { } class Children extends Parent { }
copy
class FamilyTree <T> { public void addFamily(T data) { … } }
copy
FamilyTree<? extend Parent> tree = new FamilyTree<>(); // Parent와 Parent를 상속하는 Type만 가능하다 tree.addFamily(new Child()); // 가능 tree.addFamily(new Parent()); // 가능 tree.addFamily(new GrandParent()); // 불가능 FamilyTree<? super Child> tree = new FamilyTree<>(); // Child나 Child의 부모 Type만 가능하다. tree.addFamily(new Child()); // 가능 tree.addFamily(new Parent()); // 가능 tree.addFamily(new Chicken()); // 불가능
copy




[심화] Generic 디컴파일?


Generic으로 설계된 클래스를 컴파일하여 class 파일로 변경 후 다시 디 컴파일하면 어떤 모습일까요?

컴파일 후 디 컴파일 했을 때 class 파일이 어떤 정보를 가지고 있는지도 확인할 수 있으므로 나름 의미가 있습니다.

*온라인 디컴파일 사이트


원본


public class Wrapper<T> { T data; public Wrapper(T data) { this.data = data; } public T getData() { return data; } }
copy


Procyon


public class Wrapper<T> { T data; public Wrapper(final T data) { this.data = data; } public T getData() { return this.data; } }
copy

Procyon 디 컴파일러를 이용했을 때는 제네릭에 대한 정보를 정상적으로 잘 보여줍니다.

다만 추가적으로 생성자의 매개변수가 final이 작성되어있고, getData() 메서드에 data -> this.data로 변경하여 보여주는 것을 알 수 있습니다.


JAD


public class Wrapper { public Wrapper(Object data) { this.data = data; } public Object getData() { return data; } Object data; }
copy

JAD로 디컴파일 했을 때는 Formal Type Parameter가 Object로 대체된 것을 보입니다.

해당 디컴파일러가 빠른 변환을 위해 제네릭 정보를 생략한 것으로 보입니다.

그러나 Wrapper.class 파일을 실제로 오픈해서 byte code를 분석해보면 Generic에 대한 정보가 존재합니다

image

LocalVariableTable과 함께 LocalVariableTypeTable이 별도로 생성됬습니다.

image

Signature, Constant Pool에서도 Generic 참조자에 대한 정보가 존재합니다.
(#23, #25, #29, #30)

다만 Formal Type Parameter를 Object 자체로 봐도 무방하지 않는 이유는

image

테스트를 위해 별도의 클래스를 만들고 확인해본 결과 Signature를 심볼릭 참조자로 하고 있지만 java.lang.Object로 서술하고 있기 때문입니다.

정리하면 컴파일 했을 때, Wrapper.class 파일에는 Generic에 대한 정보가 존재하나, Formal Type ParameterObject로 판단하는 것으로 보입니다.

image

그리고 재미있는 점은 Generic 클래스를 사용하지 않고 Object 클래스로 일반화된 클래스를 만들었을 때, 사용하던 참조 자료형 형 변환에서 사용되는 opcode 명령어(checkcast)가 Generic 클래스에서도 동일하게 사용되고 있습니다.

// Object getData(); Box box = new Box(); Chicken chicken = (Chicken) box.getData(); // T getData(); - Generic Chicken Box<Chicken> box = new Box<>(); Chicken chicken = box.getData();
copy

box.getData() 메서드를 사용할 때 동일한 byte code가 생성된다는 점이죠.
작동방식은 동일하다고 볼 수 있습니다.

결론적으로 정리하면

  • class 파일에는 Generic 정보가 존재한다
  • 컴파일 후에는 Formal Type Parameter를 Object로 서술한다
  • Generic 클래스는 컴파일 시점에만 타입 체크를 해주는 클래스로 확인할 수 있다
  • Object로 일반화된 클래스나 Generic으로 만든 일반화된 클래스나 작동방식은 다르지 않다
도전자 질문
작성된 질문이 없습니다
이용약관|개인정보취급방침
알유티씨클래스|대표, 개인정보보호책임자 : 이병록
이메일 : cs@codelatte.io|운영시간 09:00 - 18:00(평일)
사업자등록번호 : 824-06-01921|통신판매업신고 : 2021-성남분당C-0740
주소 : 경기도 성남시 분당구 대왕판교로645번길 12, 9층 24호(경기창조혁신센터)
파일
파일파일
Root
파일

Object로 일반화된 클래스와 Generic으로 설계된 클래스를 비교해보세요

Output
root$