ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 제네릭(Generic)
    JAVA 2021. 10. 28. 15:20

    제네릭(Generic)은 다양한 타입의 객체를 다루는 메서드나 컬랙션 클래스의 타입을 지정해준다.

    제네릭(Generic)은 컴파일시 타입체크를 하게 도와준다.

     

     

    타입의 모호함

    제네릭이 등장하기 전 자바는 타입 캐스팅시 발생하는 타입의 모호함 때문에 문제를 겪었다.

    	public static void main(String[] args) {
    		List g1 = new List();
    
    		g1.add(500);//autoBoxing
    		g1.add(100);//autoBoxing
    		g1.add("Hello"); //String
    
    		System.out.println(g1.get(0)); //O
    		System.out.println((Integer)g1.get(1)); //O
    		System.out.println((Integer)g1.get(2)); //X
    
    	}
        
        class List {
    
    	private Object[] arr = new Object[5];
    	private int size = 0;
    
    	public void add(Object value) {
    		arr[size++] = value;
    
    	}
    
    	public Object get(int index) {
    		return arr[index];
    
    	}
    	
    }

    Obeject 타입의 객체를 담을수 있는 배열을 가지고 있는 List 클래스이다. Main 부분에서 List 인스턴스를 생성 후 두개의 정수값과 String 타입의 객체를 추가 하였다. 그리고 결과 확인을 위해 컴파일을 후 실행이 되었다.

    2줄의 코드는 문제없이 실행된다

    하지만 결과 출력 도중 이코드에서 예외가 발생하는데 이유는 배열의 2번째 인덱스에 저장된 값은 String 타입인데 Integer 로 타입 캐스팅을 시도 했기 때문 ClassCastException 이 발생 했다. 

    System.out.println((Integer)g1.get(2));
    Exception in thread "main" java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Integer (java.lang.String and java.lang.Integer are in module java.base of loader 'bootstrap')
    	at Exam.main(Exam.java:14)

     

    여기서 중요한점은 분명 코드 작성후 실행 까지 컴파일 단계에서 우리는(컴파일러,코드작성자) 이코드가 문제가 있더는걸 인지 못하고 런타임환경(실행도중)에서 예외가 발생 후 오류를 인지하게 된다. 이처럼 타입의 모호함컴파일 단계에서 찾아내지 못하고 어플리케이션 동작 중에 나타나므로 주의해야하고 피해야한다.

    해결방법

    간단한 해결방법은 필요로 하는 타입별로 List 클래스를 추가로 만들면 된다.

    class IntegerList {
    
    	private Integer[] arr = new Integer[5];
    	private int size = 0;
    
    	public void add(Integer value) {
    		arr[size++] = value;
    
    	}
    
    	public Integer get(int index) {
    		return arr[index];
    
    	}
    
    }
    
    class StringList {
    
    	private String[] arr = new String[5];
    	private int size = 0;
    
    	public void add(String value) {
    		arr[size++] = value;
    
    	}
    
    	public Integer get(int index) {
    		return arr[index];
    
    	}
    
    }

    이처럼 Interger 타입과 String 타입으로 나누어서 작성하면 문제가 해결된다. 하지만 지금은 두개의 타입에서 발생하는 문제만 해결하면 되지만 타입이 여러개로 늘어나면 그만큼 새로운 List 클래스를 작성해야 하고 타입말고는 같은 역할을 하는 객체가 중복해서 늘어나는 것은 좋지 못한 코드가 된다.

    제네릭 사용

    이런문제를 해결하기 위해 제네릭이 등장했다.

    public static void main(String[] args) {
    
    		List<Integer> list1 = new List();//Integer
    		List<String> list2 = new List();//String
    		
    		list1.add(10);
    		list2.add("hello");
    		
    		System.out.println(list1.get(0));
    		System.out.println(list2.get(0));
    		
    		
    		
    
    	}
    
    class List<T> {
    
    	private Object[] arr = new Object[5]; //배열은 제네릭을 쓰지못한다.
    	private int size = 0;
    
    	public void add(T value) {
    		arr[size++] = value;
    
    	}
    
    	public T get(int index) {
    		return (T) arr[index];
    
    	}
    
    }

    제네릭으로 작성된 List 클래스의 인스턴스 생성시 제네릭타입을 명시하여 생성한다. 이후 해당 제네릭타입을 사용하는 필드나 메서드들은 해당 제네릭타입에만 사용가능한(필드 값 추가, 파마리터 인자값 등) 제약조건이 생긴다.이를 어기면 컴파일오류가 발생한다. 그리고 제네릭 클래스를 생성하게 되면 따로 형변환도 필요없게 된다. 

    list1.add("Hello"); //X

     

    제네릭을 사용 할 수 없는 경우(수정예정)

    위의 코드중 LIst 배열은 제네릭타입으로 지정하지 않고 Object 타입으로 생성 하였다.그 이유는 배열참조 변수를 컴파일 할시 new연산자 때문이다.컴파일시 new가 생성하는 타입을 미리 파악하여 JVM Heap 영역에 메모리를 확보한다. 하지만 제네릭을 사용하면 컴파일시에 타입이 정해지지 않기 때문에 애러가 발생한다.

    ~배열은 불변하지만 재네릭은 공변한다.

     

    List<T> list = new LIst(); // X 컴파일 애러발생

     

    다양한 제네릭의 사용

    제네릭 타입을 상속을 통해 타입의 범위를 지정 할 수 있다.

    class  NumberList<T extends Numeber>{...}

    NumberList 클래스의 제네릭타입은 Numeber 타입의 객체와 Number 객체를 상속하는 객체타입만 사용할수 있다.

     

    제네릭타입 메서드사용

    class Myclass <T>{
    	
    	
    	public T doSmomeThing1(T parm){ //클래스의 제네릭을 넘겨받아 리턴타입 과 매개변수의 제네릭타입이 T 이다.
    		return T ;
    		
    	}
    	public <T> T doSmomeThing2(){ //클래스와 메소드의 제네릭은 서로 다른 타입이다.
    		return T;
    		
    	}
        public <T> void doSmomeThing5(T parm1){//매개변수를 제네릭타입으로 받을 수 있다.
         	...
    	}
    	public <E>String doSmomeThing(E parm1,T parm2){ //클래스의 제네릭과 메소드의 제네릭타입을 같이 사용 가능하다.
    		
    		return new String("String");
    		
    	}
    		public <제네릭타입 선언> {반환타입} {메서드명}( T pram1, T pram2) {
            ...
            }
            //위의 형식으로 선언가능

    정적 메서드 제네릭타입 사용하기

    기본적으로 정적할당되는 변수나 메서드는 제네릭사용이 불가능하다.

    class Myclass <T>{
    	
        static T value; //에러발생
        static <E> E value;//에러발생
        
        static T method1(){...} //에러발생,클래스의 제네릭타입을 사용
    		
            
    	}

    정적메서드에 제네릭타입에 생성하여 사용이 가능하다.

     

        public static <T> void doSmomeThing1(T parm1){
         	...
    	}
    	public static <T> String doSmomeThing2(T parm1,T parm2){ 
    		
    		return new String("String");
    		
    	}

     

    'JAVA' 카테고리의 다른 글

    자바 가상머신(JVM) 의 구조  (0) 2021.05.14
    정수 배열의 분포를 출력하는 메소드  (0) 2021.04.28

    댓글

Designed by Tistory.