회고록 블로그

[공부 필기] Java 기본 공부하기 (34) 본문

2. 프로그래밍 언어 공부/Java

[공부 필기] Java 기본 공부하기 (34)

김간장 2021. 12. 17. 21:37

공부 중인 강의 : 윤성우 선생님, 윤성우의 열혈 Java 프로그래밍 강의.

링크 : https://cafe.naver.com/cstudyjava

 

윤성우의 프로그래밍 스터디그룹 [C/... : 네이버 카페

윤성우의 스터디 공간입니다. C와 JAVA를 공부하시는 분들은 모두 들어오세요. ^^

cafe.naver.com

 

※ 강의 청강 중 필요한 내용만 필기함

※ 틀린 필기가 있을 수 있음..

 


1. 다중 '타입 매개변수' 정의하기

- 앞에서는 Box<T>와 같이 "T"라는 타입 매개변수를 하나만 정의했었지만, 이제는 2개 이상의 타입 매개변수를 정의해볼 것

 

- 우선 타입 매개변수가 1개일 때 복습하기 (예시 코드)

   → 실제로 이렇게 코드를 구성하지는 않겠지만, 학습 목적이라 대충 짰음

   →LogIn 인스턴스 생성 시 자료형은 LogIn<User>로 한정하겠다는 의미가 담긴 코드

[그림1]

 

- 매개변수가 2개일 때 예시 코드

   → 타입 매개변수의 이름은 반드시 "T"일 필요가 없음

   → LogIn은 제네릭 클래스로 정의됨

   → 그 안에는 타입 매개변수로 U(user를 의미)와 S(system을 의미)가 있음

[그림2]

 

- 타입 매개변수의 이름을 짓는 규칙(관례)

   → 가끔적 [한 문자]로 이름을 지을 것

   → 그 한 문자는 [대문자]로 할 것

   → 보편적으로 아래와 같이 선택해서 사용함 (물론 반드시 이렇게 써야하는건 아님)

         * E (Element)

         * K (Key)

         * N (Number)

         * T (Type)

         * V (Value)

   → 앞서 타입 매개변수가 하나일 때 이름을 "T"를 설정했는데

        사실 "하나의 대문자로 설정할 것"이라는 관례는 있지만

        반드시 "T로 해야한다"는 규칙은 없음(대부분의 사람들이 T를 사용할 뿐임)

 

 

- 주의해야할 사항은 "타입 인자"에는 클래스(객체) 타입만 올 수 있음

   → 즉, 아래와 같이 기본 자료형은 올 수 없음

LogIn<int> login = new LogIn<int>();

   → 하지만, wrapper 클래스는 올 수 있음

LogIn<Integer> login = new LogIn<Integer>();

 

- 매개변수화 타입에 대한 꿀팁 한가지

   → [방법2]와 같이 코드를 작성하면, 컴파일러가 다이아몬드 기호(<>) 사이의 들어갈 객체를 유추해서 채워줌

         * 아래의 코드에서는 <> 사이에 "Person" 객체가 들어감

// 인스턴스 생성 방법1
Box<Person> p1 = new Box<Person>();
// 인스턴스 생성 방법2
Box<Person> p2 = new Box<>();

   → 일반적으로 [방법2]를 많이 사용하는 편 (편하기 때문)

 

2. "매개변수화 타입"(e.g. Box<Person>)을 "타입 인자"(e.g. Person)로 전달

- 용어 정리 : 아래의 코드에서 매개변수화 타입과 타입 인자를 찾기

   → 매개변수화 타입 : Box<Person>

   → 타입 인자 : Person

// 메인 메소드
Box<Person> p = new Box<>();

 

- [매개변수화 타입]도 "타입 인자"로 전달할 수 있음

   → 마치 마트료시카 인형처럼..

   → 예시

public class Box<T> {
	T obj;
	
	public void set(T o) {
		obj = o;
	}
	
	public T get() {
		return obj;
	}
}
Box<Integer> aBox = new Box<>();
aBox.set(3); // 오토박싱

Box<Box<Integer>> bBox = new Box<>();
bBox.set(aBox);

Box<Box<Box<Integer>>> cBox = new Box<>();
cBox.set(bBox);

System.out.println(cBox.get().get().get().toString()); // 실행결과: 3

   → 그림으로 정리하자면 아래와 같음

[그림3]

 

 

3. 제네릭 클래스의 타입 인자를 제한하기

- 제네릭 클래스의 타입 인자를 제한할 수도 있음

   → 이미 [제네릭]을 통해 인스턴스 타입을 제한하고 있는데, 거기에 더해서 제네릭 클래스의 타입 인자도 제한할 수 있음

   → 예를 들면, 제네릭 클래스의 타입 인자로 올 수 있는 인스턴스는 "Person 객체 혹은 Person 객체를 상속하는 인스턴스"로 제한할 수 있음

// Box<T> 제네릭 클래스를 아래와 같이 수정
public class Box<T extends Person> {
	....
}
// 메인 메소드
Box<Student> student1 = new Box<>(); // Student는 Person을 상속하였다고 가정함
Box<Person> person1 = new Box<>(); // Person이 올 수도 있음

   → 위의 코드에서는 프로그래머가 정의한 상속관계를 예시로 보였지만 (e.g. Person과 Student 클래스는 상속 관계)

         Java 기본 라이브러리의 상속관계가 들어가도 마찬가지임

 

 

# 참고사항

솔직히 이미 [제네릭]은 "전달 받는 인스턴스를 제한"할 수 있는 것 같은데

왜 타입 인자까지 제한하도록 문법을 만들었는지 이해가 되지 않는다.

 

강의에서는 '타입 인자를 제한했을 때의 효과'에 대해서 이야기 해준다.

 

불필요하게 전체를 다 받아들이는게 부담스러워 일부만 인자로 올 수 있도록 제한하는 것도 맞지만

타입 인자를 제한 했을 때 부수적으로 따라오는 긍정적인 결과 때문이라고 한다.

 

사실, 예시 코드를 만들면서 가장 답답했던 부분이 "Object 클래스의 메소드만 사용할 수 있다는 점"이었다.

그리고, 타입 인자를 제한 했을 때 부수적으로 따라오는 긍정적인 결과 중 하나도 바로 그걸 해결할 수 있다는 것이다.

 

예를 들어보자.

여기 LogIn 클래스와 GameUser 클래스, WebSiteUser 클래스가 있다.

 

LogIn 클래스는 제네릭 클래스이고, 로그인에 필요한 기능들이 있는 객체이다.

(LogIn 제네릭 클래스에는 GameUser 혹은 WebSiteUser 인스턴스만 전달할 수 있게 할 것이라고 가정)

 

그리고 GameUser는 인게임에서 회원 가입한 사용자, WebSiteUser는 웹사이트에서 회원 가입한 사용자를 담는 객체이다.

[그림4]

 

이제, LogIn 클래스 안에다가

GameUser 혹은 WebSiteUser 객체를 전달받아

그 객체의 username을 비교한 후 회원 가입을 한 사용자인지 확인하는 "checkValidUserInfo 메소드"를 만들려고 한다.

 

문제는 LogIn 제네릭 클래스의 타입 인자로 전달되는 인스턴스가 아직 무엇이 될지 모르기 때문에 (인스턴스 생성 전이기 때문)

Object 클래스(최상위 클래스)의 변수와 메소드만 호출이 가능하다는 것이다.

(userInfo를 GameUser 혹은 WebSiteUser 객체라고 못 박아 두고, 그 객체의 멤버 변수인 username에 접근을 하고 싶지만..

그렇게 할 수가 없음)

[그림4] 전부 Object의 멤버 변수와 멤버 메소드만 접근할 수 있다.

 

컴파일러 입장에서는 타입 인자로 무엇이 올지 모르기 때문에 당연히 위의 상황을 허용해주지 않는다. (에러발생)

 

이때, 타입 인자를 제한하는 게 굉장히 큰 역할을 한다.

 

우선, GameUser와 WebSiteUser의 부모클래스를 만든다. (이하 'User' 클래스라고 함)

그리고 공통된 변수를 부모 클래스 'User'로 옮김.

[그림5]

그리고, "제네릭 클래스 LogIn"의 타입 인자를 extends 키워드로 제한한다.

User 객체를 상속한 객체만 타입 인자로 받을 수 있게.

[그림6]

그리고 checkValidUserInfo를 작성해보자.

[그림7]

그러면 아래와 같이 처음 설계했던 대로 checkValidUserInfo 메소드가 잘 동작하고 원하는 결과를 출력할 수 있다.

[그림8]

 

즉, 불필요하게 많은 클래스를 인자로 전달하는 것을 방지하는 효과도 있지만,

제한한 클래스의 멤버 변수에 접근할 수도 있게 되므로, 제네릭 클래스의 타입 인자를 제한하는 문법을 설계한 것이라고 한다.

##

 

4. 제네릭 클래스의 타입 인자를 인터페이스로 제한하기

- 앞서 "extends 키워드를 통해 타입 인자 제한하기"를 배웠음

   extneds 키워드 뒤에는 "인터페이스"가 올 수도 있음

   → 인터페이스를 직접적으로 구현했거나, 간접적으로 구현한 클래스만 "T"로 전달될 수 있음

public class Box<T extends Comparable> {
	....
}

   → 간접적으로 인터페이스를 구현한 경우는 아래 글 복습

2021.11.17 - [2. 개발 공부/Java] - [공부 필기] Java 기본 공부하기 (18)

 

 

- 이때 아래와 같이 인스턴스를 생성할 수 있음

// 메인 메소드
Box<Person> p = new Box<>(); // Person 클래스에 Comparable 인터페이스를 구현하고 있다고 가정

 

- 타입 인자를 인터페이스로 제한했을 때 생기는 효과로는 "인터페이스에 있는 메소드를 호출"할 수 있다는 점이 있음

   → 물론 인터페이스를 구현하는 클래스에서 추상 메소드를 정의해야겠지만..

public class Person implements Comparable {
	...
	public int compareTo(Object o) {
    	...
    }
}
public class Box<T extends Comparable> {
	...
    public void printResult(T o) {
    	...
        o.compareTo(new Object());
        ...

 

5. 타입 인자를 동시 제한하기

- 타입 인자를 클래스와 인터페이스로 동시 제한할 수도 있음

public class Box<T extends Number & Comparable> {
	....
}

 

 

Comments