- 특정 타입으로 객체를 만드는 경우 : 일일이 각 타입별로 자료형을 만들어야한다.
- Object를 이용하며 다형성과 다운캐스팅을 이용한 경우 : 코드 재사용의 문제는 해결되었지만 타입 안전성 문제가 발생
- 매개변수로 어떤 자료형도 받을 수 있기 때문에 오류 발생 가능성이 있다.
- 반환 결과를 받고 다운캐스팅을 사용해야하기 때문에 번거롭다.
- 타입 안전성
- 반환 타입의 안전성
- 제네릭 클래스(
public class GenericClass<T>
)로 선언 - 제네릭 클래스 내부에 T(타입 매개변수)를 사용하여 설계한다.
제네릭 클래스는 생성시점에 타입 매개변수가 확정된다.
new GenericClass<Integer>()
(타입 직접 입력 방식)
-
타입 직접 입력 방식
GenericClass<Integer> genericClass = new GenericClass<Integer>();
-
타입 추론 방식
GenericClass<Integer> genericClass = new GenericClass<>();
기본적으로 대문자로 표기하며 T이외에도 용도에 따라 맞는 단어의 첫 글자를 사용하는 관례를 따른다. 그래서 그냥 대문자 표시된 한글자다? ⇒ 타입 매개변수다.
- T : type
- E : Element
- V : Value
- K : Key
- S, U, V : 2nd, 3rd, 4th types
- 제네릭 : “일반적인”, “범용적인” 이라는 뜻으로 타입 상관없이 범용적으로 사용 가능 하게 한다는 의미이세 일맥상통한다.
- 제네릭 타입(Generic Type:
class GenericBox<T>
) : 클래스나 인터페이스를 정의할 떄 타입 매개변수를 사용하는 것을 말함 - 타입 매개변수(Type parameter:
T
) : 제네릭 타입이나 메서드에 사용되는 변수로, 런타임에서 실제 타입으로 대체된다. - 타입 인자(Type Argument) : 제네릭 타입을 사용할 때 제공되는 실제 타입
-
타입 인자로 원시타입을 사용할 수 있다. 참 거짓
-
거짓. 타입 인자는 래퍼타입만 가능하다.
public class practice1 {
// 제네릭 클래스 생성
private static class GenericClass<T> {
private T value;
public GenericClass(T value) {
this.value = value;
}
public T getValue() {
return this.value;
}
}
public static void main(String[] args) {
// 제네릭 생성
GenericClass<Integer> genericClassInteger1 = new GenericClass<>(1);
GenericClass<Integer> genericClassInteger2 = new GenericClass<>(2);
GenericClass<String> genericClassString1 = new GenericClass<String>("hello world!");
System.out.println(genericClassInteger1.getValue());
System.out.println(genericClassInteger2.getValue());
System.out.println(genericClassString1.getValue());
}
}
// 제네릭 클래스 생성
private class Container<T> {
private T item;
public void setItem(T value) {
this.item = value;
}
public T getItem() {
return this.item;
}
public boolean isEmpty() {
return item == null;
}
}
public class Pair<K, V> {
private K first;
private V second;
public void setFirst(K first) {
this.first = first;
}
public void setSecond(V second) {
this.second = second;
}
public K getFirst() {
return first;
}
public V getSecond() {
return second;
}
@Override
public String toString() {
return "Pair{" +
"first=" + first +
", second=" + second +
"}";
}
}
아래의 Animal 부모와 Dog, Cat 자식이 있을때 Hospital Class를 Object를 사용한 방식, 제네릭 타입으로 변경한 방식을 기술하고 장단점을 기술하시오.
public class Animal {
private String name;
private int size;
public Animal(String name, int size) {
this.name = name;
this.size = size;
}
public String getName() {
return name;
}
public int getSize() {
return size;
}
public void sound() {
System.out.println("동물 울음 소리");
}
@Override
public String toString() {
return "Animal{" +
"name='" + name + '\'' +
", size=" + size +
'}';
}
}
public class Dog extends Animal{
public Dog(String name, int size) {
super(name, size);
}
@Override
public void sound() {
System.out.println("멍멍");
}
}
public class Cat extends Animal {
public Cat(String name, int size) {
super(name, size);
}
@Override
public void sound() {
System.out.println("냐옹");
}
}
public class DogHospital {
private Dog animal;
public void set(Dog animal) {
this.animal = animal;
}
public void checkup() {
System.out.println("동물 이름: " + animal.getName());
System.out.println("동물 크기: " + animal.getSize());
animal.sound();
}
public Dog bigger(Dog target) {
return animal.getSize() > target.getSize() ? animal : target;
}
}
public class CatHospital {
private Cat animal;
public void set(Cat animal) {
this.animal = animal;
}
public void checkup() {
System.out.println("동물 이름: " + animal.getName());
System.out.println("동물 크기: " + animal.getSize());
animal.sound();
}
public Cat getBigger(Cat target) {
return animal.getSize() > target.getSize() ? animal : target;
}
}
- 장점: 타입 안전성
- 단점 : 불필요한 코드 중복 발생
public class HospitalV1 {
// Dog or Cat type -> Animal
private Animal animal;
public void set(Animal animal) {
this.animal = animal;
}
public void checkup() {
System.out.println("동물 이름: " + animal.getName());
System.out.println("동물 크기: " + animal.getSize());
animal.sound();
}
public Animal getBigger(Animal target) {
return animal.getSize() > target.getSize() ? animal : target;
}
}
-
장점 : HospitalV0처럼 Dog용 Cat용을 따로 만들지 않아도 됨
-
단점 : 매개변수로 Cat을 보내야하는데 Dog를 보내는 오류를 범할 수 있다.
AnimalHospitalV1 dogHospital = new AnimalHospitalV1(); AnimalHospitalV1 catHospital = new AnimalHospitalV1(); Cat cat = new Cat("냐옹이1", 300); // 개병원에 cat 타입을 매개변수로 넣어도 컴파일 오류를 내지 않음 dogHospital.set(cat);
public class HospitalV2<T> {
private T animal;
public void set(T animal) {
this.animal = animal;
}
public void checkup() {
System.out.println("동물 이름: " + animal.getName());
System.out.println("동물 크기: " + animal.getSize());
animal.sound();
}
public Animal getBigger(Animal target) {
return animal.getSize() > target.getSize() ? animal : target;
}
}
- 문제 : 이렇게 짜면 프로그램입장에서 T가 어떤 타입인지 모르기 때문에 Object의 매서드밖에 사용하지 못한다. 그래서
getName()
,getSize()
… 와 같은 인스턴스 매서드를 사용하지 못한다. 위의 코드는 컴파일 오류가 발생한다.
public class HospitalV3<T extends Animal> {
private T animal;
public void set(T animal) {
this.animal = animal;
}
public void checkup() {
System.out.println("동물 이름: " + animal.getName());
System.out.println("동물 크기: " + animal.getSize());
animal.sound();
}
public T getBigger(T target) {
return animal.getSize() > target.getSize() ? animal : target;
}
}
- 장점 : 타입 안전성과 코드 중복을 다 잡았다.
제네릭 타입을 클래스 전체가 아닌 특정 메서드 단위로 도입할때 사용되며 해당 매서드 호출 시점에서 매개변수의 타입에 따라 제네릭 타입이 결정되고 호출된다.
// 스태틱 메서드
public static <T> T staticGenericMethod(T t){return t};
// 인스턴스 메서드
public <T> T instanceGenericMethod(T t){return t};
Class.<Integer>staticGenericMethod(num);
instance.<Integer>instanceGenericMethod(num);
// 추론 방식
Class.staticGenericMethod(num);
- 제네릭 타입(Class 단위로 제네릭이 선언된 경우) : 객체를 생성하는 시점에 타입 결정
new GenericCalss<Integer>()
- 제네릭 메서드(메서드 단위로 제네릭이 선언된 경우) : 메서드를 호출하는 시점에서 타입 결정
GenericMethod.<Integer>genericMetod(num)
-
제네릭 타입에서 제네릭 메서드 선언이 가능하다. 참 거짓
-
public T genericMethod(T t)
는 제너릭 메서드이다. 참 거짓 -
제네릭 타입에서 인스턴스 제네릭 메서드가 정의된경우 타입 매개변수는 어느것이 우선순위일까?
-
세모
- static method : 불가능
- instance method : 가능
-
거짓, 이건 그냥 메서드이다 .다면 타입 매개변수가 선언된 것 뿐이다. 아래에 가 있는게 제네릭 메서드이다.
public <T> T genericMethod(T t)
-
제네릭 메서드가 우선이다. 즉 제네릭 타입이 Integer로 생성되었다 하더라고 인스턴스 메서드가 String으로 호출되었다면 String으로 메서드 관련 작업을 수행한다.
public class HospitalV4 {
public static <T extends Animal> void checkup(T animal) {
System.out.println("동물 이름: " + animal.getName());
System.out.println("동물 크기: " + animal.getSize());
animal.sound();
}
public static <T extends Animal> T getBigger(T a, T b) {
return a.getSize() > b.getSize() ? a : b;
}
}
*
와?
기호로 사용되며 타입 인자가 “정해진 제네릭 타입”을 전달 받아 활용할 때 사용한다. 좀더 직관적으로 모든 타입을 받을 수 있는 타입이락 생각하면 된다.
-
제네릭 메서드
public static <T> void printGenericV1(Box<T> box) { System.out.println(box.get()); };
-
와일드카드
public static void printWildcardV1(Box<?> box) { System.out.println(box.get()); }
-
메서드 내에서 타입 매개변수가 사용되는 경우 T를 사용 할 수 밖에 없다.
// 제네릭 메서드를 사용 할 수 밖에 없는 경우 public static <T> T getBoxValue1(Box<T> box) { T value = box.get(); // 타입 매개변수 사용 return value; }
// 이따구로 사용할 수 없다. public static ? getBoxValue2(Box<?> box) {
? value = box.get(); return value;
}
- ?는 반드시 Parent를 상속한 Child나 Parent이기 때문에 Parent 타입과 Parent에 정의된 메서드를 활용 할 수 있다.
- ?는 최소 Child 이상 받을 수 있으며 특정 타입과 메서드는 사용 할 수 없다.(사용하려면 Object타입과 메서드를 사용할 수 있다.)
-
위의 제네릭 메서드와 와일드카드를 사용한 메서드에 Box box를 매개변수로 입력했을때 어떻게 처리되는지 설명하시오.
-
와일드 카드는 항상 <>안에서만 정의된다 참 거짓
-
답
- 제네릭 메서드 : 컴파일 시점에서 타입 추론에 의해서 T가 Dog로 확정
<Dog> void method(Box<Dog> box)
로 컴파일 → 이후 메서드 실행 - 와일드카드 메서드 : ?는 그냥 아무 타입이나 다 받을 수 있다는 의미로 제네릭과 관련이 없다. ? = Object를 갈긴것돠 동일하다.
- 제네릭 메서드 : 컴파일 시점에서 타입 추론에 의해서 T가 Dog로 확정
-
참, ?는 제네릭 타입인 T와 관련된 문법으로 단독으로 사용될 수 없다.
- 조회 : O(1)
- 끝자리 삽입 : O(1)
- 중간 삽입 : O(n)
- 끝자리 삭제 : O(1)
- 중간 삭제 : O(n)
- 배열의 길이 동적으로 변경 할 수 없기 때문에 너무 크게 생성하면 불필요한 메모리가 낭비되고 너무 작게 생성하면 오류가 발생 할 수 있다.
-
아래의 조건에 맞게 ArraysListV1를 구현하시오. 단 원소는 Object 자료형이다.
// 생성자 1. 기본 생성자, 배열의 크기는 기본 5로 고정 2. 매개변수로 int를 받는 경우 해당 크기로 배열 생성 // 메서드 public int size(); public void add(Object e); public Object get(int index); public Object set(int index, Object e); public int indexOf(Object o); @Override public void toString();
-
동적 배열 할당 기능을 추가하여 ArrayListV2를 구현하시오. 제공 기능은 MyArrayListV1과 동일
-
특정 인덱스의 원소를 삭제(remove) 추가(add(index, data)) 기능을 추가하여 MyArrayListV3를 구현하시오.
-
제네릭을 도입하여 MyArrayListV4를 구현하시오.
- 제네릭은 new 키워드에 사용할 수 없다. 그래서 기본적으로 Object[] 을 사용하되 안에 들어가고 나오는 원소의 타입만 타입변수로 관리될 수 있도록 신경써주면된다.
-
주의사항
- 삽입, 삭제시에는 항상 size가 변경된다는 것을 유념하자.
-
기본적으로 Array와 동일하나 삽입시에 새로운 길이의 배열로 갱신될 경우 O(n)이 될 수 있다.
- 조회 : O(1)
- 끝 자라 삽입 : O(n)
- 첫 자리 삽입 : O(1)
- 끝 자리 삭제 : O(n)
- 중간 자리 삭제 : O(n)
- 장점
- 끝이 아닌 자리의 삽입, 삭제를 빨리하기 위해 사용한다.
- 단점
- 조회 성능이 좋지않다.
항상 head에 대한 조작인와 3자 노드 prev, next, new or targetNode의 3자 대면을 기억하자.
-
Node Class 구현
// 생성자의 매개변수로 Object item을 받는다. // toString();
-
Node로 이뤄진 다양한 메서드를 구현하시오.(일단 static method로 구현한다.)
static void printAll(Node first) : 모든 노드 탐색 static Node getLastNode(Node first) : 마지막 노드 조회 static Node getNode(Node first, int index) : 특정 index의 노드 조회 static void add(Node first, Object item) : 끝 노드에 노드에 데이터 추가
-
MyLinkedListV1를 구현하시오, 위의 메서드를 제공해야한다.
public void add(Object o); public Node getLastNode(); public Object set(int index, Obejct o); public Object get(int index); private getNode(int index); public int indexOf(Object o); public int size(); public string toString();
-
MyLinkedListV2를 구현하시오. 추가 메서드 : add(int index, Object o), Node remove(int index)
-
MyLinkedListV3를 구현하시오, 제네릭을 적용하시오.
List 자료형은 순서가 있고 중복을 허용하는 자료형으로 구현체로는 ArrayList, LinkedList가 있다.
-
내가 작성한 MyArrayList와 MyLinkedList의 공통 메서드를 MyList로 만들자
// 공통 메서드 size add add get set remove indexOf
public interface List<T> { // 동일하게 제네릭으로 선언한다. }
-
인터페이스를 정의하는 것 까진 ok 근데 이걸 어떻게 임플시키지?
public class MyArrayList<T> implements List<T> { }
-
아래와 같은 BatchProcess가 있다. 해당 코드를 다형성을 이용해 유지보수가 편한 코드로 변경하고 그 근거를 설명하시오.
public class BatchProcessorV1 { private final MyArrayList<Integer> list = new MyArrayList<>(); public void logic(int size) { for(int i = 0; i < size; i++) { list.add(0, i); } } }
-
유지 보수를 위한 인터페이스(MyList)에 의존하도록 변경
public class BatchProcessorV3 { // 구현체가 아닌 인터페이스에 의존 private final MyList<Integer> list; // 생성자의 매개변수로 구현체를 넣어 언제든 변경 할 수 있다. public BatchProcessorV3(MyList<Integer> myList) { this.list = myList; } public void logic(int size) { for(int i = 0; i < size; i++) { list.add(0, i); } } }
- 인터페이스인 List에 의존하면서 이후 List 인터페이스를 임플 받은 더 좋은 성능을 가진 구현체들을 만들어서 갈아끼워만주면 기존의 메서드를 수정하지 않고 그대로 사용 할 수 있다.
- 그리고 각 구현체의 고유 기능 사용시에는 해당 구현체인 하위 타입으로 다운캐스팅하여 사용 하면 된다. => 전략패턴
-
각 구현체의 logic 메서드의 성능을 비교하시오.
객채의 특정 행위를 인터페이스로 정의하고, 이를 구현하는 여러 클래스들을 만들어 동적으로 객체의 행위를 변경할 수 있도록 하는 디자인 패턴
- 위에서 만든 BatchProcessor 처럼 구현체에 의존하는 것이아니라 myList라는 인터페이스에 의존하면서 런타임 시점에서 구현체가 갈아끼워지며 동적으로 객체의 행위를 변경 할 수 있게 되는것이다.
- 컴파일 의존 관계 : 특정 행위가 인터페이스에 의존
- 런타임 의존 관계 : 특정 행위가 실제 구현된 객체에 의존