본문 바로가기

Computer Science/Programming

JAVA - Generics

728x90

제네릭스

  • Generics는 파라미터화된 타입을 의미한다. 즉, 메소드에 타입(Integer, String, 유저 정의 타입)을 파라미터로 하여 메서드, 클래스, 인터페이스에 전달할 수 있게 하는 기능이다. 이를 이용하여 다양한 데이터 타입을 가지고 동작하는 클래스를 만들 수 있다. 클래스, 인터페이스 또는 메서드와 같은 엔터티가 파라미터화된 타입에 기반에 수행된다면 이를 제네릭스 엔터티라고 한다.

왜 제네릭스를 쓰는가?

  • Object 클래스는 모든 다른 클래스들의 조상 클래스이다. Object 타입 참조 변수는 어떤 객체든 참조할 수 있다. 이런 특징은 타입 안정성을 해친다. 제네릭스를 사용하면 타입 안정성 효과가 있다.
  • JAVA의 Generics는 C++의 template과 비슷하다. HashSet,ArrayList, HashMap 등이 제네릭스를 잘 활용하고 있는 예이다. 제네릭 타입에 두 가지 접근법이 있는데 이 둘은 근본적으로 상당히 다른 점을 갖고 있다.

자바 제레릭스의 타입

  1. Generic Method : 일반 메서드처럼 파라미터와 리턴값을 갖지만, actual type에 의해 인용되는 type parameter를 가지고 있다. 이는 제네릭 메서드가 더 범용적으로 사용되게 한다. 컴파일러는 타입 안정성을 고려하는데 이를 통해 프로그래머가 type casting 없이 쉽게 코드를 짤 수 있게 한다.
  2. Generic Classes : 일반 클래스와의 차이점은 타입 파라미터 섹션을 가진다는 것 뿐이다. 하나 이상의 파라미터 타입이 있을 수 있다. 이런 클래스들을 parameterized classes 혹은 parameterized types라고 한다.

 

제네릭 클래스

public class Ex011 {

    public static void main(String[] args) {
        
        // Integer 타입 인스턴스
        Test<Integer> iObj = new Test<Integer>(15);
        System.out.println(iObj.getObject());

        // String 타입 인스턴스
        Test<String> sObj = new Test<String>("Str");
        System.out.println(sObj.getObject());

    }
}

class Test<T> {
    // T 타입 오브젝트 선언
    T obj;

    // 생성자
    Test(T obj) {
        this.obj = obj;
    }
    public T getObject() {
        return this.obj;
    }
}
/*

15
Str

*/
  • 여러 개의 타입 파라미터를 넣을 수도 있다

제네릭 메서드

public class Ex013 {
    public static void main(String[] args) 
    {
        genericDisplay(11);
        genericDisplay("this is string");
        genericDisplay(3.14);
    }

    static <T> void genericDisplay(T element) 
    {
        System.out.println(element.getClass().getName() + " = " + element);
    }
}
/*
    java.lang.Integer = 11
    java.lang.String = this is string
    java.lang.Double = 3.14
 */
  • 제네릭스는 기본형 타입과는 쓸 수 없고 참조형 타입하고만 어울릴 수 있다.
    • 다만 기본형 타입 배열 같은 경우에는 괜찮다. 배열은 참조형이기 때문이다.
ArrayList<int[]> arr = new ArrayList<>(); // OK.
Test<int> obj = new Test<int>(20); // OK.
  • 인자 타입이 다른 제네릭 타입을 쓰는 경우
package ch1;

public class Ex011 {

    public static void main(String[] args) {
        
        Test<Integer> iObj = new Test<Integer>(5);
        System.out.println(iObj.getObject());

        Test<String> sObj = new Test<String>("String is a string");
        System.out.println(sObj.getObject());

        iObj = sObj; // Type mismatch: cannot convert from Test<String> 

    }
    
}

class Test<T> {
    // T 타입 오브젝트 선언
    T obj;

    // 생성자
    Test(T obj) {
        this.obj = obj;
    }
    public T getObject() {
        return this.obj;
    }
}

→ iObj, sObj 모두 Test 타입에 속하지만, 두 개는 각각 파라미터 타입이 다르기 때문에 다른 타입을 참조하고 있는 것이다. 제네릭스는 이런 타입 안정성 기능이 포함되어 있다.

네이밍 컨벤션

  • T - Type
  • E - Element
  • K - Key
  • N - Number
  • V - Value
  • 제네릭스의 장점
    • 코드 재사용
    • 타입 안정성 : 컴파일러 단계에서 에러가 나 미리 알 수 있음
ArrayList al = new ArrayList(); // 타입 특정 X

al.add("Sachin");
al.add("Rahul");
al.add(10); // 컴파일러가 허용

String s1 = (String)al.get(0);
String s2 = (String)al.get(1);

String s3 = (String)al.get(2); // 런타임 에러 발생

/////////////////////////////////////////////////

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

al.add("Sachin");
al.add("Rahul");

al.add(10); // 컴파일 에러 발생하여 미리 방지 가능

String s1 = (String)al.get(0);
String s2 = (String)al.get(1);
String s3 = (String)al.get(2);

////////////////////////////////////////////////

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

al.add("Sachin");
al.add("Rahul");

String s1 = al.get(0); // 굳이 타입 캐스팅 하지 않아도 됨
String s2 = al.get(1);
  • 코드 재사용성

→ 비교 메서드의 재사용 예시

package ch1;

public class Ex015 
{
    public static void main(String[] args) 
    {
        Integer[] a = { 100, 22, 58, 41, 6, 50 };
 
        Character[] c = { 'v', 'g', 'a', 'c', 'x', 'd', 't' };
 
        String[] s = { "Virat", "Rohit", "Abhinay", "Chandu","Sam", "Bharat", "Kalam" };
 
        System.out.print("Sorted Integer array :  ");
        sort_generics(a);
 
        System.out.print("Sorted Character array :  ");
        sort_generics(c);
 
        System.out.print("Sorted String array :  ");
        sort_generics(s);
       
    }
    // T가 Comparable을 구현한 타입이어야 함
    public static <T extends Comparable<T>  > void sort_generics(T[] a) 
    {

        for(int i = 0; i < a.length -1; i++)
        {
            for(int j = 0; j < a.length - i - 1; j++)
            {
				// Comparable 인터페이스는 compareTo 메서드를 가지고 있음
				// <T extends Comparable<T> > 가 없다면 컴파일 에러 발생.
                if(a[j].compareTo(a[j+1]) > 0) 
                {
                    swap(j, j+1, a);
                }
            }
        }

    
        for(T i : a)
        {
            System.out.print(i + ", ");
        }
        System.out.println();

    }

    public static <T> void swap(int i, int j, T[] a) 
    {
        T t = a[i];
        a[i] = a[j];
        a[j] = t;
    }

}

와일드카드<?>

TODO

https://www.geeksforgeeks.org/wildcards-in-java/

728x90