안녕하세요 jju_developer 입니다.
오늘은 제네릭 타입에 대해 간단하게 소개하는 시간을 갖겠습니다.
✔제네릭(Generic)이란 무엇일까요?
타입을 파라미터화해서 컴파일시 구체적인 타입이 결정되도록 하는 것
• 자바5부터 새로 추가된 기능이다.
• 컬렉션, 람다식(함수적 인터페이스), 스트림, NIO에서 널리 사용된다.
• 제네릭을 모르면 도큐먼트를 해석할 수 없다.
class ArrayList<E>
매개변수 처럼 E 라고 쓰고 파라미터화 한것입니다.
설계할때 타입이 뭔지는 모르고 코딩할때 타입을 정확히 쓰는 용도로 쓰입니다.
default <T,U> BiConsumer<T,U> andThen(BiConsumer<? super T,? super U> after) {...}
메소드 이름은 andThen이고
BiConsumer<t,u> </t,u>은 반환타입이다.
BiConsumer라는 클래스 타입이고 <t,u>
t,u 는 제네릭이라고 한다.
• 제네릭을 모르면 도큐먼트를 해석할 수 없다.
지금 제네릭을 배우지 않았기 때문에 위 코드들이 해석이 안되는 것이 당연합니다.
✔제네릭을 사용하는 코드의 이점
컴파일 시 강한 타입 체크를 할 수 있다.
• 실행 시 타입 에러가 나는 것 방지
• 컴파일 시에 미리 타입을 강하게 체크해서 에러 사전 방지
타입 변환(casting)을 제거한다. -> 프로그램 성능이 향상된다.
제네릭을 사용하게 되면 타입 변환을 하지 않기 때문에 실행할때 타입 에러가 나지 않게 되는 것입니다.
그렇다면 제네릭 타입은 무엇일까요?
✔제네릭 타입이란?
• 타입을 파라미터로 가지는 클래스와 인터페이스
• 선언 시 클래스 또는 인터페이스 이름 뒤에 "<>" 부호 붙임
• "< >" 사이에는 타입 파라미터 위치
예)
* public class 클래스명<T> { ... }
* public interface 인터페이스명<T> { ... }
지금까지는 제네릭 타입을 사용하지 않았기 때문에 빈변하게 타입 변환을 하게 되었습니다.
Object타입은 다양한 타입으로 넣어서 사용할수 있는 이점은 있지만,
String타입이 자동으로 Object 타입으로 자동 타입 변환 되는 경우가 잦고 그렇게 되면 String 타입의 메서드가 보이지 않기 때문에 Object 타입을 String 타입으로 강제 변환하는 경우가 있었습니다.
처음부터 클래스 설계를 할때 정해진 타입 (String, 사용자 타입) 으로 하면 그 타입만 사용 할 수 있는데
이거를 정해진 타입이 아니라 제네릭 타입으로 만든다면 어떻게 될까요?
✔제네릭 타입 사용?
• 클래스 선언할 때 타입 파라미터 사용
• 컴파일 시 타입 파라미터가 구체적인 클래스로 변경
public class Box<T> {
private T t;
public T get() {
return t;
}
public void set(T t) {
this.t = t;
}
}
위에 코드 처럼 제네릭으로 만들었으면,
위에 코드를 수정하지 않더라도 아래 처럼 다양한 사용자 정의 타입이나, String 타입을 넣을 수 있다.
Box<String> box1 = new Box<String>();
box1.set("hello");
String str = box1.get();
✔제네릭 타입 사용 목적을 정리하자면,
다양한 타입을 사용하면서 형 변환을 하지 않기 위해 제네릭 타입을 사용합니다.
예제로 설명 드리자면
클래스 2개 생성하였습니다.
첫번째 클래스는 Apple 클래스
두번째 클래스는 Object형인 Box 클래스
public class Apple {
}
public class Box {
private Object object;
public void set(Object object) {
this.object = object;
}
public Object get() {
return object;
}
}
public class BoxExample {
public static void main(String[] args) {
Box box = new Box();
box.set("사과 박스"); //Box는 Object 타입인데 "사과박스"인 String을 넣어 자동 형변환 되었다.
String name = (String)box.get(); //강제 형변환 진행, String = (String) Object
box.set(new Apple()); //Object 타입에 사용자 클래스인 Apple을 넣는다.
Apple apple = (Apple) box.get(); // 강제 형변환 진행, Apple = (Apple) Object
}
}
만약 제네릭 타입의 박스 클래스라면 어떻게 변화 될까요?
public class Box<T> {
private T t;
public T get() { return t; }
public void set(T t) { this.t = t; }
}
먼저 제네릭 타입으로 Box 클래스를 생성한 다음,
public class BoxExample {
public static void main(String[] args) {
//첫번째 형변환 없이 String 타입 넣기
Box<String> box1 = new Box<String>();
box1.set("hello");
String str = box1.get(); //들어갈때 나올때 형변환이 되지 않는다.
//두번째 형변환 없이 Integer 타입 넣기
Box<Integer> box2 = new Box<Integer>();
box2.set(6); // Integer<= int //박싱됨.
int value = box2.get(); // int <= Integer //언박싱됨.
}
}
하나의 메인 메소드안에 여러 타입의 Box를 선언하여 형변환없이 그대로 사용이 가능한 것을
볼 수 있습니다.
제네릭 타입 파라미터는 한개만 사용할수 있나요??
✔제네릭 타입은 멀티 타입 파라미터(class<K,V,...>, interface<K,V,...>)
제네릭 타입은 두 개 이상의 타입 파라미터 사용 가능
자바 7부터는 다이아몬드 연산자 사용해 간단히 작성과 사용 가능
Product<Tv, String> product = new Product<Tv, String>();
Product<Tv, String> product = new Product<>();
<멀티 파라미터의 예제를 보도록 하겠습니다.>
public class Car {
}
public class Tv {
}
//이 프로덕트 클래스를 멀티 제네릭 타입의 클래스로 만들어 보겠습니다~
public class Product < T , M >{ //T,M은 아무 알파벳이나 설정 가능하다.
private T kind;
private M model;
public T getKind() {
return kind;
}
public void setKind(T kind) {
this.kind = kind;
}
public M getModel() {
return model;
}
public void setModel(M model) {
this.model = model;
}
}
public class ProductExample {
public static void main(String[] args) {
// 치비 클래스 타입과 Sting 타입
Product<Tv, String> product1 = new Product<Tv, String>();
product1.setKind(new Tv()); //Kind는 T 타입이다.(product 클래스에서 정의함)
product1.setModel("스마트 TV"); //이렇게 셋팅 할때 내가 지정해준 String으로 사용 가능
// 가져올때에는 겟은 Tv 타입이고
// 겟 모델할때는 스트링 타입이니까 스트링 타입이라고 써줘야함.
Tv tv = product1.getKind();
String tvmodel = product1.getModel();
// Product<Car, String> product2 = new Product<Car, String>(); //아래처럼 생략 가능
Product<Car, String> product2 = new Product<>();
product2.setKind(new Car());
product2.setModel("디젤 자동차");
Car car = product2.getKind();
String carModel = product2.getModel();
// 이렇게 프로덕트 클래스에서 타입을 두개를 받는 제네릭 타입을 사용하였다.
// 타입 변환을 하지 않고도 다형성을 구현 할 수 있다.
}
}
✔제너릭 메소드 ( < T , R > R method(T t))
메소드 이름: method 그 매개변수 이름(T t)
원래 메서드 선언을 methodName (){} 인데
<T,R> R methodName(T t){} 이렇게 쓴다.
-R은 리턴 타입
-(T t)는 매개변수
이렇게 멀티 파라미터를 쓰는 것인데
학생의 이름을 정의할때 받는 타입을 String으로 줄 수 있습니다.
// 제네릭 메소드의 기본형
public <타입파라미터,...> 리턴타입 메소드명(매개변수,...) {...}
public <T> Box<T> boxing(T t) { ... }
// 제네릭 메소드의 호출 방식
리턴타입 변수 = <구체적타입> 메소드명(매개값);
리턴타입 변수 = 메소드명(매개값);
Box<Integer> box = <Integer> boxing(100); //타입 파라미터를 명시적으로 Integer로 지정
Box<Integer> box = boxing(100); //타입 파라미터를 Integer로 추정
Box<T> : 리턴타입 ==> Box<Integer>
예시를 보겠습니다.
public class Box <T> { //제네릭 타입을 파라미터로 받는 클래스
private T t;
public T get() { return t; } //get은 T타입이다.
public void set(T t) { this.t = t; }
}
public class Util {
// 클래스 내부의 메소드에서 제네릭 메소드를 사용한 예시이다.
public static <T> Box<T> boxing(T t) {
Box<T> box = new Box<T>();
box.set(t);
return box;
}
}
박싱 = 메소드 이름
Box<T>는 리턴타입을 의미
box 객체안에 set(t);를 하고
리턴은 Box<T>객체가 리턴 되는 것이빈다.
정적 메서드 이므로 Util.boxing으로 해서 호출 가능합니다.
Util.boxing(100);
근데 이 타입을 Integer로 받고 이 박싱의 리턴은 무엇일까요?
아까 <T> 리턴타입 boxing (T,t) 였으니까
박싱의 메소드안에 들어올 t는 인터저 타입이고 그렇다면 리턴타입은? 아까 정의한 Box<T>니까
이거를 정리하면!!!
public class BoxingMethodExample {
public static void main(String[] args) {
// <T> Box<T>
Box<Integer> box1 = Util.<Integer>boxing(100);
box1.get();
int intValue = box1.get(); //언박싱 인티저를 int에 넣었다.
// 이번에는 Util박스에 스트링을 넣어보자
Box<String> box2 =Util.<String>boxing("오근주");
String strValue = box2.get();
}
}
각각의 키와 벨류를 주고 같은 객체인지 아닌지를 compare하는 클래스 3개의 예시를 보여드리겠습니다.
public class Pair<K, V> {
private K key;
private 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; }
}
우선 pair를 설명하는 설명서를 만들고
public class Util {
// 제너릭 메소드
// 메소드 이름: compare
// p1, p2 가 매개변수이름이고 얘내의 타입은 멀티제너릭 타입인 Pair<K, V>
// Pair<K, V>의 리턴타입은 boolean이다. 그리서 이 boolean 앞에 KV를 적은거임.
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;
}
}
Util 클라스에서 제너릭 메소드를 만들며 비교가 어떻게 되는지
return keyCompare && valueCompare; // True &&True == True 가 되도록 설정했습니다.
public class CompareMethodExample {
public static void main(String[] args) {
// 먼저 Pair의 타입은 <integerm String > 타입에 p1이다.
Pair<Integer, String> p1 = new Pair<>(1, "사과"); // 키는 1, 벨류에는 사과가 들어감.
Pair<Integer, String> p2 = new Pair<Integer, String>(1, "사과");
//이렇게 두개의 객체를 생성하고
//제너릭 메소드를 이용하여 두개의 객체를 비교함?
//아까 Util 클래스에서 정의한 compare 메소드를 호출할껀데 매개변수의 타입이 하나는 인티저 하나는 스트링이다.
// Util.<Integer, String> compare(p1,p2);//여기의 리턴이 불리언 타입이다.
boolean result = Util.<Integer, String> compare(p1,p2);//여기의 리턴이 불리언 타입이다.
if(result) { //만약 리절트가 트루면
System.out.println("논리적으로 동등한 객체입니다.");
}else {
System.out.println("논리적으로 동등하지 않은 객체입니다.");
}
}
}
이제 실행 main 메서드에서
Pair의 두개의 객체를 생성하고 이 값은 논리적으로 같게 설정을해서
같은지 아닌지를 보았습니다.
이번에는 키와 벨류에 String을 넣어보겠습니다.
public class CompareMethodExample {
public static void main(String[] args) {
Pair<String, String> p3 = new Pair<>("user1","jju개발자");
Pair<String, String> p4 = new Pair<>("user1","jju개발자");
boolean strResult = Util.<String, String> compare(p3,p4);//여기의 리턴이 불리언 타입이다.
if(strResult) {
System.out.println("논리적으로 동등한 객체입니다.");
}else {
System.out.println("논리적으로 동등하지 않은 객체입니다.");
}
}
}
이상 자바에서 사용하는 Generic에 대한 기초 설명을 마치겠습니다.
감사합니다 :)
'주니어 기초 코딩공부' 카테고리의 다른 글
15장 컬렉션 프레임워크 (0) | 2022.12.01 |
---|---|
JA14장 람다식 (0) | 2022.12.01 |
12장 JAVA Thread의 개념 및 자바의 스레드 구현과 실행 (0) | 2022.11.30 |
01 java 기초_사칙연산 메서드 구현 조건문 예제 (2) | 2022.11.07 |
01 java 기초_Switch Case 조건문 예제 (0) | 2022.11.05 |