컴파일 타임 타입 안전을 제공하면서 타입이나 메서드가 다양한 타입의 객체에서 작동할 수 있도록 Java 타입 시스템을 확장하도록 설계
제네릭의 개요
-JDK1.5부터 추가된 기능이면, 스레드. 컬렉션. 람다식, 스트림 등에서 사용된다.
-아울러, java.docs에 보면 제네릭 타입이 매우 많아서, 반드시 제네릭의 개념을 알고 접근해야 한다.
제네릭 타입이란 타입을 파라메터화하여 , 실행 시에 구체적으로 해당하는 타입으로 결정이 되는 것을 의미한다.
제네릭을 사용하면 컴파일시에 강한 타입 체크뿐만 아니라, 타입 변환(Casting)을 사전에 제거할 수가 있다.
제네릭의 개념 및 선언
타입을 파라메터로 갖는 클래스 및 인터페이스를 칭한다.
-선언을 할 떄, 클래스 또는 인터페이스명 뒤에 <>(꺽쇠)가 붙는다.
-아울러, 꺽쇠 사이엥는 타입 파라미터가 위치하게 된다.
타입 파라메타란 것은 제네릭 클래스나 인터페이스를 설계 시에 보통 알파벳 한 문자로 표식을 한다,
그후, 개발 코드에서는 직접 타입 파라미터에 구체적 클래스를 지정해야 한다.
제네릭 사용의 예
제네릭 타입을 사용한 경우
-클래스 선언할떄, 타입 파라미터를 기술하고, 컴파일 시 타입 파라미터가 구체적인 클래스로
변경이 된다.
=> 객체생성할때 타입을 지정해서 get set하고 형변환도 일어나지 않게 한다.
nongenerics 제네릭 사용하지 않을때
package kr.co.kihd.nongeneric;
public class Person {
private Object object;
public Person() {
}
//리턴타입 Object
public Object getObject() {
return object;
}
//매개변수타입이 Object ==> 모든 클래스를 다 매개변수, 리턴타입으로 하겠다.(다형성)
public void setObject(Object object) {
this.object = object;
}
}
package kr.co.kihd.nongeneric;
//제네릭 사용전 타입체크할 경우 비효율
public class PersonTest {
public static void main(String[] args) {
Person person = new Person();
person.setObject("최지만"); //업케스팅(Stirng -> Object)
String str = (String) person.getObject(); //다운케스팅(Object -> String)
System.out.println(str);
}
}
person이라는 클래스를 제네릭 타입으로 설계
package kr.co.kihd.generic;
//person이라는 클래스를 제네릭 타입으로 설계함.
public class Person<T> {
//T라는 타입이 아직 정해지지 않았다
//T는 개발코드에서 정해진다.
//static은 올수가 없다.(타입이 어떤것이 올지 모르는데 미리 클래스영역에 올려둘순 없다.)
private T t;
//생성자
public Person() {
}
//getter setter
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
package kr.co.kihd.generic;
public class Apple {
@Override
public String toString() {
return "사과";
}
}
package kr.co.kihd.generic;
public class PersonTest {
public static void main(String[] args) {
//Person제네릭 클래스에 T를 String만 들어오게끔 무조건 만들겠다.
Person<String> person = new Person<>();
person.setT("최지만");
String str = person.getT();
System.out.println(str);
//데이터 통일화, 타입변환제거, 컴파일시 예외를 발생
Person<Integer> person2 = new Person<>();
person2.setT(100); //자동 언박싱
int value = person2.getT();
System.out.println(value);
//사용자 정의형 클래스의 제네릭 -> Apple클래스 타입만 가능
Person<Apple> person3 = new Person<>();
person3.setT(new Apple());
Apple apple = person3.getT();
System.out.println(apple);
}
}
Tv example
package kr.co.kihd.generic;
public class Tv<E> {
private E e;
//생성자
public Tv() {
}
//getter setter
public E getE() {
return e;
}
public void setE(E e) {
this.e = e;
}
}
package kr.co.kihd.generic;
public class TvTest {
public static void main(String[] args) {
Tv<String> tv = new Tv<>();
tv.setE("SAMSUNG AMOLED TV");
String tvname = tv.getE();
System.out.println("나의 TV는 " +tvname+ "입니다.");
}
}
멀티 타입 파라메터
-제네릭은 2개 이상의 타입 파라메터를 사용해서 선언할 수가 있으며,
각 타입 파라메터는 콤마(,)로 구분한다.
nomal class
package kr.co.kihd.multitype;
//일반클래스
public class Car {
//멤버변수
private String brand;
private int year;
private int month;
//생성자
public Car() {
}
//생성자 오버로딩
public Car(String brand, int year, int month) {
super();
this.brand = brand;
this.year = year;
this.month = month;
}
//getter, setter
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
}
package kr.co.kihd.multitype;
//일반클래스
public class Tv {
private int year;
private int month;
//생성자
public Tv() {
}
//생성자 오버로딩
public Tv(int year, int month) {
super();
this.year = year;
this.month = month;
}
//getter, setter
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
}
generics class
package kr.co.kihd.multitype;
//제네릭 클래스인데 타입 파라미터가 2개이다. (설계클래스)
public class Product<T,M> {
private T t;
private M m;
//getter, setter
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
public M getM() {
return m;
}
public void setM(M m) {
this.m = m;
}
}
package kr.co.kihd.multitype;
//일반클래스
public class Car {
//멤버변수
private String brand;
private int year;
private int month;
//생성자
public Car() {
}
//생성자 오버로딩
public Car(String brand, int year, int month) {
super();
this.brand = brand;
this.year = year;
this.month = month;
}
//getter, setter
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
}
제네릭 메서드
매개변수 타입과 리턴 타입으로 타입 파라매터를 갖는 메서드를 칭한다.
제네릭 메서드는 리턴 타입 앞에 꺽쇠 기호를 추가하고, 타입 파라메터를 기술
하여, 타입 파라메터를 리턴 타입과 매개변수에 사용한다.
제네릭 메서드를 호출하는 두가지 방법이 있다.
package kr.co.kihd.genericmethod;
public class Person<T> {
private T t;
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
package kr.co.kihd.genericmethod;
public class Util {
//<T>는 제네릭 타입 메서드를 칭하는 것이며,
//리턴타입과 매개변수 타입을 동일하게 <T>타입으로 가져가야한다.
//외부로부터 T타입을 받아서 Person<T>라는 제네럴 클래스의 인스턴스를
//생성하여 리턴을 해주는 역할을 하는 메서드.
public static <T> Person<T> changing(T t){
Person<T> person = new Person<>();
person.setT(t);
return person;
}
}
package kr.co.kihd.genericmethod;
public class PersonTest {
public static void main(String[] args) {
//Person<Integer> p1 = Util.<Integer>changing(100);
Person<Integer> p1 = Util.changing(100);//auto boxing
int value = p1.getT();
System.out.println("Person객체가 가지고 있는 값 " + value);
//일반적인 코드
//Person<String> p2 = Util.<String>changing("홍길동");//auto boxing
Person<String> p2 = Util.changing("홍길동");//auto boxing
String value2 = p2.getT();
System.out.println("Person객체가 가지고 있는 값 " + value2);
}
}
example 2
package kr.co.kihd.genericmethod2;
public class Member<T> {
private T t;
//getter, setter
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
package kr.co.kihd.genericmethod2;
public class MemberTest {
//제네릭 클래스인 Member를 리턴함.
//MemberTest클래스의 인스턴스 메서드.
public <T> Member<T> boxing(T t){
Member<T> member = new Member<>();
member.setT(t);
return member;
}
//MemberTest클래스의 정적 메서드
//wrapping()
public static <T> Member<T> wrapping(T t){
Member<T> member = new Member<>();
member.setT(t);
return member;
}
public static void main(String[] args) {
MemberTest mTest = new MemberTest();
//Member<Integer> member = mTest.<Integer>boxing(100);
Member<Integer> member = mTest.boxing(100);
System.out.println("Member클래스의 값 : " + member.getT());
Member<String> str = mTest.boxing("홍길동");
System.out.println("Member클래스의 값 : " + str.getT());
}
}
타입 파라메터의 제한
메서드의 타입 파라메터를 구체적으로 타입을 제한하고자 할 때 사용한다,.
-상속이나 구현 관계를 이용하여 타입 파라메터를 제한할 수 있다.
-상위 타입은 클래스 뿐 아니라 인터페이스도 가능하지만, 인터페이스라고 하여
implements를 사용하지 않고 동일하게 extends를 사용한다.
-위와 같이 extends로 메서드를 선언했다면, 메서드의 매개값으로 상위타입은 제한된다.
package kr.co.kihd.typeparameter;
public class Compare {
//매개변수 T타입을 Number(추상클래스)타입이거나 Number를 상속받은
//자손 클래스만 T타입으로 들어올수 있다.
public static <T extends Number> int compare(T t1, T t2) {
//Number클래스의 doubleValue()는 원래 추상메서드
//자손클래스(Integer, Double등)가 doubleValue()를 오버라이딩
//했기떄문에 문제되지 않음.
double value1 = t1.doubleValue();
double value2 = t2.doubleValue();
return Double.compare(value1, value2);
}
}
package kr.co.kihd.typeparameter;
public class CompareTest {
public static void main(String[] args) {
//int result = Compare.compare(100, 200); //앞의값이 크면 1 아니면 -1 같으면 0
//int result = Compare.compare(200, 200);
int result = Compare.compare(300, 200);
System.out.println(result);
//String 클래스는 Number클래스하고는 아무런 관계가 없다.
//Compare.compare("최지만", "김지만");
//Number클래스는 Object클래스의 자손이기 때문에 매개변수 타입으로 적합하지 않음.
//Compare.compare(new Object(), new Object());
//result = Compare.compare(new Integer(100), new Double(75.11));
result = Compare.compare(100, 75.11); //auto boxing
System.out.println(result); //앞에것이 크면 1
//Number는 추상클래스 이므로 인스턴스를 생성하지 못한다.
//result = Compare.compare(new Number(), new Number());
}
}