이펙티브 자바- 제네릭(Generic)

Coodori·2024년 2월 16일
0

Study

목록 보기
10/10
post-thumbnail

모든 방면에서 탄탄하고 기술도 아는 개발자가 되고싶다....

문제 발생) 자바의 문법에 대해서 알고있는 것도 다시 한번 두들겨 보자

요즘 많은 기업들이 요구하는 기업스택들과 다양한 자바프로그래밍들이 있다. 하나 하나 쌓아 올려가던 도중 무언가를 할때 가장 중요한건 기본기의 기반이 아닐까 라는 생각이 들었다.

사용법은 알지만 정확하게 짚고 넘어가고 다시 한번 두들겨보자는 생각에 이펙티브 자바 책을 필요할때 원하는 부분과 이론적인 부분을 정확하게 넘어가서 기본기가 단단한 개발자가 되자는 생각에 해당 포스팅을 적는다.

제네릭이란?

일반적인 이라는 뜻이다.
데이터 형식에 의존하지 않고 하나의 값이 여러 다른 데이터 타입들을 가질 수 있도록 하는 방법이다.

클래스 내부에서 지정하는 것이 아닌 외부에서 사용자에 의해 지정되는 것을 의미한다.

미리 지정하는 것이 아닌 필요해 의해 지정 => 다양한 메소드,클래스에서 확인가능

여기서 <> 안의 Integer가 제너릭이다.

장점

  1. 캄파일 단계에서 제네릭 검사를 하여 잘못된 타입을 방지한다.
    => 가장 좋은 것은 컴파일 에러이다.

  2. 클래스 외부에서 타입을 지정해주기에 관리가 편하다.

  3. 코드 재사용성이 높아져 객체지향프로그래밍이 가능하다.

규칙(꼭 지켜야하는것은 아님)

<T> - TYPE
<E> - Element
<K> - Key
<V> - Value
<N> - Number

클래스 및 인터페이스

제네릭타입은 하나 또는 여러개로 지정이 가능하다.
이렇게 Generic_AGeneric_B를 선언한다.

사용법은 제네릭은 외부에서 타입을 결정하기 때문에 객체를 생성할때 당시 구체적인 타입을 명시해 주어야한다.

만약 뒤쪽 <>에 생략을 했을 경우 앞쪽 타입과 같은 타입으로 선언이 된다.

주의점은 타입 파라미터로는 참조 타입만 가능하다.
int,double,char 등 기본형은 올수 없다.
즉) 직접 만들어준 클래스도 가능하다.

이제 제네릭의 장점을 살펴보자

이러한 제네릭 클래스가 있다.

<E> 처럼 타입을 적어주면 외부에서 객체를 생성할때 당시 해당 타입을 결정 해줄수있다.

  • String 으로 지정해주었을때는 제네릭 타입이 String 으로 변경
  • Integer 일땐 Integer로 변경

여러 API에서 볼 수 있다.

제네릭 메소드

<E>를 붙여서 클래스 내에서 타입을 검증할수 있는 수단으로 사용을 하여 재사용성을 높였다.
하지만 그 외에도 메소드에 한정된 제네릭 사용 가능하다.

[접근제어자] <제네릭타입> [반환타입] [메소드명]([제네릭타입] [파라미터]){
}

string ==== java.lang.String
integer ==== java.lang.Integer

즉, genericMethod()는 파라미터 타입에 따라 T가 결정된다.

이러한 방식이 필요한이유는 static 메소드로 선언 할때 필요하다.

이펙티브 자바나 다른 자바를 공부하다보면 static 메소드는 싱글턴 방식보장 하기위해 정적으로 미리 프로그램 실행시에 메모리에 이미 올라가 있다는 것을 알수 있다.

객체 생성을 통해 접근할 필요없이 이미 메모리에 올라가있다는 것을 알수 있다.

하지만 미리 생성한다는건 외부에서 타입을 넣어주지 않아 추론이 불가능하다는 것인데 타입을 어디서 얻어오는 것일까?

실제로도

class GenericMethod<E> {
	static E genericMethod(E o) {	
		return o;
	}
	
}
 
class Main {
 
	public static void main(String[] args) {

		GenericMethod.getnerMethod(3);
 
	}
}

해당은 에러의 결과를 내준다.
이미 메모리에 로드되어 접근은 가능하지만 타입을 지정할 방법이 없어서 에러가 나는 것이다.

그래서 아까 우리가 사용한 방법인

        static <E> E staticGenericMethod(E o){
            return o;
        }

처럼 적어주는 것이다.

사실상 여기서 E타입은 제네릭 클래스 E타입과 다른 독립적인 타입이라고 볼 수 있다.
제네릭 메소드는 제네릭 클래스 타입과 별도로 지정이 된다는 것이다.

제네릭 프로그래밍은 한마디로 <> 괄호 안에 타입을 파라미터로 보내 제네릭 타입을 지정해주는것.

제네릭 메소드는 정적(static)사용시에 이미 메모리 로드가 되어있는 타입을 추론시켜주기 위해 사용하는 것이다.

public class GenericMethod {

    public static void main(String[] args) {
        GenericMethodTest<String> test  = new GenericMethodTest<>();

        String string = "hello";
        Integer integer = 123;
        System.out.println("string ==== "+ test.genericMethod(string).
                getClass().getName());
        System.out.println("integer ==== " + test.genericMethod(integer).
                getClass().getName());

        System.out.println("***************** ");
        System.out.println("static String === "+ GenericMethodTest.staticGenericMethod(string)
                .getClass().getName());
        System.out.println("static integer === "+ GenericMethodTest.staticGenericMethod(integer)
                .getClass().getName());

    }

    static class GenericMethodTest<E>{
        private E element;

        void set(E element){
            this.element= element;
        }

        E get(){
            return element;
        }

        <T> T genericMethod(T o){ /// 주목
            return o;
        }

        static <E> E staticGenericMethod(E o){
            return o;
        }
    }
}

string ==== java.lang.String
integer ==== java.lang.Integer


static String === java.lang.String
static integer === java.lang.Integer

제한된 Generic(제네릭)과 와일드카드

해당 부분이 이펙티브 자바에서 나온 부분이다.

현재까지는 T에 String을 넣으면 String이 되고 다른 직접 만든 클래스를 넣으면 그것이 되었다.
즉, 제네릭은 참조 타입이면 뭐든 가능했다.

하지만 개발자가 아무거나 넣어서 의도된 기능을 수행하지 못한다면...?
String 에게 이름을 꺼내달라고하면...?
Int에게 성별을 꺼내달라고하면....?

이러한 이유때문에 특정 범위내로 좁혀서 제한하여 제네릭을 선언한다.

총 3가지

  1. super : 하한 경계
  2. extends : 상한 경계
  3. ?: 와일드 카드

K extends T VS ? extends T 차이점

유형 경계 지정은 같으나 K는 특정타입으로 지정이 가능하지만 ? 는 타입이 지정되지 않는다.

  1. K extends Number
  • Number를 상속하는 Integer, Double 등이 가능하며 타입이 지정되고 메소드를 호출할 경우 해당 지정된 타입으로 변환
  1. ? extends Number
  • Number를 상속하는 Integer, Double 등이 가능하며 타입이 지정되고 메소드를 호출할 경우 지정 타입이 없어 타입 참조 불가능

결론적으로 특정 타입의 데이터를 조작하고자 하면 K 같은 제네릭 인수로 지정을 해주어야한다.

static class Card <E extends Comparable<? super E>> { }

을 예로 들자

이 메소드에는 총 2가지가 명시되어있다.

  1. E extends Comparable
  2. ? super E

1번부터

Comparable 를 상속받은 E여야하니깐 Comparable은 최상위 타입이 된다.
고로 E는 무조건 Comparable을 구현해야한다.

	    public static void main(String[] args) {
        Card<CardShape> eCard = new Card<>();
    }
    
    static class CardShape implements Comparable<CardShape>{

        @Override
        public int compareTo(CardShape o) {
            return 0;
        }
    }

이렇게 하게되면 CardShape는 상위타입인 Comaprable를 구현했기에 해당 자리에 파라미터로 들어갈 수 있다.

즉, 상위타입을 extends한다는 것은 해당 상위타입을 필수로 구현하라는 뜻이다.

2번

해당은 만약 CardShape 말고 모든 도형의 모양을 받아와서 비교해야할 경우는 어떨까?

이것을 Up-Casting 이라고 표현한다.

<E extends Comparable<E>> 일경우 E에 CardShape가 들어가면 무조건 뒤에도 CardShape가 들어가기에 타입 미스매칭이 될 것이다.

즉, \<? super E> 를 해주면서 업캐스팅이 발생하더라도 안정성을 보장 받을 수 있는 것이다.

public static void main(String[] args) {
        Card<CardShape> eCard = new Card<>();
    }

    static class Card <E extends Comparable<? super E>> { }

    static class Shape{
        //code
    }
    static class CardShape extends Shape implements Comparable<Shape>{

        @Override
        public int compareTo(Shape o) {
            return 0;
        }
    }

?

public class ClassName extends Object {} 와 다른것이 없다.
어떤 타입이 와도 좋다는 의미이다.

데이터 중심이 아닌 기능 사용에만 관심이 있는 경우에 사용할 수 있다.

결론

자바 7까지는 명시적인 타입인수를 사용해야했다.

하지만 와일드카드, 제네릭이 나오면서 API를 유연하게 만들수 있게 되었고 객체지향 프로그래밍의 재사용성을 높일 수 있었다.

또한 super extends를 통해 구현해야할 인터페이스 혹은 상위 타입에 대한 업캐스팅을 안전하게 명시해주었다.

컴파일 오류를 즉시 검사하여 곤란한 상황에서 벗어나게 해주었으며 개발자를 편하게 만들어준 길이였다.

REFERENCE

이펙티브 자바 책
https://st-lab.tistory.com/153

profile
https://coodori.notion.site/0b6587977c104158be520995523b7640

0개의 댓글