[Java] Generic

GilLog·2021년 8월 16일
0

Java

목록 보기
25/29
post-thumbnail

🙆‍♂️ import 🙇‍♂️

자바 [JAVA] - 제네릭(Generic)의 이해[st-lab.tistory]

Java Generic 자바 제네릭[슬기로운 개발생활 - choi jeong heon]


Generic

Generic하나의 Data 형식에 의존하지 않고,

하나의 값여러가지 Data Type을 가질 수 있도록 하는 방법이다.

Generic : 포괄적인, 단 하나에 정해지지않고 범용적이고 일반적인 것


흔히 아래와 같이 사용된다.

ArrayList<Integer> intList = new ArrayList<Integer>();

ArrayList<String> strList = new ArrayList<String>();

// 객체<타입> 객체명 = new 객체<타입>();

위처럼 <> 문구 안에 해당 객체의 Type을 Class 내부가 아닌,

외부에서 사용자에 의해 지정되는 것을 의미한다.

<> : <>의 parametertype paramerters, type variable로 부르고,
Java7 이후 부터 <>는 Diamond로 불리운다.


Generic은 한마디로 정의하면 특정 Type을 미리 지정하는 것이 아닌,

필요에 의해 지정할 수 있도록 타입을 일반화한 Type이다.

이러한 GenericJava 5부터 추가되었다.


Generic 사용 이유

Generic을 사용하는 이유는 아래 코드를 살펴보면 알 수 있다.

만약 아래와 같이 ArrayList를 사용하는 경우를 생각해보자.

// OK
List list = new ArrayList();

// OK
list.add("gillog");

// Type missmatch: cannot convert from Object to String !
String str = list.get(0);

List의 Data Type은 Object 이다.

Object는 Java에서 모든 Class의 조상 Class 이므로 어떤 종류의 Class로도 접근 가능하다.

따라서 2번째 라인까지는 정상 수행 가능하지만,

3번째 라인에서 Object Type을 return하기 때문Type missmatch Complie Error가 발생한다.

해당 3번째 라인이 정상 동작하려면 아래와 같이 수정되어야 한다.

String str = (String)list.get(0);

위와 같은 Casting과정을 거치면 문제없지만,

만약 해당 Collection에 수많은 Data가 들어있고, 모든 Data를 꺼내야 하는 상황에서는

매번 Casting 과정이 필요하게 되고, 이는 시스템 성능의 큰 저하를 야기한다.


또한 String type의 data가 저장된 Collection아래와 같이 Casting을 시도 한다면,

// OK
List list = new ArrayList();

// OK
list.add("gillog");

// ClassCastException !
int val = (int)list.get(0);

ClassCastException이 발생하며 Application이 종료될 수 있다.


이를 위해 Java 5 부터 추가된 것이 바로 Generic 이다.

다시 위 코드를 Generic을 사용하여보자.


// OK
List<String> list = new ArrayList<>();

// OK
list.add("gillog");

// OK
String str = list.get(0);

Casting 과정 없이 Data를 정상적으로 가져오고,

ClassCastException에서 자유로워 질 수 있다.


Generic 사용 장점

Generic을 사용하면 아래의 장점을 누릴 수 있다.

  1. 잘못된 Type(Type Missmatch)을 Complie 단계에서 방지할 수 있다.

  2. Class 외부에서 Type을 지정함으로 써, Type 체크와 변환 과정이 필요 없어 관리하기 용이하다.

  3. 비슷한 기능을 사용할 경우에 코드 재사용성이 높아진다.


Generic 사용 방법

먼저 GenericType Parameter Naming Conventions를 살펴보자.

문자용도
EElement
KKey
NNumber
TType
VValue
S, U, V, ...2nd, 3rd, 4th, ... types

Generic은 반드시 한 글자일 필요는 없지만, Oracle에서 권장하는 사용 용도별 Generic 문자 목록이다.

그럼 해당 문구를 가지고 Generic을 사용하는 방법아래 코드와 함께 살펴보자.


Class 및 Interface

public class Gillog <T> {}

Generic Type은 기본적으로 Class나 Interface에서 위와 같은 형태로 선언한다.

두 개의 Generic Type을 작성하는 것 역시 가능하다.

public Interface Gillog <T, K> {}

이렇게 해당 Class나 Interface에서 사용할 타입Class, Interface 외부에서 지정할 수 있게 사용한다.


Generice Type을 활용한 Class나 Interface를 사용할 때는,

해당 객체 생성 시점에 구체적인 타입을 명시해야 한다.


public Class Gillog <T, K> {

        public static void main(String[] args) {
            Gillog<String, Integer> gillog = new Gillog<String, Integer>();
        }

}

여기서 TString Type이 되고, KInteger Type이 된다.

이때, Type Parameter로 명시 가능한 Type은 Reference Type(참조 타입)만이 가능하다.

int, double, boolean, char 와 같은 primitive type(원시 타입)은 지정할 수 없다.


만약 Type Parameter의 Type을 제한하고 싶은 경우 아래와 같이 extends를 활용하면 된다.

public Class Gillog <T, K> {

        public <U extends Number> void logNumber(U u) {
            System.out.println("Log ==" + String.valueOf(u));
        }

        public static void main(String[] args) {
            Gillog<String, Integer> gillog = new Gillog<String, Integer>();
                gillog.logNumber(new Integer(286));
                // error excuted
                gillog.logNumber("286");
        }

}

Generic Method

Generic을 Method에 선언하여 사용 역시 가능하다.

public <T> gillogMethod(T t) { ... }

AccessModifier <GenericType> ReturnType MethodName(GenericType parameterName)

Generic Class와 함께 사용해보면 아래와 같다.

public Class Gillog <T> {

        public <U> void logNumber(U u) {
            System.out.println("Log ==" + String.valueOf(u));
        }

        public static void main(String[] args) {
            Gillog<Integer> gillog = new Gillog<Integer>();
            gillog.logNumber(new Integer(286));
            // error excuted
            gillog.logNumber("286");
        }

}

이렇게 Generic ClassMethod에서 별도의 Generic을 구분지어 사용하는 이유는,

Generic Method를 static method로 사용할 때 필요하기 때문이다.

만약 아래 코드를 살펴 보면

public Class Gillog <T extends Number> {

	public void logNumber(T t) {
            System.out.println("Log ==" + String.valueOf(t));
        }
        
        public static <U extends Number> void staticLogNumber(U u) {
            System.out.println("Log ==" + String.valueOf(u));
        }

        public static void main(String[] args) {
            Gillog<Integer> gillog = new Gillog<Integer>();
            gillog.logNumber(new Integer(286));
            
            Gillog.staticLogNumber(new Integer(286));
        }

}

logNumber() method에서 사용할 Generic Type T의 경우,

유형 지정을 위해new 생성자로 Gillog <T extends Number> Class를,

Instance화 시킬 때 Integer Type을 지정해주었다.

하지만 static methodstaticLogNumber()의 경우 이미 JVM 구동시에 Memory에 onload 되는 형태이다.

따라서 해당 Class를 Instance화 시키는 과정이 없어 Type 지정을 생성자를 통해 지정할 수 없다.

이럴때 별도의 Generic을 사용해야 한다.


Type Parameter 제한

Gneric을 사용할 때 Type Parameter의 범위를 제한 할 수 있다고 앞서 설명했다.

Type Parameter의 범위를 제한할 때extends, super를 사용할 수 있는데

간단히 먼저 설명하면

extends해당 Class, sub Class 포함 Class로 범위를 제한하고,

super해당 Class, super Class 포함 관계 Class로 범위를 제한한다.

위와 같은 관계의 Class가 있을때 extendssuper를 활용한 Type 지정은 아래와 같다.

extends

extends 는 해당 타입을 포함한 자식 타입 까지만 가능하다.

// L, I Type 가능
<K extends I> 

// L Type만 가능
<K extends L> 

// O Type만 가능
<K extends O> 

// G, I, L, O Type 가능 
<K extends G> 

super

super는 해당 타입을 포함한 부모 타입만 가능하다.

// I, G Type 가능
<K super I> 

// L, I, G Type만 가능
<K super L> 

// O, G Type만 가능
<K super O> 

// G Type만 가능 
<K super G> 
profile
🚀 기록보단 길록을 20.10 ~ 22.02 ⭐ Move To : https://gil-log.github.io/

0개의 댓글