일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 오케스트레이션
- 김영한
- 동시성
- Kubernetes
- 실전 자바 고급 1편
- container
- 알고리즘
- 도커
- 시작하세요 도커 & 쿠버네티스
- 스레드
- 리스트
- java
- 쓰레드
- Collection
- LIST
- 멀티 쓰레드
- Thread
- contatiner
- 도커 엔진
- 스레드 제어와 생명 주기
- 쿠버네티스
- 자바
- 인프런
- 컨테이너
- 시작하세요! 도커 & 쿠버네티스
- 자료구조
- 실전 자바 중급 2편
- Docker
- 제네릭스
- 중급자바
- Today
- Total
쌩로그
김영한의 실전 자바 - 중급 2편 - Sec 02. 제네릭 - Generic1 본문
목차
- 포스팅 개요
- 본론
2-1. 제네릭이 필요한 이유
2-2. 다형성을 통한 중복 해결 시도
2-3. 제네릭 적용
2-4. 제네릭 용어와 관례
2-5. 제네릭 활용 예제 - 요약
1. 포스팅 개요
해당 포스팅은 김영한의 실전 자바 중급 2편 Section 2의 제너릭 - Generic1
에 대한 학습 내용이다.
프로젝트 생성 part는 생략한다.
학습 레포 URL : https://github.com/SsangSoo/inflearn-holyeye-java-mid2
(해당 레포는 완강시 public으로 전환 예정이다.)
2. 본론
2-1. 제네릭이 필요한 이유
다음과 같이 Integer
와 String
을 보관하는 객체가 있다고 하고 이것을 활용하는 코드를 보자.
// Integer를 보관하는 객체
public class IntegerBox {
private Integer value;
public void set(Integer value) {
this.value = value;
}
public Integer get() {
return value;
}
}
// 문자열을 보관하는 객체
public class StringBox {
private String value;
public void set(String value) {
this.value = value;
}
public String get() {
return value;
}
}
// 활용
public class BoxMain1 {
public static void main(String[] args) {
IntegerBox integerBox = new IntegerBox();
integerBox.set(10);
Integer integer = integerBox.get();
System.out.println("integer = " + integer);
StringBox stringBox = new StringBox();
stringBox.set("hello");
String str = stringBox.get();
System.out.println("str = " + str);
}
}
// 결과
integer = 10
str = hello
Integer
, String
을 담는 Box 객체를 만들었다.
그런데, 두 타입 뿐만 아니라 다른 타입을 담는 Box 객체를 만들어야 한다면 그 타입에 따른 Box 클래스를 또 만들어야 한다.XxxBox
클래스를 만들어야 할 것이다.
이 문제를 어떻게 해결할 수 있을까?
2-2. 다형성을 통한 중복 해결 시도
Object
타입은 모든 타입의 부모이다.
따라서 다형성을 사용해서 이 문제를 간단히 해결할 수 있(을 것 같)다.
// Object 박스
public class ObjectBox {
private Object value;
public void set(Object value) {
this.value = value;
}
public Object get() {
return value;
}
}
// 활용 예제 코드
public class BoxMain2 {
public static void main(String[] args) {
ObjectBox objectBox = new ObjectBox();
objectBox.set(10);
Integer integer = (Integer) objectBox.get();
System.out.println("integer = " + integer);
ObjectBox stringBox = new ObjectBox();
stringBox.set("hello");
String str = (String) stringBox.get();
System.out.println("str = " + str);
// 여기까진 이전과 동일결과
// 잘못된 타입의 인수 전달시 ClassCastException 발생
integerBox.set("문자 100");
Integer result = (Integer) integerBox.get();
System.out.println("result = " + result);
}
}
// 결과
integer = 10
str = hello
Exception in thread "main" java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Integer (java.lang.String and java.lang.Integer are in module java.base of loader 'bootstrap')
at generic.ex1.BoxMain2.main(BoxMain2.java:18)
이전과 동일한 결과를 얻을 수 있어 잘 작동하는 것 같아보이지만,
이후 협업이나 유지보수시 잘못된 타입의 인수가 전달되면 타입의 불일치로 예외가 발생할 수 있다.
사실 set()
메서드로 값을 넣을 때는 문제가 되지 않지만, 값을 꺼내올 때 문제가 발생한다.
숫자타입이 입력되기를 기대하여 integerBox
라는 이름을 썼지만, 문자열의 입력이 가능하기 때문에 실수로 문자열을 집어넣을 수도 있다.
타입에 따른 XxxBox
클래스를 만드는 중복을 제거했지만, 입력시 실수로 원하지 않는 타입이 들어갈 수 있는 타입 안정성 문제가 발생한다.
Object
받았던 박스는
- 타입 안정성 문제는 없지만,
- 중복 문제를 제거하여 코드르 재사용할 수 있었다.
XxxBox
는
- 코드의 중복이 발생하여 코드의 재사용은 불가했지만,
- 타입 안정성 문제는 해결했다.
이 두가지의 장점과 단점을 동시에 아우르는 것이 바로 자바에서 제공하는 제네릭이다.
2-3. 제네릭 적용
제네릭을 사용하면 코드 재사용
과 타입 안정성
이라는 장점을 한 번에 잡을 수 있다.
사용 예제는 다음과 같다.
public class GenericBox<T> {
private T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
<>
를 사용한 클래스를 제네릭 클래스라고 한다.- 기호는 다이아몬드라고 한다.
- 제네릭 클래스를 사용할 때는
Integer
,String
같은 타입을 미리 결정하지 않는다. - 대신 클래스명 오른쪽에
<T>
와 같이 선언하면 제네릭 클래스가 된다.- 여기서 T 를 타입 매개변수라고 한다.
- 타입 매개변수는 이후에
Integer
,String
으로 변할 수 있다.
- 클래스 내부에 T 타입이 필요한 곳에
T value
와 같이 타입 매개변수를 적어두면 된다.
사용 예
public class BoxMain3 {
public static void main(String[] args) {
GenericBox<Integer> integerBox = new GenericBox<>(); // 생성 시점에 T의 타입 결정
integerBox.set(10);
Integer integer = integerBox.get();
System.out.println("integer = " + integer);
GenericBox<String> stringBox = new GenericBox<>(); // 생성 시점에 T의 타입 결정
stringBox.set("hello");
String str = stringBox.get();
System.out.println("str = " + str);
// 원하는 모든 타입 사용 가능
GenericBox<Double> doubleBox = new GenericBox<>();
doubleBox.set(10.5);
Double doubleValue = doubleBox.get();
System.out.println("doubleValue = " + doubleValue);
}
}
// 결과
integer = 10
str = hello
아래는 컴파일 오류 장면이다.

Integer
를 담을 박스에 문자를 넣으니 컴파일 오류가 발생했다.
- 다이아몬드 안에 타입을 지정하면 객체 생성 시점에 T의 타입이 결정된다.
- 제네릭 클래스를 사용하면
GenericBox
객체를 생성하는 시점에 원하는 타입을 마음껏 지정할 수 있다.
참고로 이때는 GenericBox<Integer>
, Generic<String>
과 같은 코드가 실제로 만들어지는 것이 아니다.
자바 컴파일러가 우리가 입력한 타입 정보를 기반으로 이런 코드가 있다고 가정하고 컴파일 과정에 타입 정보를 반영한다.
이 과정에서 타입이 맞지 않으면 컴파일 오류가 발생한다.(컴파일 오류 장면 참고)
타입 추론
왼쪽에 변수를 선언할 때 제네릭 클래스 타입을 선언하면서 다이아몬드 안에 타입을 지정했으면,
우측에 제네릭 클래스를 생성할 때 다이아몬드에는 타입을 지정하지 않아도 된다.
왼쪽에 있는 제네릭 다이아몬드의 타입을 보고, 추론을 하기 때문이다.
이처럼 자바가 스스로 타입 정보를 추론해서 개발자가 타입 정보를 생략할 수 있는 것을 타입 추론이라고 한다.
타입추론 예시
GenericBox<Integer> integerBox = new GenericBox<>(); // O
GenericBox<> integerBox = new GenericBox<Integer>(); // Identifier expected(컴파일 오류)
타입추론이 아무 때나 할 수 있는 것이 아니라, 문법상 가능할 때만 가능하다.
2-4. 제네릭 용어와 관례
제네릭의 핵심
- 사용할 타입을 미리 결정하지 않음.
- 클래스 내부에서 사용하는 타입을 클래스를 정의하는 시점에 결정하는 것이 아니라 실제 사용하는 생성 시점에 타입을 결정한다.
제네릭의 타입 매개변수와 타입 인자
- 제네릭 클래스의 타입 매개변수는 사용할 타입에 대한 결정을 나중으로 미룬다.
- 반면 메서드의 매개변수는 사용할 값에 대한 결정을 나중으로 미룬다.
- 제네릭에서 사용하는 용어도 메서드와 같이 매개변수, 인자의 용어를 그대로 가져다 사용한다.
- 다만 값이 아니라 타입을 결정하는 것이기 때문에 앞에 타입을 붙인다.
- 타입 매개변수 :
GenericBox<T>
에서T
- 타입 인자
GenericBox<Integer>
에서Integer
GenericBox<String>
에서String
- 타입 매개변수 :
- 다만 값이 아니라 타입을 결정하는 것이기 때문에 앞에 타입을 붙인다.
용어 정리
- 제네릭(Generic) 단어
- 제네릭이라는 단어는 일반적인, 범용적인이라는 영어 단어 뜻이다.
- 풀어보면 특정 타입에 속한 것이 아니라 일반적으로 범용적으로 사용할 수 있다는 뜻이다.
- 제네릭 타입 (Generic Type)
- 클래스나 인터페이스를 정의할 때 타입 매개변수를 사용하는 것을 말한다.
- 제네릭 클래스, 제네릭 인터페이스를 모두 합쳐서 제네릭 타입이라 한다.
- 타입은 클래스, 인터페이스, 기본형(
int
등)을 모두 합쳐서 부르는 말이다.
- 타입은 클래스, 인터페이스, 기본형(
- 예 :
class GenericeBox<T> { private T t; }
- 여기에서
GenericBox<T>
를 제네릭 타입이라 한다.
- 타입 매개변수 (Type Parameter)
- 제네릭 타입이나 메서드에서 사용되는 변수로, 실제 타입으로 대체된다.
- 예 :
GenericBox<T>
- 여기에서
T
를 타입 매개변수라 한다.
- 타입 인자(Type Argument)
- 제네릭 타입을 사용할 때 제공되는 실제 타입이다.
- 예 :
GenericBox<Integer>
- 여기에서
Integer
를 타입 인자라 한다.
제네릭 명명 관례
- 타입 매개변수는 일반적인 변수명처럼 소문자로 사용해도 문제가 없다.
- 하지만 일반적으로 대문자를 사용하고 용도에 맞는 단어의 첫 글자를 사용하는 관례를 따른다.
- E - Element
- K - Key
- N - Number
- T - Type
- V - Value
- S,U,V etc. - 2nd, 3rd, 4th types
제네릭 기타
다음과 같이 한번에 여러 타입 매개변수를 선언할 수 있다.
class Data<K, V> {}
타입 인자로 기본형은 사용할 수 없다.
제네릭의 타입 인자로 기본형(int
, double
, ...)은 사용할 수 없다.
대신에 래퍼 클래스(Integer
, Double
)를 사용하면 된다.
로 타입(row type)
public class RowTypeMain {
public static void main(String[] args) {
GenericBox integerBox = new GenericBox();
integerBox.set(10);
Integer result = (Integer) integerBox.get();
System.out.println("result = " + result);
}
}
- 다이아몬드엔 아무것도 선언하지 않았다.
- 이를 원시 타입 혹은 로 타입이라고 한다.
- 로 타입은 자바에 제네릭이 등장할 당시 하위 호환을 위해 두었다.
- 로 타입은 사용하지 않고, 제네릭 타입을 사용해야 한다.
2-5. 제너릭 활용 예제
- Animal (부모)
- Dog(Animal 상속)
- Cat(Animal 상속)
public class AnimalMain1 {
public static void main(String[] args) {
Animal animal = new Animal("동물", 0);
Dog dog = new Dog("멍멍이", 100);
Cat cat = new Cat("냐옹이", 50);
Box<Dog> dogBox = new Box<>();
dogBox.set(dog);
Dog findDog = dogBox.get();
System.out.println("findDog = " + findDog);
Box<Cat> catBox = new Box<>();
catBox.set(cat);
Cat findCat = catBox.get();
System.out.println("findCat = " + findCat);
Box<Animal> animalBox = new Box<>();
animalBox.set(animal);
Animal findAnimal = animalBox.get();
System.out.println("findAnimal = " + findAnimal);
}
}
// 결과
findDog = Animal{name='멍멍이', size=100}
findCat = Animal{name='냐옹이', size=50}
findAnimal = Animal{name='동물', size=0}
참고로 Box<Anmimal>
에 해당하는 부분은 Dog
와 Cat
이 Animal
을 상속하므로, Animal
의 하위타입인 Dog
와 Cat
도 들어갈 수 있다.
public class AnimalMain2 {
public static void main(String[] args) {
Animal animal = new Animal("동물", 0);
Dog dog = new Dog("멍멍이", 100);
Cat cat = new Cat("냐옹이", 50);
Box<Animal> animalBox = new Box<>();
animalBox.set(animal);
animalBox.set(dog);
animalBox.set(cat);
Animal findAnimal = animalBox.get();
System.out.println("findAnimal = " + findAnimal);
}
}
// 결과
findAnimal = Animal{name='냐옹이', size=50}
get()
을 하면Animal
이 나온다.- 그리고 출력을하면 마지막으로 들어갔던 값이 출력된다.
3. 요약
제네릭 클래스에 대해 알아보았다.
- 제네릭 클래스로 인해서 중복 코드 제거와 타입 안정성을 한 번에 잡을 수 있다.
- 제네릭 클래스의 타입 매개변수로 인해서 클래스가 생성될 시점에 내부의 타입 결정을 추론하여 결정할 수 있었다.
'Language > JAVA' 카테고리의 다른 글
김영한의 실전 자바 - 중급 2편 - Sec 04. 컬렉션 프레임워크 - ArrayList (0) | 2025.01.09 |
---|---|
김영한의 실전 자바 - 중급 2편 - Sec 03. 제네릭 - Generic2 (0) | 2025.01.05 |
인프런 - 김영한 - 실전 자바 중급 1편 요약 및 정리 (3) | 2024.12.08 |
인프런 - 김영한 - 실전 자바 기본편 요약 및 정리 (2) | 2024.10.31 |
Java - Scope (2) | 2023.12.11 |