ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Camel][Java] Generics Class (제네릭 클래스)
    Java/개념 정리 2020. 2. 29. 19:22

    Generics 클래스를 사용하는 이유

     영어 Generics은 일반화라는 의미를 가지고 있습니다. 우리가 배우고자 하는 일반화(Generics)의 대상은 자료형입니다. Generics을 설명하기에 앞서 Generics이 유용하고 필요한 이유를 설명하기위해 아래 코드를 통해 설명하겠습니다. 

    class Camel {
    	int weight; 
        	public Camel (int weightOfCamel) {
        		weight = weightOfCamel;
        	}
        	public void showWeight() {
        		System.out.println("Weight : "+weight);
        	}
    }
    class AnimalCage {
    	Object item;
        	public void putInto(Object item) {
        		this.item = item;
        	}
        	public Object takeOut(){
        		return item;
        	}
    }
    class ObjectAnimalCage {
    	public static void main ( String[] args ) {
        		AnimalCage ac = new AnimalCage();
            	ac.putInto(new Camel(10));
            	Camel c = (Camel)ac.takeOut();
            	c.showWeight();
            
            	AnimalCage ac2 = new AnimalCage();
            	ac2.putInto("AfreecaCamel");   // 문법적으로는 문제가 없다. 하지만 AnimalCage 클래스는 문자열을 담기 위해 정의한 클래스가 아니다.
            	Camel c2 = (Camel)ac2.takeOut();
            	c2.showWeight();
        	}
    }

     위의 코드에서는 Object클래스를 기반으로 AnimalCage 클래스를 정의해서 Camel 뿐만아니라 다른 동물들도 저장할 수 있습니다. 하지만 위의 코드를 실행해보면 에러가 발생하는 것을 확인할 수 있습니다. 메인 메소드 내부의 ac2.putInto 메소드를 확인하면 인자에 문자열이 전달되는 것을 확인할 수 있습니다. 그렇기 때문에 ac2.takeOut() 메소드에서 예외가 발생하게 됩니다. 이것은 문법적으로는 문제가 없지만 실행 시 예외로 인한 에러가 발생하게되는 것입니다. 이 에러는 컴파일 에러가 아니라서 문제점을 발견하기가 쉽지 않습니다. 이러한 문제를 해결하기 위해 아래처럼 코드를 작성할 수 있습니다. 

    class Camel {
    	int weight; 
        	public Camel (int weightOfCamel) {
        		weight = weightOfCamel;
        	}
        	public void showWeight() {
        		System.out.println("Weight : "+weight);
        	}
    }
    class CamelCage {
    	Camel item;
        	public void putInto(Camel item) {
        		this.item = item;
        	}
        	public Camel takeOut(){
        		return item;
        	}
    }
    class CamelBaseCamelCage {
    	public static void main ( String[] args ) {
        		CamelCage cc = new CamelCage();
            	cc.putInto(new Camel(10));
            	Camel c = (Camel)cc.takeOut();
            	c.showWeight();
            
            	CamelCage cc2 = new CamelCage();
            	cc2.putInto("AfreecaCamel");  
                	Camel c2 = (Camel)cc2.takeOut();
            	c2.showWeight();
        	}
    }

    위의 코드를 실행해보면 여전히 에러가 발생하는 것을 확인할 수 있습니다. 하지만 이전에 발생하는 에러와의 차이점은 이번 에러는 컴파일과정에서 발생하는 에러라는 점입니다. 이 방식의 장점은 자료형에 대한 안전성이 보장된다는 것입니다. 이러한 이유로 Object 클래스를 기반으로 코드를 작성하는 것을 권하지 않습니다. 

     

    하지만 위의 코드처럼 Object 클래스를 기반으로 코드를 작성하는 것 대신 CamelCage 클래스를 정의해 코드를 작성하는 것의 문제점은 상황에 따라서 다수의 클래스를 정의해야할 수 있다는 것입니다. Camel 외에 다른 동물을 추가하려면 PigCage, LionCage와 같은 클래스를 각각 정의해줘야 한다는 것입니다. 이러한 번거로움을 해결하기 위해 사용되는 것이 바로 Generics인 것입니다. 

     

    Generics 클래스의 정의 방법 및 활용

    class Camel {
    	int weight; 
        	public Camel (int weightOfCamel) {
        		weight = weightOfCamel;
        	}
        	public void showWeight() {
        		System.out.println("Weight : "+weight);
        	}
    }
    class Pig {
    	int num; 
        	public Pig (int numOfPig) {
        		weight = numOfPig;
        	}
        	public void showNum() {
        		System.out.println("Num : "+num);
        	}
    }
    class AnimalCage <T>{
    	T item;
        	public void putInto(T item) {
        		this.item = item;
        	}
        	public T takeOut(){
        		return item;
        	}
    }
    class GenericsAnimalCage {
    	public static void main ( String[] args ) {
        		AnimalCage<Camel> cc = new AnimalCage<Camel>();
            	cc.putInto(new Camel(10));
            	Camel c = cc.takeOut();
            	c.showWeight();
            
            	AnimalCage<Pig> pc = new AnimalCage<Pig>();
            	pc.putInto(new Pig(20));
            	Pig p = pc.takeOut();
            	p.showNum();
        	}
    }

     위의 코드는 Generics 클래스를 활용한 예시입니다. AnimalCage 클래스의 이름을 확인하면 <T> 를 확인할 수 있습니다. 이것이 바로 Generics 클래스입니다. T 는 type의 약자입니다. AnimalCage<T>는 AnimalCage 클래스의 인스턴스를 생성하려면 자료형 정보를 인자로 전달해야한다는 것을 말해줍니다. 인스턴스 생성시 자료형 정보로 Camel을 전달하면 클래스 내부에 존재하는 T가 전부 Camel로 변경되어 인스턴스가 생성되는 것입니다. 

     이처럼 하나의 클래스 정의만으로 다수의 클래스를 정의한 효과를 가져다주는 것이 Generics인 것입니다. 

     

     

    Generics 메소드의 정의와 호출

    class Camel {
    	public String toString() { return "Camel"; }
    }
    class Pig {
    	public String toString() { return "Pig"; }
    }
    class AnimalCage {
    	int cnt=0;
        	public <T> void showInst(T inst) {
        		System.out.println(inst);
                cnt++;
        	}
            void showCnt() { System.out,println("Cnt : "+ cnt);}
    }
    class GenericsMethod {
    	public static void main ( String[] args ) {
        		Camel c = new Camel();
                Pig p = new Pig();
                
                AnimalCage ac = new AnimalCage();
                ac.<Camel>showInst(c); 	// Camel 출력. 일반적으로 ac.showInst(c); 로 사용.
                ac.<Pig>showInst(p);  	// Pig 출력. 일반적으로 ac.showInst(p); 로 사용.
                ac.showCnt(); 		// Cnt : 2 출력
        	}
    }

    위 코드는 Generics 메소드의 정의방법 및 호출방법을 보여주는 예시입니다. main 메소드 내부를 확인해보면 2줄의 showInst 메소드가 호출되는 코드를 볼 수 있습니다. 컴파일러는 메소드 호출 시 전달되는 참조변수의 자료형을 근거로 자료형 정보를 판단할 수 있기 때문에 showInst 메소드 앞의 <Camel>과 <Pig>가 생략 가능합니다. 

     또한 아래의 코드처럼 다수의 자료형 매개변수를 선언하고 각각에 다른 자료형 정보를 전달할 수 있습니다. 

    class AnimalCage {
    	int cnt=0;
        	public <T, U> void showInst(T inst, U inst2) {
        		System.out.println(inst);
                	System.out.println(inst2);
                	cnt++;
        	}
            void showCnt() { System.out,println("Cnt : "+ cnt);}
    }

    이러한 방식은 메소드뿐만아니라 클래스에도 적용됩니다. 

    class AnimalCage <T, U>{
    	T item1;
        	U item2
        	public void putInto(T item) {
        		item1 = item;
        	}
        	public void putInto(U item) {
        		item2 = item;
        	}
    }

     

     Generics 클래스 및 메소드 정의 시 주의사항

     Generics 클래스 및 메소드는 어떤 자료형을 기반으로 하던간에 실행이 가능하도록 정의되어야 합니다. 특정 자료형에서 실행이 불가능하면 컴파일에러를 발생하게됩니다. 

    class Camel {
    	public <T> void CamelMethod(T inst) {
        	inst.showWeight();   		// 컴파일 에러를 발생한다. 
            System.out.println(inst); 	// 정상적으로 작동한다.
        }
    }

    위의 코드를 통해 설명하겠습니다. 매개변수 inst를 통해 showWeight 메소드를 호출하는 것을 확인할 수 있고, 이 부분에서 에러가 발생하게됩니다. 그 이유는 Generics 클래스 및 메소드에서는 매개변수인 inst가 참조하는 인스턴스에 showWeight 메소드가 존재해야 실행가능하기 때문입니다. 그렇다면 매개변수 inst를 출력하는 코드는 어떻게 정상적으로 수행될까? 그 이유는 println 메소드는 Object형 참조변수를 인자로 받기때문입니다. 모든 클래스는 Object 클래스를 상속하기 때문에 println 메소드를 통한 인자전달은 항상 가능합니다. 또한 toString 메소드의 경우에도 Object 클래스에 정의되어 있는 메소드이기 때문에 언제나 사용가능합니다. 

     

    매개변수의 자료형 제한

    앞서 언근한 'Generics 클래스 및 메소드는 어떤 자료형을 기반으로 하던간에 실행이 가능하도록 정의되어야 한다'는 특징은 때때로 불편한 점이 아닐수 없습니다. 그렇기 때문에 Java의 Generics에서는 매개변수의 자료형에 제한을 줄 수 있습니다. 

    class Camel {
    	public <T extends Animal> void CamelMethod(T inst) {
        		inst.showWeight(); 		
            	System.out.println(inst); 
        	}
    }

    위 코드를 확인해보면 <T> 대신에 <T extends Animal> 로 적혀있는 것을 볼 수 있습니다. 이 것은 T가 Animal을 상속 또는 구현하는 클래스의 자료형이어야 한다는 제한을 주는 것입니다. 여기서 한가지 더 알아야 할 점은 클래스의 상속에는 extends를 사용하고 구현에는 implements를 사용하지만, Generics 자료형 제한 시에는 둘의 구분 없이 오직 extends만 사용합니다. 

     또한 T[ ] arr 처럼 선언함으로써 매개변수로 전달되는 대상을 배열의 인스턴스로만 제한할 수도 있습니다.

    댓글

Camel`s Tistory.