티스토리 뷰
제네릭스
- 메서드나 컬렉션클래스에 대해 컴파일시 타입체크를 해주는 기능.
장점
- 컴파일 시 의도하지 않는 타입객체가 저장되는 것을 막으므로 타입안정성 제공
- 타입의 체크와 형변환을 생략할 수 있기 때문에 코드의 간략화
> 제네릭클래스 기본형태
class Student<T>{ private T name; List<T> list = new ArrayList<>();
public void setName(T name){ this.name=name;
list.add(name); } public T getName(){ return this.name; } }
제네릭 클래스를 만드는 방법은 클래스 명옆에 <T>를 붙이고 이 T와 같은 타입을 가지는 곳에 똑같이 T를 써주면 된다.
T를 '타입변수'라고 하며, Type의 첫글자를 의미한다. 굳이 T가 아니라 아무 대문자를 써도 상관이 없지만 기존 코드들에 전부 이와 같은 타입변수를 사용하므로 통일하는 것이 좋다.
Map과 같이 타입변수가 여러개인 경우에는 Map<K, V>와 같이 사용하면 된다.
K는 Key의 첫글자, V는 Value의 첫글자이다.
이들 T,K,V는 타입문자만 다를 뿐 임의의 참조 타입을 가리킨다는 점은 같다.
> Student라는 제네릭 클래스의 '타입변수'에 String타입을 지정해보겠다.
//인스턴스 생성시 타입변수를 String으로 지정 할 경우 형태 Student<String> s = new Student<String>(); //타입변수를 String으로 지정할 경우 T라는 타입변수가 모두 String으로 치환 class Student<String>{ private String name; List<String> list = new ArrayList<>(); public void setName(String name){ this.name=name; list.add(name); } public String getName(){ return this.name; } public void printList(){ for(String name : list){ System.out.print(name+","); } } }
제네릭 클래스는 객체의 인스턴스 생성시 '타입변수'를 지정하게 되는데 지정된 '타입변수'에 따라 해당 클래스에 존재하는 '모든 타입변수'가 '지정한 타입변수'로 치환된다.
List<String> list = new ArrayList<>();
이 부분을 보면 인스턴스 생성시 new ArrayList<>();와 같은 형태를 취하고 있는데..
본래에는 <>같이 할 수 없고 <String>처럼 지정해야 했으나 jdk1.7 부터 이런 방식을 허용하고 있다.
'<>'같은 문자를 '다이아몬드 지시자'라고 한다.
또 본래 List자료구조는 타입변수를 지정하지 않으면 모든 요소의 값들을 저장할 수 있지만, 꺼내 쓸 때는 다음과 같이 형변환을 해줘야한다.
Student s = new Student(); //제네릭 타입변수를 지정하지 않았다. s.setName("홍길동"); String name = (String)s.list.get(0);
//타입변수가 없기 때문에 List타입 멤버변수 list는 요소를 Object로 관리 때문에 get()메서드로 요소를 가져올 때 형변환이 필요함.
다음과 같이 타입변수를 지정하면 형변환을 생략할 수 있다.
Student<String> s = new Student<>(); //타입변수를 String지정 s.setName("홍길동"); String name = s.list.get(0); //인스턴스 생성 시 타입변수를 지정하면 list변수 형변환 생략가능
제네릭스 '타입변수'에 들어갈 수 있는 타입을 제한하는 방법
타입변수 T는 인스턴스 생성시 타입변수에 들어갈 수 있는 타입에 제한이 없다.
class Movie{} class RomanceMovie extends Movie{} class ActionMovie extends Movie{} class Drama{} class MyMovie<T>{ List<T> l = new ArrayList<>(); } public class GenericsEx2 { public static void main(String[] args){ MyMovie<RomanceMovie> romance = new MyMovie<>(); MyMovie<Drama> drama = new MyMovie<>(); //드라마는 영화가 아니지만 제약이 없기 때문에 허용함. } }
위 코드를 보면 MyMovie제네릭 클래스에 들어갈 수 있는 타입변수의 타입에는 제한이 없다.
그래서 영화가 아닌 드라마로도 MyMovie의 인스턴스를 생성할 수 있다. 하지만 이렇게 되면 클래스 설계 의미상 맞지 않는다.
그래서 아래와 같이 <T extends Movie>와 같이 지정해두면, 'Movie클래스를 상속받고 있는 타입'으로만 타입 변수로 제한할 수 있다.
class MyMovie<T extends Movie>{ List<T> l = new ArrayList<>(); }
그렇기 때문에 이와 같이 하면 Drama클래스는 타입변수로 사용할 수 없게 한다.
또한 extends 부모를 interface로도 할 수 있으며..
interface MovieProvider{} class ComicMovie implements MovieProvider{}; class MyMovie<T extends MovieProvider>{ List<T> l = new ArrayList<>(); }
클래스와 인터페이스를 부모로 동시에 지정해서 둘 다 상속받고 있는 객체에 대해서만 타입변수로 사용하도록 제한 가능하다.
class ThrillerMovie extends Movie implements MovieProvider{} class MyMovie<T extends Movie & MovieProvider>{ List<T> l = new ArrayList<>(); }
static 멤버에는 타입변수를 사용할 수 없다.
타입변수를 실제 결정하는 시기는 인스턴스를 생성할 때이지만, static멤버는 클래스가 메모리에 올라갈 때 static영역이라는 곳에서 활동하는 녀석이기 때문에 컴파일러는 이 static에 정의되어 있는 타입변수가 실제 어떤 타입을 나타내는 것인지 모르기 때문이다.
interface MovieProvider{} class Movie implements MovieProvider{ public String toString(){ return "movie"; } } class RomanceMovie extends Movie{ public String toString(){ return "romance"; } } class ActionMovie extends Movie{ public String toString(){ return "action"; } } class Video<T extends Movie>{ List<T> videoList = new ArrayList<>(); public void add(T t){ videoList.add(t); } } class HomeTheater{ static void playMovie(Video<T> v){ System.out.println(v.videoList.get(0).toString()+" play!!"); } }
static 멤버에는 타입변수를 사용할 수 없다고 했다. 위에서 보는 것과 마찬가지로 playMovie(Video<T> v);는 사용할 수 없다. 그 대신 playMovie(Video<Movie> v); 처럼 특정 타입변수를 지정해준다면 사용할 수 있지만 하나의 타입만 담을 수 있다는 제약이 따른다.
이럴 때 사용하는 것이 와일드카드(?) 라는 것이다. 와일드 카드는 Video<?>와 같이 표현하며
playMovie()메서드에 지정하면 다음과 같다.
static void playMovie(Video<?> v); static void playMovie(Video<? extends Movie> v); static void playMovie(Video<? super Movie> v);
<?> : <? extends Object>와 동일. Object의 자손이 올 수 있으므로, 모든 참조형 타입이 가능하다.
<? extends T> : T와 T의 자손만 가능
<? super T> : T와 T의 부모만 가능
제네릭 메서드
메서드에도 제네릭 타입을 지정할 수 있다.
제네릭 클래스에 정의하는 타입변수와 제네릭 메서드에 정의하는 타입변수는 별개이다.
class Test<T>{ T integer; public <T> void print(T name){ System.out.println(name); } } Test<Integer> n = new Test<>(); n.print(new ArrayList()); n.print("준이");
메서드를 제네릭 메서드로 만드는 방법은 메서드 선언부의 리턴타입 앞에 '타입변수'를 붙이는 것이다.
메서드가 제네릭 메서드이면, 제네릭 클래스의 타입변수와는 별개로 수행된다.
코드에서
Test<T>, T integer 와
public <T> void print(T name);은 다르다는 뜻이다.
Test<Integer> n =new Test<>();와 같이
제네릭 클래스 인스턴스 생성으로 인해서 멤버변수인 T integer => Integer integer로 변경될 뿐.
메서드가 제네릭 메서드라 이것의 타입변수들에는 영향을 주지 않는다.(일반 메서드 였다면 영향을 받음)
제네릭 메서드를 호출하는 방법은
n.<Date>print(new Date()); 와 같이 호출하면 되는데.. 매개변수 인자로 들어가는 타입에 의해서 제네릭 메서드의 타입변수가 결정되기 때문에 메서드 앞에 <Date>는 암묵적으로 생략해도 된다.
그런데 생략하지 않고 n.<Date>print("string");과 같이 하면 타입변수는 Date타입이기 때문에 인자에 이 외의 타입이 들어가게 되면 컴파일 에러가 발생한다.
Collections.sort메서드를 살펴보면 제네릭 메서드란걸 알 수 있다.
>Collections.sort메서드
public static <T> void sort(List<T> list, Comparator<? super T> c) { list.sort(c); }
static메서드라서 타입변수를 사용할 수 없지만, 제네릭 메서드로 만들어 버리면 타입변수를 사용할 수 있다.
그리고 다시 얘기하자면 <? super T>라는 것은 타입변수 T자신과 T의 조상들을 담을 수 있다는 뜻이다.
위에서 설명한 것 처럼 제네릭 메서드를 호출할 때는
Collections.<String>sort(List<String> list, Comparator<? super String> c);
또는
Collections.sort(List<Movie> list, Comparator<? super Movie> c );
와 같은 형식으로 메서드를 호출이 가능하다.
그리고 이 sort라는 제네릭 메서드는 그냥 사용할 수 없고, 구현체 클래스를 만들어서 sort메서드의 두 번째 매개변수 인자에 제공되어야 한다.
다음은 Movie클래스의 멤버변수인 moviList를 Comp라는 Comparator구현체 클래스를 만들어 sort메서드를 호출해서 'moviName'을 대상으로 오름차순 정렬하는 코드이다.
class NineteenLimitMovie<T>{ public static <T extends Movie> void extractMovie(List<T> l){ for(int i = l.size()-1; i>=0; i--){ Movie m = l.get(i); if(m.limit >= 19){ l.remove(i); } } } } //장르영화의 부모 클래스 class Movie { String movieName; int limit; Movie(String movieName, int limit){ this.movieName = movieName; this.limit = limit; } } //액션영화 class ActionMovie extends Movie{ String movieName; ActionMovie(String name, int limit){ super(name, limit); } } //코믹영화 class ComicMovie extends Movie{ ComicMovie(String name, int limit){ super(name, limit); } } //영화목록을 담는 클래스 class MovieList<T extends Movie> { List<T> movieList = new ArrayList<>(); } //정렬을 담당하는 클래스 class Comp implements Comparator<Movie>{ @Override public int compare(Movie o1, Movie o2) { Movie m1 = (Movie)o1; Movie m2 = (Movie)o2; return m1.movieName.compareTo(m2.movieName); } } public class GenericsEx3 { public static void main(String[] args){ MovieList<Movie> ml = new MovieList<>(); ml.movieList.add(new ActionMovie("Iron man",15)); ml.movieList.add(new ActionMovie("Old boy",19)); ml.movieList.add(new ActionMovie("Avengers",15)); ml.movieList.add(new ComicMovie("Bruce Almighty",12)); ml.movieList.add(new ActionMovie("Superman",12)); ml.movieList.add(new ActionMovie("Taken",19)); ml.movieList.add(new ComicMovie("Dumb and Dumber",12)); Collections.sort(ml.movieList, new Comp()); //오름차순 정렬 for(Movie am : ml.movieList){ System.out.println(am.movieName); } NineteenLimitMovie.<Movie>extractMovie(ml.movieList); //19세 이상 관람가 제거 for(Movie am : ml.movieList){ System.out.println(am.movieName); } } }
'JAVA > 정리' 카테고리의 다른 글
[Stream] 스트림 part1 (0) | 2016.07.16 |
---|---|
[Lambda Expression] 람다식 (0) | 2016.05.02 |
익명 클래스 (0) | 2016.04.07 |
Thread (0) | 2016.01.14 |
clone() 에 대하여... (0) | 2015.07.04 |