1. 제네릭 사용하는 이유
설명 보기
- 제네릭 : 클래스나 메소드에서 사용할 내부 데이터 타입을 컴파일 시에 미리 지정하는 방법
- Wrapper 타입이나 사용자가 생성한 클래스가 들어갈 수 있다.
- 제네릭이 생략되는 경우 최상위 객체인<Object>가 들어간것으로 생각한다.
- 이 때문에 다양한 타입의 데이터들이 들어갈 수 있어 컴파일 오류가 날 가능성이 있다.
- 장점
- 컴파일 시 미리 타입이 정해져, 타입 검사나 변환 같은 번거로운 작업을 생략가능 (casting을 제거)
List list = new ArrayList(); list.add("Hello"); String str = (String) list.get(0); List<String> list = new ArrayList<String>(); list.add("Hello"); String str = list.get(0);
- 클래스나 메소드 내부에서 사용하는 타입 안정성을 높일 수 있다. (강한 타입 체크)
- 컴파일 시 미리 타입이 정해져, 타입 검사나 변환 같은 번거로운 작업을 생략가능 (casting을 제거)
- 사용 이유
- 해당 타입을 이용함으로써 잘못된 타입이 사용될 수 있는 가능성을 컴파일 과정에서 제거 가능
List<Youtube> youtubeList = new ArrayList<Youtube>(); youtubeList.add(new Youtube()); youtubeList.add(new DisneyPlus()); // 컴파일 에러 발생. Youtube 외에 다른 타입 저장불가
- 클래스, 인터페이스, 메소드를 정의할때 타입을 파라미터로 사용할 수 있도록 한다.
- 제네릭 : 클래스나 메소드에서 사용할 내부 데이터 타입을 컴파일 시에 미리 지정하는 방법
2. 제네릭 타입(class <T>, Interface<T>)
설명 보기
- 클래스 / 인터페이스 뒤에
<>
부호가 붙고 사이에 타입 파라미터를 적는다
- 실제 클래스가 사용될 때 구체적인 타입을 지정해 타입 변환을 최소화 시킨다.
public class 클래스명<T> { ... } public interface 인터페이스명<T> { ... }
public class Box { private Object object; public void set(Object object) { this.object = object; } public Object get() { return object; } }
public class Box<T> { private T t; public T get() { return t; } public void set(T t) { this.t = t; } } //Box<String> box = new Box<String>(); //이런식으로 간단하게 String 타입으로 설정이 가능하다
- 클래스 / 인터페이스 뒤에
3. 제네릭 메소드
설명 보기
- 제네릭 메소드 : 매개 타입과 리턴타입으로 타입 파라미터를 갖는 메소드
public <T> Box<T> boxing(T t) { }
Box<Integer> box = boxing(100);
public class Util { public static <T> Box<T> boxing(T t) { Box<T> box = new Box<T>(); box.set(t); return box; } }
public class GenericMethodExample { public static void main(String[] args) { Box<Integer> box1 = Util.boxing(100); int intValue = box1.get(); Box<String> box2 = Util.boxing("홍길동"); String strValue = box2.get(); } }
- 제네릭 메소드 : 매개 타입과 리턴타입으로 타입 파라미터를 갖는 메소드
4. 제한된 타입 파라미터 (<T extends 최상위타입>)
설명 보기
- 타입 파라미터에 구체적인 타입을 제한하는 기능
- 타입 파라미터에 지정되는 구체적인 타입 : 상위타입 or 상위 타입의 하위 또는 구현 클래스
public <T extends Number> int compare(T t1, T t2) { double v1 = t1.doubleValue(); double v2 = t2.doubleValue(); return Double.compare(v1, v2); }
public class BoundedTypeParameterExample { public static void main(String[] args) { // String value = Util.compare("a", "b"); // String은 Number 타입이 아니므로 컴파일 오류 발생 int result1 = Util.compare(1, 2); // int -> Integer (자동 Boxing) System.out.println(result1); int result2 = Util.compare(4.5, 3); // double -> Double (자동 Boxing) System.out.println(result2); } }
5. 와일드카드 타입 <?>, <? extends …>,<? super ..>
설명 보기
- 와일드카드(?) : 제네릭 타입을 매개값이나 리턴타입으로 사용할 때 사용
- 제네릭타입<?> : Unbounded Wildcards (제한 없음)
- 타입 파라미터를 대치하는 구체적인 타입으로 모든 클래스나 인터페이스 타입이 올 수 있다.
- 제네릭 타입<? extends 상위타입> : Upper Bounded Wildcards (상위 클래스 제한)
- 타입 파라미터를 대치하는 구체적인 타입으로 본인이나 하위 타입만 올 수 있다.
- 제네릭 타입<? super 하위타입> : Lower Bounded Wildcards (하위 클래스 제한)
- 타입 파라미터를 대치하는 구체적인 타입으로 본인이나 상위 타입이 올 수 있다.
- 제네릭타입<?> : Unbounded Wildcards (제한 없음)
public class Course<T> { private String name; private T[] students; public Course(String name, int capacity) { this.name = name; students = (T[]) (new Object[capacity]); //타입 파라미터로 배열을 생성하려면 new T[n] 형태로 배열을 생성할 수 없고 (T[]) (new Object[n])으로 생성해야한다. } public String getName() { return name; } public T[] getStudents() { return students; } public void add(T t) { // 배열에 비어있는 부분을 찾아서 수강생을 추가하는 메소드 for (int i = 0; i < students.length; i++) { if (students[i] == null) { students[i] = t; break; } } } }
- Course<?> : 수강생은 모든 타입(Person, Worker, Student, HighStudent)이 될 수 있다.
- Course<? extends Student> : 수강생은 Student와 HighStudent만 될 수 있다.
- Course<? super Worker> : 수강생은 Worker와 Person만 될 수 있다.
public class WildCardExample { public static void registerCourse(Course<?> course) { //모든 과정 System.out.println(course.getName() + " 수강생: " + Arrays.toString(course.getStudents())); } public static void registerCourseStudent(Course<? extends Student> course) { //학생 과정 System.out.println(course.getName() + " 수강생: " + Arrays.toString(course.getStudents())); } public static void registerCourseWorker(Course<? super Worker> course) { // 직장인과 일반인 과정 System.out.println(course.getName() + " 수강생: " + Arrays.toString(course.getStudents())); } public static void main(String[] args) { Course<Person> personCourse = new Course<Person>("일반인과정", 5); personCourse.add(new Person("일반인")); personCourse.add(new Worker("직장인")); personCourse.add(new Student("학생")); personCourse.add(new HighStudent("고등학생")); Course<Worker> workerCourse = new Course<Worker>("직장인과정", 5); workerCourse.add(new Worker("직장인")); Course<Student> studentCourse = new Course<>("학생과정", 5); studentCourse.add(new Student("학생")); studentCourse.add(new HighStudent("고등학생")); Course<HighStudent> highStudentCourse = new Course<>("고등학생과정", 5); highStudentCourse.add(new HighStudent("고등학생")); registerCourse(personCourse); registerCourse(workerCourse); registerCourse(studentCourse); registerCourse(highStudentCourse); // 모든 과정 등록 가능 System.out.println(); // registerCourseStudent(personCourse); (X) // registerCourseStudent(workerCourse); (X) registerCourseStudent(studentCourse); registerCourseStudent(highStudentCourse); // 학생 과정만 등록 가능 System.out.println(); registerCourseWorker(personCourse); registerCourseWorker(workerCourse); // 직장인과 일반인 과정만 등록 가능 // registerCourseWorker(studentCourse); (X) // registerCourseWorker(highStudentCourse); (X) } }
- 와일드카드(?) : 제네릭 타입을 매개값이나 리턴타입으로 사용할 때 사용
6. 제네릭 타입의 상속과 구현
설명 보기
- 제네릭 타입도 상속이 가능하다
- 자식 제네릭 타입은 추가적으로 타입 파라미터를 가질 수 있다
public class ChildProduct<T, M, C> extends Product<T, M> { }
- 제네릭 타입도 상속이 가능하다
7. 기타
- generic T와 와일드카드 ? 의 차이
- 제네릭 T는 클래스를 설계할 때 쓰이고 ?는 설계된 클래스를 사용할 때 (주로 함수의 매개변수)로 사용한다.
- T는 하나의 타입으로 고정, ? 는 하나의 타입으로 고정되지 않는다. (범위지정)
8. Daily Quiz
'Dev > ESTsoft 오르미' 카테고리의 다른 글
컬렉션 (0) | 2024.02.23 |
---|---|
리스트 (0) | 2024.02.23 |
예외처리 (0) | 2024.02.23 |
JVM 조사 : ClassLoader (1) | 2024.02.23 |
인터페이스 (0) | 2024.02.23 |