4/26, 4/29 지네릭스

박세현·2024년 5월 16일

JAVA

목록 보기
16/22
post-thumbnail

지네릭스

1. 지네릭스가 등장한 이유

  • 다양한 형태의 자료형을 쓰기 위해 사용함
    4/29 11:38시점

  • 다양한 형태의 자료형 사용 시 기존 Object클래스를 활용한 경우

    • 1) 형변환의 번거로움
    • 2) 타입 안정성이 떨어지는 문제

    가 존재함

  • 이러한 단점을 보완하고 다양한 형태의 자료형을 쓸 수 있게끔 하도록 나온게 지네릭스


예시)

ㄴ Box클래스 정의
ㄴ 자료형 : Object
ㄴ 멤버변수 item 정의 // 접근제어자 private
ㄴ get(), set() 함수 정의

ㄴ Apple클래스 정의
ㄴ 자료형 : String
ㄴ get() 함수 정의

ㄴ Grape클래스 정의
ㄴ 자료형 : String
ㄴ get() 함수 정의

ㄴ 오류발생 : 반환값 자료형이 Apple이 아니라 Object라서 자료형이 안맞음

ㄴ 자료형 Object 로 맞춰 줌
ㄴ 어? 근데 자료형이 축소되니 Apple에 정의된 get()메서드에 접근이안되네?
ㄴ 자료형을 넓혀주자ㅏㅏ

ㄴ Object : 모든 클래스의 상위 클래스
ㄴ 상위클래스 -> 하위 클래스 : 강제 형변환, instanceof
ㄴ 자료형을 Apple로 넓히니 접근이 되는 군

ㄴ 단점 1) 매번 이렇게 형변환 하기 귀찮음

ㄴ 상위클래스 -> 하위 클래스 : instanceof, 강제 형변환
-> 자료형은 넓어졌지만 내께 아닌것의 자료형을 가지고 올 것을 대비해 instanceof로 매번 확인해줘야 함
= 단점 2) 타입 안정성이 떨어진다




2. 지네릭스란?

  • 다양한 형태의 자료형을 쓰기 위해 사용함
  • 지네릭스는 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일시의 타입체크(compile-time type check)를 해주는 기능이다.
  • 객체의 타입을 컴파일 시에 체크하기 때문에 객체의 타입 안정성을 높이고 형변환의 번거로움이 줄어든다
  • 다룰 객체의 타입을 미리 명시해줌으로써 번거로운 형변환을 줄여준다.

참고) 지네릭스 사용 이전에는...
다양한 자료형을 수용 -> Object 클래스 사용

Object 클래스 사용 단점
1) 타입 안정성 X // instanceof
2) 형변환의 번거로움 // 강제형변환




2. 지네릭 클래스의 장점

예시)

ㄴ 지네릭 클래스 적용

ㄴ 자료형 명시해주니 바로 형변환 과정 없이 Apple의 get()메서드가 접근 가능
-> 형변환 번거로움 해결

ㄴ 내꺼가 아닌 하위 자료형 담을 수x // instanceof 할 필요x
-> 타입의 안전성 확보

참고) instanceof : 객체의 생성된 출처를 체크해보는 연산자




3. 지네릭스의 용어

1) 지네릭 클래스 : Box<T>

class Box<T> { 
  	...
    ...
}


2) 타입매개변수 : T

package exam05;

public class Box<T> {
    private T item;

    public void setItem(T item) {
        this.item = item;
    }

    public T getItem() {
        return item;
    }
}
package exam01;

public class Ex01 {
    public static void main(String[] args) {
        Box<Apple> appleBox = new Box<Apple>();
    }
}

ㄴ T가 Apple로 자료형 설정됨



3) 원시타입 : Box

class Box<T> { 
  	...
    ...
}



4. 지네릭클래스 특징

  • 지네락 클래스의 자료형 결정 시점
    -> 객체를 생성하는 시점에 타입 힌트를 통해서 형변환이 발생
    ex) Object -> Apple
    Box<Apple> appleBox = new Box<Apple>();
  • 지네릭 타입의 자료형은 객체 생성 시점에 결정되므로 처음부터 자료형이 결정되어야 하는 변수에는 사용 불가
    • 정적 변수는 사용 불가
    • 배열 사용 불가



4. 지네릭스의 제한

1) 타입 매개변수와 동일한 자료형

2) static멤버에 타입 변수 T를 사용할 수 없다.

3) 지네릭 타입의 배열을 생성하는 것도 허용하지 않는다.




5. 지네릭 클래스의 객체 생성과 사용


타입 매개변수는 컴파일시 제거 -> Object -> 객체가 생성될때 타입 매개변수의 자료형으로 형변환
(타입은 객체 생성시 결정)

사용의 제한점)
static 멤버 변수에는 타입 매개변수 사용 불가

  • 처음부터 자료형이 명시 되어야 공간을 할당

배열 생성 불가

  • new 연산자 : 배열 공간 생성 : 명확한 자료형을 알아야 공간을 계산 ..

예시) 지네릭클래스의 자료형 결정 시점

  • 컴파일 시점에는 자료형이 명확하게 결정되어 있어야 컴파일 가능
    -> 컴파일러가 알 수 있는 형태로 자료형을 결정

<T> : 형식상 오류
-> 컴파일 시점에 형식상의 오류를 해결하기 위해서 지네릭 타입은 모두 제거

ㄴ 근데 자료형이 제거된 상태로도 컴파일 할 수 없음

ㄴ 컴파일시점에는 자료형이 명확하게 결정되어야 컴파일 가능한데 자료형이 뭐가 될지 모르므로 어떠한 자료형도 될수 있는 자료형(Object)으로 설정
= 모든 클래스는 Object의 하위 클래스임을 알고 있다 -> 자료형을 Object로 결정
ㄴ 그럼 자료형 결정 시점은 언제?
-> 객체를 생성하는 시점에 타입 힌트를 통해서 형변환이 발생
ex) Object -> Apple
Box<Apple> appleBox = new Box<Apple>();



예시2) 지네릭 클래스에 정적 변수는 사용 불가

private static T item2; : 오류발생
ㄴ 왜? 처음부터 공간을 할당 받으므로 자료형이 명확해야 함
= 지네릭 클래스의 자료형은 객체 생성 시점에 결정되므로 처음부터 자료형이 결정되어야 하는 변수에는 사용 불가



예시3) 지네릭 클래스에 배열 사용 불가

ㄴ 오류 발생
ㄴ 왜? new 연산자 때문 : 메모리에 공간 생성 역할
ㄴ 배열에서 공간 생성을 위해서는 자료형이 명확해야 함
ㄴ 공간을 만들기 위해선 공간의 크기가 명확해야 생성가능함
ㄴ 지네릭 클래스의 자료형은 객체 생성 시점에 결정되므로 처음부터 자료형이 결정(= 공간의 크기가 결정)되어야 하는 배열에는 사용 불가

예시)

ㄴ Box 지네릭 클래스 정의
ㄴ 멤버변수 item 정의 (자료형 : T, 접근제어자 : private)
ㄴ set(), get() 메서드 정의

ㄴ Apple 클래스 정의
ㄴ get() 메서드 정의 (자료형 : String)

ㄴ Grape 클래스 정의
ㄴ get() 메서드 정의 (자료형 : String)

<Apple> : 사용하고자 하는 자료형 명시 -> Box 클래스에 있는 T가 Apple이 됨

참고) Box 클래스에 있는 T가 Apple이 됨

ㄴ 앞에있는 자료형만 보고도 무슨자료형인줄 알 수 있기 때문에 뒤에는 생략가능

ㄴ 들어있는 값 확인을 위해 toString 메서드 재정의

Apple apple = appleBox.getItem(); : "사과"가 조회되길 의도했을 것임

ㄴ item이 Apple클래스의 주소를 같고 있을거라 예상하고 봤는데 Apple클래스의 get()가 안보이넹
-> 보이지 않는 이유가 있음
-> 지네릭클래스 시점 = 컴파일 시점 ≠ 객체생성 시점
-> 지네릭 클래스의 자료형 결정 시점 : 객체생성시

ㄴ 컴파일 시점에는 자료형 Object = 즉, Box 클래스 내부에선 아직 자료형이 Object
ㄴ 객체생성 시점에 자료형이 Apple

ㄴ 컴파일 시점에는 자료형이 Object임
ㄴ 아직 자료형 Apple 아님

ㄴ 자료형이 뭐가 될 지 모르는 상태니까(Apple일수도 있고 Grape일수도 있고...) Apple의 get메서드 호출 안됨

ㄴ 힌트를 조금 더 주면 컴파일 시점에도 자료형이 Object가 아닌 다른 자료형으로 좁힐 수 있지 않을 까?
-> 다형성 함 이용해보자

ㄴ Apple 과 Grape의 공통점으로 묶어서 설계를 추가하고 정의 : Fruit 추상클래스 정의
ㄴ 틀만 정하는 형태의 클래스

참고) Fruit클래스를 인터페이스로 만드는게 더 적합할 수도 있겠지만 좀 더 기능을 보여주기 위해 추상클래스로...

ㄴ Fruit 추상클래스 상속받음

ㄴ Fruit 추상클래스 상속받음

ㄴ T라는 클래스가 어느 특정클래스의 하위클래스 임을 명시하면 컴파일 시점에 굳이 Object가 아닌 특정클래스로 바꿈
ㄴ T의 상위 클래스가 Fruit이 됨으로서 굳이 모든 자료형이 될 수 있는 Object가 아닌 조금 더 한정된 자료형인 Fruit이 된다
ㄴ T가 Fruit이 되었다 해도 get()이 Apple일지 Grape일지는 객체 생성 시점에 알 수 있다

ㄴ 컴파일 시점 자료형 : Fruit
-> T가 Fruit의 하위 클래스라는 정보가 있으므로 컴파일 시점의 T자료형을 Object가 아닌 Fruit로 변경
-> 상속을 통해 컴파일 시점 자료형을 Object에서 Fruit로 한정함

참고) 자료형 구조도

ㄴ T는 이제 Fruit
ㄴ 이제 get이 보인다
= Grape의 get()일수도 있고 Apple의 get()있는 상태
-> 호출 시 Grape의 get()일지 Apple의 get()일지 정해 짐

ㄴ 이제 "사과" 바로 출력 가능

정리!!

예시) &

ㄴ Eatable인터페이스 정의

ㄴ Box클래스가 Eatable인터페이스 상속받음

ㄴ 오류 발생
ㄴ 자료형이 Fruit이면서 Eatable 인터페이스도 구현한 자료형만 T에 들어가는게 가능

ㄴ Eatable 인터페이스 구현
ㄴ 오류해결
-> Grape는 Box 클래스의 T에 담지 못함
왜? Eatable 인터페이스 구현안해서




6. 제한된 지네릭 클래스

  • <T extends 타입> -> T 는 타입의 하위 클래스
  • <T extends 타입1 & 타입2> -> T는 타입1의 하위 클래스 이고 타입2 인터페이스의 구현 클래스


예시)

ㄴ extends : 자료형 한정할 때 사용
ㄴ & : 만써도 인터페이스를 굳이 implement 통해서 상속안받아도 implement 처럼 기능함, 형식임
= extends ... &... 뒤는 인터페이스라고 보자


ㄴ Fruit : 클래스, Eatable : 인터페이스

ㄴ Fruit이 클래스 일수도 있고 인터페이스 일 수도 있다




7. 와일드 카드

  • <?> : <? extends Object>

  • 상한제한 : <? extends 타입> -> T는 타입의 하위 클래스
  • 하한제한 : <? super 타입> -> T는 타입의 상위 클래스

  • <? extends 클래스형 & 인터페이스형> : 사용불가, 지네릭 클래스에서만 가능

toString : 참조변수만 호출해도 투스트링메서드 호출??

ㄴ 컴파일 시에 형식상 오류라서 <Apple>, <Grape> 사라짐
-> 메서드 중복정의

<?> = Object의 하위 자료형
ㄴ Apple도 가능하고 Grape도 가능


ㄴ 상한제한 하한제한
ㄴ 방향성 기억하기

필기 놓짐...4.29 10시부터 다시보자...ㅠ


예시)

ㄴ Apple 클래스에 toStirng() 메서드 재정의
-> 참조변수만 출력해도 주소값이 아닌 "사과" 출력

ㄴ Grape 클래스에 toStirng() 메서드 재정의
-> 참조변수만 출력해도 주소 값이 아닌 "포도" 출력

ㄴ Jucier 클래스 정의
ㄴ static make() 메서드 정의

ㄴ 지네릭 클래스 정의
ㄴ add()메서드 정의
ㄴ get()메서드 정의

ㄴ 출력

ㄴ 메서드 오버로드 : 매개변수 자료형, 갯수 등등 이 다르면 같은 이름의 메서드를 사용하지만 다른 함수임
ㄴ 음? 근데 왜 에러가 뜨지?
ㄴ 동일한 함수라고 함 = 메서드 중복 정의
ㄴ 왜? 매개변수 자료형이 Apple, Grape로 다른뎅ㅠ

ㄴ 지네릭클래스의 자료형은 객체 생성시에 결정되고 그전인 컴파일시점에는 <타입매개변수> = 형식상 오류로 인식해 컴파일러가 삭제하고 무슨 자료형이 올지 모르니 다 될수 있는 Object자료형이된 다음 객체생성시점에 찐 Apple, Grape로 자료형이 설정됨
-> 즉 컴파일 시점에는 두 메서드 자료형이 동일함으로 함수의 시그니처가 모두 같아서 메서드오버로드 안되고 메서드 중복 정의라 오류 뜸
ㄴ 음? 그러면 make메서드로는 Apple만 구현 가능하고 Grape는 구현이 안되잖앙
-> 용도가 제한적이야
-> 와일드 카드로 해결하자

<?> : Apple도 가능하고 Grape도 가능
<?> = <? extends Object> = Object의 하위 클래스
<?> : 보통 메서드의 매개변수로 많이 쓰임
ㄴ 지네릭 클래스에 있는 타입매개변수의 자료형이 뭐가 될 지 모를 때 보통 <?> 를 함수의 매개변수나 반환값 타입으로 지정

ㄴ 아 이거 가능하면 안되는데 가능하네
ㄴ 과일만 쥬스로 만들 수 있어야 하는데 장난감으로 쥬스를 만든다니 이상하잖아...
ㄴ 내가 의도한건 이게 아니야
-> 상한제한을 활용하자

ㄴ Fruit 추상 클래스 정의

ㄴ Fruit 추상 클래스 상속받음

ㄴ Fruit 추상 클래스 상속받음

<? extends Fruit> : 상한제한 적용함

ㄴ 상한제한 적용되서 이제 장난감으로는 쥬스 못만듬
ㄴ 가능한 자료형은 Apple, Grape, Fruit



예시) 하한제한

<? super Apple> : 하한제한
ㄴ 가능한 자료형은 Apple, Fruit, Object

ㄴ 에러 뜸 : 하한제한

ㄴ 하지만 Fruit자료형으로는 가능




8. 지네릭 메서드

지네릭클래스<T> vs 지네릭메서드<T>의 차이점

  • 타입을 클래스에 정의하면 : 지네릭 클래스
    ex) class Box<T> : T의 자료형은 객체가 생성될 때 결정

  • 타입을 메서드의 반환값 타입 앞에 정의하면 : 지네릭 메서드
    ex) public <T, U> String method(T str1, U str2); : T, U의 자료형은 함수가 호출될 때 결정

  • 지네릭클래스가 아니면 다 지네릭메서드
  • 지네릭메서드가 아니면 다 지네릭클래스
  • 와일드카드는 지네릭메서드에서만 정의 가능
  • 즉 와일드카드가 있다 -> 지네릭 메서드다
  • 메서드는 호출될때 결정
  • 클래스는 객체만들때 결정

예시) 지네릭 메서드



예시) 지네릭클래스<T> vs 지네릭메서드<T>



예시) 지네릭클래스<T> vs 지네릭메서드<T>



예시) 지네릭클래스<T> vs 지네릭메서드

ㄴ 지네릭 메서드 : 메서드 호출 될 때 결정

ㄴ 지네릭 클래스 : 객체 생성 시 결정

ㄴ 지네릭 클래스 : 객체 생성 시 결정

ㄴ 지네릭 메서드 : 메서드 호출 될 때 결정




9. 지네릭 타입의 제거

profile
귤귤

0개의 댓글