일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 리눅스 #기초설정 #가이드 #명령어
- 스파르타코딩클럽 #부트캠프 #IT #백엔드 #머신러닝 #AI #서버 #자동화 #SQL #기본문법 #데이터베이스
- sasac #aws 클라우드 #아키텍트 과정 #가상화 #vmbox #vmware #esxi #tar #selinux
- 명령어 #기초 #비밀번호 설정
- vmware #가상화 #aws 클라우드 #아키텍트 #과정 #가상머신 #컨테이너 #docker
- mysql #linux #설정 #wordpress #웹사이트 #db 연결 #
- virtualbox #vmware #router #nat #pat #네트워크 구성도 #aws #ubuntu #
- 리눅스 #
- ubuntu #설정변경 #vmware #vmbox #linux #명령어
- 스파르타코딩클럽 #부트캠프 #IT #백엔드 #머신러닝 #딥러닝 #AI #서버 #자동화 #SQL #기본문법 #데이터베이스
- 사용자 그룹관리
- 리눅스 #기초 #네트워크 #포트 번호 #역할
- 스파르타코딩클럽 #부트캠프 #IT #백엔드 #OSI #ISO #AI #서버 #자동화 #SQL #기본문법 #데이터베이스 #DBMS #Oracle #MongoDB #아키텍쳐 #DB
- 스파르타코딩클럽 #부트캠프 #IT #백엔드 #머신러닝 #딥러닝 #AI #서버 #자동화 #SQL #기본문법 #데이터베이스 #DBMS #Oracle #MongoDB #아키텍쳐 #DB
- 리눅스 #명령어 #내용정리 #mac #특수권한
- 프로세스 #CPU #시공유 #커널
- 스파르타코딩클럽 #부트캠프 #IT #백엔드 #머신러닝 #AI #서버 #자동화 #SQL #기본문법 #데이터베이스 #웹개발
- 쓰레드 #쓰레드풀 #프로세스
- 인바운드 #아웃바운드 #방화벽설정
- 리눅스 #명령어 #사용자 계정 정보 관리
- selinux #실행모드 변경 #설정방법
- tar #build #배포 #통신포트 #설정방법 #linux #apache
- 비트 #바이트 #이진수
- 스파르타코딩클럽 #부트캠프 #IT #백엔드 #머신러닝 #AI #서버 #자동화 #SQL #KDT #기본문법 #데이터베이스 #Computer #Science #CPU #메모리
- storage #로컬스토리지 #세션스토리지 #백그라운드 서비스
- 공간복잡도 #공간자원 #캐시메모리 #SRAM #DRAM #시간복잡도
- samba #가상머신 #daemon
- haproxy #wordpree #php #linux #가상화 #가상머신 #내용정리
- oracle vmbox #rocky #linux9 #명령어 #암호화인증 #해시알고리즘
- 리눅스 #사용자계정 #정보관리
- Today
- Total
요리사에서 IT개발자로
스파르타 부트캠프 Java 제네릭 정리 (다락방) 본문
제네릭 없이 다형성만 활용하여 코드를 작성할 경우
다형성을 사용하면 코드의 중복을 제거할 수 있고
기존 코드를 재사용할 수 있으나
다형성만 사용한다면 타입 안전성 문제가 발생한다.
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(100);
System.out.println("integerStore.getField() = " + integerStore.getField());
// Long
LongStore longStore = new LongStore();
longStore.setField(100000);
System.out.println("longStore.getField() = " + longStore.getField());
// String
StringStore stringStore = new StringStore();
stringStore.setField("문자열입니다");
System.out.println("stringStore.getField() = " + stringStore.getField());
}
}
다형성을 사용하기 전 코드에서는
코드 중복이 발생한다.
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);
System.out.println("integerStore.getField() = " + integerStore.getField());
// Casting : Object -> Integer
Integer integer = (Integer) integerStore.getField();
System.out.println(integer);
// String
ObjectStore stringStore = new ObjectStore();
stringStore.setField("sparta");
System.out.println("stringStore.getField() = " + stringStore.getField());
// Casting : Object -> String
String string = (String) stringStore.getField();
System.out.println(string);
// Long, Float, 다른 Object 등등..
// Integer가 아닌 String을 넣는다면?
integerStore.setField("Spring");
System.out.println("integerStore.getField = " + integerStore.getField());
// Integer integerString = (Integer) "Spring"; ?????
Integer integerString = (Integer) integerStore.getField();
System.out.println(integerString);
}
}
// ClassCastException 발생!
하위 클래스는 상위 클래스를 참조할 수없다.
Object타입이기에
Integer타입, Long타입, String타입을 참조해도
컴파일 에러가 발생하지 않는다.
하지만
해당 값을 출력할 때는 해당값이 저장되지 않는 문제가 발생한다.
타입 안전성에 대한 문제를 해결하는 방법은
제네릭을 활용함으로 가능할 수 있다.
제네릭은
특정 타입에 제한되지 않고 범용적으로 사용할 수 있다.
<> 을 사용한것이 제네릭이며
제네릭 클래스는 Type을 미리 결정하지 않는다.
생성하는 시점에 Type을 결정한다.
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);
System.out.println("integerStore.getField() = " + integerStore.getField());
// integerStore를 new 하는 순간 Integer로 생성되어있다.
// 즉, 캐스팅이 필요없다.
Integer integer = integerStore.getField();
System.out.println(integer);
// 생성하는 뒷부분의 <> 안의 타입은 생략이 가능하다.
GenericStore<String> stringStore = new GenericStore<>();
stringStore.setField("spring");
System.out.println("stringStore.getField() = " + stringStore.getField());
String string = stringStore.getField();
System.out.println(string);
// Long, Double, Obejct 등등..
}
}
GenericStore<T>는 명시된 타입이 없기때문에
Main클래스에서 생성시점에
Integer타입으로 결정되어
integerStore.setField("Sparta")는
컴파일 에러가 발생한다(문자열은 저장될 수 없다)
GenericStore를 생성하고 <Integer>으로 선언하게 됨으로
GenericStore<Integer>로 변환하는것이고
GenericStore를 생성하고 <String>으로 선언하게 됨으로
GenericStore<String>으로 변환하여 사용되는것
컴파일에 T에 대한 정보를 작성한 코드에 맞추어 적용한다.
(타입이 불일치하면 위처럼 컴파일 에러가 발생한다)
생성 부분에 <>타입은 생략할 수있다.
GenericStore<Integer> integerStore = new GenericStore<>();
제네릭은 여러개의 타입을 선언할 수 있다.
public class KeyValueType<K, V> {
...
}
class MultipleType<T, U> {
...
}
제네릭 키워드 대신에 다른것들이 들어갈 수는 있으나
규칙이 있고
대문자를 사용해야하며
키워드 첫글자를 사용해야한다.
E => Element
K => Key
N => Number
T => Type
V => Value
S, U, V
Type이 여러개인 경우라면 각각
Second, Third, Fourth
타입을 지정하지 않아도된다.
GenericStore stringStore = new GenericStore();
타입을 지정하지 않으면 T가 Object로 지정되지만
현재는 사용하지 않아야된다.
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 Grape extends Fruit {
public Grape(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;
}
}
Apple과 Grape는 Fruit을 상속받고있다.
FruitShop appleShop = new FruitShop();
FruitShop grapeShop = new FruitShop();
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();
}
FruitShop클래스안에 setFruit가 있고
Input값으로 fruit를 받으며
Fruit(부모클래스)를 상속받는 Apple과 Grape는
fruit에 데이터를 입력하여 출력이 가능하다.
Apple apple = new Apple("사과", 1);
Grape grape = new Grape("포도", 2);
위와 같이 Apple(사과,1 )과 Grape(포도,2)는
appleShop.setFruit(apple);
appleShop.advertise();
grapeShop.setFruit(grape);
grapeShop.advertise();
위와같이 appleShop에 setFruit를 하게되면
appleShop.advertise();
grapeShop.advertise();
위 두개의 메소드가 실행되어 위처럼 출력되는 것을 확인할 수 있다.
// 사과 가게인데 포도를 가질 수 있다.
appleShop.setFruit(grape);
appleShop.advertise();
또한 apple과 grape의 데이터는
String과 int타입이기에 타입이 일치하므로
호환도 가능하다.
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;
}
}
제네릭을 사용하여 T를 선언하고 extends Fruit을 하게되면
다형성으로 인해서 Fruit과 그 하위 class만 받을 수 있고
Fruit의 메서드들을 사용할 수 있게되며
타입 안전성으로 appleShop에 grape를 넣을 수 없게된다.
제네릭타입은 생성시점에 타입이 지정되며 다운 캐스팅이 필요없다(형변환)
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();
위처럼 다른 타입을 넣게되면 컴파일 오류가 발생한다
그 이유는
Grape grape = new Grape("포도", 2);
grape는 Grape클래스로 명시가 되어있는데.
FruitShop<Apple> appleShop = new FruitShop();
위처럼 정의되있는 appleShop에
// 다른 타입을 넣게되면 컴파일 오류 발생
appleShop.setFruit(grape);
Grape로 정의된 grape를 넣으려고 하면 오류가 발생한다.
하지만
Apple grape = new Apple("포도", 2);
이렇게하면 오류가 발생하지 않게 할 수는 있다.
제네릭 메서드
접근제어자 + 반환타입 + 메서드 이름 + (매개 변수) { 구현 코드 }
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);
}
}
위처럼
longValue는
GenericMethod.<Long>genericMethod(value)를 해주면
1이 출력이되고
Integer integerValue는
GenericMethod.<Integer>numberGenericMethod(10)을 하면
10이 출력되는것을 확인할 수 있다.
위처럼 제네릭 메소드를 사용할 수 있다.
genericMethod는 String 타입도 가능하다
하지만 numberGenericMethod 같은 경우
Number를 상속받고있어
Number의 하위 클래스만 사용할 수 있다.
public static void main(String[] args) {
Long value = 1L;
// 타입을 생략한다.
Long longValue = GenericMethod.genericMethod(value);
Integer integerValue = GenericMethod.numberGenericMethod(10);
}
위처럼 타입을 생략해도 사용할 수 있다.
컴파일러가 자동으로 넣어준다 생각하면 된다.
우선순위
static 메서드는 제네릭 메서드만 적용할 수 있다.
new로 생성하여 사용하는
일반 메서드는 제네릭타입과 제네릭 메서드 모두 적용할 수 있다.
// 제네릭 타입 설정
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는 extends Fruit으로 제한을 두었고
제네릭 메서드의 T는
제한을 두지 않았기 때문에 Object로 취급된다.
.getClassName(grape)는 Object메서드 이기에 사용할 수 있다.
위와같이 메소드와 클래스의 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;
}
다른이름을 사용해야한다.
제네릭에서 사용하는 와일드 카드는
<?> 키워드를 사용한다
와일드 카드는 제네릭 타입을 활용할 때 사용한다.
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());
}
}
제네릭 메서드는
타입 추론에 의해서 T는 전달받은 값이 되는것이며
<?>와일드 카드를 사용하면
Shop과 관련된 모든 타입을 T처럼 받을 수 있다.
<? extends Object>와 같다.
이미 정해진 제네릭 타입을 활용할 때 쓴다.
와일드 카드를 사용하면 제네릭 메서드가 아니다.
일반 메서드이며 모든 타입을 받을 수 있다.
대신에 타입 추론 과정은 복잡하다.(가독성)
와일드 카드를 사용하면 단순하게 동작하고
보통 제네릭메서드가 아니라 와일드 카드를 사용한다
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());
}
}
와일드 카드도 위처럼
extends Fruit으로 제한을 설정할 수 있고
다른타입이 입력되면 컴파일 오류가 발생한다.
제네릭타입처럼 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>를 사용하고
나머지는 와일드 카드를 사용한다
형변환이 필요한경우에만
즉
형변환이 필요한 경우는 <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));
}
}
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();
}
T를 Apple로 변경했다고 보면된다.
Compile이 완료된 후
Runtime때 Generic과 관련된 정보를 삭제하는것이
Eraser이다.
https://www.youtube.com/watch?v=QcXLiwZPnJQ
https://hs-backend.tistory.com/143
'Java' 카테고리의 다른 글
크기가 작은 부분문자열 (Java) (0) | 2024.06.03 |
---|---|
스파르타 부트캠프 Java 추상클래스와 상속 정리 (다락방) (0) | 2024.05.29 |
스파르타 부트캠프 Java 제네릭(Generics) (0) | 2024.05.25 |
스파르타 부트캠프 자바 5강 쓰레드 상태와 제어 (0) | 2024.05.12 |
스파르타 부트캠프 자바 4강 예외처리 (0) | 2024.05.11 |