안녕하세요, 오늘은 GENERIC 에 대해 내용을 정리해보았습니다.
💡 제네릭 없이, 다형성만 활용하여 코드를 작성한다면?다형성
타입 안정성 문제
public class IntegerStore {
private Integer field;
public void setField(Object object) {
this.field = field;
}
public Integer getField() {
return field;
}
}
public class LongStore {
private Long field;
public void setField(Object object) {
this.field = field;
}
public Long getField() {
return field;
}
}
public class StringStore {
private String field;
public void setField(Object object) {
this.field = field;
}
public String getField() {
return field;
}
}
public class Main {
public static void main(String[] args) {
// Integer
IntegerStore integerStore = new IntegerStore();
integerStore.setField(1000);
// Long
LongStore longStore = new LongStore();
// String
StringStore stringStore = new StringStore();
}
}
코드 중복 발생
Obejct 를 활용한 다형성
public class ObjectStore {
private Object field;
public void setField(Object object) {
this.field = field;
}
// return Type이 Obejct 이다.
public Object getField() {
return field;
}
}
public class Main {
public static void main(String[] args) {
// Integer
ObjectStore integerStore = new ObjectStore();
integerStore.setField(10);
// Casting : Object -> Integer
Integer integer = (Integer) integerStore.getField();
System.out.println(integer);
// String
ObjectStore stringStore = new ObjectStore();
stringStore.setField("sparta");
// Casting : Object -> String
String string = (String) stringStore.getField();
System.out.println(string);
// Long, Float, 다른 Object 등등..
// Integer가 아닌 String을 넣는다면?
integerStore.setField("Spring");
// Integer integerString = (Integer) "Spring"; ?????
Integer integerString = (Integer) integerStore.getField();
System.out.println(integerString);
}
}
// ClassCastException 발생!
Child child = new Parent(); 불가능.💡 제네릭을 활용하여 타입 안정성 문제를 해결해보자.제네릭
<…> 을 사용한것이 제네릭이다.public class GenericStore<T> {
private T field;
public void setField(T field) {
this.field = field;
}
public T getField() {
return field;
}
}
public class Main {
public static void main(String[] args) {
// T의 Type이 결정되는 순간
GenericStore<Integer> integerStore = new GenericStore<Integer>();
// Compile 오류 발생
integerStore.setField("Sparta");
integerStore.setField(1000);
// integerStore를 new 하는 순간 Integer로 생성되어있다.
// 즉, 캐스팅이 필요없다.
Integer integer = integerStore.getField();
System.out.println(integer);
// 생성하는 뒷부분의 <> 안의 타입은 생략이 가능하다.
GenericStore<String> stringStore = new GenericStore<>();
stringStore.setField("spring");
String string = stringStore.getField();
System.out.println(string);
// Long, Double, Obejct 등등..
}
}
<> 타입은 생략이 가능하다.public class KeyValueType<K, V> {
...
}
class MultipleType<T, U> {
...
}
GenericStore stringStore = new GenericStore();
💡 제네릭에 타입을 지정하여 타입 안정성을 확보하면 좋지만, 너무 자유롭다. ex) 단순히 로 지정하게되면 모든 클래스를 대입할 수 있다. 그래서 사용하는 것이 **Bounded Type Parameter** 이다.제네릭 타입 범위 제한
public class Fruit {
private String name;
private int size;
public Fruit(String name, int size) {
this.name = name;
this.size = size;
}
public String getName() {
return name;
}
public int getSize() {
return size;
}
public void sayColor() {
System.out.println("나는 무색이야");
}
}
public class Apple extends Fruit {
public Apple(String name, int size) {
super(name, size);
}
@Override
public void sayColor() {
System.out.println("나는 빨간색이야");
}
}
public class Grape extends Fruit {
public Grape(String name, int size) {
super(name, size);
}
@Override
public void sayColor() {
System.out.println("나는 보라색이야");
}
}
public class FruitShop {
private Fruit fruit;
public void setFruit(Fruit fruit) {
this.fruit = fruit;
}
public Fruit getFruit() {
return fruit;
}
public void advertise() {
System.out.println("맛있는 " + fruit.getName() + " 팝니다.");
System.out.println("크기는 " + fruit.getSize() + " 입니다.");
fruit.sayColor();
}
// 사이즈가 큰 것은 반환한다.
public Fruit compareSize(Fruit compareFruit) {
if (fruit.getSize() > compareFruit.getSize()) {
return fruit;
}
return compareFruit;
}
}
public class Main {
public static void main(String[] args) {
FruitShop appleShop = new FruitShop();
FruitShop grapeShop = new FruitShop();
Apple apple = new Apple("사과", 1);
Grape grape = new Grape("포도", 2);
appleShop.setFruit(apple);
appleShop.advertise();
grapeShop.setFruit(grape);
grapeShop.advertise();
// 사과 가게인데 포도를 가질 수 있다.
// appleShop.setFruit(grape);
// fruit 타입으로 반환된다.
Fruit fruit = appleShop.compareSize(new Apple("풋사과", 2));
// 다운 캐스팅이 필요하다.
Apple apple2 = (Apple) appleShop.compareSize(new Apple("풋사과", 2));
// 사과를 비교해야 하는데 포도를 넣으면?
// Apple apple3 = (Apple) appleShop.compareSize(new Grape("청포도", 2));
}
}
public class FruitShop<T> {
private T fruit;
public void setFruit(T fruit) {
this.fruit = fruit;
}
public T getFruit() {
return fruit;
}
public void advertise() {
// T를 사용하면 어떤 타입이 들어올 지 모르기 때문에 Object와 같다.
// 하위 클래스들의 메서드나, 필드를 미리 선언하여 사용할 수 없다.
// System.out.println("맛있는 " + fruit.getName() + " 팝니다.");
// System.out.println("크기는 " + fruit.getSize() + " 입니다.");
// fruit.sayColor();
}
public T compareSize(Fruit compareFruit) {
// T = Object
// if (fruit.getSize() > compareFruit.getSize()) {
// return fruit;
// }
// return compareFruit;
}
}
T에 들어올 수 있는 값들이 너무 자유롭다.
한마디로, Object가 제공하는 메서드만 호출할 수 있다.
public class FruitShop<T extends Fruit> {
private T fruit;
public void setFruit(T fruit) {
this.fruit = fruit;
}
public T getFruit() {
return fruit;
}
public void advertise() {
System.out.println("맛있는 " + fruit.getName() + " 팝니다.");
System.out.println("크기는 " + fruit.getSize() + " 입니다.");
fruit.sayColor();
}
// 사이즈가 큰 것은 반환한다.
public T compareSize(T compareFruit) {
if (fruit.getSize() > compareFruit.getSize()) {
return fruit;
}
return compareFruit;
}
}
public class Main {
public static void main(String[] args) {
FruitShop<Apple> appleShop = new FruitShop();
FruitShop<Grape> grapeShop = new FruitShop();
Apple apple = new Apple("사과", 1);
Grape grape = new Grape("포도", 2);
appleShop.setFruit(apple);
appleShop.advertise();
grapeShop.setFruit(grape);
grapeShop.advertise();
// 다른 타입을 넣게되면 컴파일 오류 발생
appleShop.setFruit(grape);
// fruit 타입으로 반환된다.
Fruit fruit = appleShop.compareSize(new Apple("풋사과", 2));
// 다운 캐스팅이 필요없다.
Apple returnApple = appleShop.compareSize(new Apple("풋사과", 2));
}
}
T extends Fruit💡 메서드에 제네릭을 적용한다.제네릭 메서드
<T> 를 메서드 반환타입 앞에 위치시킨다.public Object get(Long id) { … }public class GenericMethod {
public static <T> T genericMethod(T t) {
System.out.println("T 출력 : " + t);
return t;
}
public static <T extends Number> T numberGenericMethod(T t) {
System.out.println("number T 출력 : " + t);
return t;
}
}
public class Main {
public static void main(String[] args) {
Long value = 1L;
// 타입을 명시한다.
Long longValue = GenericMethod.<Long>genericMethod(value);
Integer integerValue = GenericMethod.<Integer>numberGenericMethod(10);
}
}
<T extends Number>public class Main {
public static void main(String[] args) {
Long value = 1L;
// 타입을 생략한다.
Long longValue = GenericMethod.genericMethod(value);
Integer integerValue = GenericMethod.numberMethod(10);
}
}
// 제네릭 타입 설정
public class FruitBox<T extends Fruit> {
private T fruit;
public void setFruit(T fruit) {
this.fruit = fruit;
}
// 제네릭 메서드 설정
public <T> T getClassName(T t) {
System.out.println("fruit class" + fruit.getClass().getName());
System.out.println("t class" + t.getClass().getName());
return t;
}
}
public class Main {
public static void main(String[] args) {
Apple apple = new Apple("사과", 1);
Grape grape = new Grape("포도", 2);
FruitBox<Apple> appleBox = new FruitBox<>();
appleBox.setFruit(apple);
Grape grape2 = appleBox.getClassName(grape);
}
}
제네릭 메서드가 우선순위를 가진다.
제네릭 클래스의 T는 클래스의 T
<T extends Fruit> 으로 제한을 두었다.제네릭 메서드의 T는 메서드의 T
<T extends Fruit> 으로 제한을 두지 않았다. == Obejct로 취급된다..getClass()는 Obejct의 메서드이기 때문에 사용할 수 있다.이런 경우 꼭 제네릭 메서드에는 T 대신 다른 이름을 사용해야 한다.
public <S> S getClassName(S s) {
System.out.println("fruit class" + fruit.getClass().getName());
System.out.println("s class" + s.getClass().getName());
return s;
}
💡 제네릭에서 사용하는 와일드카드는 `?` 키워드를 사용한다.와일드카드
와일드 카드는 제네릭 타입을 활용할 때 사용한다.
Unbounded Wildcards
? 사용public class Shop<T> {
private T product;
public void setProduct(T product) {
this.product = product;
}
public T getProduct() {
return product;
}
}
public class WildCard {
// 제네릭 메서드 사용
static <T> void genericMethod(Shop<T> shop) {
System.out.println("T Shop Product = " + shop.getProduct());
}
// ? 와일드 카드 사용
static void wildCardGenericMethod(Shop<?> shop) {
System.out.println("? : " + shop.getProduct());
}
}
제네릭 메서드 사용
? 와일드 카드 사용
<? extends Object> 와 같다.와일드 카드를 사용하면 제네릭 메서드가 아니다.
타입 추론 과정은 복잡하다.
Upper Bounded Wildcards
<? extends 상위클래스> 사용public class WildCard {
// extends + 제네릭 메서드 사용
static <T extends Fruit> void genericMethod(Shop<T> shop) {
System.out.println("T Shop Product = " + shop.getProduct());
}
// extends + 와일드 카드 사용
static void extendsGenericMethod(Shop<? extends Fruit> shop) {
Fruit fruit = shop.getProduct();
System.out.println("? extends Fruit : " + fruit.getName());
}
}
와일드 카드 또한 제한을 설정할 수 있다.
Fruit class의 기능을 사용할 수 있다.
언제 제네릭 메서드를 사용하는가?
public class WildCard {
// T 를 반환
static <T extends Fruit> T genericMethod(Shop<T> shop) {
T t = shop.getProduct();
System.out.println("T Shop Product = " + shop.getProduct());
return t;
}
// Fruit 타입을 반환, 전달할 타입을 명확하게 할 수 없다.
static Fruit extendsGenericMethod(Shop<? extends Fruit> shop) {
Fruit fruit = shop.getProduct();
System.out.println("? extends Fruit : " + fruit.getName());
return fruit;
}
}
public class Main {
public static void main(String[] arts) {
Apple apple = new Apple("사과", 1);
Grape grape = new Grape("포도", 2);
Shop<Apple> appleShop = new Shop();
Shop<Grape> grapeShop = new Shop();
appleShop.setProduct(apple);
grapeShop.setProduct(apple);
// T를 반환
Apple returnApple = Wildcard.genericMethod(appleShop);
Grape returnGrapeb = Wildcard.genericMethod(grapeShop);
// ? extends Fruit의 경우 Fruit을 반환한다.
Fruit apple2 = Wildcard.extendsGenericMethod(appleShop);
Fruit grape2 = Wildcard.extendsGenericMethod(grapeShop);
// 즉, 형변환이 필요하다.
Apple apple2 = (Apple) Wildcard.extendsGenericMethod(appleShop);
Grape grape2 = (Grape) Wildcard.extendsGenericMethod(grapeShop);
}
}
<T>를 사용하고 나머지는 와일드 카드를 사용한다.<? super 하위클래스> 사용public class Main {
public static void main(String[] arts) {
Shop<Object> objectShop = new Shop();
Shop<Fruit> fruitShop = new Shop();
Shop<Apple> appleShop = new Shop();
Shop<Grape> grapeShop = new Shop();
// Fruit 이상의 상위 클래스 허용
init(objectShop);
init(fruitShop);
// 불가능
init(appleShop);
init(grapeShop);
}
static void init(Shop<? super Fruit> shop) {
shop.setProduct(new Apple("사과", 10));
}
}
💡 Generic은 Compile 시점에 사용된다.Eraser
// 컴파일 이전
// 상위 클래스 제한
public class FruitBox<T extends Fruit> {
private T fruit;
public void setFruit(T fruit) {
this.fruit = fruit;
}
public T getFruit() {
return fruit;
}
}
public class Main {
Apple apple = new Apple("사과", 1);
Grape grape = new Grape("포도", 2);
FruitBox<Apple> appleBox new FruitBox<>();
appleBox.set(apple);
Apple apple = appleBox.getFruit();
}
------------------------------------------------------
// 컴파일 이후
public class FruitBox<Apple extends Fruit> {
private Apple fruit;
public void setFruit(Apple fruit) {
this.fruit = fruit;
}
public Apple getFruit() {
return fruit;
}
}
public class Main {
Apple apple = new Apple("사과", 1);
Grape grape = new Grape("포도", 2);
FruitBox appleBox new FruitBox<>();
appleBox.set(apple);
Apple fruit = (Apple) appleBox.getFruit();
}
// 컴파일 이전
public class Eraser<T> {
// 불가능
public void createEraser() {
return new T();
}
// 불가능
public boolean instanceOf(Object obj) {
return obj instanceOf T;
}
}
-----------------------------------------------
// 컴파일 이후, Runtime
public class Eraser {
// 불가능
public void createEraser() {
return new Object();
}
// 불가능
public boolean instanceOf(Object obj) {
return obj instanceOf Object;
}
}
createEraser()new Object 로 타입이 생성된다.instanceOf()정리
오늘의 코드카타
import java.util.ArrayList;
import java.util.List;
class Solution {
public int[] solution(int[] arr) {
List<Integer> stk = new ArrayList<>();
int i = 0;
while (i < arr.length) {
if (stk.isEmpty()) {
stk.add(arr[i]);
i++;
} else if (stk.get(stk.size() - 1) == arr[i]) {
stk.remove(stk.size() - 1);
i++;
} else {
stk.add(arr[i]);
i++;
}
}
if (stk.isEmpty()) {
return new int[]{-1};
}
int[] answer = new int[stk.size()];
for (int j = 0; j < stk.size(); j++) {
answer[j] = stk.get(j);
}
return answer;
}
}