회고록 블로그

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

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

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

김간장 2021. 12. 20. 23:17

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

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

 

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

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

cafe.naver.com

 

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

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

 


1. 제네릭에 대해서 더 알아보기

- 제네릭 클래스는 상속 관계를 가질 수 있음

 

- A<T>를 기반으로 만들어진 A<Person> (타입 인자가 Person임)

  B<T>를 기반으로 만들어진 B<Person>이 있다고 가정하자

 

- 이때, A<Person> 인스턴스와 B<Person> 인스턴스는 직접 상속관계로 묶어준 것은 아니지만

  A<T> 클래스와 B<T> 클래스가 상속관계이기 때문에, 해당 클래스에서 파생되어 상속관계를 갖게 됨

 

- UML로 표현하면 아래와 같이 표현..?

 

- 다만 주의할 점은, 타입 인자(= Person)가 같은 것 끼리 상속 관계가 되는 것이지

  타입 인자가 다른 인스턴스까지 상속 관계로 묶이는 것은 아님

- 코드로 표현하면 아래와 같이 표현할 수 있음

   → 예시

class A<T> {
	...
}

class B<T> extends A<T> {
	...
}
// 메인 메소드
A<String> aBox = new A<>("hi!1");
B<String> bBox = new B<>("hi!2");

A<String> aaBox = new B<>("hi!3");
// A<String> aaBox = new B<String>("hi!3"); 와 동일

 

2. 타겟 타입

- 일단 제네릭 메소드에 대해서 다시 복습할 필요가 있음

   → 예시 코드

public class Stuff {
	public <T> Box<T> get(T o) {
		Box<T> b = new Box<>();
		b.set(o);
		return b;
	}
}
Stuff stf = new Stuff(); // 메소드 호출을 위해서 인스턴스 생성 먼저
// 사실 "메소드 호출만을 위해서" 생성한거라, 차라리 static 메소드로 선언하는게 더 나았을듯함

Box<Integer> boxInt = stf.<Integer>get(5); 
// T는 Integer 클래스로 설정됨
// 리터럴이 인자로 왔기 때문에 오토박싱 진행

   → 하지만 앞서 공부한 것과 같이 stf.<Integer>get(5); 문장에서 <Integer>는 생략될 수 있음

         * 생략된다면 컴파일러가 "Box<Integer> boxInt" 문장을 보고 자동으로 적절한 타입 인자(= Integer)를 찾아서 넣어줌

Stuff stf = new Stuff(); // 메소드 호출을 위해서 인스턴스 생성 먼저

Box<Integer> boxInt = stf.get(5);

 

- 사실 타입 인자가 생략된 코드를 보면 알 수 있듯이

  아래 문장을 단순하게 바라보면 인스턴스 생성을 위한 문장이 아니라 [메소드 호출]을 하기 위한 문장으로 보이는게 사실

stf.get(5);

   → 물론 get 메소드까지 들어가서 확인해보면 "인스턴스를 생성해서 리턴"해주고,

         그 "리턴한 값을 boxInt 참조변수에 대입"하는 코드라는걸 알 수 있지만.

 

- 메소드를 먼저 호출한 후

   해당 메소드 안에서 인스턴스를 생성하고, 생성한 인스턴스를 리턴해서 boxInt 참조변수에 값을 대입해주는 순서로 진행되는데

 

- 컴파일러는 놀랍게도 "Box<Integer> boxInt = stf.get(5);"라는 문장을 보면서 "Box<Integer> 타입의 인스턴스가 생성되어 boxInt에 대입될 것"을 추측해줌

   → 1차원적으로 보면 그저 메소드를 호출하는 코드로만 보이는데.. 그걸 조목조목 따져서 판단해주는게 놀라움

       생각보다 스마트한 기계였음..

 

- 어쨌든, 컴파일러가 get 메소드의 "T"를 유추할 때 참고하는 이 "Box<Integer>"

  이걸 [타겟 타입]이라고 부름

Box<Integer> boxInt = stf.get(5);

 

- 사실 Java 문법을 사용하는데 있어서는 이 용어가 주요하지 않을 수 있음

  하지만 공식 문서, 참고 문헌 등을 읽을 때 '타겟 타입'이라는 용어가 나올 때를 대비해서 용어를 기억해두는 것을 권장

 

 

3. 와일드카드 (본격적으로 공부하기 전 사전지식 학습단계)

- 제네릭 메소드와 일반적인 메소드를 다시 복습할 필요가 있음

   → 아래와 같은 클래스들이 있다고 가정

         * 학습을 위한 코드임

// LogIn.java
public class LogIn {
	// 제네릭 메소드
	// 인스턴스 생성하지 않고 메소드 호출하기 위해서 static으로 선언
	public static <T> String checkValidUserInfo(Box<T> o) {
		if(o.get() instanceof User)
			return "로그인 성공";
		else
			return "로그인 실패";
	}
// Box.java
public class Box<T> {
	T obj;

	public void set(T o) {
		obj = o;
	}
	
	public T get() {
		return obj;
	}
}
// GameUser.java
public class GameUser extends User {
	GameUser(String name, String pw) {
		super(name, pw);
		this.uniqueCode = 0;
	}
}
// 메인 메소드
public static void main(String[] args) {
		GameUser me = new GameUser("soySauce", "1234");
		
		Box<GameUser> meBox = new Box<>();
		meBox.set(me);
		
		Box<String> stringBox = new Box<>();
		stringBox.set("me!");
		
		System.out.println(LogIn.<GameUser>checkValidUserInfo(meBox));	
		System.out.println(LogIn.<String>checkValidUserInfo(stringBox));
		...

   → 제네릭 메소드의 타입 매개변수가 "T"가 아니라 "Box<T>"인 점을 유의해야함

 

   → 이때, 메인 메소드에서 LogIn<GameUser> 인스턴스가 아닌 LogIn<String> 인스턴스를 인자로 전달한다면?

         * 실행한 결과는 신경쓰지  않는다고 가정하고,

            실행이 되느냐 안되느냐를 따졌을 때는 당연히 실행이 가능함(즉, LogIn<String>은 인자로 전달 가능한 값이라는 얘기)

System.out.println(LogIn.<String>checkValidUserInfo("me!"));

 

- 그렇다면 제네릭 메소드를 아래와 같이 수정해도

  여전히 LogIn<GameUser> 인스턴스와 LogIn<String> 인스턴스는 인자로 전달이 가능할까

   → 제네릭 메소드의 매개변수를 "Box<T> o"에서 "Box<Object> o"로 수정함

// LogIn.java
public class LogIn {
	// 제네릭 메소드
    public static <T> String checkValidUserInfo(Box<Object> o) { // 매개변수를 Box<Object> o로 수정
		if(o instanceof User)
			return "로그인 성공";
		else
			return "로그인 실패";
	}
// 메인 메소드
public static void main(String[] args) {
		GameUser me = new GameUser("soySauce", "1234");
		
		Box<GameUser> meBox = new Box<>();
		meBox.set(me);
		
		Box<String> stringBox = new Box<>();
		stringBox.set("me!");
		
		System.out.println(LogIn.<GameUser>checkValidUserInfo(meBox));	
		System.out.println(LogIn.<String>checkValidUserInfo(stringBox));
		...

   → 정답은 "불가능"

       * 아래와 같이 에러가 발생함

   → 그 이유에 대해서 알아봐야함

 

- 제네릭 메소드의 매개변수가 Box<Object>이고

   GameUser 인스턴스나 String은 모두 Object를 최상위 클래스로 갖기 때문에 실행이 가능할 것 같지만, 실행되지 않음

   → 타입 매개변수 Box<T>와 Box<Object>는 다르기 때문

   → 앞서 학습한 제네릭의 상속에 대한 부분과 비슷한 이유 때문

   → 앞에서 공부할 때, 타입 인자가 "같은" 인스턴스끼리 상속관계를 갖는다고 했었음

         * 앞에서 봤던 그림을 다시 가져옴 (아래 그림)

 

   → 즉, 제네릭 메소드의 매개변수를 "Box<Object> o"로 수정한 위의 코드에서

        제네릭 메소드와 메인 메소드의 상황은 아래와 같은 그림으로 구성됨

   → 정리하자면 다이아몬드 기호 안에 있는 타입 매개변수 T로 전달되는 타입 인자가 같아야 상속관계도 가질 수 있음

         * 타입 매개변수 T로 전달되는 인자가 다르면 서로 아무 관련이 없다는 것

 

 

 

 

Comments