Java Generic 제네릭

반응형

Generic

제네릭 타입은 컴파일 단계에서 잘못된 타입 사용될 수 있는 문제를 제거 할수있다.
컬렉션, 람다식(함수적 인터페이스), 스트림, Nio에서 널리사용
#제너릭을 모르면 api도큐먼트 해석이 어려우니 학습해야한다

제네릭의 이점은

컴파일 시 강한 타입 체크를 할수있다(완벽하게 타입을 체크)

  • 실행 시 타입 에러가 나는 것 방지
  • 컴파일 시에 미리 타입을 강하게 체크해서 에러 사전 방지
    타입변환 제거 가능

제네릭 타입이란

타입을 파라미터로 가지는 클래스와 인터페이스
선언 시 클래스 또는 인터페이스 이름 뒤에 <> 부호를 붙인다
<> 사이에는 타입 파라미터 위치한다
타입 파라미터는 일반적으로 대문자 알파벳 한문자로 표현 …
개발 코드에서는 타입 파라미터 자리에 구체적인 타입을 지정해야함

제네릭 타입 사용여부에 따른 비교

제네릭 타입을 사용하지 않은 경우
Object 타입 사용 > 빈번한 타입 변환 발생 > 프로그램 성능저하.
제네릭 타입 사용한 경우
클래스 선언할 때 타입 파라미터 사용
컴파일 시 타입 파라미터가 구체적인 클래스 변경가능

제네릭 Ex)

public class ProductExample {  
public static void main(String[] args) {  
Product<Tv, String> product1 = new Product<Tv, String>();  
product1.setKind(new Tv());  
product1.setModel("스마트Tv");  
Tv tv = product1.getKind();  
String tvModel = product1.getModel();  
  
// public class Product<Tv, String> {  
// private Tv kind;  
// private String model;  
//  
// public Tv getKind() { return this.kind; }  
// public String getModel() { return this.model; }  
//  
// public void setKind(Tv kind) { this.kind = kind; }  
// public void setModel(String model) { this.model = model; }  
// }  
  
Product<Car, String> product2 = new Product<Car, String>();  
product2.setKind(new Car());  
product2.setModel("디젤");  
Car car = product2.getKind();  
String carModel = product2.getModel();  
  
// public class Product<Car, String> {  
// private Car kind;  
// private String model;  
//  
// public Car getKind() { return this.kind; }  
// public String getModel() { return this.model; }  
//  
// public void setKind(Car kind) { this.kind = kind; }  
// public void setModel(String model) { this.model = model; }  
// }  
}  
}
public class ABox<T> {  
private T t;  
  
public T get(){ // get메소드 리턴  
return t;  
}  
public void set(T t) {  
this.t = t;  
}  
}
public class ABoxExample { // Generic 타입 기본.  
  
public static void main(String[] args) {  
ABox<String> box1 = new ABox<String>();  
box1.set("Hi");  
String str = box1.get();  
  
ABox<Integer> box2 = new ABox<Integer>();  
box2.set(5);  
Integer value = box2.get();  
}  
  
}

멀티 타입 파라미터

제네릭 타입은 두 개 이상의 타입 파라미터 사용 가능
각 타입 파라미터 콤마로 구분
Ex) class<K, V, …> { } , interface <K, V, … > { }
자바 7부터는 다이아몬드 연산자 사용해 간단히 작성과 사용 가능

public class BTv {  
  
}
public class BCar {  
  
}
public class BProduct<T, M> {  
private T kind;  
private M model;  
  
public T getKind() {  
return this.kind;  
}  
public M getModel() {  
return this.model;  
}  
  
public void setKind(T kind) {  
this.kind = kind;  
}  
public void setModel(M model) {  
this.model = model;  
}  
}
public class BProductExample {  
  
public static void main(String[] args) {  
BProduct<BTv, String> product1 = new BProduct<BTv, String>();  
product1.setKind(new BTv());  
product1.setModel("스마트Tv");  
BTv tv = product1.getKind();  
String tvModel = product1.getModel();  
  
BProduct<BCar, String> product2 = new BProduct<BCar, String>();  
product2.setKind(new BCar());  
product2.setModel("디젤");  
BCar car = product2.getKind();  
String carModel = product2.getModel();  
}  
  
}

제네릭 메소드

매개변수 타입과 리턴 타입으로 파라미터를 갖는 메소드
제네릭 메소드 선언 방법

  • 리턴 타입 앞에 <> 기호를 추가하고 타입 파라미터 기술
  • 타입 파라미터를 리턴 타입과 매개변수에 사용…
public class CBox<T> {  
private T t;  
  
public T get() {  
return t;  
}  
public void set(T t) {  
this.t = t;  
}  
  
}
public class CUtil {  
public static <T> CBox<T> boxing(T t){  
CBox<T> box = new CBox<T>();  
box.set(t);  
return box;  
}  
}
public class CBoxingMethodExample {  
  
public static void main(String[] args) {  
CBox<Integer> box1 = CUtil.<Integer>boxing(100);  
int intValue = box1.get();  
  
CBox<String> box2 = CUtil.boxing("길동이");  
String setValue = box2.get();  
  
}  
  
}

타입 파라미터로 K와 V를 선언해서 메소드 두개의 값이 동일한지 비교하기.

public class DPair<K, V> {  
private K key;  
private V value;  
  
public DPair(K key, V value){  
this.key = key;  
this.value = value;  
}  
  
public void setKey(K key) {  
this.key = key;  
}  
public void value(V value) {  
this.value = value;  
}  
  
public K getKey() {  
return key;  
}  
public V getValue() {  
return value;  
}  
}
public class DUtil {  
public static <K, V> boolean compare (DPair<K, V> p1, DPair<K, V> p2){  
boolean keyCompare = p1.getKey().equals(p2.getKey());  
boolean valueCompare = p1.getValue().equals(p2.getValue());  
return keyCompare && valueCompare;  
}  
  
}
public class DCompareMethodExample {  
  
public static void main(String[] args) {  
DPair<Integer, String> p1 = new DPair<Integer, String>(1, "사과");  
DPair<Integer, String> p2 = new DPair<Integer, String>(1, "사과");  
boolean result1 = DUtil.compare(p1, p2); // <Integer, String>  
if(result1) {  
System.out.println("논리적으로 동등한 객체");  
}else {  
System.out.println("동등하지 않은 객체");  
}  
  
DPair<String, String> p3 = new DPair<String, String>("User1", "사과");  
DPair<String, String> p4 = new DPair<String, String>("User2", "사과");  
boolean result2 = DUtil.compare(p3, p4); // <Integer, String>  
if(result2) {  
System.out.println("논리적으로 동등한 객체");  
}else {  
System.out.println("동등하지 않은 객체");  
}  
}  
  
}

제한된 타입 파라미터 <T extends 최상위 타입>

타입 파라미터에 지정되는 구체적인 타입 제한할 필요

  • 상속 및 구현 관계 이용해 타입 제한
  • 상위 타입은 클래스 뿐만 아니라 인터페이스도 가능
    타입 파라미터를 대체할 구체적인 타입
  • 상위 타입이거나 하위 또는 구현 클래스만 지정가능
public class EUtil {  
public static <T extends Number> int compare(T t1, T t2) {  
double v1 = t1.doubleValue();  
double v2 = t2.doubleValue();  
  
return Double.compare(v1, v2);  
}  
}
public class EBoundedTypeParameterExample {  
  
public static void main(String[] args) {  
int result1 = EUtil.compare(20,50);  
System.out.println(result1);  
  
int result2 = EUtil.compare(50, 3.5);  
System.out.println(result2);  
  
  
  
}  
  
}

와일드카드 타입 (중ㅇ)

세가지 형태

  • 제네릭타입 <?> Unbounded Wildcards(제한없음)
    타입 파라미터를 대치하는 구체적인 타입으로 모든 클래스나 인터페이스 타입이 올 수 있다.
    -제네릭타입<? extends 상위타입> Upper Bounded Wildcards (상위 클래스 제한)
    타입 파라미터를 대치하는 구체적인 타입으로 상위 타입이나 하위 타입만 올수 있다.
    -제네릭타입<? super 하위타입> Lower Bounded Wildcards (하위 클래스 제한)
    타입 파라미터를 대치하는 구체적인 타입으로 하위 타입이나 상위 타입이 올수 있다.
public class FCourese<T> {  
private String name;  
private T[] students;  
  
public FCourese(String name, int capacity) {  
this.name = name;  
students = (T[]) (new Object[capacity]); // 타입 파라미터로 배열을 생성하려면 new T[n]형태로 배열을 생성할수 없구 , (T[]) (new Object[n])으로 생성해야 한다.  
}  
  
public String getName() {  
return name;  
}  
public T[] getStudents() {  
return students;  
}  
  
public void add(T t) { // 배열에 비어있는 부분을 찾아서 수강생을 추가하는 메소드.  
for(int i=0; i<students.length; i++) { // i를 students.length 길이 만큼 반복하고  
if(students[i] == null) { // students[i]가 null과 같다면  
students[i] = t; // students[i]를 t에 저장  
break;  
}  
}  
}  
}
public class FHighStudent extends FStudent {  
public FHighStudent(String name) {  
super(name);  
}  
}

public class FPerson {  
private String name;  
  
public FPerson(String name) {  
this.name = name;  
}  
  
public String getName() {  
return name;  
}  
public String toString() {  
return name;  
}  
}

public class FStudent extends FPerson {  
public FStudent(String name) {  
super(name);  
}  
}

public class FWorker extends FPerson {  
public FWorker(String name) {  
super(name);  
}  
}
import java.util.Arrays;  
  
public class FWildCardExample {  
public static void registerCourse(FCourese<?> coures) {  
System.out.println(coures.getName() + " 수강생: " + Arrays.toString(coures.getStudents()));  
}  
public static void registerCourseStudent(FCourese<? extends FStudent> coures) {  
System.out.println(coures.getName() + " 수강생: " + Arrays.toString(coures.getStudents()));  
}  
public static void registerCourseWorker(FCourese<? super FWorker> coures) {  
System.out.println(coures.getName() + " 수강생: " + Arrays.toString(coures.getStudents()));  
}  
public static void main(String[] args) {  
FCourese<FPerson> personCoures = new FCourese<FPerson>("일반인과정", 5);  
personCoures.add(new FPerson("일반 "));  
personCoures.add(new FWorker("직장 "));  
personCoures.add(new FStudent("학생 "));  
personCoures.add(new FHighStudent("고등학생"));  
  
FCourese<FWorker> FworkerCourse = new FCourese<FWorker>("직장과정",5);  
FworkerCourse.add(new FWorker("직장"));  
  
FCourese<FStudent> FstudentCourse = new FCourese<FStudent>("학생과정", 5);  
FstudentCourse.add(new FStudent("학생"));  
FstudentCourse.add(new FHighStudent("고등학생"));  
  
FCourese<FHighStudent> FhighStudentCourse = new FCourese<FHighStudent>("고등학생과정", 5);  
FhighStudentCourse.add(new FHighStudent("고등학생"));  
  
registerCourse(personCoures);  
registerCourse(FworkerCourse);  
registerCourse(FstudentCourse);  
registerCourse(FhighStudentCourse);  
System.out.println();  
  
registerCourseStudent(FstudentCourse);  
registerCourseStudent(FhighStudentCourse);  
System.out.println();  
  
registerCourseWorker(personCoures);  
registerCourseWorker(FworkerCourse);  
  
}  
  
}

제네릭 타입을 부모 클래스로 사용할 경우

  • 타입 파라미터는 자식 클래스에도 기술해야함
    -추가적인 타입 파라미터를 가질수 있음.
    제네릭 인터페이스를 구현할 경우
  • 제네릭 인터페이스를 구현한 클래스도 제네릭 타입

제네릭 타입도 다른 타입과 마찬가지로 부모 클래스가 될수있다.

자식 제네릭 타입은 추가적으로 타입 파라미터를 가질수있다.

public interface GStorage<T>{  
public void add(T item, int index);  
public T get(int index);  
}
public class GStoragelmpl<T> implements GStorage<T> {  
private T[] array;  
  
public GStoragelmpl(int capacity) {  
this.array = (T[])(new Object[capacity]);  
}  
  
@Override  
public void add(T item, int index) {  
array[index] = item;  
}  
  
@Override  
public T get(int index) {  
return array[index];  
}  
}

public class GProduct<T,M> {  
private T kind;  
private M model;  
  
public T getKind() {  
return this.kind;  
}  
public M getModel(){  
return this.model;  
}  
  
public void setKind(T kind) {  
this.kind = kind;  
}  
public void setModel(M model) {  
this.model = model;  
}  
}  
class Tv{  
  
}
public class GChildProduct<T,M,C> extends GProduct<T,M> {  
private C company;  
public C getCompany() {  
return this.company;  
}  
public void setCompany(C company) {  
this.company = company;  
}  
}
public class GChildProductAndStorageExample {  
public static void main(String[] args) {  
GChildProduct<Tv, String, String> product = new GChildProduct<>();  
product.setKind(new Tv());  
product.setModel("SmartTV");  
product.setCompany("Samsung");  
  
GStorage<Tv> storage = new GStoragelmpl<Tv>(100);  
storage.add(new Tv(), 0);  
Tv tv = storage.get(0);  
}  
}
반응형