728x90
제네릭
@2024.04.15
제네릭(Genenic)
💡
Java5에서 추가된 문법으로,
프로그래밍 언어에서
타입을 파라미터로 사용할 수 있게 하는 기능이다.
프로그래밍 언어에서
타입을 파라미터로 사용할 수 있게 하는 기능이다.
- 컴파일 시점에 타입을 체크하며, 타입 안전성이 보장되고 코드의 가독성이 향상
- 주로 컬렉션 라이브러리에 많이 사용하며, 객체의 타입을 컴파일 시에 체크하는 기능
- 제네릭이 없었을 때는 매번 형 변환이 필요
- 캐스팅을 줄임으로써, 코드의 오류 가능성을 줄이고 유지 보수성 향상
List list = new ArrayList(); list.add("text"); list.add(5); //list.get(0)일시Object 값으로 반환 //부모가 자식을 가리킬순있어도 자식은 부모를 가리킬순 없음 => 형변환 필요! String str = (String)list.get(0); int num = (Integert)list.get(1); //제네릭을 사용하면 매번 형변환 할 필요 없음 // 문자열만 저장하는 리스트 //-> 컴파일시 타입 체크가 이루어져 잘못된 타입 객체가 저장되는것을 방지 List<String> strList = new ArrayList<>(); String s = strList.get(0);
- 일반적으로 쓰이는 제네릭
- <T> → Type
- <E> → Element
- <K> → Key
- <V> → Value
- <N> → Number
제네릭 타입
💡
자바 프로그래밍에서 데이터 타입을 일반화하는 방법으로,
이를 통해 클래스, 인터페이스, 메서드가 다양한 타입의 객체를 다룰 수 있도록 해준다.
이를 통해 클래스, 인터페이스, 메서드가 다양한 타입의 객체를 다룰 수 있도록 해준다.
- 코드의 재사용성을 높이고, 타입의 안전성을 강화
제네릭 클래스/인터페이스
- 클래스/인터페이스 이름 뒤에 꺾쇠 안에 타입 파라미터를 선언
- class Box<T>
- inferface Box<T>
- 타입 파라미터는 클래스/인터페이스 내부에서 변수 타입, 메서드 리턴 타입, 파라미터 타입으로 사용
- 클래스/인터페이스를 사용할 때는 구체적인 타입을 지정하여 객체화
- Box<Integer> box = new Box<>();
- class Exam implements Box<String>
public class Pair<K> {
private K first;
public K getFirst(){
return first;
}
}
제네릭 메서드
💡
다양한 타입의 객체를 동일한 방식으로 처리하는 메서드
- 클래스 또는 인터페이스가 제네릭이 아니더라도 사용 가능
- 매개변수 타입, 반환 타입을 제네릭 타입으로 사용 가능
//예시 1. 컴파일러 타입 추론 public <T> void Method(T data){ } Integer[] intArray = {1, 2, 3, 4, 5}; //컴파일러는 전달된 배열의 타입을 기반으로 T의 구체적인 타입을 추론 //예를 들어, Integer[] 배열을 전달하면 T는 Integer로 추론 Method(intArray); ------------------------------------------------------------------------------------ //주의 할 내용. 컴파일러 타입 추론 불가능 public static void printWord(List<T> worldList){ } // 메소드 수정 public static <T> void printWord(List<T> worldList){ } String word = ""; printWord(word); // 컴파일 에러 // 제네릭 메소드가 아닌 제네릭 클래스나 메소드 외부에서 선언된 제네릭 타입을 사용하는 상황이기 때문에 wordList.add(word); // 컴파일 에러 -> 메소드 수정시 에러 발생 안함 ------------------------------------------------------------------------------------ // 예시 3. Integer 처리 메서드 public static <T> void addList(List<Integer> list, Integer element){ list.add(element); }
- 특징
- 타입의 안전성
- 컴파일 시점에 타입 체크하여 타입 안전성 보장
- 런타임에 발생할 수 있는 ClassCastException 예외 방지
- 재사용성
- 하나의 메서드로 다양한 타입의 객체를 처리 가능
- 타입 추론
- 메서드 호출 시점에 컴파일러가 인자의 타입을 바탕으로 적절한 타입을 추론
⇒ 사용자는 타입을 명시적으로 제공하지 않아도 된다.
- 타입의 안전성
멀티 타입 파라미터
💡
클래스나 인터페이스는 둘 이상의 타입 파라미터를 가질 수 있다.
class Pair<K, V>{
private K first;
private V second;
public K getFirst(){
return first;
}
public V getSecond(){
return second;
}
}
Pair<Integer,String> pair = new Pair<>();
제한된(bounded) 타입 파라미터
💡
특정 타입 또는 그 타입의 하위 클래스들에 대해서만 메서드나 클래스를 사용할 수 있도록 제한을 두는 방법
- 예시
- Number 클래스의 서브 클래스만 타입 파라미터로 가질 수 있는 Num 클래스 정의
// Number를 상속받는 자식들만 쓸 수 있다.
//Integer, Double, Float, Long 만 가능
class Num <T extends Number>
//T타입이 Comparable 인터페이스를 구현해야한다
<T extends Comparable<T>>
//T가 Number를 상속받고, 동시에 Comparable 인터페이스를 구현 해야한다.
<T extends Number & Comparable<T>>
상한(extends) & 하한(super)
💡
상한(extends)은 제네릭 타입이 특정 클래스의 하위 타입만 허용하도록 제한하는 것
하한(super)은 제네릭 타입이 특정 클래스의 상위 타입만 허용하도록 제한하는 것
하한(super)은 제네릭 타입이 특정 클래스의 상위 타입만 허용하도록 제한하는 것
- 차이점
- 상한(extends)
- 주로 읽기 작업에 사용
- 상한 타입의 메서드와 필드를 안전하게 사용 가능
public class NumberProcessor { // 상한을 사용하여 제네릭 메소드 정의 public static <T extends Number> double sum(List<T> numbers) { double total = 0.0; for (Number number : numbers) { total += number.doubleValue(); } return total; } public static void main(String[] args) { List<Integer> integerList = Arrays.asList(1, 2, 3); List<Double> doubleList = Arrays.asList(1.5, 2.5, 3.5); // Integer와 Double 모두 Number의 하위 클래스이므로 메소드 사용 가능 System.out.println("Sum of integers: " + sum(integerList)); System.out.println("Sum of doubles: " + sum(doubleList)); } }
- 하한(super)
- 주로 쓰기 작업에 사용
- 객체를 저장하는데 적합
public class DataWriter { // 하한을 사용하여 제네릭 메소드 정의 // ?는 와일드 카드 public static void addNumbers(List<? super Integer> list) { for (int i = 1; i <= 5; i++) { list.add(i); // Integer 추가 가능 } } public static void main(String[] args) { List<Number> numberList = new ArrayList<>(); List<Object> objectList = new ArrayList<>(); addNumbers(numberList); addNumbers(objectList); System.out.println("Number List: " + numberList); System.out.println("Object List: " + objectList); } }
- 상한(extends)
와일드 카드
💡
제네릭 프로그램에서 사용되는 일종의 표현 방법으로,
특정 타입이 아닌
모든 타입을 의미
특정 타입이 아닌
모든 타입을 의미
- ? 기호를 사용하여 표현
- 제네릭 타입을 사용하는 메서드나 클래스가 다양한 타입과 호환될 수 있도록 도와준다.
- <T>처럼 타입을 딱 정한 것과 달리 어떠한 타입이 올 수 있음을 의미
- <T>→ 주로 읽기와 쓰기, 타입별 연산을 수행할 때 유리
- <?> → 주로 읽기 작업에 적합
import java.util.ArrayList; import java.util.List; public class Main { // 와일드카드 타입의 리스트에 요소를 추가하는 메서드 public static void addWild(List<?> list){ // 컴파일 오류 발생! 와일드카드 타입의 리스트에는 어떤 타입의 요소도 추가할 수 없음 list.add(1); } // 제네릭 메서드: 리스트에 요소를 추가하는 메서드 public static <T> void addList(List<T> list, T element){ list.add(element); } public static void main(String[] args) { // Integer 타입의 요소를 가진 리스트 초기화 List<Integer> initList = new ArrayList<>(); // String 타입의 요소를 가진 리스트 초기화 List<String> strList = new ArrayList<>(); // 와일드카드 타입의 리스트에 요소 추가 시도 addWild(initList); // 컴파일러는 List<?>로 처리 addWild(strList); // 컴파일러는 List<?>로 처리 // 제네릭 메서드 호출 시 매개변수 타입을 명시 addList(initList, 1); // Main.<String>addList(strList, "hello"); addList(strList, "hello"); // 결과 출력 System.out.println(initList); // [1] System.out.println(strList); // [Hello] } }
- 종류
- Unbounded Wildcard(제한 없는 와일드카드)
- ? 형식
- 모든 타입이든 다 허용
// 어떤 타입이든 될 수 있는 객체 리스트 List<?>
- Bounded Wildcare(제한 있는 와일드카드) : 2가지 형태 존재
- Upper Bounded Wildcard(상한 제한 와일드카드)
- ? extend Type
- 지정된 타입 또는 그 하위 타입만 허용
// Number 또는 Number의 하위 클래스 타입의 객체를 포함하는 리스트 List<? extends Number>
- Lower Bounded Wildcard(하한 제한 와일드카드)
- ? super Type
- 지정된 타입 또는 그 상위 타입만 허용
// Integer 또는 Integer의 상위 클래스 타입의 객체를 포함하는 리스트 List<? super Integer>
- Upper Bounded Wildcard(상한 제한 와일드카드)
- Unbounded Wildcard(제한 없는 와일드카드)
제네릭 타입 상속
- 주의할 점
- 타입 불일치
- 하위 클래스에서 상위 클래스의 타입 파라미터를 재정의 시, 상위 클래스에서 정의된 메서드와 충돌이 발생하지 않도록 주의
- 상속과 인터페이스 구현
- 클래스가 제네릭 인터페이스를 구현하거나, 제네릭 클래스를 상속받을 때, 모든 타입 파라미터를 명시적으로 정의
- 타입 불일치
제네릭 클래스 상속
💡
하위 클래스는 상위 클래스의 타입 파라미터를 그대로 사용하거나, 새로운 파라미터를 정의
@기본 형태
class Child <T> extends Praent<T>
@기본 형태
class Child <T> extends Praent<T>
- 일반 클래스 상속과 유사하나, 제네릭 타입에 대한 좀 더 복잡한 상속 구문을 다룬다.
- 제네릭 클래스를 상속할 때는 상속된 클래스의 제네릭 타입 매개변수를 다시 선언할 필요 존재
// 부모 제네릭 클래스 class Box<T> { private T value; public Box(T value) { this.value = value; } public T getValue() { return value; } } // 자식 제네릭 클래스 class BoxChild<T> extends Box<T> { // 부모 클래스의 생성자 호출 public BoxChild(T value) { super(value); } } public class Main { public static void main(String[] args) { // 부모 제네릭 클래스의 인스턴스 생성 Box<Integer> intBox = new Box<>(10); System.out.println("Integer value: " + intBox.getValue()); // 자식 제네릭 클래스의 인스턴스 생성 BoxChild<String> strBox = new BoxChild<>("hello"); System.out.println("String value: " + strBox.getValue()); } }
제네릭 타입 파라미터의 상속
💡
하위 클래스에서 상위 클래스의 타입 파라미터를 그대로 사용하면, 상위 클래스가 정의한 타입 파라미터에 따라 타입 안정성을 유지 가능
// 부모 클래스인 Box 클래스 정의
class Box<T> {
private T value;
public Box(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
// 자식 클래스인 StringBox 클래스 정의
// 부모 클래스의 타입 파라미터를 상속받아서 String 타입만을 다루는 Box 클래스를 만듦
class StringBox extends Box<String> {
public StringBox(String value) {
super(value);
}
// StringBox 클래스에는 추가적인 메서드나 필드를 정의할 수 있음
public void printUpperCase() {
System.out.println(getValue().toUpperCase());
}
}
public class Main {
public static void main(String[] args) {
// 부모 클래스인 Box를 이용하여 Integer 타입의 객체를 생성
Box<Integer> intBox = new Box<>(10);
System.out.println("Integer value: " + intBox.getValue());
// 자식 클래스인 StringBox를 이용하여 String 타입의 객체를 생성
StringBox strBox = new StringBox("hello");
System.out.println("String value: " + strBox.getValue());
strBox.printUpperCase(); // 추가적인 메서드 호출
}
}
제네릭 타입 파라미터 재정의
💡
하위 클래스에서 상위 클래스의 타입 파라미터를 다시 정의하면, 다양한 타입을 처리할 수 있는 클래스 생성 가능
@기본 형태
class Child<T,E> extends Parent<U>
@기본 형태
class Child<T,E> extends Parent<U>
- 재정의된 제네릭 타입 파라미터를 사용하면, 하위 클래스에서 부모 클래스의 제네릭 타입을 더 구체적으로 특화 가능
// 부모 제네릭 클래스 class Box<T> { private T value; public Box(T value) { this.value = value; } public T getValue() { return value; } } // NumberBox클래스는 Box 클래스를 상속하여 Box 클래스의 제네릭 타입 매개변수를 U로 재정의 // 자식 제네릭 클래스 class NumberBox<U> extends Box<U> { // 부모 클래스의 생성자 호출 public NumberBox(U value) { super(value); } } public class Main { public static void main(String[] args) { // 부모 제네릭 클래스의 인스턴스 생성 Box<Integer> intBox = new Box<>(10); System.out.println("Integer value: " + intBox.getValue()); // 자식 제네릭 클래스의 인스턴스 생성 NumberBox<Double> doubleBox = new NumberBox<>(3.14); System.out.println("Double value: " + doubleBox.getValue()); } }
728x90