이 글은 유튜브 '자바의 정석 - 기초편'을 보고 정리한 글입니다.
📂content
1. 제네릭(Generics)란?
- 컴파일시 타입을 체크해 주는 기능(compile-time type check) - JDK1.5
//Tv객체만 저장할 수 있는 ArrayList를 생성
ArrayList<Tv> tvList = new ArrayList<Tv>();
tvList.add(new Tv()); //OK
tvList.add(new Audio()); //컴파일 에러. Tv 외에 다른 타입은 저장 불가

- 객체의 타입 안정성을 높이고 형변환의 번거로움을 줄여줌 (하나의 컬렉션에는 대부분 한 종류의 객체만 저장)
제네릭의 장점
1. 타입 안정성을 제공한다.
2. 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해진다.

Exception(예외) : RuntimeError(실행 중에 발생하는 에러)
RuntimeException : 프로그래머 실수로 발생하는 에러
ClassCastException : 형변환 에러
NullPointerException : 참조변수 null
IndexOutOfBoundsException : 배열범위를 벗어나는 에러
RuntimeError보다는 CompileTimeError가 낫다. 프로그램이 실행되기 전에 수정할 수 있기 때문이다.
여기서 ClassCastException을 CompileTime에 Type정보를 주어서 컴파일러가 체크할 수 있게 만들어준 것이 제네릭이다. 마찬가지로 String str = null; 보다는 Strint str = "";이 좋다. Object[] objArr = null;보다는 Object[] objArr = new Object[0]; 또는 Object[] objArr = {};가 더 좋은 코드이다.
⍟실습
package etc;
import java.util.ArrayList;
public class GenericTest {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add(10);
list.add(20);
list.add("30"); //String을 추가
Integer i = (Integer) list.get(2); //컴파일 OK
//하지만 실행하면 에러가 난다.
System.out.println(list);
}
}

컴파일할 때는 괜찮지만, 실제로 실행을 하면 에러가 난다. list에서 꺼낼 때, Object로 꺼내기 때문에 허용을 했지만, 실제로는 String값이 들어가 있다.
package etc;
import java.util.ArrayList;
public class GenericTest {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<Integer>();
//만약에 여러개 저장하고 싶다면, Object를 사용한다. 하지만 꺼낼 때 형변환을 해야한다.
// ArrayList<Object> list = new ArrayList<Object>();
list.add(10); //list.add(new Integer(10));
list.add(20);
list.add(30); //타입 체크가 강화됨. 제네릭 덕분에
Integer i = list.get(2); //컴파일 OK. 형변환 생략 가능
//하지만 실행하면 에러가 난다.
System.out.println(list);
}
}
제네릭 타입을 써주어야 하는 클래스가 있다.
ArrayList(일반 클래스) -> ArrayList<E> (제네릭 클래스)
2. 타입 변수
- 클래스를 작성할 때, Object타입 대신 타입 변수(E)를 선언해서 사용.
public class ArrayList extends AbstractList { //일부 생략
private transient Object[] elementData;
public boolean add(Object o) {/* 내용생략 */}
public Object get(int index) {/* 내용생략 */}
...
}
위 코드가 아래와 같이 바뀌었다. Object대신 E로 바뀌었다. JDK1.5부터 일반클래스가 제네릭클래스로 바뀌었다.
public class ArrayList<E> extends AbstractList<E> { //일부 생략
private transient E[] elementData;
public boolean add(E o) {/* 내용생략 */}
public E get(int index) {/* 내용생략 */}
...
}
E는 Element(배열의 요소)의 약자로 쓰인다.
3. 타입 변수에 대입하기
- 객체를 생성 시, 타입 변수(E) 대신 실제 타입(Tv)을 지정(대입)
//타입 변수 E 대신에 실제 타입 Tv를 대입
ArrayList<Tv> tvList = new ArrayList<Tv>();
- 타입 변수 대신 실제 타입이 지정되면, 형변환 생략가능

전자는 tvList.get(0)하면 반환값이 Object이기 때문에 형변환을 해야 했는데,
후자는 반환값이 tv라서 형변환이 불필요하다.
4. 지네릭스 용어
Box<T> 지네릭스 클래스. 'T의 Box' 또는 'T Box'라고 읽는다.
T 타입 변수 또는 타입 매개변수. (T는 타입 문자)
Box 원시 타입(raw type)

5. 지네릭 타입과 다형성
- 참조 변수와 생성자의 대입된 타입은 일치해야 한다.
class Product {}
class Tv extends Product {}
class Audio extends Product {}
ArrayList<Tv> list = new ArrayList<Tv>(); //OK. 일치
ArrayList<Product> list = new ArrayList<Tv>(); //에러. 불일치
Product와 Tv가 조상-자손인관계여도 안 된다.
- 지네릭 클래스간의 다형성은 성립.(여전히 대입된 타입은 일치해야)
List<Tv> list = new ArrayList<Tv>(); //OK. 다형성. ArrayList가 List를 구현
List<Tv> list = new LinkedList<Tv>(); //OK. 다형성. LinkedList가 List를 구현
- 매개변수의 다형성도 성립
ArrayList<Product> list = new ArrayList<Product>();
list.add(new Product());
list.add(new Tv()); //OK.
list.add(new Audio)); //OK.
타입변수로 Product가 지정이 되면, Product의 자손이 저장이 될 수 있다.
boolean add(E e) { ... } -> boolean add(Product e) { ... } 가 되면서 Product와 그 자손 객체가 다형성에 의해 저장이 되기 때문이다.
E get(int index) { ... } -> Product get(int index) { ... }가 된다.
Product p = list.get(0);
Tv t = (Tv)list.get(1);
그래서 형변환이 필요가 없다.
⍟실습
Ex12_1
package etc;
import java.util.*;
class Product {}
class Tv extends Product {}
class Audio extends Product {}
class Ex12_1 {
public static void main(String[] args) {
ArrayList<Product> productList = new ArrayList<Product>();
ArrayList<Tv> tvList = new ArrayList<Tv>();
// ArrayList<Product> tvList = new ArrayList<Tv>(); // 에러.
// List<Tv> tvList = new ArrayList<Tv>(); // OK. 다형성
productList.add(new Tv()); //public boolean add(Product e) {} => Product와 그 자손 OK
productList.add(new Audio());
tvList.add(new Tv()); //public boolean add(Tv e) {}
tvList.add(new Tv());
// tvList.add(new Audio()); //에러
printAll(productList);
// printAll(tvList); // 컴파일 에러가 발생한다.
}
public static void printAll(ArrayList<Product> list) {
for (Product p : list)
System.out.println(p);
}
}
6. 지네릭 클래스의 예
1. Iterator<E>
- 클래스를 작성할 때, Object타입 대신 T와 같은 타입 변수를 사용




Iterator가 일반 클래스일 때, Object로 되어있었다. 근데 지네릭클래스로 바뀌면서 Object가 타입변수로 E로 변함.
옛날에는 형변환이 필요했다. 그런데 제네릭클래스로 바뀌고 난 후, E에 Student가 대입이 되면서 Student를 반환하게 되면서 형변환을 할 필요가 사라졌다.
⍟실습
Ex12_2
import java.util.*;
class Ex12_2 {
public static void main(String[] args) {
ArrayList<Student> list = new ArrayList<Student>();
list.add(new Student("자바왕", 1, 1));
list.add(new Student("자바짱", 1, 2));
list.add(new Student("홍길동", 2, 1));
Iterator<Student> it = list.iterator();
while (it.hasNext()) {
// Student s = (Student)it.next(); // 지네릭스를 사용하지 않으면 형변환 필요.
Student s = it.next();
System.out.println(s.name);
}
} // main
}
class Student {
String name = "";
int ban;
int no;
Student(String name, int ban, int no) {
this.name = name;
this.ban = ban;
this.no = no;
}
}
2. HashMap<K,V>
- 여러 개의 타입 변수가 필요한 경우, 콤마(,)를 구분자로 선언



역시 마찬가지로 형변환을 할 필요가 없어졌다.
⍟실습
package etc;
import java.util.*;
class Ex12_2 {
public static void main(String[] args) {
HashMap<String, Student> map = new HashMap<>(); //jdk 1.7부터 생성자에 타입지정 생략가능
map.put("자바왕", new Student("자바왕", 1,1,100,100,100));
//public Student get(Object key) {}
Student s = map.get("자바왕");
System.out.println(map);
} // main
}
class Student {
String name = "";
int ban; //반
int no; //번호
int kor;
int eng;
int math;
public Student(String name, int ban, int no, int kor, int eng, int math) {
this.name = name;
this.ban = ban;
this.no = no;
this.kor = kor;
this.eng = eng;
this.math = math;
}
}
7. 제한된 지네릭 클래스
- extends로 대입할 수 있는 타입을 제한
class FruitBox<T extends Fruit> { //Fruit의 자손만 타입으로 지정가능
ArrayList<T> list = new ArrayList<T>();
...
}
FruitBox<Apple> appleBox = new FruitBox<Apple>(); //OK
FruitBox<Toy> toyBox = new FruitBox<Toy>(); //에러. Toy는 Fruit의 자손이 아님.
- 인터페이스인 경우에도 extends를 사용
interface Eatable {}
class FruitBox<T extends Eatavle> { ... }
implements를 쓸 것 같지만 extends를 사용한다.
⍟실습
Ex12_3
package etc;
import java.util.ArrayList;
class Fruit implements Eatable {
public String toString() { return "Fruit";}
}
class Apple extends Fruit { public String toString() { return "Apple";}}
class Grape extends Fruit { public String toString() { return "Grape";}}
class Toy { public String toString() { return "Toy" ;}}
interface Eatable {}
class Ex12_3 {
public static void main(String[] args) {
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();
FruitBox<Grape> grapeBox = new FruitBox<Grape>();
// FruitBox<Grape> grapeBox = new FruitBox<Apple>(); // 에러. 타입 불일치
// FruitBox<Toy> toyBox = new FruitBox<Toy>(); // 에러.
fruitBox.add(new Fruit());
fruitBox.add(new Apple());
fruitBox.add(new Grape());
appleBox.add(new Apple());
// appleBox.add(new Grape()); // 에러. Grape는 Apple의 자손이 아님
grapeBox.add(new Grape());
System.out.println("fruitBox-"+fruitBox);
System.out.println("appleBox-"+appleBox);
System.out.println("grapeBox-"+grapeBox);
} // main
}
class FruitBox<T extends Fruit & Eatable> extends Box<T> {}
//사실 Fruit이 Eatable 인터페이스 상속을 받아서 Eatable을 받을 필요가 없다.
//2개를 상속받을 때 ','를 쓰지 않고 '&'을 쓴다.
class Box<T> {
ArrayList<T> list = new ArrayList<T>(); //item을 저장할 list
void add(T item) { list.add(item); } //박스에 item을 추가
T get(int i) { return list.get(i); } //박스에서 item을 꺼낼때
int size() { return list.size(); }
public String toString() { return list.toString();}
}
- 헷갈리면 그림으로 그려보기. 위 코드의 클래스 관계는 아래와 같다.

8. 지네릭스의 제약
- 타입 변수에 대입은 인스턴스 별로 다르게 가능
Box<Apple> appleBox = new Box<Apple>(); //OK. Apple객체만 저장가능
Box<Grape> grapeBox = new Box<Grape>(); //OK. Grape객체만 저장가능
- static멤버에 타입 변수 사용 불가
class Box<T> {
static T item; //에러
static int compare(T t1, T t2) { ... } //에러
...
static은 모든 인스턴스의 공통이기 때문이다.
- 배열 생성할 때 타입 변수 사용불가. 타입 변수로 배열 선언은 가능
class Box<T> {
T[] itemArr; //OK. T타입의 배열을 위한 참조변수
...
T[] toArray() {
T[] tmpArr = new T[itemArr.length]; //에러. 지네릭 배열 생성불가
...
출처
'🎥Back > 자바의 정석' 카테고리의 다른 글
[JAVA의 정석]제네릭 형변환 (0) | 2024.01.19 |
---|---|
[JAVA의 정석]와일드카드, 제네릭 메서드 (0) | 2024.01.19 |
[JAVA의 정석]Collections 클래스, 컬렉션 클래스 요약 (2) | 2024.01.17 |
[JAVA의 정석]HashMap (0) | 2024.01.17 |
[JAVA의 정석]TreeSet (0) | 2024.01.17 |
이 글은 유튜브 '자바의 정석 - 기초편'을 보고 정리한 글입니다.
📂content
1. 제네릭(Generics)란?
- 컴파일시 타입을 체크해 주는 기능(compile-time type check) - JDK1.5
//Tv객체만 저장할 수 있는 ArrayList를 생성 ArrayList<Tv> tvList = new ArrayList<Tv>(); tvList.add(new Tv()); //OK tvList.add(new Audio()); //컴파일 에러. Tv 외에 다른 타입은 저장 불가

- 객체의 타입 안정성을 높이고 형변환의 번거로움을 줄여줌 (하나의 컬렉션에는 대부분 한 종류의 객체만 저장)
제네릭의 장점
1. 타입 안정성을 제공한다.
2. 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해진다.

Exception(예외) : RuntimeError(실행 중에 발생하는 에러)
RuntimeException : 프로그래머 실수로 발생하는 에러
ClassCastException : 형변환 에러
NullPointerException : 참조변수 null
IndexOutOfBoundsException : 배열범위를 벗어나는 에러
RuntimeError보다는 CompileTimeError가 낫다. 프로그램이 실행되기 전에 수정할 수 있기 때문이다.
여기서 ClassCastException을 CompileTime에 Type정보를 주어서 컴파일러가 체크할 수 있게 만들어준 것이 제네릭이다. 마찬가지로 String str = null; 보다는 Strint str = "";이 좋다. Object[] objArr = null;보다는 Object[] objArr = new Object[0]; 또는 Object[] objArr = {};가 더 좋은 코드이다.
⍟실습
package etc; import java.util.ArrayList; public class GenericTest { public static void main(String[] args) { ArrayList list = new ArrayList(); list.add(10); list.add(20); list.add("30"); //String을 추가 Integer i = (Integer) list.get(2); //컴파일 OK //하지만 실행하면 에러가 난다. System.out.println(list); } }

컴파일할 때는 괜찮지만, 실제로 실행을 하면 에러가 난다. list에서 꺼낼 때, Object로 꺼내기 때문에 허용을 했지만, 실제로는 String값이 들어가 있다.
package etc; import java.util.ArrayList; public class GenericTest { public static void main(String[] args) { ArrayList<Integer> list = new ArrayList<Integer>(); //만약에 여러개 저장하고 싶다면, Object를 사용한다. 하지만 꺼낼 때 형변환을 해야한다. // ArrayList<Object> list = new ArrayList<Object>(); list.add(10); //list.add(new Integer(10)); list.add(20); list.add(30); //타입 체크가 강화됨. 제네릭 덕분에 Integer i = list.get(2); //컴파일 OK. 형변환 생략 가능 //하지만 실행하면 에러가 난다. System.out.println(list); } }
제네릭 타입을 써주어야 하는 클래스가 있다.
ArrayList(일반 클래스) -> ArrayList<E> (제네릭 클래스)
2. 타입 변수
- 클래스를 작성할 때, Object타입 대신 타입 변수(E)를 선언해서 사용.
public class ArrayList extends AbstractList { //일부 생략 private transient Object[] elementData; public boolean add(Object o) {/* 내용생략 */} public Object get(int index) {/* 내용생략 */} ... }
위 코드가 아래와 같이 바뀌었다. Object대신 E로 바뀌었다. JDK1.5부터 일반클래스가 제네릭클래스로 바뀌었다.
public class ArrayList<E> extends AbstractList<E> { //일부 생략 private transient E[] elementData; public boolean add(E o) {/* 내용생략 */} public E get(int index) {/* 내용생략 */} ... }
E는 Element(배열의 요소)의 약자로 쓰인다.
3. 타입 변수에 대입하기
- 객체를 생성 시, 타입 변수(E) 대신 실제 타입(Tv)을 지정(대입)
//타입 변수 E 대신에 실제 타입 Tv를 대입 ArrayList<Tv> tvList = new ArrayList<Tv>();
- 타입 변수 대신 실제 타입이 지정되면, 형변환 생략가능

전자는 tvList.get(0)하면 반환값이 Object이기 때문에 형변환을 해야 했는데,
후자는 반환값이 tv라서 형변환이 불필요하다.
4. 지네릭스 용어
Box<T> 지네릭스 클래스. 'T의 Box' 또는 'T Box'라고 읽는다.
T 타입 변수 또는 타입 매개변수. (T는 타입 문자)
Box 원시 타입(raw type)

5. 지네릭 타입과 다형성
- 참조 변수와 생성자의 대입된 타입은 일치해야 한다.
class Product {} class Tv extends Product {} class Audio extends Product {} ArrayList<Tv> list = new ArrayList<Tv>(); //OK. 일치 ArrayList<Product> list = new ArrayList<Tv>(); //에러. 불일치
Product와 Tv가 조상-자손인관계여도 안 된다.
- 지네릭 클래스간의 다형성은 성립.(여전히 대입된 타입은 일치해야)
List<Tv> list = new ArrayList<Tv>(); //OK. 다형성. ArrayList가 List를 구현 List<Tv> list = new LinkedList<Tv>(); //OK. 다형성. LinkedList가 List를 구현
- 매개변수의 다형성도 성립
ArrayList<Product> list = new ArrayList<Product>(); list.add(new Product()); list.add(new Tv()); //OK. list.add(new Audio)); //OK.
타입변수로 Product가 지정이 되면, Product의 자손이 저장이 될 수 있다.
boolean add(E e) { ... } -> boolean add(Product e) { ... } 가 되면서 Product와 그 자손 객체가 다형성에 의해 저장이 되기 때문이다.
E get(int index) { ... } -> Product get(int index) { ... }가 된다.
Product p = list.get(0);
Tv t = (Tv)list.get(1);
그래서 형변환이 필요가 없다.
⍟실습
Ex12_1
package etc; import java.util.*; class Product {} class Tv extends Product {} class Audio extends Product {} class Ex12_1 { public static void main(String[] args) { ArrayList<Product> productList = new ArrayList<Product>(); ArrayList<Tv> tvList = new ArrayList<Tv>(); // ArrayList<Product> tvList = new ArrayList<Tv>(); // 에러. // List<Tv> tvList = new ArrayList<Tv>(); // OK. 다형성 productList.add(new Tv()); //public boolean add(Product e) {} => Product와 그 자손 OK productList.add(new Audio()); tvList.add(new Tv()); //public boolean add(Tv e) {} tvList.add(new Tv()); // tvList.add(new Audio()); //에러 printAll(productList); // printAll(tvList); // 컴파일 에러가 발생한다. } public static void printAll(ArrayList<Product> list) { for (Product p : list) System.out.println(p); } }
6. 지네릭 클래스의 예
1. Iterator<E>
- 클래스를 작성할 때, Object타입 대신 T와 같은 타입 변수를 사용




Iterator가 일반 클래스일 때, Object로 되어있었다. 근데 지네릭클래스로 바뀌면서 Object가 타입변수로 E로 변함.
옛날에는 형변환이 필요했다. 그런데 제네릭클래스로 바뀌고 난 후, E에 Student가 대입이 되면서 Student를 반환하게 되면서 형변환을 할 필요가 사라졌다.
⍟실습
Ex12_2
import java.util.*; class Ex12_2 { public static void main(String[] args) { ArrayList<Student> list = new ArrayList<Student>(); list.add(new Student("자바왕", 1, 1)); list.add(new Student("자바짱", 1, 2)); list.add(new Student("홍길동", 2, 1)); Iterator<Student> it = list.iterator(); while (it.hasNext()) { // Student s = (Student)it.next(); // 지네릭스를 사용하지 않으면 형변환 필요. Student s = it.next(); System.out.println(s.name); } } // main } class Student { String name = ""; int ban; int no; Student(String name, int ban, int no) { this.name = name; this.ban = ban; this.no = no; } }
2. HashMap<K,V>
- 여러 개의 타입 변수가 필요한 경우, 콤마(,)를 구분자로 선언



역시 마찬가지로 형변환을 할 필요가 없어졌다.
⍟실습
package etc; import java.util.*; class Ex12_2 { public static void main(String[] args) { HashMap<String, Student> map = new HashMap<>(); //jdk 1.7부터 생성자에 타입지정 생략가능 map.put("자바왕", new Student("자바왕", 1,1,100,100,100)); //public Student get(Object key) {} Student s = map.get("자바왕"); System.out.println(map); } // main } class Student { String name = ""; int ban; //반 int no; //번호 int kor; int eng; int math; public Student(String name, int ban, int no, int kor, int eng, int math) { this.name = name; this.ban = ban; this.no = no; this.kor = kor; this.eng = eng; this.math = math; } }
7. 제한된 지네릭 클래스
- extends로 대입할 수 있는 타입을 제한
class FruitBox<T extends Fruit> { //Fruit의 자손만 타입으로 지정가능 ArrayList<T> list = new ArrayList<T>(); ... } FruitBox<Apple> appleBox = new FruitBox<Apple>(); //OK FruitBox<Toy> toyBox = new FruitBox<Toy>(); //에러. Toy는 Fruit의 자손이 아님.
- 인터페이스인 경우에도 extends를 사용
interface Eatable {} class FruitBox<T extends Eatavle> { ... }
implements를 쓸 것 같지만 extends를 사용한다.
⍟실습
Ex12_3
package etc; import java.util.ArrayList; class Fruit implements Eatable { public String toString() { return "Fruit";} } class Apple extends Fruit { public String toString() { return "Apple";}} class Grape extends Fruit { public String toString() { return "Grape";}} class Toy { public String toString() { return "Toy" ;}} interface Eatable {} class Ex12_3 { public static void main(String[] args) { FruitBox<Fruit> fruitBox = new FruitBox<Fruit>(); FruitBox<Apple> appleBox = new FruitBox<Apple>(); FruitBox<Grape> grapeBox = new FruitBox<Grape>(); // FruitBox<Grape> grapeBox = new FruitBox<Apple>(); // 에러. 타입 불일치 // FruitBox<Toy> toyBox = new FruitBox<Toy>(); // 에러. fruitBox.add(new Fruit()); fruitBox.add(new Apple()); fruitBox.add(new Grape()); appleBox.add(new Apple()); // appleBox.add(new Grape()); // 에러. Grape는 Apple의 자손이 아님 grapeBox.add(new Grape()); System.out.println("fruitBox-"+fruitBox); System.out.println("appleBox-"+appleBox); System.out.println("grapeBox-"+grapeBox); } // main } class FruitBox<T extends Fruit & Eatable> extends Box<T> {} //사실 Fruit이 Eatable 인터페이스 상속을 받아서 Eatable을 받을 필요가 없다. //2개를 상속받을 때 ','를 쓰지 않고 '&'을 쓴다. class Box<T> { ArrayList<T> list = new ArrayList<T>(); //item을 저장할 list void add(T item) { list.add(item); } //박스에 item을 추가 T get(int i) { return list.get(i); } //박스에서 item을 꺼낼때 int size() { return list.size(); } public String toString() { return list.toString();} }
- 헷갈리면 그림으로 그려보기. 위 코드의 클래스 관계는 아래와 같다.

8. 지네릭스의 제약
- 타입 변수에 대입은 인스턴스 별로 다르게 가능
Box<Apple> appleBox = new Box<Apple>(); //OK. Apple객체만 저장가능 Box<Grape> grapeBox = new Box<Grape>(); //OK. Grape객체만 저장가능
- static멤버에 타입 변수 사용 불가
class Box<T> { static T item; //에러 static int compare(T t1, T t2) { ... } //에러 ...
static은 모든 인스턴스의 공통이기 때문이다.
- 배열 생성할 때 타입 변수 사용불가. 타입 변수로 배열 선언은 가능
class Box<T> { T[] itemArr; //OK. T타입의 배열을 위한 참조변수 ... T[] toArray() { T[] tmpArr = new T[itemArr.length]; //에러. 지네릭 배열 생성불가 ...
출처
'🎥Back > 자바의 정석' 카테고리의 다른 글
[JAVA의 정석]제네릭 형변환 (0) | 2024.01.19 |
---|---|
[JAVA의 정석]와일드카드, 제네릭 메서드 (0) | 2024.01.19 |
[JAVA의 정석]Collections 클래스, 컬렉션 클래스 요약 (2) | 2024.01.17 |
[JAVA의 정석]HashMap (0) | 2024.01.17 |
[JAVA의 정석]TreeSet (0) | 2024.01.17 |