gugbab2's GitBook
  • Language
    • C++
      • 강의
        • C++ 언매니지드 프로그래밍
          • C++ 프로그래밍
          • 출력(Output)
          • 입력(Input)
          • bool 타입, Reference
          • 상수(const)
          • 문자열(string)
          • 파일 입출력
          • 개체지향 프로그래밍1
          • 개체지향 프로그래밍2
          • 개체지향 프로그래밍3
          • 캐스팅(형변환, casting)
          • 인라인 함수
          • static 키워드
          • 예외(Exception)
          • STL(Standard Template Library) 컨테이너(Container) - Vector
          • STL 컨테이너 - Map
          • STL 컨테이너 - Queue, Stack, Set, List
          • 템플릿(Template) 프로그래밍
          • 새로운 키워드(C++11 ~) 1
          • 새로운 키워드(C++11 ~) 2
          • 새로운 자료형
          • 새로운 STL 컨테이너
          • 스마트(smart) 포인터
          • 이동생성자 및 이동대입연산자
          • constexpr
          • Lamda Expression
      • 책
        • The C++ Programming Lanuaage
          • 2부 : 기본 기능
            • 6. 타입과 선언
            • 7. 포인터, 배열, 참조
            • 8. 구조체(struct), 공용체(union), 열거형(enum)
            • 10. 표현식
            • 11. 선택 연산
            • 12. 함수
            • 13. 예외 처리
            • 15. 소스 파일과 프로그램
          • 3부 : 추상화 메커니즘
            • 16. 클래스
            • 17. 생성, 소멸, 복사와 이동
            • 18. 연산자 오버로딩
            • 19. 특수 연산자
            • 20. 파생클래스
        • 씹어먹는 C++
          • 2. C++ 참조자(reference) 의 도입
          • 5.1 연산자 오버로딩(비교, 대입 연산자)
          • 5-2. 연산자 오버로딩(이항, 입출력, 타입변환, 증감 연산자)
          • 6-2. 가상(virtual) 함수와 다형성
          • 6-3. 가상 함수에 대한 지식들
          • 9-1. 코드를 찍어내는 틀 - C++ 템플릿(template)
          • 9-2. 가변 길이 템플릿(Variadic template)
          • 9-3. 템플릿 메타 프로그래밍 (Template Meta Programming)
          • 9-4. 템플릿 메타 프로그래밍2
          • 16.1 유니폼 초기화(Uniform Initialization)
          • 토막글 2. 람다(lambda)
    • Java
      • 강의
        • 김영한의 실전 자바 - 기본편
          • 절차 지향 vs 객체 지향
            • 절차 지향 프로그래밍
            • 객체 지향 프로그래밍
          • 변수
            • 클래스 변수 / 인스턴스 변수, 멤버 변수 / 지역 변수
            • 기본형 vs 참조형
          • 패키지
            • 패키지
            • CLI 환경에서 .java 파일 컴파일 && 실행
          • 접근 제어자
            • 접근 제어자 - 기본
            • 캡슐화
          • static
            • 자바 메모리 구조
            • static 기본
            • 스택 영역, 힙 영역
              • 스택 영역, 힙 영역 - 기본
              • 메소드가 실행될 때 어떤일이 일어나는가?
          • 상속
            • 상속 기본
          • 다형성(Pilymorphism)
            • 다형성 기본
            • 다형성의 활용
              • 다형성의 활용 - 기본
              • 다형성의 활용 - 추상클래스
              • 다형성의 활용 - 인터페이스
            • 다형성과 설계
              • 좋은 객체 지향 프로그래밍
        • 김영한의 실전 자바 - 중급1편
          • 1. Object 클래스
          • 2. 불변 객체
          • 3. String 클래스
          • 4. 래퍼, Class 클래스
          • 5. 열거형 - ENUM
          • 6. 날짜와 시간
          • 7. 중첩 클래스, 내부 클래스1
          • 8. 중첩 클래스, 내부 클래스2
          • 9. 예외 처리1 - 이론
          • 10. 예외 처리 - 실습
        • 김영한의 실전 자바 - 중급2편
          • 1. 제네릭 - Generic1
          • 2. 제네릭 - Generic2
          • 3. 컬렉션 프레임워크 - ArrayList
          • 4. 컬렉션 프레임워크 - LinkedList
          • 5. 컬렉션 프레임워크 - List
          • 6. 컬렉션 프레임워크 - 해시(Hash)
          • 7. 컬렉션 프레임워크 - HashSet
          • 8. 컬렉션 프레임워크 - Set
            • 레드 블랙 트리
          • 9. 컬렉션 프레임워크 - Map, Stack, Queue
            • 왜(?) Set 은 내부에서 Map 을 사용할까?
          • 10. 컬렉션 프레임워크 - 순회, 정렬, 전체 정리
        • 김영한의 실전 자바 - 고급 1편, 멀티스레드와 동시성
          • 프로세스와 스레드 소개
          • 스레드 생성과 실행
          • 스레드 제어와 생명 주기1
          • 스레드 제어와 생명 주기2
          • 메모리 가시성
          • 동기화 - synchronized
            • synchronized 키워드 이해도 체크
          • 고급 동기화 - concurrent.Lock
          • 생산자 소비자 문제1
          • 생산자 소비자 문제2
          • CAS - 동기화와 원자적 연산
          • 동시성 컬렉션
          • 스레드 풀과 Executor 프레임워크1
          • 스레드 풀과 Executor 프레임워크2
        • 김영한의 실전 자바 - 고급 2편, I/O, 네트워크, 리플렉션
          • 문자 인코딩
          • I/O 기본1
          • I/O 기본2
          • I/O 활용
          • File, Files
          • 네트워크 - 프로그램1
          • 네트워크 - 프로그램2
          • 채팅 프로그램
          • HTTP 서버 만들기
          • 리플렉션
          • 애노테이션
          • HTTP 서버 활용
        • 김영한의 실전 자바 - 고급3편, 람다, 스트림, 함형 프로그래밍
          • 람다가 필요한 이유
          • 람다
          • 함수형 인터페이스
          • 람다 활용
          • 람다 vs 익명 클래스
          • 메서드 참조
          • 스트림API1 - 기본
          • 스트림 API2 - 기능
          • 스트림 API3 - 컬렉터
          • Optional
          • 디폴트 메서드
          • 병렬 스트림
          • 함수형 프로그래밍
        • 기초 탄탄! 독하게 시작하는 Java - Part2: OOP 와 JVM
          • 2. 클래스 - 첫 번째
          • 3. 클래스 - 두번째
          • 4. 상속과 관계
          • 6. JVM(Java Virtual machine) 기본 이론
          • 7. JVM 과 GC 그리고 객체
          • 8. 불변 객체와 String 클래스
      • 책
        • 자바의 신
          • 변수
            • 클래스 변수(static) 사용 주의 케이스
            • Java volatile 과 Atomic 변수(+CAS)
          • 연산자
            • 비트 연산자 활용 예제
          • 배열
          • 참조 자료형
          • 상속
          • Object 클래스
          • interface, abstract class, enum
          • 예외
          • String 클래스
            • String 구조
            • String 문자열을 byte 로 변환하기
            • String 클래스에서 자주 사용되는 메서드
            • String 클래스로 살펴보는 불변(Immutable)객체
            • StringBuilder, StringBuffer
          • Nested 클래스
          • 어노테이션
            • 어노테이션 기본
            • 어노테이션의 사용
          • JVM 이해하기
            • 왜 JVM 을 사용해?
            • JVM, JRE, JDK
            • JVM 구조 이해하기
            • 클래스 로더 시스템
            • JIT(Just-In-Time) 컴파일러
            • GC(Garbage Collector)
              • GC Part.1
              • GC Part.2
              • GC 튜닝
          • java.lang
            • Wrapper 클래스
            • System 클래스
          • Generic
            • 제네릭 기본
            • 와일드카드
            • 와일드카드 GET / SET 경계
            • 와일드카드 extends / super 사용시기
            • 혼동할 수 있는 와일드카드 표현
          • Collection
            • 자료구조
              • 이진 탐색 트리 vs 레드 블랙 트리
            • Collection
            • List
              • ArrayList
              • Vector
              • Stack
              • LinkedList
            • Set, Queue
              • HashSet
              • LinkedHashSet
              • TreeSet
              • Priority Queue
              • ArrayDeque
            • Map
              • HashMap
              • Hashtable
              • LinkedHashMap
              • TreeMap
          • Thread
            • Thread 기본
            • Thread 와 관련이 많은, Synchronized
            • Thread 를 통제하는 메서드
            • ThreadGroup
          • I/O
            • InputStream, OutputStream
            • Reader, Writer
          • Serializable, NIO
            • Serializable
            • NIO (New IO)
          • 네트워크 프로그래밍
            • 네트워크 기본 & TCP 통신
            • UDP 통신
          • 람다
            • 함수형 인터페이스
            • 람다란?
        • 벨둥(Bealdung)
          • Java Concurrency
            • Java Concurrency Basics
              • Overview of the java.util.concurrent
              • Guide to the Synchronized Keyword in Java
              • Guide to the Volatile Keyword in Java
              • Guide to the java.util.concurrent.Future
              • ThreadLocal in Java
      • 그 외
        • 시스템 콜과 자바에서의 시스템 콜 사용례
        • 자바 NIO 의 동작원리 및 IO 모델
        • 함수형 인터페이스(FunctionInterface) - 자바8
  • Spring
    • 강의
      • 스프링 핵심 원리 - 기본편
        • 큰 흐름 잡기
        • 스프링 핵심 원리 이해1 - 예제 만들기
        • 스프링 핵심 원리 이해2 - 객체 지향 원리 적용
        • 스프링 컨테이너와 스프링 빈
        • 싱글톤 컨테이너
        • 컴포넌트 스캔
        • 의존관계 자동 주입
        • 빈 생명주기 콜백
        • 빈 스코프
      • 토비의 스프링6 - 이해와 원리
        • 3. 오브젝트와 의존관계1
        • 3. 오브젝트와 의존관계2
        • 4. 테스트
        • 5. 템플릿
        • 6.예외
        • 7. 서비스 추상화
    • 책
      • JSP 2.3 웹 프로그래밍
        • Servlet
        • JSP
        • 쿠키 / 세션
        • MVC 패턴
        • 실무 때 고민할 만한 부분
      • 스프링 입문을 위한 자바 객체지향의 원리와 이해
        • 자바와 절차적/구조적 프로그래밍
        • 객체지향의 4대 특성
        • 객체지향 설계의 5원칙
        • 스프링이 사랑한 디자인 패턴
        • IoC / DI
        • AOP(Aspect Oriented Programming), 관점 지향 프로그래밍
      • 토비의 스프링 3.1
        • Spring vs Spring Boot
        • 1. 오브젝트와 의존관계
          • 1.4 제어의 역전(IoC)
          • 1.5 스프링의 IoC
          • 1.6 싱글톤 레지스트리와 오브젝트 스코프
    • 그 외
      • 스프링 부트(SpringBoot) 탄생 배경
  • CS
    • DATA STRUCTURES
      • 선택 정렬(Selection Sort)
      • 버블 정렬(Bubble Sort)
      • 삽입 정렬(Insertion Sort)
    • OS
      • 강의
      • 책
        • 혼자 공부하는 컴퓨터구조 + 운영체제
          • 1. 컴퓨터 구조 시작하기
          • 2. 데이터
          • 3. 명령어
          • 4. CPU 의 작동원리
          • 5. CPU 성능 향상 기법
          • 6. 메모리와 캐시메모리
          • 7. 보조기억장치
          • 8. 입출력장치
          • 9. 운영체제 시작하기
          • 10. 프로세스와 스레드
    • NETWORK
      • 그 외
        • REST API
          • REST API
          • URI & MIME type
          • Collection Pattern
          • Collection Pattern 적용
          • Spring Web MVC 구현
        • SSL 인증 동작
        • DTO & JSON & CROS
          • DTO
          • 직렬화(Serialization)
          • Jackson ObjectMapper
          • CROS
        • Connection Timeout / Read Timeout
      • 강의
        • 외워서 끝내는 네트워크 핵심이론 - 기초
          • Internet 기반 네트워크 입문
            • Host 는 이렇게 외우자
            • 스위치가 하는 일과 비용
          • L2 수준에서 외울 것들
            • NIC, L2 Frame, LAN 카드 그리고 MAC 주소
            • L2 스위치에 대해서
            • LAN 과 WAN 의 경계 그리고 Broadcast
          • L3 수준에서 외울 것들
            • IPv4 주소의 기본 구조
            • L3 IP Packet 으로 외워라
            • 패킷의 생성과 전달 및 계층별 데이터 단위
            • 이해하면 인생이 바뀌는 TCP/IP 송, 수신 구조
            • IP 헤더 형식
            • 서브넷 마스크와 CIDR
            • Broadcast IP 주소와 Localhost
            • TTL 과 단편화
            • 인터넷 설정 자동화를 위한 DHCP
            • ARP 과 Ping(RTT : Round Trip Time)
          • L4 수준 대표주자 TCP 와 UDP
            • TCP 와 UDP 개요
            • TCP 연결 및 상태 변화
            • TCP 연결 종료 및 상태 변화
            • TCP, UDP 헤더 형식과 게임서버 특징
            • TCP 가 연결이라는 착각
            • TCP 연결과 게임버그
          • 웹을 이루는 핵심기술
            • DNS
            • URL, URI
        • 외워서 끝내는 네트워크 핵심 이론 - 응용
          • 네트워크 장치의 구조
            • 세 가지 네트워크 장치 구조
            • Inline 구조
            • Out of path 구조와 DPI 그리고 망중립
            • Proxy(클라이언트 입장) - 우회
            • Proxy(클라이언트 입장) - 보호와 감시
            • Reverse Proxy(서버 입장)
          • 인터넷 공유기의 작동 원리
            • 공유기 개요
            • Symmetric NAT
            • Full Cone 방식
            • Restricted Cone, Port Restricted Cone
            • 포트 포워딩
            • UPnP 와 NAT
          • 부하분산 시스템 작동 원리
            • L4 부하분산 무정지 시스템
            • 대규모 부하분산을 위한 GSLB
          • VPN과 네트워크 보안 솔루션
            • PN 과 VPN
            • IPSec VPN 과 터널링 개념
            • VPN 과 재택근무
        • 외워서 끝내는 SSL 과 최소한의 암호기술
          • 기초이론
            • Checksum (검사합)
            • Hash
          • 암호기술에 대한 이해
            • 대칭키
            • 비대칭키
          • PKI 시스템과 인터넷
            • 인터넷을 위한 비대칭키 체계
            • 공개키 신뢰를 위한 검증체계
            • 웹서비스와 공인인증서
      • 책
        • 그림으로 배우는 네트워크 원리
          • 1. 네트워크 기본
          • 2. 네트워크를 만드는 것
          • 3. 네트워크의 공통 언어 TCP/IP
    • SECURITY
      • 그 외
        • Basic Auth
        • HMAC 기반 인증
    • 그 외
      • 동기/비동기 & 블로킹/논블록킹
  • DB
    • 그 외
      • 인덱스(Index)
      • 트랜잭션(TRANSACTION)
      • 실무에서 외래키를 사용하지 않는 이유
      • ORM vs SQL Mapper
      • 문자열 vs DATE
      • EXPLAIN 명령어
    • 강의
      • Real MySQL 시즌 1
        • Part.1
          • 1강. CHAR vs VARCHAR
          • 2강. VARCHAR vs TEXT
          • 3강. COUNT(*) & COUNT(DISTINCT) 튜닝
          • 4강. 페이징 쿼리 작성
          • 5강. Stored Function
      • 토크온 41차. JPA 프로그래밍 기본 다지기
        • 1. JPA 소개
        • 2. JPA 기초와 매핑
        • 3. 필드와 컬럼 매핑
        • 4. 연관관계 매핑
        • 5. 양방향 매핑
        • 6. JPA 내부구조
        • 7. JPA 객체지향쿼리
        • 8. Spring Data JPA 와 QueryDSL 이해
    • 책
  • Software Development Methodology
    • TDD
      • 강의
        • Spring Boot TDD - 입문부터 실전까지 정확하게
          • 세션2. TDD 소개
          • 세션5. API 설계
          • 세션6. TDD 주기 첫 번째 경험
          • 세션7. TDD 주기 반복
      • 그 외
        • 단위 테스트(Unit Test) 작성의 필요성
        • JUnit5
          • A Guide to JUnit 5
          • Guide to JUnit 5 Parameterized Tests
          • AssertJ Exception Assertions
          • Testing in Spring Boot
          • Junit 과 Mockito 기반의 Spring 단위 테스트 코드 작성법
        • Code Coverage
          • Code Coverage?
    • DDD
      • 책
        • 도메인 주도 설계(Domain-Driven Design)
          • 04 - 도메인의 격리
          • 05 - 소프트웨어에서 표현되는 모델
          • 06 - 도메인 객체의 생명주기
          • 07 - 언어의 사용(확장 예제) (1)
          • 07 - 언어의 사용(확장 예제) (2)
        • 도메인 주도 개발 시작하기
          • 1. 도메인 모델 시작하기
          • 2. 아키텍처 개요
          • 3. 애그리거트
          • 4. 리포지터리와 모델 구현
            • DAO vs Repository
      • 강의
        • DDD 세레나데(NEXTSTEP)
          • 1주차
            • 도메인 주도 설계 등장 배경
            • 레거시 코드
            • 유연한 설계 - ASSERTION
          • 2주차
            • 전략적 설계 - UBIQUITOUS LANGUAGE
            • 전략적 설계 - BOUNDED CONTEXT
          • 3주차
            • 전술적 설계 - VALUE OBJECT 와 ENTITY
            • 전술적 설계 - AGGREGATE 와 REPOSITORY
            • 전술적 설계 - SERVICE
    • REFACTORING
      • 일급 컬렉션(First Class Collection) 소개와 사용해야하는 이유
  • ARCHITECTURE
    • Event Driven Architecture
  • 멘토링
    • F-Lab
      • 10회차(2024.12.29)
Powered by GitBook
On this page
  • 타입 매개변수 제한1 - 시작
  • 문제점
  • 타입 매개변수 제한2 - 다형성 시도
  • 문제점
  • 타입 매개변수 제한3 - 제네릭 도입과 실패
  • 문제점
  • 타입 매개변수 제한4 - 타입 매개변수 제한
  • 기존 문제 해결
  • 제네릭 메서드
  • 제네릭 타입
  • 제너릭 메서드
  • 타입 매개변수 제한
  • 제네릭 메서드 타입 추론
  • 제네릭 메서드 활용
  • 제네릭 메서드 활용
  • 제네릭 타입과 제네릭 메서드의 우선순위
  • 와일드카드1
  • 비제한 와일드카드
  • 와일드카드2
  • 상한 와일드카드
  • 타입 매개변수가 꼭 필요한 경우
  • 타입 이레이저(소거)
  • 타입 매개변수 미제한의 경우
  • 타입 매개변수 제한의 경우
  • 타입 이레이저 방식의 한계
  1. Language
  2. Java
  3. 강의
  4. 김영한의 실전 자바 - 중급2편

2. 제네릭 - Generic2

타입 매개변수 제한1 - 시작

동물 병원을 만들어보자.

요구사항 : 개 병원은 개만 받을 수 있고, 고양이 병원은 고양이만 받을 수 있어야 한다.

package generic.ex3;

import generic.animal.Cat;

public class CatHostpital {

    private Cat animal;

    public void set(Cat animal) {
        this.animal = animal;
    }

    public void checkUp() {
        System.out.println("동물 이름 : " + animal.getName());
        System.out.println("동물 크기 : " + animal.getSize());
        animal.sound();
    }

    public Cat bigger(Cat target) {
        return animal.getSize() > target.getSize() ? animal : target;
    }
}
package generic.ex3;

import generic.animal.Dog;

public class DogHostpital {

    private Dog animal;

    public void set(Dog animal) {
        this.animal = animal;
    }

    public void checkUp() {
        System.out.println("동물 이름 : " + animal.getName());
        System.out.println("동물 크기 : " + animal.getSize());
        animal.sound();
    }

    public Dog bigger(Dog target) {
        return animal.getSize() > target.getSize() ? animal : target;
    }
}
package generic.ex3;

import generic.animal.Cat;
import generic.animal.Dog;

public class AnimalHotpitalMainV0 {

    public static void main(String[] args) {
        DogHostpital dogHostpital = new DogHostpital();
        CatHostpital catHostpital = new CatHostpital();

        Dog dog = new Dog("멍멍이1", 100);
        Cat cat = new Cat("냐옹이1", 300);

        // 개 병원
        dogHostpital.set(dog);
        dogHostpital.checkUp();

        // 고양이 병원
        catHostpital.set(cat);
        catHostpital.checkUp();

//        // 문제1: 개 병원에 고양이 전달
//        dogHostpital.set(cat);  // 다른 타입 입력 : 컴파일 오류

        // 문제2: 개 타입 반환
        dogHostpital.set(dog);
        Dog biggerDog = dogHostpital.bigger(new Dog("멍멍이2", 200));
        System.out.println("biggerDog = " + biggerDog);
    }
}

문제점

  • 코드 재사용 X : 개 병원과 고양이 병원은 중복이 많아 보인다.

  • 타입 안정성 O : 타입 안정성이 명확하게 지켜진다.

타입 매개변수 제한2 - 다형성 시도

package generic.ex3;

import generic.animal.Animal;

public class AnimalHospitalV1 {

    private Animal animal;

    public void set(Animal animal) {
        this.animal = animal;
    }

    public void checkUp() {
        System.out.println("동물 이름 : " + animal.getName());
        System.out.println("동물 크기 : " + animal.getSize());
        animal.sound();
    }

    public Animal bigger(Animal target) {
        return animal.getSize() > target.getSize() ? animal : target;
    }
}
package generic.ex3;

import generic.animal.Cat;
import generic.animal.Dog;

public class AnimalHospitalMainV1 {

    public static void main(String[] args) {

        AnimalHospitalV1 dogHostpital = new AnimalHospitalV1();
        AnimalHospitalV1 catHostpital = new AnimalHospitalV1();

        Dog dog = new Dog("멍멍이1", 100);
        Cat cat = new Cat("냐옹이1", 300);

        // 개 병원
        dogHostpital.set(dog);
        dogHostpital.checkUp();

        // 고양이 병원
        catHostpital.set(cat);
        catHostpital.checkUp();

        // 문제1: 개 병원에 고양이 전달
        dogHostpital.set(cat);  // 매개변수 체크 실패 : 컴파일 오류 발생 X

        // 문제2: 동물 타입 반환
        dogHostpital.set(dog);
        Dog biggerDog = (Dog) dogHostpital.bigger(new Dog("멍멍이2", 200));    // 다운캐스팅 필요 ..
        System.out.println("biggerAnimal = " + biggerDog);
    }
}

문제점

  • 코드 재사용 O : 다형성을 통해서 AnimalHospitalV1 하나로 개와 고양이를 모두 처리한다.

  • 타입 안정성 X

    • 개 병원에 고양이를 전달하는 문제가 발생한다.

    • Animal 타입을 반환하기 때문에, 다운 캐스팅을 해야 한다.

    • 실수로 고양이를 입력했는데, 개를 반환하는 상황이라면 캐스팅 예외가 발생한다.

타입 매개변수 제한3 - 제네릭 도입과 실패

제네릭 타입을 선언하면 자바 컴파일러 입장에서 T 에 어떤 값이 들어올지 예측할 수 없다. 우리는 Animal 타입의 자식이 들어오기를 기대했지만, 여기 코드 어디에도 Animal 에 대한 정보는 없다.

T 에는 타입 인자로 Integer, Dog, Object 등.. 어떤 타입이 들어와도 이상하지 않다.

package generic.ex3;

public class AnimalHospitalV2<T> {

    private T animal;

    public void set(T animal) {
        this.animal = animal;
    }

    public void checkUp() {
        // T 의 타입을 메서드를 정의하는 시점에는 알 수 없다. Object 기능만 사용 가능
        // 때문에, Animal 객체의 기능을 사용하려 하면 컴파일 오류 발생

//        System.out.println("동물 이름 : " + animal.getName());
//        System.out.println("동물 크기 : " + animal.getSize());
//        animal.sound();
    }

    public T bigger(T target) {
//        // 컴파일 오류 발생
//        return animal.getSize() > target.getSize() ? animal : target;
        return null;
    }
}
package generic.ex3;

import generic.animal.Cat;
import generic.animal.Dog;

public class AnimalHospitalMainV2 {

    public static void main(String[] args) {

        AnimalHospitalV2<Dog> dogHostpital = new AnimalHospitalV2<>();
        AnimalHospitalV2<Cat> catHostpital = new AnimalHospitalV2<>();
        AnimalHospitalV2<Integer> integerHostpital = new AnimalHospitalV2<>();  // ? (의도와 맞는가?)
        AnimalHospitalV2<String> stringHostpital = new AnimalHospitalV2<>();    // ? (의도와 맞는가?)

    }
}

문제점

  • 제네릭에서 타입 매개변수를 사용하면 어떤 타입이든 들어올 수 있다.

  • 따라서 타입 매개변수를 어떤 타입이든 수용할 수 있는 Object 라고 가정하고, Object 의 기능만 사용할 수 있다.

타입 매개변수 제한4 - 타입 매개변수 제한

여기서 핵심은 <T extends Animal> 이다.

타입 매개변수 T 를 Animal 과 그 자식만 받을 수 있도록 제한을 두는 것이다. 즉 T 의 상한이 Animal 이 되는 것이다.

package generic.ex3;

import generic.animal.Animal;

public class AnimalHospitalV3<T extends Animal> {

    private T animal;

    public void set(T animal) {
        this.animal = animal;
    }

    public void checkUp() {
        System.out.println("동물 이름 : " + animal.getName());
        System.out.println("동물 크기 : " + animal.getSize());
        animal.sound();
    }

    public T bigger(T target) {
        return animal.getSize() > target.getSize() ? animal : target;
    }
}
package generic.ex3;

import generic.animal.Cat;
import generic.animal.Dog;

public class AnimalHospitalMainV3 {

    public static void main(String[] args) {

        AnimalHospitalV3<Dog> dogHostpital = new AnimalHospitalV3<>();
        AnimalHospitalV3<Cat> catHostpital = new AnimalHospitalV3<>();
//        AnimalHospitalV3<Integer> integerHostpital = new AnimalHospitalV3<>();  // 컴파일 오류
//        AnimalHospitalV3<String> stringHostpital = new AnimalHospitalV3<>();    // 컴파일 오류

        Dog dog = new Dog("멍멍이1", 100);
        Cat cat = new Cat("냐옹이1", 300);

        // 개 병원
        dogHostpital.set(dog);
        dogHostpital.checkUp();

        // 고양이 병원
        catHostpital.set(cat);
        catHostpital.checkUp();

//        // 문제1: 개 병원에 고양이 전달
//        dogHostpital.set(cat);  // 타입 체크 : 컴파일 오류 발생

        // 문제2: 동물 타입 반환
        dogHostpital.set(dog);
        Dog biggerDog = dogHostpital.bigger(new Dog("멍멍이2", 200));    // 다운캐스팅 불필요!
        System.out.println("biggerAnimal = " + biggerDog);
    }
}

기존 문제 해결

타입 안정성 X 문제

  • 개 병원에 고양이를 전달하는 문제가 발생한다. -> 해결

  • Animal 타입을 반환하기 때문에 다운 캐스팅을 해야 한다. -> 해결

  • 실수로 고양이를 입력했는데, 개를 반환하는 상황이라면 캐스팅 예외가 발생한다. -> 해결

제네릭 도입 문제

  • 제네릭에서 타입 매개변수를 사용하면 어떤 타입이든 들어올 수 있다. -> 해결

  • 그리고 어떤 타입이든 수용할 수 있는 Object 로 가정하고, Object 의 기능만 사용할 수 있다. -> 해결

    • 여기서는 Animal 을 상한으로 두어서 Animal 의 기능을 사용할 수 있다.

정리

제네릭에 타입 매개변수 상한을 사용해서 타입 안정성을 지키면서 상위 타입의 원하는 기능까지 사용할 수 있었다.

덕분에 코드 재사용과 타입 안정성이라는 두 마리 토끼를 동시에 잡을 수 있었다.

제네릭 메서드

앞서 살펴본 제네릭 타입과 지금부터 살펴볼 제네릭 메서드는 둘 다 제네릭을 사용하기는 하지만 서로 다른 기능을 제공한다.

package generic.ex4;

public class GenericMethod {

    public static Object objMethod(Object obj) {
        System.out.println("Object print : " + obj);
        return obj;
    }

    public static <T> T genericMethod(T t) {
        System.out.println("Generic print : " + t);
        return t;
    }

    public static <T extends Number> T numberMethod(T t) {
        System.out.println("Number print : " + t);
        return t;
    }
}
package generic.ex4;

public class MethodMain1 {

    public static void main(String[] args) {
        Integer i = 10;
        Object object = GenericMethod.objMethod(i);
        System.out.println("object = " + object);
//        Integer integer = (Integer)GenericMethod.objMethod(i);
        
        // 타입 인자(Type Argument) 명시적 전달
        System.out.println("타입 인자(Type Argument) 명시적 전달");
        Integer result = GenericMethod.<Integer>genericMethod(i);
        Integer integerValue = GenericMethod.<Integer>numberMethod(10);
        Double doubleValue = GenericMethod.<Double>numberMethod(20.0);

        // 타입 추론
        System.out.println("타입 추론");
        Integer result1 = GenericMethod.genericMethod(i);
        Integer integerValue1 = GenericMethod.numberMethod(10);
        Double doubleValue1 = GenericMethod.numberMethod(20.0);
    }
}

제네릭 타입

  • 정의 : GenericClass<T>

  • 타입 인자 전달 : 객체를 생성하는 시점

    • ex) new GenericClass<String>

제너릭 메서드

  • 정의 : <T> T genericMethod(T t)

  • 타입 인자 전달 : 메서드를 호출하는 시점

    • ex) GenericMethod.<Integer>genericMethod(i)

  • 제네릭 메서드는 클래스 전체가 아니라 특정 메서드 단위로 제네릭을 도입할 때 사용한다.

  • 제네릭 메서드를 정의할 때는 메서드 반환 타입 왼쪽에 다이아몬드를 사용해서 <T> 와 같이 타입 매개변수를 적어준다.

  • 제네릭 메서드는 메서드를 실제 호출하는 시점에 다이아몬드를 사용해서 <Integer> 와 같이 타입을 정하고 호출한다.

제네릭 메서드의 핵심은 메서드를 호출하는 시점에 타입 인자를 전달해서 타입을 지정하는 것이다. 따라서 타입을 지정하면서 메서드를 호출한다.

인스턴스 메서드, static 메서드

제네릭 메서드는 인스턴스 메서드와, static 메서드에 모두 적용할 수 있다.

class Box<T> { //제네릭 타입
    static <V> V staticMethod2(V t) {} //static 메서드에 제네릭 메서드 도입 
    <Z> Z instanceMethod2(Z z) {} //인스턴스 메서드에 제네릭 메서드 도입 가능
}

참고

제네릭 타입은 static 메서드에 타입 매개변수를 사용할 수 없다.

제네릭 타입은 객체를 생성하는 시점(런타임)에 타입이 정해진다. 그런데 static 메서드는 클래스 로딩 시점에 생성된다. (클래스 로딩 시점에는 런타임에 정해지는 타입을 알 수 없다..)

때문에, static 메서드에 제네릭을 도입하려면 제네릭 메서드를 사용해야 한다.

class Box<T> {
    T instanceMethod(T t) {} //가능
    static T staticMethod1(T t) {} //제네릭 타입의 T 사용 불가능
}

타입 매개변수 제한

제네릭 메서드도 제네릭 타입과 마찬가지로 타입 매개변수를 제한할 수 있다.

public static <T extends Number> T numberMethod(T t) {}

제네릭 메서드 타입 추론

제네릭 메서드를 호출할 때 <Integer> 와 같이 타입 인자를 계속 전달하는 것은 매우 불편하다.

Integer i = 10;
Integer result = GenericMethod.<Integer>genericMethod(i);

자바 컴파일러는 genericMethod() 에 전달되는 인자 i 의 타입이 Integer 라는 것을 알 수 있다.

또한, 반환 타입이 Integer result 라는 것을 알 수 있다. 이런 정보를 통해서 자바 컴파일러는 타입 인자를 추론할 수 있다.

제네릭 메서드 활용

제네릭 메서드 활용

앞서 제네릭 타입으로 만들었던 AnimalHospitalV3 의 주요 기능을 제네릭 메서드로 다시 만들어보자

package generic.ex4;

import generic.animal.Animal;

public class AnimalMethod {

    public static <T extends Animal> void checkUp(T t) {
        System.out.println("동물 이름 : " + t.getName());
        System.out.println("동물 크기 : " + t.getSize());
        t.sound();
    }

    public static <T extends Animal> T bigger(T t1, T t2) {
        return t1.getSize() > t2.getSize() ? t1 : t2;
    }
}
package generic.ex4;

import generic.animal.Cat;
import generic.animal.Dog;

public class MethodMain2 {

    public static void main(String[] args) {
        Dog dog = new Dog("멍멍이", 100);
        Cat cat = new Cat("냐옹이", 100);

        AnimalMethod.checkUp(dog);
        AnimalMethod.checkUp(cat);

        Dog targetDog = new Dog("큰 멍멍이", 200);
        Dog bigger = AnimalMethod.bigger(dog, targetDog);
        System.out.println("bigger = " + bigger);
    }
}

제네릭 타입과 제네릭 메서드의 우선순위

정적 메서드는 제네릭 메서드만 적용할 수 있지만, 인스턴스 메서드는 제네릭 타입, 제네릭 메서드 둘다 적용할 수 있다.

여기서 제네릭 타입, 제네릭 메서드의 타입 매개변수를 같은 이름으로 사용하면 어떻게 될까?

  • 제네릭 메서드의 타입 매개변수를 사용한다.

  • 소프트웨어는 대부분 더 자세한 것, 구체적인 것에 우선순위가 높다.

와일드카드1

이번에는 제네릭 타입을 조금 더 편리하게 사용할 수 있는 와일드카드에 대해 알아보자.

참고로, 와일드카드는 제네릭 타입이나, 제네릭 메서드를 선언하는 것이 아니다. 와일드카드는 이미 만들어진 제네릭 타입을 활용할 때 사용한다.

package generic.ex5;

public class Box<T> {

    private T value;

    public void set(T value) {
        this.value = value;
    }

    public T get() {
        return value;
    }
}
package generic.ex5;

import generic.animal.Animal;

public class WildcardEx {

    static <T> void printGenericV1(Box<T> box) {
        System.out.println("T = " + box.get());
    }

    // Box<Dog>, Box<Cat>, Box<Object> ...
    static void printWildcardV1(Box<?> box) {
        System.out.println("? = " + box.get());
    }

    static <T extends Animal> void printGenericV2(Box<T> box) {
        T t = box.get();
        System.out.println("이름 : " + t.getName());
    }

    // Box<Dog>, Box<Cat>, Box<Animal>
    static void printWildcardV2(Box<? extends Animal> box) {
        Animal animal = box.get();
        System.out.println("이름 : " + animal.getName());
    }

    static <T extends Animal> T printAndReturnGeneric(Box<T> box) {
        T t = box.get();
        System.out.println("이름 : " + t.getName());
        return t;
    }

    static Animal printAndReturnWildcard(Box<? extends Animal> box) {
        Animal animal = box.get();
        System.out.println("이름 : " + animal.getName());
        return animal;
    }
}

비제한 와일드카드

  • 아래 두 메서드는 비슷한 기능을 하는 코드이다. 하나는 제네릭 메서드를 사용하고 하나는 일반적인 메서드에 와일드카드를 사용했다.

  • 와일드카드는 제네릭 타입이나 제네릭 메서드를 정의할 때처럼 사용하는 것이 아니다. Box<Dog>, Box<Cat> 처럼 타입 인자가 정해진 제네릭 타입을 전달 받아서 사용할 때 사용한다.

  • 와일드카드인 ? 는 모든 타입을 다 받을 수 있다는 의미이다.

    • 다음과 같이 해석할 수 있다. ? == <? extends Object>

  • 이렇게 ? 만 사용해서 제한 없이 모든 타입을 다 받을 수 있는 와일드카드를 비제한 와일드카드라고 한다.

//이것은 제네릭 메서드이다.
//Box<Dog> dogBox를 전달한다. 타입 추론에 의해 타입 T가 Dog가 된다. 
static <T> void printGenericV1(Box<T> box) {
      System.out.println("T = " + box.get());
}

//이것은 제네릭 메서드가 아니다. 일반적인 메서드이다.
//Box<Dog> dogBox를 전달한다. 와일드카드 ?는 모든 타입을 받을 수 있다. 
static void printWildcardV1(Box<?> box) {
      System.out.println("? = " + box.get());
}

제네릭 메서드 실행 예시

  • 메서드 호출 시점에 타입 매개변수에 타입 인자를 받아 타입을 추론한다.

  • 이런 과정은 복잡하다..

//1. 전달 
printGenericV1(dogBox)

//2. 제네릭 타입 결정 dogBox는 Box<Dog> 타입, 타입 추론 -> T의 타입은 Dog
static <T> void printGenericV1(Box<T> box) {
      System.out.println("T = " + box.get());
}

//3. 타입 인자 결정
static <Dog> void printGenericV1(Box<Dog> box) {
      System.out.println("T = " + box.get());
}

//4. 최종 실행 메서드
static void printGenericV1(Box<Dog> box) {
      System.out.println("T = " + box.get());
}

와일드카드 실행 예시

  • 단순히 매개변수로 제네릭 타입을 받을 수 있는 것 뿐이다.

  • 타입 추론을 하지 않는다! (단순한 프로세스)

  • 제네릭 타입 or 제네릭 메서드가 필요한 상황이 아니라면 와일드 카드 사용이 유리

//1. 전달 
printWildcardV1(dogBox)

//이것은 제네릭 메서드가 아니다. 일반적인 메서드이다.
//2. 최종 실행 메서드, 와일드카드 ? 는 모든 타입을 받을 수 있다.
static void printWildcardV1(Box<?> box) {
      System.out.println("? = " + box.get());
}

제네릭 메서드 vs 와일드카드

printGenericV1() 제네릭 메서드를 보자. 제네릭 메서드에는 타입 매개변수가 존재한다. 그리고 메서드를 호출하는 시점에 타입 인자 전달 또는 타입 추론이 일어나며, 이 과정은 내부적으로 타입 추론 로직이 복잡하게 작동한다.

반면에 printWildcardV1() 메서드를 보자. 와일드카드는 일반 메서드에 사용할 수 있으며, 단순히 제네릭 타입을 받을 수 있는 매개변수를 선언하는 것일 뿐이다. 제네릭 메서드처럼 타입을 결정하거나 복잡한 추론이 필요하지 않다. 타입을 정의하지 않고, 타입을 사용하지도 않으므로 단순하다.

결론적으로, 제네릭 타입이나 제네릭 메서드를 반드시 정의해야 하는 상황이 아니라면, 보다 단순한 와일드카드 사용을 권장한다.

와일드카드2

상한 와일드카드

  • 제네릭 메서드와 마찬가지로 와일드카드에도 상한 제한을 둘 수 있다.

  • 여기서는 ? extends Animal 을 사용했다.

  • Animal 과 그 하위 타입만 입력 받는다. 만약 다른 타입을 입력하면 컴파일 오류가 발생한다.

  • box.get() 을 통해서 꺼낼 수 있는 타입의 최대 부모는 Animal 이 된다. 따라서 Animal 타입으로 조회할 수 있다.

  • 결과적으로 Animal 의 기능을 호출할 수 있다.

static <T extends Animal> void printGenericV2(Box<T> box) {
      T t = box.get();
      System.out.println("이름 = " + t.getName()); 
}

static void printWildcardV2(Box<? extends Animal> box) { 
      Animal animal = box.get();
      System.out.println("이름 = " + animal.getName());
}

타입 매개변수가 꼭 필요한 경우

와일드카드는 제네릭을 정의할 때 사용하는 것이 아니다. Box<Dog>, Box<Cat> 처럼 단순하게 제네릭 타입을 활용할 때 사용한다.

다음과 같이 리턴 타입을 동적으로 결정하고 싶은 경우에는 제네릭 타입 or 제네릭 메서드를 사용해야 문제를 해결할 수 있다.

리턴 타입을 동적으로 결정하고 싶을 때

static <T extends Animal> T printAndReturnGeneric(Box<T> box) {
      T t = box.get();
      System.out.println("이름 = " + t.getName());
      return t; 
}

static Animal printAndReturnWildcard(Box<? extends Animal> box) { 
      Animal animal = box.get();
      System.out.println("이름 = " + animal.getName());
      return animal;
}

printAndReturnGeneric() 은 다음과 같이 전달한 타입을 명확하게 반환할 수 있다.

Dog dog = WildcardEx.printAndReturnGeneric(dogBox)

반면에, printAndReturnWildcard() 는 전달한 타입을 명확하게 반환할 수 없다. 여기서는 고정된 Animal 타입으로 반환한다.

Animal animal = WildcardEx.printAndReturnWildcard(dogBox)

메서드 타입들을 특정 시점에 변경하려면 제네릭 타입이나, 제네릭 메서드를 사용해야 한다.

와일드카드는 이미 만들어진 제네릭 타입을 전달 받아서 활용할 때 사용한다. 따라서, 메서드의 타입들을 타입 인자를 통해 변경할 수 없다. (쉽게 이야기해서 일반적인 메서드에 사용한다고 생각하면 된다)

정리하면 제네릭 타입이나 제네릭 메서드가 꼭 필요한 상황이라면 제네릭을 사용하고 아니라면 와일드카드 사용을 권장한다.

타입 이레이저(소거)

이레이저(erasere) 는 지우개 등으로 지우다라는 뜻이다.

제네릭은 자바 컴파일 단계에서만 사용되고, 컴파일 이후에는 제네릭 정보가 삭제된다. 제네릭에 사용한 타입 매개변수가 모두 사라지는 것이다.

쉽게 이야기해서 컴파일 전인 .java 에는 제네릭 타입 매개변수가 존재하지만, 컴파일 이후인 .class 에는 제네릭 타입 매개변수가 존재하지 않는 것이다.

어떻게 변하게 되는지 다음 코드로 설명하겠다. 100% 정확한 코드는 아니고 대략 이런 방식으로 작동한다고 이해하면 충분하다.

타입 매개변수 미제한의 경우

컴파일 전

public class GenericBox<T> {

      private T value;
      
      public void set(T value) {
          this.value = value;
      }
      
      public T get() {
          return value;
      } 
}

제네릭 타입에 Interger 타입 인자 전달

void main() {
      GenericBox<Integer> box = new GenericBox<Integer>();
      box.set(10);
      Integer result = box.get();
}

이렇게 하면 자바 컴파일러는 컴파일 시점에 타입 매개변수와 타입 인자를 포함한 제네릭 정보를 활용해서 new GenericBox<Integer>() 에 대해 다음과 같이 이해한다.

public class GenericBox<Integer> {
      
      private Integer value;
      
      public void set(Integer value) {
          this.value = value;
      }
      
      public Integer get() {
          return value;
      } 
}

컴파일이 모두 끝나면 자바는 제네릭과 관련된 정보를 삭제한다. 이때 .class 파일 내 생성된 정보는 다음과 같다.

컴파일 후

  • 상한 제한 없이 선언한 타입 매개변수 T 는 Object 로 변환된다.

  • 값을 반환 받는 부분을 Object 로 받으면 안된다. 자바 컴파일러는 제네릭에서 타입 인자로 지정한 Integer 로 캐스팅하는 코드를 추가해준다.

  • 이렇게 추가된 코드는 자바 컴파일러가 이미 검증하고 추가했기 때문에, 문제가 발생하지 않는다.

public class GenericBox {

      private Object value;
      
      public void set(Object value) {
          this.value = value;
      }
      
      public Object get() {
          return value;
     }
}
void main() {
    GenericBox box = new GenericBox();
    box.set(10);
    Integer result = (Integer) box.get(); //컴파일러가 캐스팅 추가
}

타입 매개변수 제한의 경우

컴파일 전

public class AnimalHospitalV3<T extends Animal> {

      private T animal;
      
      public void set(T animal) {
          this.animal = animal;
      }

      public void checkup() {
          System.out.println("동물 이름: " + animal.getName()); 
          System.out.println("동물 크기: " + animal.getSize()); 
          animal.sound();
      }
      
      public T getBigger(T target) {
          eturn animal.getSize() > target.getSize() ? animal : target;
      }
}
//사용 코드 예시
AnimalHospitalV3<Dog> hospital = new AnimalHospitalV3<>(); 
...
Dog dog = animalHospitalV3.getBigger(new Dog());

컴파일 후

  • T 의 타입 정보가 제거되어도 상한으로 지정한 Animal 타입으로 대체되기 떄문에, Animal 타입의 메서드를 사용하는데는 아무런 문제가 없다.

  • 반환 받는 부분을 Animal 로 받으면 안되기 때문에, 자바 컴파일러가 타입 인자로 지정한 Dog로 캐스팅하는 코드를 넣어준다.

public class AnimalHospitalV3 {
      
      private Animal animal;
      
      public void set(Animal animal) {
          this.animal = animal;
      }
      
      public void checkup() {
          System.out.println("동물 이름: " + animal.getName()); 
          System.out.println("동물 크기: " + animal.getSize()); 
          animal.sound();
      }
      
      public Animal getBigger(Animal target) {
          return animal.getSize() > target.getSize() ? animal : target;
      } 
}
AnimalHospitalV3 hospital = new AnimalHospitalV3();
...
Dog dog = (Dog) animalHospitalV3.getBigger(new Dog());

자바 제네릭은 단순하게 생각하면 개발자가 직접 캐스팅 하는 코드를 컴파일러가 대신 처리해주는 것이다.

자바는 컴파일 시점에 제네릭을 사용한 코드에 문제가 없는지 확실하게 검증하기 때문에, 자바 컴파일러가 추가하는 다운 캐스팅에는 문제가 발생하지 않는다.

타입 이레이저 방식의 한계

컴파일 이후에는 제네릭 타입 정보가 존재하지 않는다. .class 자바를 실행하는 런타임에는 우리가 지정한 Box<Integer>, Box<String> 의 타입 정보가 모두 제거된다.

따라서, 런타임에 타입을 활용하는 다음과 같은 코드는 작성할 수 없다.

자바에서 제네릭 타입 T는 컴파일 후 타입 정보가 소거되므로, new T(), T.class, instanceof T, new T[]와 같이 런타임 타입 정보가 필요한 연산은 컴파일 시점에 타입을 확인할 수 없어 컴파일 에러가 발생한다.

  • 런타임 타입가 필요한 연산을 제네릭을 통해서 하면 안된다!

class EraserBox<T> {

    public boolean instanceCheck(Object param) { 
        return param instanceof T; // 컴파일 오류
    }
    
    public T create() { 
        return new T(); // 컴파일 오류
    } 
}
Previous1. 제네릭 - Generic1Next3. 컬렉션 프레임워크 - ArrayList

Last updated 15 days ago