일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |
- 이스티오
- Real MySQL
- JPA
- 토비의 스프링
- 백준
- jvm
- 자바
- redis
- thread
- gradle
- SpringBoot
- 쿠버네티스
- Java
- Stream
- 토비의 스프링 정리
- IntellJ
- MSA
- mysql
- Kotlin
- 자바 ORM 표준 JPA 프로그래밍
- GC
- spring
- 스트림
- 스프링
- Stack
- Collection
- list
- 보조스트림
- K8s
- OS
- Today
- Total
인생을 코딩하다.
[Java] Generic 본문
Java Collection Framework의 Generic programming
Java Collection Framework은 java에서 여러가지 자료구조와 알고리즘을 구현해놓은 라이브러리라고 할 수 있다.
제네렉 프로그래밍이란? "클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법"
- 변수의 선언이나 메서드의 매개변수를 하나의 참조 자료형이 아닌 여러 자료형을 변환 될 수 있도록 프로그래밍 하는 방식
- 메소드 호출시 리턴값으로 넘어오는 타입과 다를 때 각각 형변환을 해주는 문제점이 있다. 그래서 타입 형 변환에서 발생할 수 있는 문제점을 "사전에' 없애기 위해서 만들어졌다
- 실제 사용되는 참조 자료형으로 변환은 컴파일러가 검증하므로 안정적인 프로그래밍 방식
- java5 부터 추가 됨.
자바에서 정의한 제네릭의 기본 규칙
- E : 요소 (자바 컬렉션에서 주로 사용)
- K :키
- N : 숫자
- T : 타입
- V : 값
- S, U, V : 두 번째. 세 번째, 네 번째에 선언된 타입
꼭 이 규칙을 지켜야 컴파일이 되는 것은 아니다. 하지만, 다른 어떤 사람이 보더라도 쉽게 이해할 수 있도록 하려면 이 규칙을 따르는 것이 좋다.
자료형 매개변수 T
public class Generic<T> {
private T material; // T -> 제네릭클래스, Type의 약자, 자료형 매개변수
public void setMaterial(T material) {
this.material = material;
}
public T getMaterial() {
return material;
}
}
T에 어떤 것이 들어가느냐는 실제 클래스를 쓸 때 결정하면 된다, 여러개의 클래스가 쓰일 수 있다.
package java8.generic;
public class Plastic {
public String toString() {
return "재료는 Plastic 입니다";
}
}
package java8.generic;
public class Powder {
public String toString() {
return "재료는 Powder 입니다";
}
}
package java8.generic;
public class Water {
...
}
package java8.generic;
public class GenericPrinter<T> {
// Printer을 쓸 때, 재료를 정하자, T type에 적용이 되서 클래스가 생성 될 때, T타입에 모두 대입이 된다.
private T material;
public T getMaterial() {
return material;
}
public void setMaterial(T material) {
this.material = material;
}
public String toString() {
return material.toString();
}
}
package java8.generic;
public class GenericPrinterTest {
public static void main(String[] args) {
GenericPrinter<Powder> powderGenericPrinter = new GenericPrinter<>();
Powder powder = new Powder(); // 파우더에 관한 재료를 넣어준다.
// GenericPrinter 클래스에서 제네릭 타입만 생성했다고 해서 되는게 아니다. 생성을 해서 set으로 넣어줘야 한다.
powderGenericPrinter.setMaterial(powder);
System.out.println(powderGenericPrinter);
GenericPrinter<Plastic> plasticGenericPrinter = new GenericPrinter<>();
Plastic plastic = new Plastic();
plasticGenericPrinter.setMaterial(plastic);
System.out.println(plasticGenericPrinter);
// GenericPrinter<Water> waterGenericPrinter = new GenericPrinter<>();
}
}
두 개의 관해 각각 다른 프린터를 생성을 할 때, 자료형을 대입해서 쓸 수 있다. 이게 제네릭 프로그래밍이다.
여기서 하나 생각해볼것은, 만약 재료가 물이 있다면 물을 3D 프린터 재료로 할 수는 없다. 만약 제약 조건이 없다면 위에 주석 처리 한 로직처럼 그냥 쓸 수 있다. 나중에 기술이 발전해서 물로도 3D 프린터를 할 수 있다면 상관 없겠지만, 위 주석처리 한 로직을 주석처리 하지 않고 실행하게 된다면 문제가 발생 할 수 있다.
그래서 이를 해결하기 위한 방법은,
Meterial 클래스를 만들고
package java8.generic;
public class Meterial {
}
GenericPrinter 클래스에서 T type 옆에 Meterial 클래스를 상속 받아 Meterial 클래스를 상속 받은 애들만 GenericPrinter의 재료로 사용할 수 있게 해준다.
public class GenericPrinter<T extends Meterial> {
그리고 Powder, Plastic 클래스에서 상속을 해주어야 한다.
// Powder 클래스
public class Powder extends Meterial{
//Plastic 클래스
public class Plastic extends Meterial{
이렇게 하면
Water는 당연히 쓸 수 없게 된다.
또 이런 방법도 있다.
package java8.generic;
public abstract class Meterial {
public abstract void doPrinting();
}
Powder, Plastic 클래스에
package java8.generic;
public class Plastic extends Meterial {
public String toString() {
return "재료는 Plastic 입니다";
}
@Override
public void doPrinting() {
System.out.println("Plastic로 프린팅 합니다.");
}
}
package java8.generic;
public class Powder extends Meterial{
public String toString() {
return "재료는 Powder 입니다";
}
@Override
public void doPrinting() {
System.out.println("Powder로 프린팅 합니다.");
}
}
추가해준다.
package java8.generic;
public class GenericPrinter<T extends Meterial> {
// Printer을 쓸 때, 재료를 정하자, T type에 적용이 되서 클래스가 생성 될 때, T타입에 모두 대입이 된다.
private T material;
public T getMaterial() {
return material;
}
public void setMaterial(T material) {
this.material = material;
}
public String toString() {
return material.toString();
}
public void printing() {
material.doPrinting();
}
}
package java8.generic;
public class GenericPrinterTest {
public static void main(String[] args) {
GenericPrinter<Powder> powderGenericPrinter = new GenericPrinter<>();
Powder powder = new Powder(); // 파우더에 관한 재료를 넣어준다.
// GenericPrinter 클래스에서 제네릭 타입만 생성했다고 해서 되는게 아니다. 생성을 해서 set으로 넣어줘야 한다.
powderGenericPrinter.setMaterial(powder);
System.out.println(powderGenericPrinter);
GenericPrinter<Plastic> plasticGenericPrinter = new GenericPrinter<>();
Plastic plastic = new Plastic();
plasticGenericPrinter.setMaterial(plastic);
System.out.println(plasticGenericPrinter);
// GenericPrinter<Water> waterGenericPrinter = new GenericPrinter<>();
powderGenericPrinter.printing();
plasticGenericPrinter.printing();
}
}
맨 마지막 아래에 두 줄을 추가해 줌으로써
재료는 Powder 입니다
재료는 Plastic 입니다
Powder로 프린팅 합니다.
Plastic로 프린팅 합니다.
이런 결과를 얻을 수 있다.
정리하자면, <T extends 클래스> = "Bounded Wildcards" -> T 대신에 사용될 자료형을 제한하기 위해 사용한다
Material <----- Powder, Plastic
Material에 정의된 메서드를 공유할 수 있다. 상속을 통해 제한을 구현할 수 있다.
즉, 매개 변수로 넘어오는 제네릭 타입의 경계를 지정하는데 사용한다는 의미
그런데 이 방법에는 큰 단점이 있다. 매개 변수로 사용된 객체에 값을 추가할 수 가 없다는 것이다.
public <T> void genericMethod(wildcardGeneric<T> c, T addValue) {
c.setWildcard(addValue);
T value = c.getWildcard();
System.out.println(value);
}
/*
// 이렇게도 쓸 수 있다.
public <T extends Car> void boundGenericMethod(wildcardGeneric<T> c, T addValue) {
// 중간 생략
}
*/
메소드 선언부를 잘 보면 리턴 타입 앞에<>로 제네릭 타입을 선언해 놓았다. 그리고, 매개변수에서는 그 제네릭 타입이 포함된 객체를 받아서 처리한 것을 볼 수 있다. 게다가 메소드의 첫 문장을 보면 setWildcard() 메소드를 통하여 값을 할당까지 했다. 이처럼 메소드 선언시 리턴 타입 앞에 제니릭한 타입을 선언해주고, 그 타입을 매개변수에서 사용하면 컴파일 할 때 전혀 문제가 없다. 게다가 값도 할당할 수 있다.
?를 사용하는 Wildcard 처럼 타입을 두리뭉실하게 하는 것보다는 이처럼 명시적으로 메소드 선언시 타입을 지정해주면 보다 견고한 코드를 작성할 수 있다.
그러면 "내가 만든 메소드는 제네릭 타입이 두 개인데, 이럴 땐 어떻게 하지?" 라고 추가 질문을 던질 수 있을 것이다. 그럴땐 제네릭 타입의 선언을 콤마로 구분하여 나열해주면 된다.
public <S, T extends Car> void multiGenericMethod(wildcardGeneric<T> c, T addValue, S another) {
// 중간생략
{
============================================================================
자료형 매개 변수가 두 개 이상일 때
public class Point<T, V> { //<T, V> 하나는 Integer이고 하나는 Double, 다르게 쓸 수 있다.
T x;
V y;
Point(T x, V y) {
this.x = x;
this.y = y;
}
// 제네릭 메서드
public T getX() {
return x;
}
public V getY() {
return y;
}
}
제네릭 메서드를 일반 클래스에서도 쓸 수 있다.
메서드 내에서의 자료형 매개 변수는 메서드 내에서만 유효함 (지역변수와 같은 개념)
class Shape<T> { // 여기에서 T는 멤버변수나 반환값으로 쓰일 수 있다.
public static <T, V> double makleRectangle(Point<T, V> p1, Point<T, V> p2) { // 지역 변수
.....
}
}
shape의 T와 makeRectangle의 T는 전혀 다른 의미
'Java' 카테고리의 다른 글
[Java] 바이트 단위 입출력 스트림 (0) | 2020.11.23 |
---|---|
[Java] 입출력 스트림 (0) | 2020.11.19 |
[Java] String,String, String +, StringBuffer, StringBuilder, Wrapper Class, boxing & unboxing (0) | 2020.11.19 |
[Java] Mulit-thread (0) | 2020.11.18 |
[Java] Thread (0) | 2020.11.17 |