ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Genric 이 타입 일반화인 줄 만 알면, 주니어입니다.
    Java&Spring/Java 2023. 9. 4. 20:32

     

    Generic :   클라스나 메소드에서 사용할 내부 데이터 타입을 외부에서 지정하는 기법 이다.

     

    - 하나의 값이 다양한 타입을 지정 받기 위하여 사용 한다. 

     

    API 의 response class 가 정해져 있지 않은 상황에서 API 의 data body 를 Generic 으로 선언 후 개발을 진행 하고 완료후 타입을 강제 할 수 가 있다.

     

    - 컴파일 타임에 타입이 정해짐으로 따로 캐스팅을 해줄 필요가 없다.

     

     [자세한 내부 동작은 글 하단에서 다룬다.]

     

    예시의 모든 설명은 다음과 같은 상속 관계를 갖는다.

     

    클라스 Object <- Food <- Fruit <- Apple

     

    Class FruitBox(){
    	private Objecct fruit;
    }
    
    Class Call(){
    	FruitBox appleBox = new FruitBox(new Apple());
    	FruitBox bannaBox = new FruitBox(new Banana());
    
    	Banana banana =(Banana) appleBox.getFruit();  
    
      -> object 에 Apple 을 넘겼지만 Class Type 이 Apple 이 아닐때 : 런타임 오류
      -> private T fruit 인 경우 , Compile error 가 남 
    
    }

     

     

     

    메서드에서 타입 받는것을 제한하는 방식도 있다.

     

    다음 코드에서 extends Fruit 이 없으면 컴파일 에러가 난다. 

     

    그 이유로는  만약 T 가 Fruit 의 하위 타입이 아니라면 ,   List<Fruit>의 add(T fruit)  이 불가 하기 때문이다.

     

    class FruitBox<T extends Fruit>{
    	private List<Fruit> fruits = new ArrayList();
    
    	public void add(T fruit) {
    			  fruits.add(fruit);
    		} 
    }

     

    • Object class 와의 차이로는 컴파일 시점의 에러를 잡을 수 있다

     

    Generic 이 나오기 전 자바 5 버전에서는 object , 객체의 최상위 타입을 사용하여 데이터 타입을 강제 하였고 그로 인하여 컴파일 시가 아닌 런타임시에 에러가 잡히는 불안정한 소프트웨어를 야기하는 상황이 생겼다.  내가 만든 함수를 사용할려는 다른 이들이 함수 내부를 확인 해서 사용가능한 타입을 확인 하는 불편한 상황도 덤으로 생겼다.

     

    프로젝트에서 응용 예시,

    public class ApiResponse {
        private final static String SUCCESS_MESSAGE = "SUCCESS";
        ....
    
        private final String message;
    
        private final Object body;
    
        public ApiResponse(String message, Object body) {
            this.message = message;
            this.body = body;
        }
    
        public static ApiResponse success(Object body) {
            return new ApiResponse(SUCCESS_MESSAGE,body);
        }

     

    반환 값을 다음 코드에서  보기가 어렵다.

    @PostMapping(produces = "application/json;charset=UTF-8")
        public ApiResponse postMember(@Valid @RequestBody MemberDtos.MemberPostRequestDto request) {
            return ApiResponse.success(memberService.create(request));
        }

     

     

     

    비 경계 와일드 카드 :   

    모든 타입이 인자가 될 수 있게 해준다.

     

    List<?> :  비경게 와일드 카드 list 의 특징 들
    
    
    Give -> 
    
    void printList(List<Object> list){
    		for(Object item : list){
    	                               
    		}
    }
    
    test ->
    	List<String> arr = new ArrayList();
      this.printList(arr)  :  에러
    
    List<Object> 는 List<TmpType> 상속 x
    
    
    so
    
    
    void printList(List<?> list){
    	
    		for(Object item : list){
    	                               정상 출력 + 받을때 무조건 Object 적기 ,
    		}
    		list.add(null);   -> 와일드 카드 리스트에는 null 만 삽입 가능
    		list.add(3.14) : 들어오는게 뭔줄 알고 더블을 넣는거 ?
    }

     

    비경계 와일드 카드를 활용한 경계

     

    super : 내 위로만 받을게
    extends : 내 밑으로만 받을게 
    
    클라스 Object <- Food <- Fruit <- Apple
    
    void printList(List<? extends Fruit> list){
    	
    		for(Fruit item : list){
    	                            
    		}
    }
    
    ----------------------------------------------------------------------
    
    void printList(List<? super T> list){
    	
    		for(Object item : list){
    	                               
    		}
    }
    
    
    void printList(List<? super Fruit> list){
    	
    		for(Object item : list){
    	                               
    		}
    }
    
    
    List<? super Fruit> fruits = new ArrayList();
    fruits.add(new Apple());
    fruits.add(new Fruit());
    fruits.add(new Food()); // compile error  : fruits 가 List<Fruit> 인경우는 Food 상위 클라스를 추가 못함
    // 컴파일 에러

     

     

    제네릭 타입의 내부 동작

     

    자바의 중요한 개념중 하나는 하위 버전을 지원한다라는 것이 있다.

    즉 이전 자바 버전의 코드와의 호환성을 위해 제너릭 코드는 컴파일되면 , 제너릭 타입은 사라지게 된다.

     

    다음과 같은 Generic type 을 사용하는 노드 클라스가 있다.

    public class Node<T> {
    
        private T data;
        private Node<T> next;
    
        public Node(T data, Node<T> next) {
            this.data = data;
            this.next = next;
        }
    
        public T getData() { return data; }
        // ...
    }

     

    T 는 경계가 없기 때문에 컴파일시 자바 컴파일러가 Object 로 대체한다.

     

    public class Node {
    
        private Object data;
        private Node next;
    
        public Node(Object data, Node next) {
            this.data = data;
            this.next = next;
        }
    
        public Object getData() { return data; }
        // ...
    }

     

    다음과 같이 경계가 정해진 경우는 

    public class Node<T extends Comparable<T>> {
    
        private T data;
        private Node<T> next;
    
        public Node(T data, Node<T> next) {
            this.data = data;
            this.next = next;
        }
    
        public T getData() { return data; }
        // ...
    }

     

    첫 경계 클라스를 기준으로 타입을 대체한다.

    public class Node {
    
        private Comparable data;
        private Node next;
    
        public Node(Comparable data, Node next) {
            this.data = data;
            this.next = next;
        }
    
        public Comparable getData() { return data; }
        // ...
    }

     

    메서드도 클라스의 설명과 같이 컴파일러가 데이터 타입을 맞는 클라스로 대체한다.

    Bridge 메서드

    - 오버라이딩이 된 메서드를 다형성의 보존을 위해 오버로딩 메서드를 컴파일러가 만든 메서드

     

    다음과 같은 상황이 있다.

     

    class Node<T> {
        public T data;
    
        public Node(T data) {
            this.data = data;
        }
    
        public void setData(T data) {
            this.data = data;
        }
    }
    
    class CustomNode extends Node<Integer> {
        public CustomNode(Integer data) {
            super(data);
        }
    
        @Override
        public void setData(Integer data) {
    
            this.data = data + 30;
        }
    }

     

    다음과 같이 ,  커스텀 노드를 생성후 업캐스팅 이후의 문자열 값을 넣으면 , RuntimeError 가 발생한다.

    public static void main(String[] args) {
    	CustomNode node = new CustomNode(1);
        Node node2 = node; // 업캐스팅
    	node2.setData("Hello");
        // ! CustomNod.setData() 는 정수 타입만 받기 때문에 ClassCastException
    }

     

    서문의 런타임에러를 방지하는 역할을 위한 제너릭이 컴파일 에러가 아닌 런타임으로 에러가 난다.

     

    다음과 같이 코드를 변경하면 , :에러가 발생하지 않는다.

    // 에러가 발생하지 않는다고 옳은 동작은 아니다. custom class 의 함수 호출이아닌 부모클라스 setData 메서드를 호출 하게된다 .

    class Node {
        public Object data;
    
        public Node(Object data) {
            this.data = data;
        }
    
        public void setData(Object data) {
        	// print("this is node)
            this.data = data;
        }
    }
    
    class CustomNode extends Node<Integer> {
        public CustomNode(Integer data) {
            super(data);
        }
    
        @Override
        public void setData(Integer data) {
    	//	print("this is customNode)
            this.data = data + 30;
        }
    }

     

    부모와 자식클라스에 있는 setData 가 컴파일시 타입이 소거 되면서 오버라이딩이 아닌 오버 로딩 처리가된다.

    코드를 작성한 사람의 의도는 부모 클라스를 상속하여 하위 자식 클라스의 메서드에서만 받는 것이지만 , 의도하지 않게  객체 타입을 받는 코드를 런타임시 받게 된다.

     

    이러한 의도하지 않은 동작을 없에기 위하여 , 컴파일러는 런타임에 해당 제너릭 타입의 타입 소거를 위한 BridgeMethod 를 생성한다.

     

    외부에서 메서드 호출시 컴파일러가 만든 메서드를 가장 먼저 호출하고 이후에 오버라이딩이 된 메서드를 호출 한다.

    -> 정상적으로 함수를 호출하였기 때문에 예외가 발생한다. (ClassCastException)

    class CustomNode extends Node {
        public CustomNode(Integer data) {
            super(data);
        }
        
        //compiler Bridge Method 가장 처음 받는 메서드
         public void setData(Object data) {
            this.setData((Integer) data);
        }
    
        @Override
        public void setData(Integer data) {
    	//	print("this is customNode)
            this.data = data + 30;
        }
    }

     

     

     

     

     

     

    참고 )

     

    문서 ) 

    Oracle JDK 

    https://docs.oracle.com/javase/tutorial/java/generics/eraure.html

     

    Lesson: Generics (Updated) (The Java™ Tutorials > Learning the Java Language)

    The Java Tutorials have been written for JDK 8. Examples and practices described in this page don't take advantage of improvements introduced in later releases and might use technology no longer available. See Java Language Changes for a summary of updated

    docs.oracle.com

     

    영상) 

     

    설명을 너무 잘해주신 유투브 주소이다 . : https://www.youtube.com/watch?v=Vv0PGUxOzq0

     

    블로그 

    -> 공식 문서를 번역 + 해석한 글

    - 제너릭 타입 소거 컴파일 

     

    ☕ 자바 제네릭 타입 소거 컴파일 과정 알아보기

    제네릭 타입 소거 (Erasure) 제네릭은 타입 안정성을 보장하며, 실행시간에 오버헤드가 발생하지 않도록 하기위해 JDK 1.5부터 도입된 문법으로, 이전 자바에서는 제네릭 타입 파라미터 없이 자바를

    inpa.tistory.com

     

    - 자바[JAVA] 제너릭 의 이해

     

    자바 [JAVA] - 제네릭(Generic)의 이해

    정적언어(C, C++, C#, Java)을 다뤄보신 분이라면 제네릭(Generic)에 대해 잘 알지는 못하더라도 한 번쯤은 들어봤을 것이다. 특히 자료구조 같이 구조체를 직접 만들어 사용할 때 많이 쓰이기도 하고

    st-lab.tistory.com

     

Designed by Tistory.