자바나 코틀린으로 개발하다보면 내부함수들을 확인할 때가 있는데 상당부분 제네릭 타입으로 만들어져 있다.
다른 개발자들은 많이 사용하는 것 같은데 나는 코드 리딩만 해보았지 제네릭을 염두에 두고 코드를 설계한 적이 없었다.
그래서 이 참에 제네릭 개념도 다시 정리하고 테스트 코드도 작성하여 개발 파이를 넓혀보려고 한다.
Generic이란?
제네릭이란 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에서 컴파일 할 때 타입 체크를 해주는 기능이다.
객체의 타입을 컴파일 할 때 체크하기 때문에 의도하지 않은 타입의 객체가 저장되거나 호출되는 것을 막을 수 있고 형변환의 번거로움을 줄일 수 있다.
코드로 보충 설명하자면
package test;
public class Person
{
Object m_name;
void setName(Object name)
{
this.m_name = name;
}
Object getName(Object name)
{
return m_name;
}
}
public class Test
{
public static void main(String [] args)
{
Person p = new Person();
p.setName(12345);
if(p.getName() instanceof String)
{
String strName = String.valueOf(p.getName());
System.out.println(strName.trim());
} else {
System.out.println("원하는 타입이 아닙니다.");
}
}
}
위 Person Class를 사용하려면 의도하지 않는 타입이 들어갈 수 있기 때문에 사용할 때 마다 타입체크를 해야 한다는 번거로움이 생길 수 있다.
Generic과 타입변수
Object로 만든 Person 클래스를 제네릭 클래스로 바꾸어 보겠다.
public class Person<T>
{
T m_name;
void setName(T name)
{
this.m_name = name;
}
T getName(T name)
{
return m_name;
}
}
클래스 앞에 <T>를 추가해주고 Object로 되어 있는 부분을 T로 바꾸어 주면 된다.
여기서 T는 타입 변수의 한 종류이며 Type의 첫 글자에서 따온 것이다.
타입 변수의 종류는 다음과 같다.
T : Type(유형)
E : Element(요소) ex) ArrayList<E>
K : Key(키) ex) Map<K, V>
V : Value(값) ex) Map<K, V>
코드를 Object에서 제네릭 타입으로 변경하게 되면 상황에 맞게 타입을 지정하여 사용할 수 있다.
public class Test
{
public static void main(String [] args)
{
Person<String> p = new Person<String>();
p.setName("Hinos");
System.out.println(p.getName());
Person<Integer> p2 = new Person<Integer>();
p2.setName(119);
System.out.println(p2.getName());
}
}
만약 지정한 타입과 일치하지 않다면
public class Test
{
public static void main(String [] args)
{
Person<String> p = new Person<String>();
p.setName(119);
System.out.println(p.getName());
}
}
타입이 다르다는 에러가 발생하게 된다.
Generic에서 많이 실수하는 유형
- 타입 변수는 인스턴스 변수이기 때문에 Static에서 사용할 수 없다.
인스턴스 변수는 클래스의 인스턴스가 생성될 때 만들어지므로 Static 관점으로는 아직 메모리에 올라 오지 않는 인스턴스를 참조하려는 것이기 때문에 에러가 발생한다.
public class Test
{
static T Person; //Error, T cannot be resolved to a type
public static void main(String [] args)
{
Person<String> p = new Person<String>();
p.setName(119);
System.out.println(p.getName());
}
}
- 제네릭 타입의 배열을 생성할 수 없다.
public class Person<T>
{
private T m_name;
private T[] m_arr = new T[5]; // Error, Unresolved compilation problem: Cannot create a generic array of T
void setName(T name)
{
this.m_name = name;
}
T getName()
{
return m_name;
}
int arrSize()
{
return m_arr.length;
}
}
언뜻보면 당연히 될 것 같은데 아쉽게도 이 코드는 에러가 발생한다.
왜냐하면 new 연산자는 컴파일 시점에 타입 T가 정확히 무엇인지 알아야 한다.
그런데 위의 코드에 정의된 new T<5>는 컴파일하는 시점에서는 T가 어떤 타입이 될지 전혀 알 수 없기 때문이다.
제네릭 배열을 생성해야될 경우 new 연산자 대신 Reflection API의 newInstance()와 같이 동적으로 객체를 생성하는 메서드로 배열을 생성하거나 Object 배열을 생성해서 복사한 다음에 T[]로 형변환하는 방법을 사용한다고 한다.
Generic 제한
- 제한된 제네릭 클래스
말 그대로 제네릭 타입의 범위를 정하고 범위에 벗어나는 타입들을 제한하는 것이다.
public class Person<T extends PersonalInfo>
{
private T m_info;
public Person(T info) {
m_info = info;
}
void setInfo(T info) {
m_info = info;
}
T getInfo() {
return m_info;
}
}
위 코드와 같이 타입 변수 뒤에 extends와 PersonalInfo 타입을 설정하면 PersonInfo 클래스 자신과 자신을 상속 받은 클래스들만 타입을 설정할 수 있다.
public class PersonalInfo
{
String m_name;
int m_age;
public PersonalInfo(String name, int age)
{
m_name = name;
m_age = age;
}
public String getName()
{
return m_name;
}
public void setName(String name)
{
this.m_name = name;
}
public int getAge()
{
return m_age;
}
public void setAge(int age)
{
this.m_age = age;
}
}
public class Test
{
public static void main(String [] args)
{
Person<PersonalInfo> p = new Person(new PersonalInfo("Hinos", 100));
System.out.println(p.getInfo().m_name);
System.out.println(p.getInfo().m_age);
}
}
'Language > Java' 카테고리의 다른 글
Java로 자료구조(LinkedList, Stack, Queue) 구현해보기 (0) | 2021.04.26 |
---|---|
자바 == 연산자 (0) | 2021.04.12 |
Interface Comparable (0) | 2020.11.29 |
Java Cipher (0) | 2019.10.24 |
자바 synchronized (0) | 2019.08.20 |