본문 바로가기
Java/이것이 자바다

제네릭

by k-mozzi 2023. 4. 25.
반응형
Preface

 

이번 장에선 제네릭에 대해 공부했다.

 

딱히 어려운 내용은 없었지만, 클래스나 메소드마다 타입 파라미터를 붙이는 일이 꽤나 번거로웠다.

 

제네릭 사용이 처음이라 코드를 작성하는 데 시간이 조금 오래 걸리긴 하지만, 익숙해지면 시간도 단축하고 코드의 효율성도 높일 수 있을 것 같다.

 

무엇보다 이제 웬만한 자바 코드는 대부분 이해할 수 있지 않을까 싶다.


 

1. 왜 제네릭을 사용해야 하는가?

 

 

- 제네릭 타입을 이용함으로써 잘못된 타입이 사용될 수 있는 문제를 컴파일 과정에서 제거할 수 있다.

 

 

- 제네릭: 클래스, 인터페이스, 메소드를 정의할 때 타입을 파라미터(parameter)로 사용할 수 있게 한다.

 

 

- 타입 파라미터: 코드 작성 시 구체적인 타입으로 대체되어 다양한 코드를 생성하도록 한다.

 

 

- 제네릭 사용의 장점

1) 컴파일 시 미리 타입 오류를 체크할 수 있다.

2) 불필요한 타입 변환(casting)을 제거한다.

 


 

2. 제네릭 타입

 

 

- 제네릭 타입: 타입을 파라미터로 가지는 클래스와 인터페이스

1) 클래스와 인터페이스 이름 뒤에 "< >" 부호가 붙고, 사이에 타입 파라미터가 위치한다.

2) 타입 파라미터는 일반적으로 대문자 알파벳 한 글자로 표현한다.

3) 제네릭 타입을 실제 코드에서 사용하려면 타입 파라미터에 구체적인 타입을 지정해야 한다.

package ch13;

class Box<T> {
	private T t;

	public T get() {
		return t;
	}

	public void set(T t) {
		this.t = t;
	}
}

public class BoxExample {

	public static void main(String[] args) {

		Box<String> box1 = new Box<String>();
		box1.set("Hi");
		String str = box1.get();
		System.out.println(str);

		Box<Integer> box2 = new Box<Integer>();
		box2.set(2);
		int value = box2.get();
		System.out.println(value);
	}

}

 

 

- 즉, 제네릭은 클래스를 설계할 때 구체적인 타입을 명시하지 않고, 타입 파라미터로 대체했다가 실제 클래스가 사용될 때 구체적인 타입을 지정함으로써 타입 변환을 최소화시킨다.

→ 모든 타입의 저장을 허용하지만, 특정 객체에선 특정 타입의 값만 저장하고 싶을 때 사용할 수 있을 듯?

 


 

3. 멀티 타입 파라미터

 

 

- 제네릭 타입은 두 개 이상의 멀티 타입 파라미터를 사용할 수 있다.

→ 각각의 타입 파라미터는 콤마로 구분한다.

package ch13;

class Product<T, M> {
	private T kind;
	private M model;

	public T getKind() {
		return this.kind;
	}

	public M getModel() {
		return this.model;
	}

	public void setKind(T kind) {
		this.kind = kind;
	}

	public void setModel(M model) {
		this.model = model;
	}
}

class Tv {
}

class Car {
}

public class ProductExample {

	public static void main(String[] args) {

		Product<Tv, String> product1 = new Product<Tv, String>();
		product1.setKind(new Tv());
		product1.setModel("smartTv");
		Tv tv = product1.getKind();
		String tvModel = product1.getModel();
		System.out.println(tv + " " + tvModel);

		Product<Car, String> product2 = new Product<>();
        //다이아몬드 연산자를 통해 타입 파라미터 값 생략 가능
		product2.setKind(new Car());
		product2.setModel("dizel");
		Car car = product2.getKind();
		String carModel = product2.getModel();
		System.out.println(car + " " + carModel);
	}

}

 


 

4. 제네릭 메소드

 

 

- 제네릭 메소드: 매개 타입과 리턴 타입으로 타입 파라미터를 갖는 메소드

public <타입파라미터> 리턴타입 메소드명(매개변수) {...}

public <T> Box<T> boxing(T t) {...}

 

 

- 제네릭 메소드 호출 방법

1) 코드에서 타입 파라미터의 구체적인 타입을 명시적으로 지정

Box<Integer> box = <Integer>boxing(100);

2) 컴파일러가 매개값의 타입을 보고 구체적인 타입을 추정하도록 함

Box<Integer> box = boxing(100);
package ch13;

class Util {
	public static <T> Box<T> boxing(T t) {	//객체를 생성하지 않고 곧바로 클래스를 호출하여 사용할 것이라 static 할당
		Box<T> box = new Box<T>();
		box.set(t);
		return box;
	}
}

public class BoxingMethodExmple {

	public static void main(String[] args) {

		Box<Integer> box1 = Util.<Integer>boxing(100);	//객체를 생성함과 동시에 값 할당
		int intValue = box1.get();
		System.out.println(intValue);

		Box<String> box2 = Util.boxing("Kim");
		String str = box2.get();
		System.out.println(str);
	}

}

 

package ch13;

class Util2 {
	public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
		boolean keyCompare = p1.getKey().equals(p2.getKey());
		boolean valueCompare = p1.getValue().equals(p2.getValue());
		return keyCompare && valueCompare;
	}
}

class Pair<K, V> {
	public K key;
	public V value;

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

	public void setKey(K key) {
		this.key = key;
	}

	public void setValue(V value) {
		this.value = value;
	}

	public K getKey() {
		return key;
	}

	public V getValue() {
		return value;
	}
}

public class CompareMethodExample {

	public static void main(String[] args) {

		Pair<Integer, String> p1 = new Pair<Integer, String>(1, "apple");
		Pair<Integer, String> p2 = new Pair<Integer, String>(1, "apple");
		boolean result1 = Util2.<Integer, String>compare(p1, p2);
		if (result1) {
			System.out.println("논리적으로 동등한 객체입니다.");
		} else {
			System.out.println("논리적으로 동등하지 않은 객체입니다.");
		}

		Pair<String, String> p3 = new Pair<String, String>("user1", "Kim");
		Pair<String, String> p4 = new Pair<String, String>("user2", "Park");
		boolean result2 = Util2.compare(p3, p4);
		if (result2) {
			System.out.println("논리적으로 동등한 객체입니다.");
		} else {
			System.out.println("논리적으로 동등하지 않은 객체입니다.");
		}
	}

}

 


 

5. 제한된 타입 파라미터

 

 

- 제한된 타입 파라미터 선언: 타입 파라미터 뒤에 extends 키워드를 붙이고 상위 타입을 명시

→ 상위 타입은 클래스뿐만이 아니라 인터페이스도 가능하며, 인터페이스에도 extends 키워드를 사용한다. (implements 아님)

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

 

 

- 타입 파라미터에 저장되는 구체적인 타입

1) 상위 타입이거나 상위 타입의 하위 또는 구현 클래스만 가능

2) 메소드의 중괄호 안에서 타입 파라미터 변수로 사용 가능한 것은 상위 타입의 멤버로 제한

3) 하위 타입에만 있는 필드와 메소드는 사용 불가

 


 

6. 와일드카드 타입

 

 

- 와일드카드(wildcard): 코드에서 "?"에 해당

 

 

- 제네릭 타입을 매개값이나 리턴 타입으로 사용할 때 구체적인 타입 대신 와일드카드를 사용할 수 있다.

1) 제네릭타입<?>: 모든 클래스나 인터페이스 타입이 올 수 있다.

2) 제네릭타입<? extends 상위타입>: 상위 타입이나 하위 타입만 올 수 있다.

→ 자신의 타입 + 자신의 하위 타입

3) 제네릭타입<? super 하위타입>: 하위 타입이나 상위 타입이 올 수 있다.

→ 자신의 타입 + 자신의 상위 타입

package ch13;

import java.util.Arrays;

class Person {
	private String name;

	public Person(String name) {
		this.name = name;
	}

	@Override
	public String toString() {
		return name;
	}
}

class Worker extends Person {
	public Worker(String name) {
		super(name);
	}
}

class Student extends Person {
	public Student(String name) {
		super(name);
	}
}

class HighStudent extends Student {
	public HighStudent(String name) {
		super(name);
	}
}

class Course<T> {
	private String name;
	private T[] students;

	public Course(String name, int capacity) {
		this.name = name;
		students = (T[]) (new Object[capacity]);
	}

	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;
			}
		}
	}
}

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<Student>("학생 과정", 5);
		studentCourse.add(new Student("학생"));
		studentCourse.add(new HighStudent("고등학생"));
		Course<HighStudent> highStudentCourse = new Course<HighStudent>("고등학생 과정", 5);
		highStudentCourse.add(new HighStudent("고등학생"));

		registerCourse(personCourse);
		registerCourse(workerCourse);
		registerCourse(studentCourse);
		registerCourse(highStudentCourse);
		System.out.println();

		registerCourseStudent(studentCourse);
		registerCourseStudent(highStudentCourse);
		System.out.println();

		registerCourseWorker(personCourse);
		registerCourseWorker(workerCourse);
	}

}

 

 

- 타입 파라미터로 배열 생성: (T[ ]) (new Object[n]) 방식을 사용해야 한다.

→ new T[n] 형태로 배열 생성 불가

 


 

7. 제네릭 타입의 상속과 구현

 

 

- 제네릭 타입도 부모 클래스가 될 수 있다.

 

 

- 자식 제네릭 타입은 추가적으로 타입 파라미터를 가질 수 있다.

public class ChildProduct<T, M, C> extends Product<T, M> {...}

 

 

- 제네릭 인터페이스를 구현한 클래스도 제네릭 타입이 된다.

package ch13;

interface Storage<T> {
	public void add(T item, int index);

	public T get(int index);
}

class StorageImpl<T> implements Storage<T> {
	private T[] array;

	public StorageImpl(int capacity) {
		this.array = (T[]) (new Object[capacity]);
	}

	@Override
	public void add(T item, int index) {
		array[index] = item;
	}

	@Override
	public T get(int index) {
		return array[index];
	}
}

class Product1<T, M> {
	private T kind;
	private M model;

	public T getKind() {
		return this.kind;
	}

	public M getModel() {
		return this.model;
	}

	public void setKind(T kind) {
		this.kind = kind;
	}

	public void setModel(M model) {
		this.model = model;
	}
}

class Tv1 {
}

class ChildProduct<T, M, C> extends Product1<T, M> {
	private C company;

	public C getCompany() {
		return this.company;
	}

	public void setCompany(C company) {
		this.company = company;
	}
}

public class ChildProductAndStorageExample {

	public static void main(String[] args) {

		ChildProduct<Tv1, String, String> product1 = new ChildProduct<>();
		product1.setKind(new Tv1());
		product1.setModel("smartTv");
		product1.setCompany("Samsung");

		Storage<Tv1> storage = new StorageImpl<Tv1>(100);
		storage.add(new Tv1(), 0);
		Tv1 tv = storage.get(0);
	}

}

 

728x90
반응형

'Java > 이것이 자바다' 카테고리의 다른 글

람다식  (0) 2023.04.27
이자바 13장(제네릭) 확인문제  (0) 2023.04.26
이자바 12장(멀티 스레드) 확인문제  (0) 2023.04.25
멀티 스레드  (0) 2023.04.21
이자바 11장(기본 API 클래스) 확인문제  (0) 2023.04.15

댓글