본문 바로가기
나의 공부

제네릭이란 뭘까..

by 이숴 2023. 5. 18.
반응형

1. 제네릭

제네릭이란 컴파일 타임에 타입을 체크함으로써 코드의 안전성을 높여주는 기능입니다. 무슨말일까요?

 

예시를 통해 살펴보겠습니다. 

List<T>

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

위와같이 제네릭 기능을 통해 객체의 타입을 결정하게 하여 가독성을 높히고, 타입 체크를 더욱 안정적으로 할 수 있는 기능이라고 할 수 있겠습니다.

 

제네릭은 클래스나 메소드에서 사용할 내부 데이터 타입을 컴파일 시점에 미리 지정하여 타입 안정성을 보장합니다. 즉, 컴파일러가 미리 타입 검사를 수행하여 실행 시간에 타입 캐스팅 문제를 예방합니다.

 

타입 캐스팅 안정이란 것이 무엇일까요?

class Animal { }
class Dog extends Animal { }

Animal animal = new Dog(); // Dog upcasted to Animal type
Dog dog = (Dog) animal; // Animal explicitly downcasted to Dog type

본 예시는 다운캐스팅의 한 예입니다. 다운캐스팅으로 상위클래스의 타입을 참조하는 경우를 말하는데요, 제네릭을 쓰지 않는다면, list나 어떠한 객체에 무슨 타입이 들어있는지 알 수 없기 때문에 캐스팅 과정이 필수적으로 필요합니다.

 

이런 경우에서 개발자가 신경쓰지 않아도 제네릭을 이용한다면 컴퓨터가 자동적으로 오류를 찾아낼 수 있게 됩니다.

 

2. 제네릭 예시

public class GenericBox<T> {
    private T t;

    public void set(T t) { 
        this.t = t; 
    }

    public T get() { 
        return t; 
    }
}

public static void main(String[] args) {
    GenericBox<Integer> integerBox = new GenericBox<>();
    integerBox.set(new Integer(10));
    Integer someInteger = integerBox.get(); // No need for casting
    System.out.println(someInteger);
    
    GenericBox<String> stringBox = new GenericBox<>();
    stringBox.set(new String("Hello world"));
    String someString = stringBox.get(); // No need for casting
    System.out.println(someString);
}

제네릭은 컬렉션에만 사용되지 않습니다. 위의 예시와 같이 클래스에도 제네릭을 적용하여 구성할 수 있는데요.

 

위 예제에서, GenericBox<T> 클래스는 어떤 타입의 객체도 보관할 수 있는 "상자" 역할을 합니다. T는 타입 매개변수로, 실제 클래스를 사용할 때 결정됩니다. 그래서 integerBox는 Integer 타입의 객체를, stringBox는 String 타입의 객체를 보관합니다.

 

본 예시와 같이 캐스팅과정이 없어도 클래스가 어떠한 타입을 가지는지 명시를 할 수 있습니다.

 

제네릭의 특징

List<Object> list = new ArrayList<Integer>();

위 코드는 에러가 발생하지 않을까요? 안타깝게도 문법상 허용이 되지 않습니다.

 

배열의 경우와 함께 보여 알아보겠습니다.

// 배열
Object[] list = new Integer[5];

// 제네릭
List<Object> list = new ArrayList<Integer>();

배열의 경우, Integer가 Object의 하위타입이므로 코드가 성립되게 됩니다.

 

제네릭의 경우, Integer가 Object의 하위타입임은 맞지만, List와 ArrayList의 제네릭타입은 일치하지 않으면 컴파일 에러를 일으킵니다.

 

이러한 단접은 변성이라는 기능을 사용하면 해결할 수 있게됩니다.

 

변성(Variance)

변성에는 3가지 종류가 있습니다.

 

공변성(Covariance) <? extends T>

List<? extends Number> numbers = new ArrayList<Integer>();

위의 코드에서 ArrayList<Integer>는 List<? extends Number>의 서브 타입입니다. 모든 정수(Integer)는 숫자(Number)이므로, 이는 공변성을 나타냅니다.

 

반공변성(Contravariance)

List<? super Integer> numbers = new ArrayList<Number>();

위의 코드에서 ArrayList<Number>는 List<? super Integer>의 서브 타입입니다. 모든 숫자(Number)는 정수(Integer)의 슈퍼타입이므로, 이는 반공변성을 나타냅니다.

 

무공변성(Invariance)

List<Number> numbers = new ArrayList<Number>();

위의 코드에서 ArrayList<Number>는 List<Number>의 서브 타입입니다. List<Number>는 List<Integer> 또는 List<Object>의 서브타입이 아닙니다. 이는 무공변성을 나타냅니다.

 

 

JPA에서 제네릭

public interface JpaRepository<T, ID> extends Repository<T, ID> {
    <S extends T> S save(S entity);
    
    Optional<T> findById(ID id);
    
    List<T> findAll();
    
    long count();
    
    void delete(T entity);
    
    boolean existsById(ID id);
}
public interface UserRepository extends JpaRepository<User, Long> {
    // 추가적인 쿼리 메소드를 정의할 수 있습니다.
    Optional<User> findByEmail(String email);
}
반응형

댓글