티스토리 뷰

Java, JavaScript

[Java] Generic

체봄 2019. 6. 14. 22:06

Generic의 <> 안에는 여러 개의 타입을 써넣을 수 있지만, 그 타입은 반드시 참조형이어야 한다! (기본형 불가)

 

ex)

class Person<T, E> {
	T name;
    E age;
    
    Person(T name, E age) {
    	this.name = name;
        this.age = age;
    }
}


public class GenericEx {
	public static void main(String[] args) {
    	Person<String, int> p1 = new Person<String, int>("체봄", 22);	// 오류 발생!!
        System.out.println("name: "+p1.name+", age: "+p1.age);
 	}
}

위와 같이 코드를 작성하면 오류가 발생한다.

그 이유는 Generic의 <> 안에는 참조형만 쓸 수 있는데 기본형인 int 를 썼기 때문이다.

age는 정수형 숫자를 담아야 하는데 Generic에는 int형을 쓸 수가 없다. 이러한 경우에 Wrapper 클래스가 필요한 것이다!

따라서, 코드를 다음과 같이 수정하여야 한다.

class Person<T, E> {
	T name;
    E age;
    
    Person(T name, E age) {
    	this.name = name;
        this.age = age;
    }
}


public class GenericEx {
	public static void main(String[] args) {
    	Person<String, Integer> p1 = new Person<String, Integer>("체봄", new Integer(22));	// 오류 해결
        System.out.println("name: "+p1.name+", age: "+p1.age);
 	}
}

실행 결과>

name: 체봄, age: 22

 

 


 

Generic의 생략

 

위 코드의 Person<String, Integer> p1 = new Person<String, Integer>("체봄", new Integer(22)); 부분에서,

Person 클래스의 생성자 매개변수를 통해서 Generic의 <> 안에 어떤 타입이 들어갈 것인지 알 수 있으므로 <>는 생략할 수 있다.

따라서,  Person p1 = new Person("체봄", new Integer(22)); 과 같이 생략하여 쓸 수 있다.

 

심화>

package GenericEx;

class Data {
	public String name;
	
	Data(String name) {
		this.name = name;
	}
}

class Person<T, E> {
	public T data;
	public E age;
	
	Person(T data, E age) {
		this.data = data;
		this.age = age;
	}
}

public class GenericEx2 {

	public static void main(String[] args) {
		Person p1 = new Person(new Data("체봄"), new Integer(22));
		
        System.out.println("name: " + p1.data.name + ", age: " + p1.age);	// 오류 발생!!!
	}
}

위와 같이 Generic에서 <> 를 모두 생략하여 객체 p1을 생성하였을 때, 'p1.data.name' 부분 에서 오류가 발생한다.

그 이유는 <>를 생략하였으므로 p1.data의 타입이 명시되지 않아 Data 클래스의 내부 변수인 name에 접근할 수 없기 때문이다.

 

해결방법 1)

Person<Data, Integer> p1 = new Person(new Data("체봄"), new Integer(22)); 와 같이 써서 타입을 명시해준다.

 

해결방법 2)

p1.data.name을 p1.data로 바꾸면 p1.data는 Data형 객체이므로 객체의 toString 함수가 호출된다.

따라서, Data 클래스 내부에 toString 함수를 다음과 같이 오버라이딩한다.

public String toString() {    
	return name;
}

 

 


 

메소드에서의 Generic 사용

 

※ 사용법

: 접근제어자 <T> 반환형 메소드명(T 변수명) { }

-> 메소드의 매개변수에 Generic 타입을 사용하기 위함!

 

 

ex)

class Person<T> {
    // 메소드에서의 Generic 사용
    public <T> void getName(T name) {
    	System.out.println("제 이름은 " + name + " 입니다.");
    }
}

public class GenericEx {
	public static void main(String[] args) {
    	Person<String> p1 = new Person<String>();
        p1.<String> getName("체봄");	// p1.getName("체봄");으로 써도 됨
    }
}

'p1.<String> getName("체봄");' 부분에서 메소드의 매개변수를 통해 타입을 알 수 있으므로 <>를 생략하여 p1.getName("체봄");과 같이 작성하여도 된다.

 

 


 

Generic의 제한

 

Generic 사용 시 타입이 정해져 있지 않기 때문에 목적에 맞지 않는 잘못된 타입이 올 수도 있다.

이러한 경우를 막기 위해, Generic이 목적에 부합하는 클래스(또는 인터페이스)를 상속받도록 함으로써 Generic을 제한할 수 있다.

 

 

ex)

package GenericEx;

class StudentData {
	String name;
	int age;
    
    StudentData(String name, int age) {
		this.name = name;
		this.age = age;
	}
    
    public String getName() {
    	return name;
    }
    public int getAge() {
    	return age;
    }
}

class Person <T> {
	public T data;
    
    Person(T data) {
    	this.data = data;
    }
}

public class GenericEx {
	public static void main(String[] args) {
		Person p1 = new Person("체리");
	}
}

Person 클래스에 StudentData를 Generic으로 사용하여 학생의 데이터를 얻으려는 것을 예측할 수 있다.

그런데 main 메소드에서 Person 클래스에 StudentData 객체가 아닌 String 문자열을 인자로 넘겨주었다.

이 경우 본래 코드의 목적과 어긋나므로 잘못된 코드가 된다. 이러한 경우를 막기 위해 Generic의 제한이 필요한 것이다!

수정한 코드는 다음과 같다.

package GenericEx;

interface Data {
	String getName();
	int getAge();
}

class StudentData implements Data{
	String name;
	int age;
	
	StudentData(String name, int age) {
		this.name = name;
		this.age = age;
	}
	
	public String getName() {
		return name;
	}
	public int getAge() {
		return age;
	}
}

class Person <T extends Data> {
	public T data;
    
    Person(T data) {
    	this.data = data;
    }
    
    public void printInfo(T data) {
    	System.out.println("이름: "+data.getName()+", 나이: "+data.getAge());
    }
}

public class GenericEx {
	public static void main(String[] args) {
		StudentData sd = new StudentData("체봄", 22);
		Person p1 = new Person(sd);
		p1.printInfo(sd);
	}

}

우선 Person 클래스의 Generic이 Data라는 인터페이스를 상속받게 함으로써, Data를 상속받지 않은 타입이 Generic에 들어오지 못하게 제한을 해주었다. (여기서 Generic의 타입으로는 Data와 Data를 상속받은 클래스만이 가능) 

그러므로 이전의 경우처럼 Person 객체 생성 시 String 타입을 인자로 넣어주는 실수를 완전히 막을 수 있다.

Data 인터페이스를 implement한 StudentData 클래스 객체를 만들어 Person 객체의 인자에 전달해준다.

 

 

 

 

※ Generic 타입의 배열을 T arr[] = new T[5]; 와 같은 형식으로 생성할 수 없다 !

 

반응형

댓글