제네릭이란?
- JDK1.5부터 도입
- Generics add stability to your code by making more of your bugs detectable at compile time.
- 제네릭은 컴파일시에 더 많은 버그를 감지하게 하여 안정성을 더해줍니다.
- Oracle Javadoc
- 제네릭은 클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법을 의미한다.
- 생활코딩
- 제네릭은 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입 체크를 해주는 기능이다.
- 자바의 정석
- 무슨 뜻?
- 런타임때 잡는 타입 체크를 컴파일때 잡도록 해준다.
사용법
- 클래스 선언시 <>에 타입 파라미터 표시
public class Class_Name
- 클래스 생성시<>에 사용할 타입을 지정
Class_Name
-
타입 파라미터?
- T,E,K,V등으로 사용하지만, 특별한 의미로서 메타적인 정보를 포함하는게 아니고 임의의 참조형타입이며 변수로 생각하면 된다.
- 변수의 규칙을 따른다.
- 앞에 숫자가 아닌 글자로 시작해야한다.
- 한글자로된 대문자 뿐만 아니라 1개이상의 문자면 소문자도 전부 가능하다.
- 당연히 한글도 가능
public class Gtest<한글> { private 한글 some; public 한글 getSome() { return some; } public void setSome(한글 some) { this.some = some; }
왜 제네릭을 사용하는지
그냥 Object를 써도 되지 않나?
- Object는 모든 객체의 상위 객체 = 어떤 객체든 받을 수 있다.
- 그렇다면 Object를 받는 클래스나 인터페이스로 모든 객체를 받도록 하면 되지 않을까
- 간단한 5개의 Object를 받는 arrayList이다.
public class SimpleArrayList {
private int size;
private Object[] elementData = new Object[5]; // Object로 5개를 받음
public void add(Object value) { //Object를 받아서 배열에 추가
elementData[size++] = value;
}
public Object get(int idx) {
return elementData[idx];
}
}
- 50과 100을 입력을 받아서 출력 해보자.
SimpleArrayList list = new SimpleArrayList();
list.add(50);
list.add(100);
Integer value1 = (Integer) list.get(0);
Integer value2 = (Integer) list.get(1);
System.out.println(value1 + value2); // 150
- 이번엔 “50”과 “100”을 입력 받아서 출력해보자.
SimpleArrayList list = new SimpleArrayList();
list.add("50");
list.add("100");
Integer value1 = (Integer) list.get(0);
Integer value2 = (Integer) list.get(1);
System.out.println(value1 + value2);
- 어떻게 될까? 컴파일시에 나오지 않고 런타임시에 이러한 오류를 출력한다. 해결을 위해서는 Integer.parseInt((String)list.get(0))을 해주어야 하지만, 오직 String으로 값을 넣었을때만 이러한 방식이 가능하다
- Object로 값을 받을 때 문제
- 해당 값이 String일지, Integer일지 아니면 다른 클래스일지는 추가적으로 확인이 필요하다.
- 이러한 과정에서 컴파일 시에는 아무런 오류를 잡을 수 없어서 실제 실행을 해야 발견되는 에러이다.
인터페이스의 관점에서 보기
- 사용법이라기엔 잘 이해가 안된다면?
public interface InterfaceA{
public void insert(String x);
public Object search(String x);
public void remove(String x);
}
- 위는 하나의 인터페이스가 String을 받는 메소드를 가진 인터페이스
- 이는 하나의 인터페이스로 String타입이 아니면 사용이 안되며, Object로 반환타입이 존재하지만, 타입의 유효성을 확인해야 하며 타입관리가 어렵다.
- String말고 Integer도 필요하다면?
public interface InterfaceB{
public void insert(Integer x);
public Object search(Integer x);
public void remove(Integer x);
}
-
또다른 타입을 받는 새로운 인터페이스를 만들거나, 오버라이드하여 오버로드를 다시 해줘야한다.
- 단순히 타입만 바뀌는데에 기존의 코드가 중복이 된다.
-
하지만 제네릭을 사용한다면?
public interface InterfaceA<E,T>{
public void insert(E x);
public T search(E x);
public void remove(E x);
}
- 해당 인터페이스를 사용할때, 타입을 정해주면 된다.
- 메소드의 반환타입과 내부의 타입들을 지정하는게 가능해진다.
public class GenericArrayList<T> {
private Object[] elementData = new Object[5];
private int size;
public void add(T value) {
elementData[size++] = value;
}
public T get(int idx) {
return (T) elementData[idx];
}
}
- 다시 위의 arrayList를 제네릭으로 사용한다면 위와 같을 것이다.
쓰는이유 정리
- 하나의 클래스, 인터페이스에서 타입만 바꾼 새로운 인스턴스로 사용가능
- 컴파일시에 이러한 타입에러를 발견이 가능
컴파일러 시점이라는건?
- 컴파일러 시점에 타입을 확인한다 하면 컴파일 시점을 확인해 보자
public class Gtest<한글> {
private 한글 some;
public 한글 getSome() {
return some;
}
public void setSome(한글 some) {
this.some = some;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Gtest<String> a = new Gtest<>();
a.setSome("test");
Gtest<Integer> b = new Gtest<>();
b.setSome(1234);
String str = a.getSome();
int num = b.getSome();
System.out.println(a.getSome());
System.out.println(b.getSome());
}
}
- 위는 간단하게 제네릭으로 된Gtest를 String/Integer 두가지로 받아서 출력하는코드이다.
- 이 Gtest.java→Gtest.class→디 컴파일하여 결과를 확인해 보자
public class Gtest<한글> // 실제 클래스는 변함이 없다.
{
private 한글 some;
public 한글 getSome()
{
return this.some;
}
public void setSome(한글 some) {
this.some = some;
}
public static void main(String[] args)
{
Gtest a = new Gtest();//제네릭이 없어졌다.
a.setSome("test");
Gtest b = new Gtest();
b.setSome(Integer.valueOf(1234));
//자동으로 형변환이 이뤄지고 있따.
String str = (String)a.getSome();
int num = ((Integer)b.getSome()).intValue();
System.out.println((String)a.getSome());
System.out.println(b.getSome());
}
}
- 알아서 형변환을 적용해주고 있는것을 확인 할 수 있으며, 이러한 컴파일 과정으로 타입의 오류를 잡아낸다는것을 볼 수 있다.
Bounded Type Parameter (type parameter의 제한)
- 파라미터의 범위를 제한
- Number의 서브 클래스만 타입으로 가지고 싶은경우
- Class_Name
- String은 담을 수 없다.
- Integer, Float, Double, Long은 가능
- Class_Name
와일드카드 <?> - ?가 들어간것
- 객체를 할당 받을때 와일드 카드를 사용
- Gtest<?> wild = b;
- Object처럼 받아진다.
- Gtest<? extends Number> wild = b;
- Number를 포함해 Number의 하위 자손들, 상속받은 타입들만 사용가능
- Gtest<? super Number> wild = b;
- Number를 포함해 Number의 조상 타입만 사용 가능
- 좀더 와일드 카드를 이해해 보자
- 위 사진과 같이 List
는 List 의 상위 타입이 아니다. - printCollection이라는 collection을 받아서 출력하는 코드를 만들었다면.
static void printCollection(Collection<Object> c) {
for (Object e : c) {
System.out.println(e);
}
}
public static void main(String[] args) {
Collection<String> c = new ArrayList<>();
c.add("hi");
printCollection(c); // 작동도 안한다.
//Collection<Object>은 Collection<String>의 상위가 아니니까
}
- 위의 코드를 작동 하고 싶으면? <?>를 사용하면 된다.
static void printCollection(Collection<?> c) {
for (Object e : c) {
System.out.println(e);
}
}
public static void main(String[] args) {
Collection<String> c = new ArrayList<>();
c.add("hi");
printCollection(c);
}
- ?를 넣음으로서 상하관계가 아닌 어떤 타입의 값이 들어갔다는 표시가 된다.
- 여기서 좀더 제한을 둬서 Number를 상속한 것으로 바꾼다면?
static void printCollection(Collection<? extends Number> c) {
for (Number e : c) {
System.out.println(e);
}
}
public static void main(String[] args) {
Collection<String> c = new ArrayList<>();
c.add(123);
printCollection(c)
}
- 이런식으로의 활용도 가능해진다.
제네릭을 사용하지 못하는 경우
-
private T[] test = new T[10];
- 제네릭을 활용한 배열생성은 안된다.
- 배열을 만든다는것은 해당 타입만큼의 공간을 만들어내는것이다. T라는 공간을 만들지 못한다.
- new를 통해 heap의 공간을 확보해야 하지만, 제네릭은 정확한 타입이 지정된게 아니므로 공간 확보가 안된다.
- 만약 쓰고 싶다면? Array.newInstance를 이용하여 해당 타입의 크기를 제공하면서, 가능하다고 한다.
class DataSet<T> { T[] array; public DataSet ( Class<T> clazz , int size ) { array = (T[])Array.newInstance ( clazz , size ); } } //사용시 DataSet<Integer> dataSet = new DataSet<Integer> ( Integer.class , 10 ); [출처] [Java] 제네릭(Generic)으로 배열(Array) 생성하기|작성자 공감 프로그래머
-
위와 마찬가지로 static변수로 사용할 수 없다.
- 제네릭 메소드는 static 메소드가 가능하다.
제네릭 메서드?
- 파라미터와 리턴 타입으로 type parameter를 갖는 메서드
- 사용법
public <T,E> T m1(E p) {
System.out.println(p);
return p;
}
instance.<String>m1("sring");//사용시 <타입>을 넣어서 지정한다.
-
는 제네릭 메서드라는 표식이후, 반환 타입, 입력 타입을 지정이 가능하다. - 리턴 타입에 관계 없이 제네릭 메서드라는 것을 컴파일러에게 알려주는 과정이 필요
- 일반 클래스 내에서도 사용가능
- 클래스의 지정하는 타입파라메터와 제네릭 메서드에 정의 된 타입 파라미터는 상관이 없다는 의미도 됨
public class Gtest2<한글> {
private 한글 some;
public 한글 getSome() {
return some;
}
public void setSome(한글 some) {
this.some = some;
}
//제네릭 메서드 - 여기 한글은 클래스의 한글일까? 아니다.
public <한글> 한글 m1(한글 p) {
System.out.println(p);
return p;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Gtest2<String> a = new Gtest2<>(); //String이라고 클래스 타입 지정
Gtest2<Integer> b = new Gtest2<>();
a.m1("String");
a.m1(1123213323);//a는 String이라 되어있지만, 실제로는 잘 동작한다.
a.<String>m1("sring");
a.<String>m1(123);//해당 메서드는String을 받기때문에 여기서 에러가 생긴다.
}
}
- static을 사용할 수 있는 이유?
- static메서드의 경우 메서드의 틀만 공유된다.
- 이러한 틀안에서 지역변수처럼 타입 파라미터가 다양하게 오가는 형태로 사용이 가능하다 한다.
static public void setSome(한글 some) { // 제네릭 메서드가 아니므로 static이 안된다.
this.some = some;
}
static public <한글> 한글 m1(한글 p) {// static이 가능
System.out.println(p);
return p;
}
제네릭이 활용된 곳
- 가장 큰 활용 Collection
- Queue
, Queue ,… - ArrayList
, ArrayList ,…
- Queue
마무리 - 제네릭이란?
- 하나의 클래스,인터페이스에서 여러 타입을 사용하게 해준다.
- 이를 컴파일 시에도 알 수 있도록 체크를 해주는 기능
- 재사용성을 높여준다.
- 타입의 안정성을 높여준다.
출처
- 쉽게 배우는 자료구조 with 자바
- https://yaboong.github.io/java/2019/01/19/java-generics-1/
- https://books.google.co.kr/books?id=B9hYEAAAQBAJ&pg=PA83&lpg=PA83&dq=generic의+실체+java&source=bl&ots=1qeGBuwUdV&sig=ACfU3U2daAscQSx5s_LcB49n7pcTK_syOw&hl=ko&sa=X&ved=2ahUKEwiB3I23xrT5AhXuqFYBHZd0DeIQ6AF6BAgiEAM#v=onepage&q=generic의 실체 java&f=false
- https://velog.io/@cekim/이펙티브-자바-제네릭
- https://rightx2.tistory.com/13
- http://www.tcpschool.com/java/java_generic_concept
- https://miyakita.tistory.com/211
- https://st-lab.tistory.com/153
- https://blog.naver.com/PostView.naver?blogId=yo2dh&logNo=10179428528&categoryNo=15&parentCategoryNo=0&viewDate=¤tPage=1&postListTopCurrentPage=1&from=search
- https://thecodinglog.github.io/java/2020/12/15/java-generic-wildcard.html