[Java] 타입을 더 유연하게 다루기 : Generic

정재현·2024년 1월 3일

Java

목록 보기
33/43
post-thumbnail

Generic이란

  • 타입의 안전성을 해치지 않고 중복되거나 필요없는 코드를 줄여주는 것
    ex. 같은 로직을 수행하는 함수를 타입을 지정해서 여러번 구현해야 하는 작업을 1회로 구현 가능

Generic 사용 방법

  1. 클래스 또는 메서드에 사용 가능
  2. 클래스 이름 뒤에 <>문법 안에 들어가야 할 타입 변수를 지정
  3. 선언 해둔 타입 변수는 해당 클래스 내에서 특정한 타입이 들어갈 자리에 대신 들어갈 수 있습니다. 2번에서는 private 프로퍼티인 t의 타입이 들어가야 할 자리에 들어갔네요
  4. 메서드의 리턴타입에 들어가는 것 역시 마찬가지 입니다.
  5. 여기부터는 제네릭을 통해 구현한 클래스를 사용하는 부분입니다, 클래스에 선언했기 때문에 인스턴스를 만들기 위해서 타입변수에 들어갈 실제 변수의 값을 넣어줘야 합니다. 여기서는 String이네요
  6. 아까 타입변수로 대체 해뒀던 곳에 String이 들어가있기 때문에, 이와 같이 사용 할 수 있습니다.

제네릭 용어 정리하기

  • Generic<T>의 클래스처럼, 제네릭을 사용한 클래스제네릭 클래스라고 한다.
  • 제네릭에서 <>사이에 들어가는 변수명 T는 타입 변수라고 한다.
  • Generic 클래스를 원시 타입 이라고 한다.
	public class Generic<T> { ... }

	Generic<String> stringGeneric = new Generic<>();
  • 예시
// 1.
public class Generic<T> {
	// 2.
    private T t;
    // 3.
    public T get() {
        return this.t;
    }

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

    public static void main(String[] args) {
		// 4.
        Generic<String> stringGeneric = new Generic<>();
		// 5.
        stringGeneric.set("Hello World");
				
        String tValueTurnOutWithString = stringGeneric.get();

        System.out.println(tValueTurnOutWithString);
    }
}

제네릭의 제한

  • 객체의 static 멤버에 사용 할 수 없다.
    • 타입 변수는 인스턴스 변수로 간주되고, 모든 객체에 동일하게 동작해야하는 static 필드 특성상 사용 불가
      • 즉, 변수에는 static을 사용할 수 없다.
	static T get() { ... } // 에러

	static void set(T t) { ... } // 에러
  • 제네릭 배열을 생성 할 수 없다.

제네릭 변수 문법

  • 다수의 타입변수를 사용 가능
	public class Generic<T, U, E> {
	    public E multiTypeMethod(T t, U u) { ... }
	}

	Generic<Long, Integer, String> instance = new Generic();
	instance.multiTypeMethod(longVal, intVal);
  • 상속과 타입의 관계는 그대로 적용됩니다.
    • 대표적으로 부모 클래스로 제네릭 타입변수를 지정하고, 그 안에 자식클래스를 넘기는 것은 잘 동작한다.
  • 와일드 카드를 통해 제네릭의 제한을 구체적으로 정할 수 있다.
    • 다형성 때문에 제한을 해야 함
    1. <? extends T> : T와 그 자손들만 사용 가능
    2. <? super T> : T와 그 조상들만 가능
    3. <?> : 제한 없음
	public class ParkingLot<T extends Car> { ... }

	ParkingLot<BMW> bmwParkingLot = new ParkingLot();
	ParkingLot<Iphone> iphoneParkingLot = new ParkingLog(); // error!

제네릭 메서드 문법

  • 메서드를 메서드를 스코프로 제네릭을 별도로 선언 할 수 있다.
    • 즉, 메서드를 제네릭 타입으로 선언 가능
      • 메서드 안에서 해당 메서드 안에서만 적용되는 제네릭 타입을 부여하면서 사용 가능
	public static <T extends Comparable<? super T>> void sort(List<T> list) { ... }
    또는
    static <T> void sort(List<T> list, Comparator<? super T> c) { ... }
    
    // <T extends Comparable<? super T>> : generic 타입
    	// <T extends Comparable<T>> : Comparable 인터페이스를 구현한 클래스 타입
        // <? super T> : T와 타입이 같거나 조상클래스만 ?에 가능하다는 뜻
        	// ex. A클래스가 있고 A클래스의 조상 B가 있다고 가정했을 때, ?에는 A, B, Object가 가능
    // void : return 타입
    // sort : 함수 이름
    // List<T> list : 매개변수
  • 간단 요약
	
    static <T> T 함수명(T 매개변수) {
        ...
    }
    
    // static : 호출 시에 매게 타입을 지정하기 때문에 static이 가능 ( static이 아니어도 됨 )
    // <T> : Generic 타입
    // T : 리턴 타입 (다른 타입으로 지정해도 상관 없음)
    // 함수명 : 사용자가 지정한 함수 이름
    // T : 매개변수의 타입 ( U, E 등으로 다른 이름으로 지정해서 활용 가능 )
  • 이렇게 반환 타입 앞에 <> 제네릭을 사용한 경우, 해당 메서드에만 적용되는 제네릭 타입변수를 선언 할 수 있다.
  • 타입변수를 클래스 내부의 인스턴스 변수 취급하기 때문에 제네릭 클래스의 타입변수를 static 메서드에는 사용 할 수 없었지만, 제네릭 메소드의 제네릭 타입 변수는 해당 메소드에만 적용되기 때문에 메소드 하나를 기준으로 선언하고 사용 할 수 있다.

같은 이름의 변수를 사용했다고 해도 제네릭 메소드의 타입변수는 제네릭 클래스의 타입변수와 다르다.

즉, T, U, E 등은 모두 표현의 방식일 뿐이다.

    ex.
    public class Generic<T, U, E> {
		// Generic<T,U,E> 의 T와 아래의 T는 이름만 같을뿐 다른 변수
    	static <T> void sort(List<T> list, Comparator<? super T> c) { ... }
	}

참고한 사이트

https://devlog-wjdrbs96.tistory.com/201


profile
공부 기록 보관소

0개의 댓글