JAVA
[JAVA] 공변성이 없는 제네릭, 제네릭 와일드 카드
정공자씨
2024. 4. 26. 16:11
제네릭 타입은 공변성을 가지지 않는다
[ 공변성, 반공변성 ]
- 서로 다른 타입간에 함께 변할 수 있는 특징
- 서로 다른 타입간의 형 변환인 업캐스팅(공변성), 다운캐스팅(반공변성)과 유사함
제네릭 타입으로 지정되면, 상하 관계에 있는 타입끼리도 형 변환이 불가
- 제네릭의 타입 파라미터끼리는 상하 관계(상속 관계 등)가 있다고 해도 캐스팅(형 변환)이 불가능함
- (상위 클래스) Object, (하위 클래스) Number를 제네릭 타입 파라미터로 사용했을 때
- Number를 Object로 업캐스팅 불가
- (상위 클래스) Number, (하위 클래스) Integer를 제네릭 타입 파라미터로 사용했을 때
- Integer를 Number로 업캐스팅 불가
[ 객체 타입은 상하관계가 있음 ]
- 다형성의 성질을 사용할 수 있음
- 조상 - 자손의 상속관계에 있기 때문에, 형 변환(캐스팅)이 가능
Object parent = new String("정공자"); // 업캐스팅 String child = (String)parent; // 다운캐스팅
와일드카드가 나온 배경 : 공변성이 없는 제네릭 특징의 문제점
타입 파라미터(매개변수)로 제네릭을 사용하는 경우, 외부에서 대입되는 타입 인자의 형 변환(캐스팅)문제가 발생하기 때문
- 제네릭을 지정하면, 지정한 타입 파라미터만 사용하도록 제한할 수 있는데
- 만일 메서드의 파라미터 타입을 Object으로 설정하여 사용하려고 할 때, 외부에서 대입되는 타입 인자가 Object가 아닌 경우(예를 들어 String, Integer타입이면)
- 제네릭은 타입 파라미터가 완전히 똑같은 타입만 받기 때문에 업캐스팅이 되지 않음
- 즉, 제네릭에서는 다형성이 적용되지가 않아서 매개변수를 상위 클래스인 Object 타입으로 정하더라도, 제네릭이 String(타입 인자가 String)으로 정해져 있다면
- 타입 인자가 Object타입이 아니므로, String이 Object 타입으로 업캐스팅 되지 않음
→ 이를 해결하기 위해 나온 기능이 제네릭 와일드 카드임
제네릭 타입 매개변수끼리 형 변환을 할 수 없어서 발생하는 문제점 확인하기
1. 배열을 이용할 때
- 메서드의 파라미터 타입이 Object(상위 클래스)이고, 외부에서 대입되는 타입 인자인 Integer(하위 클래스)인 경우, 업캐스팅이 됨
- 즉, Integer(하위 클래스)가 Object(상위 클래스)로 업캐스팅이 됨
public class Main {
static void getArray(Object[] arr) {
for (Object obj : arr) {
System.out.println(obj);
}
}
public static void main(String[] args) {
Integer [] integers = {1, 2, 3};
getArray(integers); // [1, 2, 3]
}
}
- Integer[ ] 배열 타입이 Object[ ] 배열 타입으로 업캐스팅이 되어 사용 가능
2. List의 제네릭에 타입 파라미터를 지정하여 넘기기
- 타입 인자가 Integer이고 받는 매개변수의 타입이 Object일 때, 메서드 호출 부분에서 컴파일 에러 발생
- 제네릭으로 지정하면, 상위 클래스(Object)와 하위 클래스(Integer) 사이에 업캐스팅(형 변환)이 되지 않음
- List의 제네릭의 경우, 받는 매개변수의 타입을 Object로 설정 하였고, 외부에서 대입되는 타입 인자가 Integer인 경우에
- 일반 메서드처럼 Integer가 Object로 업캐스팅 되는 것이 아님
- 즉, Integer 타입이 Object 타입(상위 클래스)으로 업캐스팅 되지 않음
- 이유는 타입 인자와 타입 파라미터가 똑같은 타입만 받을 수 있기 때문에
- Integer타입이 Object 타입(상위 클래스)으로 업캐스팅 되지 않음
제네릭 와일드카드
와일드 카드
- ? : 모든 클래스 타입이나 인터페이스 타입이 적용될 수 있음
사실 <?>만 사용하면 Object(상위 클래스)와 의미가 다르지 않으므로, 보통 제네릭의 타입을 한정해주는 키워드(extends) 등과 함께 사용함
제네릭 타입을 한정해주는 키워드와 와일드카드를 함께 사용하기
- 상속 관계(상하 관계)가 있는 클래스들의 제네릭 적용 범위를 제한해줄 수 있음
와일드카드 | 설명 |
|
<?> | 모든 참조 타입이 가능함 | |
<? extends A> | 상위 클래스를 제한 | A와 그 하위 클래스만 타입으로 가능 |
<? super A> | 하위 클래스를 제한 | A와 그 상위 클래스만 타입으로 가능 |
와일드 카드 사용하기
1. <?> : 어떤 참조 타입도 저장 가능
- List <?> : List 자료형에 어떤 참조 타입이든지 저장할 수 있음
2. extends : 타입의 상한을 제한하기
- <? extends A> 받을 수 있는 타입의 상한을 제한
- 상속관계에 있는 경우에, 제한 타입은 A(부모 클래스)가 상한이고, 하위 클래스만 받을 수 있음
class Person {
String name;
public Person(){}
public Person(String name) {
this.name = name;
}
}
class Employee extends Person {
int salary;
public Employee() {}
public Employee(String name, int salary) {
super.name = name;
this.salary = salary;
}
}
class Student extends Person{
int grade;
public Student(String name, int grade) {
super.name = name;
this.grade = grade;
}
}
public class Main {
public static void main(String[] args) {
// List에 저장되는 데이터는 Person 객체
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("정공자"));
personList.add(new Person("비정공"));
personList.add(new Person("컴공자"));
// List에 저장되는 데이터는 Employee 객체
List<Employee> employeeList = new ArrayList<Employee>();
employeeList.add(new Employee("정공자", 10000));
employeeList.add(new Employee("비정공", 20000));
employeeList.add(new Employee("컴공자", 30000));
// List에 저장되는 데이터는 Student 객체
List<Student> studentList = new ArrayList<Student>();
studentList.add(new Student("정공자", 1));
studentList.add(new Student("비정공", 2));
studentList.add(new Student("컴공자", 3));
// List<?> : List에 어떤 객체를 담고 있어도 전달 가능
printPersonList(personList);
printPersonList(employeeList);
printPersonList(studentList);
// List<? extends Employee> : List에 Employee, 이하 하위클래스만 담을 수 있음
printEmployeeList(employeeList);
// printEmployeeList(personList); 상위 클래스는 불가
// List<? extends Student> : List에 Student, 이하 하위클래스만 담을 수 있음
printStudentList(studentList);
// printStudentList(personList); 상위 클래스는 불가
}
}
[ 예제 ] List<? extends Number>
- List에 저장되는 타입의 상한을 제한해줌
- Number (상위 클래스) > Integer, Double (하위 클래스)
- Number가 사용할 수 있는 타입의 상한이고, 이하 하위클래스인 Integer, Double 등만 사용 가능
3. super : 타입의 하한을 제한하기
- <? super A> 받을 수 있는 타입의 하한을 제한
- 상속관계에 있는 경우에, 제한 타입은 A(자식 클래스)가 하한이고, 상위 클래스만 받을 수 있음
public class Main {
// 모든 클래스 객체가 저장된 List를 전달
// ? : Person, Student, Employee 객체가 저장된 List 모두 전달 가능
public static void printPersonList(List<?> list) {
for (Object obj : list) {
System.out.println( ((Person)obj).getName() );
}
}
// Employee클래스와 상위 클래스 타입이 저장된 List를 전달 가능
public static void printEmployeeList(List<? super Employee> list) {
for (Object obj : list) { // 어떤 타입일지 몰라서 Object(상위클래스)로 받음
System.out.println( ((Employee)obj).getSalary() );
}
}
// Student클래스와 상위 클래스 타입이 저장된 List를 전달 가능
public static void printStudentList(List<? super Student> list) {
for (Object obj : list) { // 어떤 타입일지 몰라서 Object(상위클래스)로 받음
System.out.println( ((Student)obj).getGrade() );
}
}
public static void main(String[] args) {
// List에 저장되는 데이터는 Person 객체
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("정공자"));
personList.add(new Person("비정공"));
personList.add(new Person("컴공자"));
// List에 저장되는 데이터는 Employee 객체
List<Employee> employeeList = new ArrayList<Employee>();
employeeList.add(new Employee("정공자", 10000));
employeeList.add(new Employee("비정공", 20000));
employeeList.add(new Employee("컴공자", 30000));
// List에 저장되는 데이터는 Student 객체
List<Student> studentList = new ArrayList<Student>();
studentList.add(new Student("정공자", 1));
studentList.add(new Student("비정공", 2));
studentList.add(new Student("컴공자", 3));
// List<?> : List에 어떤 객체를 담고 있어도 전달 가능
printPersonList(personList);
printPersonList(employeeList);
printPersonList(studentList);
// List<? super Employee> : List에 Employee, 상위클래스(person)만 담을 수 있음
printEmployeeList(employeeList);
printEmployeeList(personList); //상위 클래스
// printEmployeeList(studentList);
// List<? super Student> : List에 Student, 상위클래스(person)만 담을 수 있음
printStudentList(studentList);
printStudentList(personList); //상위 클래스
}
}
출처