본문 바로가기
(혼) (공) (자)

제네릭

by 만석이 2024. 2. 4.

 

자바에서 자료구조는 배열이있었다.
하지만 index만을 이용해서 데이터 저장하는 방식은 불편했다.
이러한 단점을 극복한 것이 자바에서 제공하는 컬렉션 프레임워크이다.

 

 

 

List를 보기전에 먼저 제네릭 이라는것을 알아야한다.

 

 

제네릭?
클래스가 다루어야할 데이터타입을 명시하는것 
외부로 부터 데이터를 받아서 명시한다.

<T> 는 Type
<E> 는 Element
<K> 는 Key
<N> 는 Number
<V> 는 Value







제네릭의 예시를 보자

                                                                   
 일반 코드

public class DataList {

private Object[] data;
private int size;
private int defaultSize = 10;


public DataList() {
data = new Object[defaultSize];
}

public DataList(int size) {
data = new Object[size];
}

public void add(Object value) {
data[size++] = value;
}

public Object get(int index) {
return data[index];
}

public int size() {
return size;
}

}

public class DataList_Main {
public static void main(String[] args) {
DataList list = new DataList();

list.add(10);
list.add("문자");
list.add(10.33);

for(int i=0; i
Object data = list.get(i);

if(data instanceof Integer) {
System.out.println("정수 : " +(int)data);
}else if(data instanceof Double) {
System.out.println("정수 : " +(double)data);
}else if(data instanceof String) {
System.out.println("정수 : " +(String)data);
}
}
}
}

private접근제어자로 data이름으로 배열을 만들어주었는데 
타입은 Object로써 최상의 클래스를 주었다.
이 의미는 어떤 타입이든 저장할수있다는 것이다.


그후 size 와 defaultSize  변수를 만들어주었다.
그리고 기본생성자와 매개변수 생성자를 만들어주었다.

main에서 생성자 생성시 매개변수 입력안하면 
기본 생성자가 실행되고 입력시에는 매개변수 생성자가 실행된다.

그후  add 메서드를 만들어주었는데, 입력한값을 저장해주는것이다. 

여기서 주의해서 볼것이있다. 

바로 data[size++]인데 여기서 사용되는 size는 
매개변수 생성자에서 받는 size와는 별개의 값이라는것이다.

매개변수의 size는 지역변수로써 괄호를 나감과 동시에 
그 영향은 끝나게된다.

그후 매개변수를 입력받는 get메서드를 만들어주었다.
이 메서드는 사용자에게 정수값을 입력받아서 해당 정수에 
위치한 value값을 불러온다.

여기서 또 주의해서 볼것이있다. 

왜 get앞에 Object를 쓴것일까?

그 이유는 바로 데이터를 Object타입으로 반환해주기 위해서이다.

이게 무슨말일까?

Object는 최상위 클래스로 어떤 타입이던 저장할수있다고했다.
get메서드를 보면 return값으로 data[index]를 받아온다. 

즉 해당위치에 저장되있는 data값을 가져오는건데 
이 가져올 값의 타입은 뭐가될지 모른다. 

int가될수도있고 String이 될수도있다. 
왜냐하면 data 배열을 애초에 Object로 저장해놨으니까.
사용자가 어떤값이든 넣을수있으니까.
불러올때 int다 String이다 정할수가없는것이다.

그래서 Object로 지정해서 반환해준다.





제네릭코드

public class DataList<T> {

    private Object[] data;
    private int size;
    private int defaultSize = 10;

    public DataList() {
        data = new Object[defaultSize];
    }

    public DataList(int size) {
        data = new Object[size];
    }

    public void add(T value) {
        data[size++] = value;
    }

    public T get(int index) {
        @SuppressWarnings("unchecked")
        T result = (T) data[index];
        return result;
    }

    public int size() {
        return size;
    }
}


public class DataList_Main {
    public static void main(String[] args) {
        // 타입을 명시하여 DataList 객체 생성
        DataList<Object> list = new DataList<>();

        list.add(10);
        list.add("문자");
        list.add(10.33);

        for (int i = 0; i < list.size(); i++) {
            Object data = list.get(i);

            // 타입에 대한 안전성을 고려한 형변환과 예외 처리
            if (data instanceof Integer) {
                System.out.println("정수 : " + (int) data);
            } else if (data instanceof Double) {
                System.out.println("실수 : " + (double) data);
            } else if (data instanceof String) {
                System.out.println("문자열 : " + (String) data);
            }
        }
    }
}

위의 일반코드와 달라진점을 보자. 

먼저 클래스이름 뒤에 <T>를 써주었다. 
이는 타입을 지정해주겠다 라는것인데, 
이것은 main에서 생성자를 불러올때 효과가 발생한다. 

main을 보게되면,
        DataList<Object> list = new DataList<>(); 
즉 객체타입을 Object로 해주겠다는것이다.

만약  DataList< Integer> list = new DataList<>();
이라면 객체안의 모든 매개변수를 입력받는 값은 
전부 정수타입이여야한다. 객체 자체가 Integer 타입이기에 
정수밖에 저장하지 못하기때문이다.

그럼 첫번째 질문 

get메서드 앞에 왜 T를 써주었을까?

애초에 클래스뒤에 <T>를 써줌으로써 클래스의 타입은
지정해주었다. 만약 Integer로 타입을 지정해주었다면
매개변수로 받을수있는 값은 정수뿐이다.
그렇다면 get으로 가져올값은 모조리 정수일것이다.

그런데 왜 T를 한번더 사용해서 타입을 명시해줘야할까?

그 이유는 바로 제네릭의 타입소거라는 특성때문이다. 
타입소거란 컴파일이 되면 가지고있던 타입의 정보를 지워버리는것이다. 

무슨말이지?

즉 add메서드를 통해서 값을 받을때 받음과 동시에 소거한다.
그럼 main에서 get메서드를 통해서 값을 불러오는데 

그때는 이미 타입이 소거되서 get 메서드에 T를 정의해주지않으면 컴파일 오류가 난다는것이다.






마지막으로 이글을 적으면서 궁금했던것이 있었다.

Object 타입으로 지정해주면 어떤 타입의 데이터간에 저장이가능하다. 정말 간편하다. 

그런데 제네릭을 사용하게 되면 입력받을수있는 타입이 지정되기때문에 다양한 타입의 데이터를 받지못한다. 

즉 일반적으로 작성하는 코드와 다를게 없다는 생각을 하게되었다. 


하지만 이유가 다있었다...


제네릭을 사용하면 타입을 명시적으로 지정할 수 있고, 컴파일러가 타입 체크를 수행하여 타입 안정성을 높일 수 있기때문이다.

 

'(혼) (공) (자)' 카테고리의 다른 글

컬렉션 프레임워크 - Map  (0) 2024.02.05
컬렉션 프레임워크 - List  (0) 2024.02.05
object [ ]  (1) 2024.02.04
parse 메서드  (0) 2024.02.04
wrapper클래스  (1) 2024.02.04