It's going to be one day 🍀

안녕하세요! 매일 매일 공부하려고 노력하는 백엔드 개발자 지망생의 공부 흔적입니다.

Back-End/Java # 교육

[Java 교육] 제한된 타입/와일드카드 타입/제네릭 상속, 구현

2jin2 2024. 2. 14. 17:18

[공부 내용 정리]

제한된 타입 파라미터 (<T extends 최상위타입>)

타입 파라미터에 구체적인 타입을 제한하는 기능

public **<T extends 상위타입>** 리턴타입 메소드(매개변수, ...) {
}

→ 제한된 타입 파라미터를 선언하려면 타입 파라미터 뒤에 extends 키워드가 붙고 상위 타입을 명시하면 됨.

package chapter10;

public class Util {
    public static <T extends Number> int compare(T t1, T t2) {
        double value1 = t1.doubleValue();
        double value2 = t2.doubleValue();
        // compare : value1이 작다면 -1 리턴, value1이 크다면 1 리턴, 같다면 0 리턴.
        return Double.compare(value1, value2);
    }
}

→ Util.java

  • 위 코드에서 doubleValue() 메소드는 숫자를 double 타입으로 변환함.
  • Double.compare 메소드는 비교 메소드.
package chapter10;

public class BoundedTypeExample {
    public static void main(String[] args) {
//        int result = Util.compare("a", "b"); // 이렇게 쓰면 컴파일 오류. 타입으로 Number가 와야됨
        int result = Util.compare(1, 2); // int -> Integer (자동 Boxing)
        System.out.println(result);

        int result2 = Util.compare(4.5, 4.5); // double -> Double (자동 Boxing)
        System.out.println(result2);
    }
}

→ BoundedTypeExample.java

 

와일드카드 타입<?>, <? extends …>, <? super ..>

제네릭 타입을 매개값이나 리턴 타입으로 사용할 때 구체적인 타입 대신에 와일드 카드를 3가지 형태로 사용할 수 있음.

ex) 수강생이 될 수 있는 4가지 클래스로 와일드카드 타입 실습을 해보자.

package chapter10;

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.java

package chapter10;

import java.util.Arrays;

public class WildCardExample {

    static void registerCourse(Course<?> course) {
        // toString에 배열을 넣으면 배열의 요소들을 하나하나씩 출력해줌.
        System.out.println(course.getName() + " 수강생: "+Arrays.toString(course.getStudents()));
    }
    static void registerStudentCourse(Course<? extends Student> course) {
        System.out.println(course.getName() + " 수강생: "+Arrays.toString(course.getStudents()));
    }

    static void registerWorkerCourse(Course<? super Worker> course) { // super을 사용해 상위 클래스 지정(Worker와 Person 올 수 있음)
        System.out.println(course.getName() + " 수강생: "+Arrays.toString(course.getStudents()));
    }

    public static void main(String[] args) {
        // Person에는 하위 클래스 전부 들어갈 수 있음
        Course<Person> personCourse = new Course<>("일반인 과정", 4);
        personCourse.add(new Person("일반인"));
        personCourse.add(new Worker("직장인"));
        personCourse.add(new Student("학생"));
        personCourse.add(new HighStudent("고등학생"));

        // Worker에는 자기 자신 (Worker)만 들어갈 수 있음
        Course<Worker> workerCourse = new Course<>("직장인 과정", 4);
        workerCourse.add(new Worker("직장인"));

        // Student에는 자기 자신(Student)와 하위 클래스인 HighStudent가 들어갈 수 있음.
        Course<Student> studentCourse = new Course<>("학생 과정", 4);
        studentCourse.add(new Student("학생"));
        studentCourse.add(new HighStudent("고등학생"));

        // HighStudent 에는 자기 자신 (HighStudent)만 들어갈 수 있음
        Course<HighStudent> highStudentCourse = new Course<>("고등학생 과정", 4);
        highStudentCourse.add(new HighStudent("고등학생"));

        // 모든 클래스가 다 들어갈 수 있음!
        registerCourse(personCourse);
        registerCourse(workerCourse);
        registerCourse(studentCourse);
        registerCourse(highStudentCourse);
        System.out.println("---------------------------------");

        // registerStudentCourse는 Student와 HighStudent를 제외한 나머지 클래스를 넣으면 에러 발생.
        
//        registerStudentCourse(personCourse);
//        registerStudentCourse(workerCourse);
        registerStudentCourse(studentCourse);
        registerStudentCourse(highStudentCourse);
        System.out.println("---------------------------------");

        // registerWorkerCourse는 Person과 Worker를 제외한 나머지 클래스를 넣으면 에러 발생.
        registerWorkerCourse(personCourse);
        registerWorkerCourse(workerCourse);
//        registerWorkerCourse(studentCourse);
//        registerWorkerCourse(highStudentCourse);

    }
}

→ WildCardExample.java

 

위 예시처럼

  • 제네릭타입<?> : Unbounded Wildcards (제한 없음)
    • 모든 클래스나 인터페이스 타입이 올 수 있음.
  • 제네릭 타입<? extends 상위타입> : Upper Bounded Wildcards (상위 클래스 제한)
    • 해당 클래스 기준 본인과 하위 타입만 올 수 있음.
    • ex) registerStudentCourse에 Student, HighStudent만 들어가는 것 처럼
  •  
  • 제네릭 타입<? super 하위타입> : Lower Bounded Wildcards (하위 클래스 제한)
    • 해당 클래스 기준 본인과 상위 타입만 올 수 있음.
    • ex) registerWorkerCourse에 Person과 Worker만 들어가는 것 처럼
    상속 (inherit)
    public class Product<T, M> {
    	private T kind;
    	private M model;
    
    	public T getKind() {
    		return kind;
    	}
    
    	public M getModel() {
    		return model;
    	}
    
    	public void setKind(T kind) {
    		this.kind = kind;
    	}
    
    	public void setModel(M model) {
    		this.model = model;
    	}
    }
    
    → 부모 제네릭 클래스 Product.java→ 자식 제네릭 클래스 ChildProduct.java

Daily Quiz

1. 제네릭에 대한 설명으로 틀린 것은 무엇입니까?

  1. 컴파일시 강한 타입 체크를 할 수 있다. O
  2. 타입 변환(casting)을 제거한다. O
  3. 제네릭 타입은 타입 파라미터를 가지는 제네릭 클래스와 인터페이스를 말한다. O
  4. 제네릭 메소드는 리턴 타입으로 타입 파라미터를 가질 수 없다. X

2. ContainerExample 클래스의 main() 메소드는 Container 제네릭 타입을 사용하고 있습니다. main() 메소드에서 사용하는 방법을 참고해서 Container 제네릭 타입을 선언해보세요.

public class ContainerExample {

	public static void main(String[] args) {
		Container<String> stringContainer = new Container<>();
		stringContainer.set("홍길동");
		String str = stringContainer.get();

		Container<Integer> intContainer = new Container<>();
		intContainer.set(6);
		int value = intContainer.get();
	}
}

package chapter10;

public class Container<T> {
    private T name;

    public void set(T name) {
        this.name = name;
    }
    public T get() {
        return name;
    }
}

3. TwoContainerExample 클래스의 main() 메소드는 TwoContainer 제네릭 타입을 사용하고 있습니다. main() 메소드에서 사용하는 방법을 참고해서 TwoContainer 제네릭 타입을 선언해보세요.

public class TwoContainerExample {

	public static void main(String[] args) {
		TwoContainer<String, String> container = new TwoContainer<>();
		container.set("홍길동", "도적");
		String name = container.getKey();
		String job = container.getValue();

		TwoContainer<String, Integer> secondContainer = new TwoContainer<>();
		secondContainer.set("홍길동", 35);
		String name2 = secondContainer.getKey();
		Integer age = secondContainer.getValue();
	}
}

package chapter10;

public class TwoContainer<T, M> {
    private T key;
    private M value;
    public void set(T key, M value) {
        this.key = key;
        this.value = value;
    }
    public T getKey() {
        return key;
    }
    public M getValue() {
        return value;
    }
}

4. Util.getValue() 메소드는 첫 번째 매개값으로 Pair 타입과 하위 타입만 받고, 두 번째 매개값으로 키를 받습니다. 리턴값은 키값이 일치할 경우 Pair에 저장된 값을 리턴하고, 일치하지 않으면 null을 리턴하도록 getValue() 제네릭 메소드를 정의해보세요.

public class UtilExample {
	public static void main(String[] args) {
		Pair<String, Integer> pair = new Pair<>("홍길동", 35);
		Integer age = Util.getValue(pair, "홍길동");
		System.out.println(age);

		ChildPair<String, Integer> childPair = new ChildPair<>("삼길동", 20);
		Integer childAge = Util.getValue(pair, "이길동");
		System.out.println(childAge);

		// OtherPair는 Pair를 상속하지 않으므로 예외가 발생해야합니다.
		/* OtherPair<String, Integer> otherPair = new OtherPair<>("삼길동, 20");
		   int otherAge = Util.getValue(otherPair, "삼길동");
		   System.out.println(otherAge); */
	}
}
public class Pair<K, V> {
	private K key;
	private V value;

	public K getKey() {
		return key;
	}

	public V getValue() {
		return value;
	}

	public Pair(K key, V value) {
		this.key = key;
		this.value = value;
	}
}
public class ChildPair<K, V> extends Pair<K, V> {
	public ChildPair(K key, V value) {
		super(key, value);
	}
}
public class OtherPair<K, V> {
	private K key;
	private V value;

	public OtherPair(K key, V value) {
		this.key = key;
		this.value = value;
	}

	public K getKey() {
		return key;
	}

	public V getValue() {
		return value;
	}
}

->

package chapter10;

public class Util {
    public static <K, V> V getValue(Pair<K, V> pair, String key) {
        if (pair.getKey().equals(key)) {
            return pair.getValue();
        }
        return null;
    }
}

실행결과는 다음과 같습니다.

35
null