본문 바로가기
Java

Java_제네릭(Generic)

by 유서담 2023. 11. 17.

제네릭 (Generic)

 

  • 소스 레벨에서 데이터 타입이 결정되지 않고, 별칭(타입파라미터)만 지정한다.
    • 데이터 타입은 필드의 타입, 매개변수 타입, 리턴타입 등이다.
  • 객체 생성시점, 구현클래스 구현시점, 메소드 실행시점에 별칭을 대신할 데이터 타입을 외부에서 지정하는 것이다.
  • 제네릭은 <> 다이아몬드 표기법으로 타입파라미터(별칭)를 지정한다.
    • 객체 생성싯점, 구현클래스 구현시점, 메소드 실행시점에 데이터타입을 지정한다.
    • 데이터타입은 클래스 혹은 인터페이스 타입만 가능하다.
    • 기본 자료형은 제네릭의 데이터 타입으로 지정할 수 없다.

 

제네릭 클래스

 

  • 타입 파라미터를 하나 이상 가지고 있는 클래스
  • 여러 종류의 객체를 다루는 클래스를 제네릭 클래스로 정의하면 타입의 안전성을 보장받고, 형변환 코드를 제거할 수 있다.

제네릭 클래스 예

  • 자료구조(Set, List, Map<K, V>)
  • 데이터를 반복처리하는 것(Iterator, Enumeration)

 

제네릭 클래스 구현시 주의사항

  • T타입의 배열을 생성할 수 없다.
  • T타입의 클래스 변수를 생성할 수 없다.

 

제네릭 클래스 정의 예시

public class GenericBox<T> {

	private T data;
	
	public void add(T data) {
		this.data = data;
	}
	
	public T get() {
		return data;
	}
}

 

public class App {

	public static void main(String[] args) {
		
        // T의 타입이 String
		GenericBox<String> box1 = new GenericBox<String>();
		box1.add("홍길동");
		String value = box1.get();
		
        // T의 타입이 Integer
		// new GenericBox<> 타입파라미터 적는 곳을 생략해도 무방하다
		GenericBox<Integer> box2 = new GenericBox<>();
		box2.add(100);
		int value2 = box2.get();
		
	}
}

 

 

제네릭 클래스 사용하는 이유

 

  • 클래스내부에서 사용할 데이터의 타입을 외부에서 지정하는 것이다.
  • 객체 생성시점에 데이터 타입을 지정한다.

 


// Fruit 객체만을 저장하기 위해 FruitBox 제네릭 클래스
public class FruitBox<T extends Fruit> {

	T fruit;
	
	public void add(T t) {
		this.fruit = t;
	}
	
	public T get() {
		return fruit;
	}
}

// 참고로 Fruit 클래스는 이러하다

public class Fruit {
	
}

 

public class App3 {

	public static void main(String[] args) {
		
		FruitBox<Apple> box1 = new FruitBox<Apple>();
		box1.add(new Apple());
		Apple apple= box1.get();
		
	}
}

// 참고로 Apple 객체의 클래스는 이러하다

public class Apple extends Fruit{

}

 

public class App3 {

	public static void main(String[] args) {

    	FruitBox<Banana> box2 = new FruitBox<Banana>();
		box2.add(new Banana());
		Banana banana = box2.get();
		
	}
}

// Banana 객체 클래스
public class Banana extends Fruit{

}

 

 

Fruit 객체를 상속받은 Apple 객체와 Banana 객체가 타입 파라미터에 들어가도 오류가 발생되지 않음을 알 수 있다

그러나 이런식으로 제네릭 타입을 특정한 객체로 해두면 다른 타입은 들어가지 못한다

 

 

 

주석에도 적어놨지만

FruitBox 제네릭 클래스에 타입 파라미터를 <T extends Fruit> 으로 지정했기 때문에 Fruit 객체와 Fruit을 상속받은 객체들만 사용할 수 있음을 알 수 있다

 


 

 

위와 반대로 제네릭 클래스의 타입 파리미터가 상속받은 값이 아니면 어떻게 반응할까

클래스의 형변환처럼 형변환이 가능할까

 

public class App4 {

	public static void main(String[] args) {

		// box1은 Fruit 객체를 저장하는 박스 객체다
		FruitBox<Fruit> box1 = null;
		
		box1 = new FruitBox<Fruit>();
    }
}

 

 

위 클래스에 아래와 같은 코드를 넣으면 어떻게 반응할까??

 

box1 = new FruitBox<Apple>();
box1 = new FruitBox<Banana>();

 

 

타입파라미터간에 형변환은 불가능하다

 

 

Type mismatch 에러가 발생한다

제네릭 객체의 타입 파라미터끼리는 형변환이 불가능한 것을 알 수 있다

 


 

와일드카드

 

  • 제네릭 타입에 다형성을 적용할 때 사용한다.
  • '?', 'extends', 'super'를 사용해서 다룰 객체의 상한과 하한을 지정할 수 있다.
    • <?> : 제한없음.  타입 파라미터의 구체적인 타입으로 모든 클래스, 모든 인터페이스 타입이 가능하다
    • <? extends X > : 상위 클래스 제한(X포함 X를 상속받은 자식들만).  타입 파라미터의 구체적인 타입으로 상위타입 혹은 지정된 상위타입의 하위타입만 가능하다
    • <? super x> : 하위 클래스 제한(x포함 x의 조상들만).  타입 파라미터의 구체적인 타입으로 하위타입 혹은 지정된 하위타입의 상위타입만 가능하다

 

와일드카드 예시 

 

 public void 전공과정등록(Course<? extends Student> course) { ... }
    // Course<Student> c = new Course<>();		
    // Course<CollegeStudent> c = new Course<>();
    // Course<PostGraduatedStudent> c= new Course<>();
    // 위의 3개 과정은 전공과정등록() 메소드의 매개변수에 전달할 수 있는 과정이다.

    public void 대학원과정등록(Course<? extends PostGraduatedStudent> course) { ... }
    // Course<PostGraduatedStudent> c= new Course<>();
    // 위의 과정은 전공과정등록() 메소드의 매개변수에 전달할 수 있는 과정이다.

    public void 직장인및일반인과정등록(Course<? super Worker> course) { ... }
    // Course<Worker> c = new Course<>();		
    // Course<Person> c = new Course<>();
    // 위의 2개 과정은 전공과정등록() 메소드의 매개변수에 전달할 수 있는 과정이다.

 


 

인터페이스에 제네릭 사용하기

 

// 구현클래스 정의 싯점에 데이터타입을 지정하기

public inteface Comparable<T> {
    int compareTo(T other);
  }
  
  //제네릭 인터페이스를 구현한 클래스의 T가 Product로 대체된다.
  public class Product implements Comparable<Product> {
    private String name;
    private int price;
    
public int compareTo(Product other) {
      return this.price - other.price;
    }
  }

 

 

 // 구현클래스 정의시점에 데이터타입을 지정하지 않고, 객체 생성시점에 지정하게 하기
 
 public interface Collection<E> {
    boolean add(E e);
  }

  public class ArrayList<E> implements Collection<E> {
    ...
    public boolean add(E e) {
  수행문
    }
  }

 

 

 

제네릭 메소드

 

  • 메소드의 선언부에 타입파라미터가 선언된 메소드
    • 메소드의 선언부에 <T>와 같은 타입파라미터 정의가 있어야 한다
    • 제네릭 메소드는 제네릭클래스가 아닌 곳에서도 정의할 수 있다.
 class Box<T> {
	public void add(T t) { ... }	
	public T get(int index) { ... } 

	public <T> T[] toArray(T[] arr) { ... } // 제네릭 메소드
	//이 메소드의 T는 Box<T>와 아무 상관이 없다.
	//이 메소드의 T는 메소드 실행시 전달된 배열의 타입에 전적으로 의존한다.
   }

   class Arrays {
	public static <T> void sort(T[] arr) { ... }	// 제네릭 메소드

	//sort() 메소드 실행시 전달받은 배열의 타입이 T의 타입이 된다.
	//Arrays.sort(new String[]{"김유신", "강감찬"}); ---> <T>는 <String>이 된다.
   }
   class ApplicationContext {
	public <T> T getBean(String beanName, Class<T> clazz) { ... }
	//getBean() 메소드 실행시 전달받은 객체의 설계도 타입이 T의 타입이 된다.
	//ctx.getBean("service", UserService.class) ---> <T>는 <UserService>가 된다.
   }

'Java' 카테고리의 다른 글

Java_Map, Comparable  (2) 2023.11.21
Java_Collection Framework, Iterator  (0) 2023.11.21
Java_StringBuilder/Buffer, Date 클래스, Math 클래스  (0) 2023.11.16
Java_Wrapper 클래스  (0) 2023.11.16
Java_String 클래스  (0) 2023.11.14