티스토리 뷰

 

 

 

JAVA로 애플리케이션을 만들 때 그냥 아무 생각 없이 <> 안에 객체를 넣어서 해당 타입만 들어오게만 하도록 사용하였습니다.

어떻게 사용되는지 모르고 그냥 썼었는데 이번을 계기로 개념과 제대로 된 사용법을 익힐라고 합니다.

 

 

 

제네릭(Generic) 이란?

 

다양한 타입의 객체들을 다루는 메서드나 클래스에 컴파일 시 타입체크를 해주는 기능이다.

클래스나 메서드에서 사용할 내부 데이터 타입을 외부에서 지정하는 방법이다.

/** 제네릭 미사용 **/
List stringList = new ArrayList<>();

stringList.add("코딩맨");		// 추가 된다.
stringList.add(1);		// 추가 된다.


/** 제네릭 사용 : <String> **/
List<String> list = new ArrayList<>();

list.add("코딩맨");	// 추가된다.
list.add(1);		// 컴파일 오류 발생

 

 

잠깐 문제!

// Object는 모든 클래스의 조상이다!
List<Object> objList = new ArrayList<Integer>();

 

해당 코드가 컴파일 오류가 나는지 안 나는지 확신이 없다면 제네릭(Generic)에 대해 잘 모르는 것이다.

그렇다면 이 블로그로 갈증을 해결하길 바랍니다.

 

 

 

 

 

제네릭의 장점

 

1. 타입의 안정성을 제공한다.

위에 코드를 보면 제네릭 미사용 했을 때 stringList에 String타입의 객체만 넣고 싶은데 숫자 1 도 같이 리스트에 추가되는 것을 볼 수 있다. 

이렇게 개발자가 의도하지 않은 타입의 객체가 저장될 수도 있으며,

저장되어서 잘못 추가된 해당 객체를 꺼내 올 때 다른 타입으로 잘못 형변환이 발생할 수 있는 것이다.

그래서 제네릭(Generic)을 사용하여 클래스나, 메서드, 컬렉션 클래스에 내가 원하는 타입만 넣게 되어 타입의 안정성을 제공하게 된다.

 

 

2. 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해진다.

제네릭(Generic)을 사용하여 미리 타입을 명시에 주었을 때 해당 타입 데이터만 사용한다.

그러므로 번거롭게 다른 타입이었던 것을 해당 타입으로 변경해 주는 형변환을 줄여주거나 생략할 수 있어서 개발 생산성이 향상한다.

 

 

 

 

 

제네릭 타입 표

 

타입 설명
<T> Type  ,  참조 타입
<E> Element  ,  요소
<K> Key  ,  키
<V> Value  ,  값
<N> Number  ,  숫자

 

 

 

 

 

제네릭 클래스의 선언

 

 

제네릭 타입 표에서 보고해당 변수를 사용하여 Object를 T, E, K 등으로 나타낼 수 있는 것을 알 수 있다.  

Unit 객체를 생성할 때 참조변수와 생성자에 대입된 타입(매개변수화된 타입)이 반드시 일치해야 한다.!

 

Marine이라는 클래스가 존재한다고 하자!

// Unit 객체 생성
Unit<Marine> marineUnit = new Unit<Marine>();
// 참조변수 : marineUnit
// 생성자 : Unit<Marine>()

 

 

 

 

해피 고양이

 

 

 

맨 위에서 깜짝 문제 냈을 때의 정답이 여기서 알게 되는데,

<> 안에 들어가는 제네릭 타입이 상속 관계여도 일치하지 않으면 오류가 난다는 것이다.

Marine은 FightUnit의 자손이라고 가정하겠다.

Unit<Marine> marineUnit = new Unit<Marine>();			//  OK
Unit<FightUnit> unit = new Unit<Marine>();		    // Error

 

 

Tip) 제네릭을 한 번에 이해하기는 어려으므로 여러 번 다양하게 보고, 사용하면 익숙해질 것이다. 

 

 

 

 

와일드 카드

 

<? extends T>

 

와일드 카드의 상한 제한을 둔 것이다.

즉, T와 그 자손들만 가능하며 T의 하위 클래스만 올 수 있다는 말이다. 

Number 클래스 하위에 Integer, Double 같은 숫자 클래스들이 있다.

 

예시 코드

public class ClassName <K extends Number> { 
	... 
}
 
public class Main {
	public static void main(String[] args) {
 
		ClassName<Double> a1 = new ClassName<Double>();	// OK!
 
		ClassName<String> a2 = new ClassName<String>();	// error!
	}
}

 

 

 

<? super T>

와일드 카드의 하한 제한을 둔 것이다.

즉, T와 그 조상들만 가능하며 T의 상위 클래스만 올 수 있다는 말이다. 

 

예시 코드

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Number> numbersList = new ArrayList<>();
        numbersList.add(10);
        numbersList.add(20.5);
        numbersList.add(30L);

        printList(numbersList, new NumberComparator());
    }

    public static void printList(List<? extends Number> list, Comparator<? super Number> comparator) {
        list.sort(comparator);
        for (Number item : list) {
            System.out.println(item);
        }
    }

    static class NumberComparator implements Comparator<Number> {
        @Override
        public int compare(Number num1, Number num2) {
            return Double.compare(num1.doubleValue(), num2.doubleValue());
        }
    }
}

/**  출력 
 *   10
 *   20.5
 *   30
 */

 

코드 설명

 

  1. printList 메서드에 Comparator 인터페이스를 추가하여 정렬 기능을 제공한다.
    해당 메서드는 List<? extends Number> 타입의 리스트와 Comparator<? super Number>를 인자로 받는다.
  2. NumberComparator 클래스 내부에서는 숫자요소를 비교한다.

 

 

Comparator<? super T>를 작성하는 이유

만약에 T가 Animal 클래스이고 Comparator를 사용한다면, Animal의 하위 클래스인 Dog이나 Cat등과 같은 객체들을 비교할 수 있어서 사용됩니다.

즉, 가급적 T의 상위 타입으로 Comparator를 정의함으로써, T 타입과 그 하위 타입을 모두 다룰 수 있게 된다는 것입니다. 

 

 

 

 

제네릭 메서드

 

클래스와 다르게 반환타입 이전에 <> 인 제네릭 타입을 선언했다.

[접근 제한자] <제네릭 타입> [반환타입] [메소드명]([제네릭타입] [파라미터]) { ... }
public <T> T MethodName(T t){ ... }	// 제네릭 메소드

public 옆에 있는 <T>와 MethodName안에 있는 매개변수에 있는 T는 전혀 다른 별개의 것입니다.

그래서 같은 타입 문자 T를 사용해도 같은 것이 아니라는 것에 주의해야 합니다.

 

 

 

 

사용 예시

 

예시로 Queue로 구현하여 작성해 보았다. 더 보기를 누르면 코드가 나온다.!

 

더보기
import java.util.LinkedList;
import java.util.Queue;

public class GenericQueue {

    // 제네릭한 Queue 메소드
    public static <T> void processQueue(Queue<T> queue) {
        while (!queue.isEmpty()) {
            T element = queue.poll();
            System.out.print(element + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        Queue<Integer> integerQueue = new LinkedList<>();
        Queue<Double> doubleQueue = new LinkedList<>();
        Queue<String> stringQueue = new LinkedList<>();

        // 데이터를 큐에 추가
        integerQueue.offer(1);
        integerQueue.offer(2);
        integerQueue.offer(3);

        doubleQueue.offer(1.1);
        doubleQueue.offer(2.2);
        doubleQueue.offer(3.3);

        stringQueue.offer("apple");
        stringQueue.offer("banana");
        stringQueue.offer("orange");

        System.out.println("<= Integer Queue =>");
        processQueue(integerQueue);

        System.out.println("<= Double Queue =>");
        processQueue(doubleQueue);

        System.out.println("<= String Queue =>");
        processQueue(stringQueue);
    }
}
/* 출력!
<= Integer Queue =>
1 2 3 
<= Double Queue =>
1.1 2.2 3.3 
<= String Queue =>
apple banana orange 
*/

 

 

 

감사합니다.

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
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 30 31
글 보관함