Generics

허용운
공부하다가 제네릭스와 HashMap 그리고 팩토리메소드를 이용한 인스턴스 생성에 생각해볼만한 내용이 있어 리플을
남깁니다. 한번쯤 살펴 보시는게 좋을거 같습니다.

지네릭스 : JAVA5에서 추가된 기능을 캐스팅 하지 않고도 클래스를 쓸 수 있게 해주는 기능

지네릭스는 3가지 형태로 제공이 되어 집니다.
1. 파라미터화된 타입.
2. 와일드카드(wild card, ?)
3. 파라미터화된 메서드
4. 사용자가 정의한 파라미터화된 타입

가정 (상세 코드는 동봉한 파일을 참조하세요.)
1. 모든 자동차 의 부모인 Car 라는 클래스가 있다.Car 에는 자신의 정보를 출력하는 show, speed 메서드가 제공된다.
2. Car로 부터 상속받은 Bus 와 Taxi 라는 클래스가 있다. 상속받은 Bus와 Taxi는 Car가 제공하는 show 와 speed 메서드를 재정의하여 제공한다. Bus는 move 메서드를 제공한다.

지네릭스와파라미터화된 타입지네릭스와파라미터화된 메서드사용자가정의한 파라미터화된 타입
{code}
Vector <Car> vc = new Vector<Car>(5,5);
vc.add(new Car());vc.add(new Car());vc.add(new Car());vc.add(new Car());
vc.add(new Taxi());vc.add(new Bus());
Vector <Bus> vb = new Vector<Bus>(5,5);
vb.add(new Bus());vb.add(new Bus());vb.add(new Bus());

//vc=vb; //error--> ensure complie-time type safety
Vector<? extends Car> vec= vb;

for(Car c:vec){
c.show();
((Bus)c).move();
}

|

Vector<Car> vc = new Vector<Car>(5,5);
vc.add(new Car());vc.add(new Car());vc.add(new Car());vc.add(new Car());
vc.add(new Taxi());vc.add(new Bus());
printVector(vc);
printVector2(vc);
printVector3(vc);

//와일드카드 사용
public static void printVector(Vector<? extends Card> vec) {
for(Car c:vec){
if(c instanceof Bus){
c.show();
((Bus)c).move();
}else{
c.show();
c.speed();
}//물론 Taxi에 대한 처리도 따로 해야 한다.
}

//파라미터화된 메서드
public static<T extends Car> void printVector2(Vector<T> v){
printVector(v);
}

//파라미터화된 메서드
public static<T extends Car> void printVector3(Vector<T> v){
Iterator <T> iter = v.iterator();
while(iter.hasNext()) {
Car c= iter.next();
c.show();
}
}

|

import java.util.*;

public class Carbarn<E extends Car> {
private Vector<E> barn = new Vector<E>(5,5);

public void add(E element) {
barn.add(element);
}

public E get(int index) {
return barn.get(index);
}

public void shuffle(){
Collections.shuffle(barn);
}

public Vector<E> getAllCars() {
return barn;
}
}

import java.util.Iterator;

public class ParameterizedCar {

/**

  • @param args
    */
    public static void main(String[] args) {
    Carbarn<Car> vc = new Carbarn<Car>();
    vc.add(new Car());
    vc.add(new Taxi());
    vc.add(new Bus());

for (Car c : vc.getAllCars()) {
c.show();
System.out.println(c.speed());
}

System.out.println("






--");
Carbarn<Taxi> vcb = new Carbarn<Taxi>();
vcb.add(new Taxi());vcb.add(new Taxi());
vcb.add(new Taxi());

//vcb.add(new Car());
//Carbarn<Car> cc = vcb;

System.out.println("






--");
printCarbarn(vcb);
printCarbarns(vc);
printCarbarns2(vc);
}

private static<T extends Car> void printCarbarns2(Carbarn<T> ctx) {
// TODO Auto-generated method stub
printCarbarns(ctx);
}

private static void printCarbarns(Carbarn<? extends Car> ctx) {
// TODO Auto-generated method stub
Iterator<? extends Car> iter = ctx.getAllCars().iterator();
while (iter.hasNext()) {
Car car = iter.next();
car.show();
System.out.println(car.speed());
}

}

private static void printCarbarn(Carbarn<Taxi> vcb) {
Iterator<Taxi> iter = vcb.getAllCars().iterator();
while (iter.hasNext()) {
Taxi taxi = (Taxi) iter.next();
taxi.show();
System.out.println(taxi.speed());
}
}
}

|

위 코드는 지네릭스를 생성할때 
1. Vector<Car>
2. Vector<? extends Car> // 자식 타입을 와일드 카드를 이용하여 입력 하는 방법
3. 메서드 리턴문 앞에 <T extends Car> // 자식 타입을 와일드 카드 없이 메서드의 아규먼트로 입력 하는 방법
4. 클래스를 선언할 때 클래스 이름뒤에 <E extends Car>로 선언 // 사용자 정의 파라미터타입

4개의 사용법을 보여 준다. 
한가지더 HashMap을 이용한 지네릭스 예제를 살펴 보고 그다음에 팩토리 메소드를 이용한 처리법을 살펴보자

{code:java}
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;

public class CarbarnHashMap {
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub

		HashMap<String, Car> hm = new HashMap<String, Car>();
		hm.put("3", new Taxi());hm.put("4", new Bus());
		Car[] cc = new Car[3];
		cc[0] = new Car();
		cc[1] = new Taxi();
		cc[2] = new Bus();
		hm.put("5", cc[0]);hm.put("6", cc[1]);hm.put("7", cc[2]);
		Set<String> set = hm.keySet();
		for (String str : set) {
			System.out.println("key" + str);
		}
		
		Iterator<String> iter = set.iterator();
		while (iter.hasNext()) {
			String key = iter.next();
			Car car = hm.get(key);
			car.show();
		}
		
		Collection<Car> cv = hm.values();
		for (Car col : cv) {
			col.show();
		}		
	}
}

이펙티브자바 세컨드 에디션을 보면 다음과 같은 코드가 제공된다.
{code:java}
//Service provider framework sketch
//Service interface
public interface Service {
	... // Service-specific methods go here
}

//Service provider interface
public interface Provider {
	Service newService();
}

//Noninstantiable class for service registration and access
public class Services {
	private Services() { } // Prevents instantiation (Item 4)
	//Maps service names to services
	private static final Map<String, Provider> providers =
			new ConcurrentHashMap<String, Provider>();
	public static final String DEFAULT_PROVIDER_NAME = "<def>";

	//Provider registration API
	public static void registerDefaultProvider(Provider p) {
		registerProvider(DEFAULT_PROVIDER_NAME, p);
	}
	
	public static void registerProvider(String name, Provider p){
		providers.put(name, p);
	}
	
	//Service access API
	public static Service newInstance() {
		return newInstance(DEFAULT_PROVIDER_NAME);
	}
	
	public static Service newInstance(String name) {
		Provider p = providers.get(name);
		if (p == null)
			throw new IllegalArgumentException
			("No provider registered with name: " + name);
		
		return p.newService();
	}
}

내용인 즉슨 2개의 인터페이스를 이용해서 생성되는 서비스를 Services 의 해쉬맵에 담았다가
필요시점에 꺼내서 쓰는 예제인데.. 이를 팩토리 메소드로 구현을 해 놓았다.

팩토리 메소드를 쓰면 다음과 같은 점이 생성자 보다 장점으로 제공 되는데 원문을 옮겨 쓰면 다음과 같다

1. One advantage of static factory methods is that, unlike constructors, they have names.
스태틱 팩토리 메소드는 생성자와 달리 알맞은 이름을 줄 수 있다.
2. A second advantage of static factory methods is that, unlike constructors,
they are not required to create a new object each time they're invoked
스태틱 팩토리 메소드는 생성자와 달리 호출될 ?마다 새로운 객체를 생성하지 않아도 된다.
3. A third advantage of static factory methods is that, unlike constructors,
they can return an object of any subtype of their return type.
생성자는 자신이 정의된 클래스의 인스턴스만 리턴할수 있지만 스태틱 팩토리 메소드는 자신이 선언된 것과
같은 타입의 인스턴스는 모두 리턴할수 있다.
4. A fourth advantage of static factory methods is that they reduce the verbosity
of creating parameterized type instances.
스태틱 팩토리 메소드는 객체의 파라메터 생성을 줄일수 있다.

위의 설명중 4번째가 지네릭스와 관련된 부분이라 설명을 하려 한다.

클래스를 생성해서 생성자를 호출하게 될경우 파라메터의 유형의 분명하게 지정해줘야 한다.
문제는 분명하게 관계가 명백함에도 불구하고 쓸데 없이 두번에 걸쳐서 지정을 해줘야 한다.


Map<String, List<String>> m = new HashMap<String, List<String>>();

위 코드는 맵을 처리하는 양이 증가 하거나 할수록 코드의 복잡도를 증가 시킨다.
다음과 같은 형태로 변경하자


public static <K, V> HashMap<K, V> newInstance() {
 return new HashMap<K, V>();
}

Map<String, List<String>> m = HashMap.newInstance();

샘플로 제공된 Serivces 클래스에서는 바로 이부분이 수정 대상이 되겠다


	private static final Map<String, Provider> providers =
			new ConcurrentHashMap<String, Provider>();

ps. 영어 해석에 자신이 없는 관계로 원문을 올려 놓을테니.. 해석해보셔 좋습니다~


A fourth advantage of static factory methods is that they reduce the verbosity
of creating parameterized type instances. Unfortunately, you must specify
the type parameters when you invoke the constructor of a parameterized class
even if they're obvious from context. This typically requires you to provide the
type parameters twice in quick succession:
Map<String, List<String>> m =
new HashMap<String, List<String>>();
This redundant specification quickly becomes painful as the length and complexity
of the type parameters increase. With static factories, however, the compiler
can figure out the type parameters for you. This is known as type inference. For
example, suppose that HashMap provided this static factory:
public static <K, V> HashMap<K, V> newInstance() {
return new HashMap<K, V>();
}
Then you could replace the wordy declaration above with this succinct alternative:
Map<String, List<String>> m = HashMap.newInstance();
Someday the language may perform this sort of type inference on constructor
invocations as well as method invocations, but as of release 1.6, it does not.

문서에 대하여

  • 작성일자 : 허용운
  • 작성자 : 2009년 01월 06일
  • 이 문서는 오라클클럽 에서 작성하였습니다.
  • 이 문서를 다른 블로그나 홈페이지에 게재할 경우에는 출처를 꼭 밝혀 주시면 고맙겠습니다.