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); //상위 클래스		
	}
}

 

 

 

 

 

출처

 

 

https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EC%A0%9C%EB%84%A4%EB%A6%AD-%EC%99%80%EC%9D%BC%EB%93%9C-%EC%B9%B4%EB%93%9C-extends-super-T-%EC%99%84%EB%B2%BD-%EC%9D%B4%ED%95%B4