Java | Generic

바다·2024년 4월 29일
0

Java

목록 보기
15/18
post-thumbnail

Generic

클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법

class Person<T> {
    public T info;
}

Person<String> p1 = new Person<String>();
//p1.info -> String 타입 
Person<StringBuilder> p2 = new Person<StringBuilder>();
//p2.info -> StringBuilder 타입

우리는 왜 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("부장");
        //info에 StudentInfo나 EmployeeInfo를 넣고 싶은 것이지만 컴파일 상에서 아무 문제가 없음
        EmployeeInfo ei = (EmployeeInfo) p1.info;
        System.out.println(ei.rank);
        
        //그렇다고 Student, Employee 두 개의 클래스를 구현하기엔 코드의 중복!!!이 발생한다
    }
}

Generic의 특징

복수의 제네릭

복수의 제네릭을 사용할 때는 <T, S>와 같은 형식을 사용한다!
T와 S 대신 어떤 문자를 사용해도 된다

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, Integer> p1 = new Person<>(new EmployeeInfo(1), 1); 
    }
}

기본 데이터 타입과 제네릭

제네릭은 참조 데이터 타입에 대해서만 사용할 수 있다!
기본 데이터 타입을 사용해야 할 경우에는 -> Wrapper 클래스를 사용

import java.util.ArrayList;

public class GenericDemo {
    public static void main(String[] args) {
        ArrayList<int> arr = new ArrayList<>(); //오류 발생
        ArrayList<Integer> arr = new ArrayList<>(); //문제 없음
    }
}

제네릭의 생략

Person을 생성할 때 들어오는 e, i의 타입을 통해 Generic으로 들어오는 T와 S의 타입을 알 수 있다

public class GenericDemo {
    public static void main(String[] args) {
        EmployeeInfo e = new EmployeeInfo(1);
        Integer i = new Integer(10);
        Person p1 = new Person(e,i);
        //원래는 Person<EmployeeInfo, Integer> 이라고 명시 해줘야 함
    }
}

메소드에 적용

필드가 아닌 메소드에도 Generic을 사용할 수 있다

class Person <T, S> {
    //제네릭을 메소드에 적용하기
    public <U> void printInfo(U info) {
        System.out.println(info);
    }
}

제네릭의 제한

선언한 제네릭에 아무 타입이 오는 것이 싫다!
최소한의 범위를 정해주고 싶다면? 제한을 걸어둘 수 있다!

extends

  • 제네릭으로 올 수 있는 데이터 타입을 특정 클래스의 자식으로 제한할 수 있다
  • 상속 뿐만 아니라 구현의 관계에서도 사용할 수 있다
interface info {
    int getLevel();
}

class EmployeeInfo implements Info {
    public int rank;

    EmployeeInfo(int rank) {
        this.rank = rank;
    }

    public int getRank() {
        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<EmployeeInfo> p1 = new Person<EmployeeInfo>(new EmployeeInfo(1));

        //에러 발생
        Person<String> p2 = new Person<String>("부장");
    }
}

super

  • 제네릭으로 올 수 있는 데이터 타입을 특정 클래스의 상위 타입으로 제한할 수 있다
  • extends보다 자주 사용하지 않는다
class Person<T super Integer> {
    public T level;
    
    Person(T level) {
        this.level = level;
    }
}

public class GenericDemo {
    public static void main(String[] args) {
        //정상적으로 작동
        Person<Integer> p1 = new Person<>(1);
        Person<Object> p1 = new Person<>(Object o);
        
        //에러 발생
        Person<String> p2 = new Person<>();
    }
}

<?> 와일드 카드

  • <?> 는 와일드 카드로, <? extends Object>와 마찬가지이다
  • 어떤 타입이든 상관이 없다는 의미
  • 타입 마라미터를 대치하는 구체적인 타입으로 모든 클래스와 인터페이스 타입이 올 수 있다

Generic 사용 주의사항

  1. 제네릭 타입의 객체는 생성이 불가
class Sample<T> {
    public void method() {
        T t = new T();  //생성 안 됨
    }
}
  1. static 멤버에 제네릭 타입이 올 수 없음
class Employee<T> {
    private String name;
    private int age = 0;
    
    public static T addAge(int n) {
        //static 메서드의 반환 타입으로 사용 불가
        //왜냐하면 static은 제네릭 객체가 생성되기도 전에 이미 자료 타입이 정해져 있어야 하기 때문
    }
}
  1. 제네릭으로 배열 선언 주의
class Sample<T> { }

public class Main {
    public static void main(String[] args) {
        Sample<Integer>[] arr1 = new Sample<>[10];
        //생성 불가능
        //제네릭 클래스 자체를 배열로 만들 수는 없다
        //하지만? 제네릭 타입의 배열 선언은 허용된다
        
        Sample<Integer>[] arr2 = new Sample[10];
        //제네릭 타입을 생략해도 위에서 이미 정의했기 때문에 Integer가 자동으로 추론됨
        arr2[0] = new Sample<Integer>();
        arr2[1] = new Sample<>();
        
        //Integer가 아닌 타입은 저장 불가능
        arr[2] = new Sample<String>();
    }
}
profile
ᴘʜɪʟɪᴘᴘɪᴀɴs 3:14

0개의 댓글