제네릭 보세요오오!!!!!!!!!!!!!!

GO-TE·2023년 12월 4일
1

TIL

목록 보기
2/2
post-thumbnail
??? : 이렇게!!!!!!이러케!!!!!! 이럭계!!!!!!!!!

들어가기 전

제네릭… 이라고 생각했을 때, 그냥 아무 타입 넣고 싶을 떄 쓰는거 아니야? 라는 막연한 생각만이 있었다.
구현 과제 내줬을 때 제네릭을 떠올리며 썼던 적도 없고… 그냥 개념적으로 알고 있었지 활용까지는 힘든 것 같다.
유데미 자바 강의를 듣고 제가 이해 한 대로 쓰는 것이니, 틀린 부분 있으면 지적 환영 호감도 99 드리겠습니다.


제네릭?

  • 자바에서 데이터 타입을 일반화 하는 것을 의미한다.
  • 제네릭은 클래스나 메소드에서 사용할 내부 데이터 타입을 컴파일 시 미리 지정하는 방식이다.
  • 런타임 환경에 아무런 영향이 없는 컴파일 시점의 전처리 기술이다.

이것만 보고 제네릭을 이해했다면 그냥 천재하세요.

이해엔 코드가 딱이야… 이코딱

ArrayList<Integer> list = new ArrayList<>();

저저 각괄호가 보이는가? 저게 제네릭이다 이말이야 (<> 를 다이아몬드라고 하더라)
저 괄호 안에 무엇을 넣어도 그에 맞는 ArrayList를 쓸 수 있지 않았나 ?
범용성 있게 데이터 타입에 구애받지 않고 쓸 수 있게 하는 것이 제네릭이다.

<> 를 가지는 클래스와 인터페이스를 말한다 ! 메소드도 가능

그래서 왜 씀 ?

코드를 보자

public class CustomList {
	ArrayList<Integer> list = new ArrayList<>();

	public void addElement(int element) {
		list.add(element);
	}
}

나만의 List를 만들어보자. 인스턴스를 만든다면 원소는 int형만 만들수 있다. 아까도 말했 듯이 데이터 타입에 구애받지 않고 범용성 있도록 만들어준다.

public class CustomList<T> {
	ArrayList<T> list = new ArrayList<>();

	public void addElement(E element) {
		list.add(element);
	}
}

제네릭을 활용하면 데이터 타입에 구애 받지 않을 수 있다.

두번째로 불필요한 타입 변환을 제거해준다.

이거슨 또 무슨말이나면

List list = new ArrayList();
list.add("hello");
String str = (String) list.get(0);

제네릭을 사용하지 않으면 이렇게 귀찮게 해줘야 한다. 우리의 시간은 소중하니까 애용하자

마지막으로 컴파일때 알아서 강하게 타입 체크를 해준다.

컴파일 때 에러를 미리 다 잡아버려서, 실행중에 에러 RuntimeException을 방지해준다.
이렇게 잡아주면 디버깅할 때 편하다고 GPT가 그랬다.

정리

  1. 인터페이스, 클래스, 메소드를 범용성 있게 만들어준다.
  2. 불필요한 타입 변환을 제거해준다.
  3. 컴파일 시 강한 타입 체크를 해준다.

와일드카드

이건 또 뭐냐? 싶겠지만 라이브러리 좀 뜯어본 사람들이라면 많이 봤을 것이다.

public static double sum(List<? extends Number> list) {
	double sum = 0d;
	for (Number number : list) {
			sum += number.doubleValue();
	}
	return sum;
}

매개변수를 보면 List<? extends Number> 에서 “?”가 와일드 카드다.
저게 머임 ??? 싶겠지만… 쉽게 말해서 제네릭에 올 수 있는 타입을 제한 하도록 쓰는거다.

코드를 대충 해석해보면
리스트를 받는다 → 리스트의 모든 원소들을 더한다 → 그 더한 값을 리턴한다.
근데 더할 수 있는건 상식적으로 숫자만 가능하다.
그러면 Number Class를 상속 받은 애들만 오도록 하는거다. String이나 char이 오면 더하지를 못하니까 !
와일드카드는 제네릭으로 올 수 있는 타입을 제한한다.

와일드 카드는 크게 두개로 볼 수 있다.

  • 상한 경계 와일드카드 (Upper Bounded Wildcard) 와일드카드 타입에 extends를 사용해서 와일드카드 타입의 최상위 타입을 정의한다. 위 예시로 든 코드가 상한 경계 와일드카드라고 볼 수 있겠다.
  • 하한 경계 와일드카드 (Lower Bounded Wildcard) 얘는 직감적으로 상한과 반대 관계라고 느낄 수 있겠다.
    super를 이용해 와일드카드 타입의 최하위 타입을 정의한다. 요런식으로 말이지
static void addNumbers(List<? super Number> numbers) {...}

231229 추가

마무리에서 언급했듯이, 와일드카드가 이해 안되는 부분에 다뤄보겠다.

상한에서 get, 하한에서 set이 안되는 이유를 알아보기 전에 알아야 할 것이 있다.

공변, 불공변 (covariant, invariant)

  • 공변 : A가 B의 하위 타입일 때, T<A>T<B>의 하위 타입이면, T는 공변
  • 불공변 : A가 B의 하위 타입일 때, T<A>T<B>의 하위 타입이 아니면, T는 불공변

갑자기 이걸 왜 알아야하냐고 물어본다면, 자바에서 배열은 공변으로 정했고, 제네릭은 불공변으로 하기로 했기 때문에 알아야한다.
이를 바탕으로 예제 코드를 보자.

public static void main(String[] args) {
  Integer[] integers = new Integer[] {1, 2, 3};
  printArray(integers);
}
  
void printArray(Object[] arr) {
	for (Object e : arr) {
	System.out.println(e);
	}
}

위 코드는 배열에 따른 공변을 보여주기 위함이다. 정상적으로 잘 실행되며, Integet는 Obeject를 상속하고, 배열은 공변이므로 Integer를 넣어도 잘 실행된다.

public static void main(String[] args) {
	List<Integer> list = Arrays.asList(1, 2, 3);
    printCollection(list);
}

void printCollection(Collection<Object> c) {
	for (Object e : c) {
    	System.out.println(e);
    }
}

위 코드는 컴파일 에러가 발생한다. 이유는 제네릭은 불공변이기 때문이다.
그러면 어케함? 이라는 생각이 들텐데, 그래서 와일드카드를 배운 이유가 여기있다.

어케하는데?

직감적으로 위 코드를 보았을 때, 될 것 같지만 안되는 상황을 보며 불편함을 호소할 수 밖에 없다. 그래서 와일드카드가 있는거다. 요로케 사용하면 된다.

public static void main(String[] args) {
	List<Integer> list = Arrays.asList(1, 2, 3);
    	printCollection(list);
}

void printCollection(Collection<?> c) {
	for (Object e : c) {
    	System.out.println(e);
    }
}

하지만 와일드카드는 모든 타입에 대해서가 아닌, 익명의 타입을 뜻한다. (unknown type)
이 부분을 코드로 보자

public static void main(String[] args) {
	Collection<?> c = new ArrayList<String>();
    c.add(new Object());

위 코드를 보자. 컴파일 에러가 발생한다. 왜???????
일단 add를 해주려면 제네릭타입인, E 혹은 E의 자식을 매개변수로 줘야한다. 근데 와일드카드는 익명이므로 Number, Integer, 사용자 정의 클래스 등등 아무거나 다~넣을 수 있다. add로 넘겨주는 파라미터가 unknown 타입을 상속받은 클래스여야 하는데, 제한을 두지 않아 파라미터가 자식인지 뭔지 구분을 할 수가 없다.

근데 get은 가능하다. 왜냐면 값을 꺼낸 결과가 무엇이든 간에 확인이 필요하지도 않고, 적어도 Object를 상속받는다는 것을 보장가능하기 때문이다.


마무리

당신도 이제 제네릭을 쓸 수 있게 되었다.

하지만 와일드카드는 내가 찾다 보니 이해가 좀 많이 안되는 부분이 있었다.
(상한에서 set, 하한에서 get이 외않되?)
20231229추가
조금씩 정리해서 다시 포스팅 하도록 그러도록 노력해보도록 하겠다.
생각보다 자바에 대해 모르는게 천지삐까리였다. 내일도 해야징 나 자신 화이팅!!!!!!!!!!!!!!!

Reference
[Java] 제네릭과 와일드카드 타입에 대해 쉽고 완벽하게 이해하기(공변과 불공변, 상한 타입과 하한 타입) [MangKyu's Diary:티스토리]

profile
늘 배우는 자세로

0개의 댓글