728x90
1. 지네릭스
- 정의
- 동적일 필요없는 타입을 정적으로 변환 → 컬렉션 클래스에 컴파일 시의 타입 체크를 해주는 기능 → 런타임 에러를 줄여준다.
- 클래스에서 지정한 지네릭스 타입으로 인해 다양한 타입의 객체들을 다루는 메서드에 type을 제한할 수 있다.
- type 제한: 지네릭스 타입 객체 및 지네릭스 타입의 자손 타입 객체 가능
- method 중 지네릭스가 지정된 method는 반환시 지네릭스 타입을 반환한다
- 장점
- 타입 안정성을 제공한다
- 형변환 error(classCastException)을 방지한다.
- 의도치 않은 타입의 객체를 저장하는 것을 막고, 저장된 객체를 꺼내올 때 월래의 타입과 다른 타입으로 형변환되어 발생할 수 있는 오류를 줄여준다.
- 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해진다.
- 타입 안정성을 제공한다
ArrayList<Tv> tvList = new ArrayList<Tv>(); // 컬렉션 클래스에 들어갈 수 있는 type 지정
tvList.add(new Tv());
tvList.add(new TvBaby());
// 지네릭스가 지정한 클래스 및 자손 클래스 를 메서드에 넣을 수 있다.
tvList.get(new TvBaby());
// get으로 나올 때 Tv type으로 나온다. ArrayList에서 해당 method의 type을 지네릭스로 지정했기 때문
// ArrayList class source code
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
...
public E get(int index) {
Objects.checkIndex(index, size);
return elementData(index);
}
...
}
2. 타입 변수
public class ArrayList<E> extends AbstractList<E>
public transient [E] elementData;
public boolean add(E o) {/* 내용 생략 */}
public E get(int index) {/* 내용 생략 */}
...
}
// 지네릭 타입에 실제 class type 대입한 형태
public class ArrayList<Tv> extends AbstractList<Tv>
public transient [Tv] elementData;
public boolean add(Tv o) {/* 내용 생략 */}
public Tv get(int index) {/* 내용 생략 */}
...
}
- <E>
- class에서 지네릭스를 사용할 수 있도록 의미 없는 문자를 넣은 것
단, 해당 위치에는 참조형 타입만 들어간다. - method로 예를 들면 매개변수로 생각하면 된다. 이후 내가 직접 객체를 생성할 때 class Type을 넣어주면 된다.
- class에서 지네릭스를 사용할 수 있도록 의미 없는 문자를 넣은 것
- 지네릭스가 들어갈 수 있는 곳
- class 선언부
- class 명 옆
- 상속 클래스 옆
- 메서드 선언부
- method명 앞
- 메서드 선언부가 작성되면 해당 메서드는 무조건 메서드 선언부의 generic의 영향을 받는다.
- 필드 타입
- 메서드
- 매개변수 타입
- 반환타입
- class 선언부
- 지네릭스가 사용 불가한 곳
- static : 지네릭스가 iv로 간주되기 때문
- new 연산자 이후 : 컴파일 시점에 타입 T가 무엇인지 정확히 알아야하기 때문
3. 지네릭스의 용어
class Box<T> {}
// Box : 원시 타입
// T : 타입 변수 또는 타입 매개변수
// Box<T> : 지네릭 클래스
- 타입 변수(타입 매개변수)
- 항상 타입 변수와 일치한 class가 와야 한다
- 다형성 불가
- 원시 타입
- 다형성 가능
// 타입 변수 다형성 불가
ArrayList<Tv> list = new ArrayList<Tv>();
ArrayList<Product> list = new ArrayList<Tv>(); // 에러
// 원시 타입 다형성 가능
List<Tv> list = new ArrayList<Tv>();
List<Tv> list = new LinkedList<Tv>();
// 생성된 객체를 이용한 method는 상속관계 허용 -> 실제로는 class에서 어떻게 설정했는지에 따라 다르다
// 하지만 형변환 필요
// 아래 예시에서는 지네릭스 타입 변수가 Product이므로 get으로 꺼낼시 모든 Product type으로 반환된다.
ArrayList<Product> list = new ArrayList<Product>();
list.add(new Product());
list.add(new Tv()); // 가능
list.add(new Audio()); // 가능
4. 지네릭스 타입과 다형성
- 규칙 3가지
- 객체 생성 시 generic type은 일치해야 한다.
- 객체 생성 시 원시타입은 불일치해도 된다.
- 생성된 객체를 이용한 mehtod는 상속관계 허용 대신 형변환 필요하다.
ArrayList<Tv> list = new ArrayList<Tv>();
class 자체에 지네릭스 타입을 지정하는 이유
- 해당 클래스 메서드나 변수로 들어오는 값을 지네릭스 타입과 지네릭스 타입의 자손으로 제한 가능
- 다른 클래스의 메서드에서 매개변수로 해당 클래스의 타입을 지정 시 들어오는 객체를 제한 가능
class Try {
public static void main(String[] args) {
ArrayList<Product> productList = new ArrayList<Product>();
ArrayList<Tv> tvList = new ArrayList<>();
productList.add(new Tv());
productList.add(new Audio());
tvList.add(new Tv());
tvList.add(new Audio()); // 1. 제한 error.
printAll(productList);
printAll(tvList); // 2. 제한 error.
}
// 다른 class의 메서드 매개변수
public static void printAll(ArrayList<Product> list) {
for (Product p : list) {
System.out.println(p);
}
}
}
class Product{}
class Tv extends Product {}
class Audio extends Product{}
5. 지네릭스 사용하는 class
- Iterator <E>
- HashMap <K, V>
6. 제한된 지네릭 클래스
- 필요한 이유
- class Tv<T> {}를 사용할 때 <T> 타입 변수에 아무 클래스나 다 넣을 수 있다.
- T에 들어갈 수 있는 타입을 제한하기 위해 제한된 지네릭 클래스를 만들었다.
- T extends Type: Type을 구현한 것들로만 지네릭스 지정 가능
- class type 제한 방법
- 단일만 가능
- 위치는 무조건 interface 구현보다 처음에 작성
- interface 구현 요구
- 다중 가능
- 위치는 class type 이후
- implements 사용 안 함
- class type interface 복합 사용 시
- &로 연결
- class type 제한 방법
// 제공된 조상 class 와 인터페이스들
class Box<T>{}
interface Eatable{}
interface Eatable1{}
// class type으로 제한하기
class FruitBox <T extends Fruit> extends Box<T>
// 인터페이스 구현 제약
class FruitBox <T extends Eatable> extends Box<T>
class FruitBox <T extends Eatable & Eatable1> extends Box<T>
// class type과 인터페이스 구현 둘다 제한
class FruitBox <T extends Fruit & Eatable> extends Box<T>
- extends Box에 타입변수 <T>를 붙이는 이유
- 규칙이다. 안 써도 되지만 쓰도록 권장
- 만약 Box라는 조상 클래스 타입변수에 제한된 generic 이 존재 시 자손 class도 해당 제한된 generic을 extends 해줘야 한다.
class Box<T extends Toy> {
ArrayList<T> list = new ArrayList<>();
void add(T item) {
list.add(item);
}
T get(int i) {
return list.get(i);
}
int size() {
return list.size();
}
public String toString() {
return list.toString();
}
}
class FruitBox<T extends Toy & Eatable> extends Box<T> {
}
7. 와일드카드
- class 다형성으로 인해서 동일한 정적 메서드 (static method)를 만들 경우 문제가 발생한다.
- T에 맞춰서 동일한 static method를 생성해야한다. == 중복 메서드 발생
- 해당 중복메서드는 class 파일 입장에서 지네릭스만 다르다고 오버로딩을 허용해주지 않아 컴파일 에러 발생 == 메서드 중복 정의
- 해당 문제점을 해결하기 위해서 와일드 카드로 유연하게 타입을 받을 수 있도록 한다.
- 정의 : 와일드카드는 지네릭스가 아닌 지네릭스 내부에 들어갈 수 있는 타입 중에서 해당 문제점들을 해결하기 위해 만들어진 유연한 타입이다.
7.1. 예시
- 와일드 카드 정의 파악하기
// 지네릭스의 내부 타입 지정시 타입의 다형성 적용 불가
ArrayList<Tv> list = new ArrayList<Tv>();
// 와일드 카드란 유연한 타입을 이용해서 지네릭스 타입 유연성 적용
ArrayList<? extends Tv> list = new ArrayList<Tv>();
ArrayList<? extends Tv> list = new ArrayList<CaptionTv>();
- 사용자 구현 와일드 카드
public class Algorithm {
public static void main(String[] args) {
// 와일드 카드는 지네릭스가 아닌 지네릭스의 타입에 들어가는 type 중 유연한 type 이다.
FruitBox<? extends Fruit> fb = new FruitBox<Fruit>();
FruitBox<? extends Fruit> fb2 = new FruitBox<Apple>();
}
}
class Fruit {}
class Apple extends Fruit {}
class Banana extends Fruit {}
class FruitBox<T> {} // 지네릭 타입으로 어떤 타입이 들어올지 단일타입이 올지, 와일드카드 타입이 올지 대기중
- static method의 다형성 해결 문제점
class Juicer {
static Juice makeJuice (FruitBox<Fruit> box) {
// to-do
}
}
FruitBox<Fruit> fruitBox = new FruitBox<>();
FruitBox<Apple> appleBox = new FruitBox<>();
Juicer.makeJuice(fruitBox);
Juicer.makeJuice(appleBox); // error
---
class Juicer {
static Juice makeJuice (FruitBox<Fruit> box) {
// to-do
}
// error
static Juice makeJuice (FruitBox<Apple> box) {
// to-do
}
}
FruitBox<Fruit> fruitBox = new FruitBox<>();
FruitBox<Apple> appleBox = new FruitBox<>();
Juicer.makeJuice(fruitBox);
Juicer.makeJuice(appleBox); // error
- 와일드 카드로 해결
class Juicer {
static Juice makeJuice (FruitBox<? extends Fruit> box) {
// to-do
}
}
FruitBox<Fruit> fruitBox = new FruitBox<>();
FruitBox<Apple> appleBox = new FruitBox<>();
Juicer.makeJuice(fruitBox);
Juicer.makeJuice(appleBox);
7.2. 와일드 카드 종류
- <? extends T> 와일드카드의 상한 제한
- <? super T> 와일드 카드의 하한 제한
- <?> 제한 없음, 모든 타입이 가능
- 지네릭스 제한과 차이점
이름 | 정의 | 사용 | 차이 |
제한된 지네릭 클래스 | extends | T extends Fruit | 지네릭 타입 T 유지 |
와일드 카드 | ? extends T ? super T ? |
? extends/super Fruit | 실제 타입 작성 |
8. 지네릭 메서드
- class에 선언된 지네릭스의 타입은 원칙적으로 모든 곳에 영향을 끼친다.
- 해당 클래스의 E type이 정해지면 매개변수, method 반환타입에 동일하게 적용된다.
public class ArrayList<E> extends AbstractList<E>
public transient [E] elementData;
public boolean add(E o) {/* 내용 생략 */}
public E get(int index) {/* 내용 생략 */}
...
}
- 하지만 지네릭스 메서드를 작성하게 되면 해당 메서드만큼은 class의 type에 영향을 받지 않고 지네릭 메서드의 영향을 받는다.
public interface Map<K, V> {
...
// 지네릭 메서드의 K, V와 Map의 K, V 는 별개이고
// 지네릭 메서드 선언으로 인해서 해당 메서드 모든 부분의 K,V는 다 지네릭 메서드 K,V의 영향을 받는다
static <K, V> Map<K, V> of() {
return (Map<K,V>) ImmutableCollections.EMPTY_MAP;
}
}
// 알아보기 쉽게 지네릭 명칭만 바꿈
public interface Map<K, V> {
...
static <methodK, methodV> Map<methodK, methodV> of() {
return (Map<methodK, methodV>) ImmutableCollections.EMPTY_MAP;
}
}
9. 중간 정리
- Class
- <T> :기본 타입 형식
- <T extends type & interface> : 제한된 타입 형식
위 2가지의 generics type이 들어갈 수 있음만 작성 가능
- 실제 객체 생성 시
- <T> 혹은 <T extends type & interface>에 들어갈 수 있는 실제 class type을 작성
- 이때 다형성을 적용하고 싶을 때 <? 와일드카드 타입 > 사용한다.
- method
- 매개변수
- 메서드가 받는 매개변수 class type에 generic가 정의되어 있으면 실제 객체 생성때와 똑같이 작성하면 된다.
- method 반환타입 앞에 작성된 타입변수
- 지역변수들의 타입을 지정해 주는 generics type
- class에 generics 타입이 존재하지 않아도 사용 가능
- static과 인스턴스 상관없음
- 실제 메서드 사용 시 생략가능
- 매개변수
10. Annotation
- 자바 어노테이션은 컴파일러에게 코드 분석 정보를 제공하기 위한 메타데이터이다.
- 메타데이터란 데이터를 설명하는 데이터로, 코드에 대한 설명이나 구조, 실행 방법 등의 정보를 컴파일러나 런타임 시 사용할 수 있도록 도와준다.
- 어노테이션은 주로 @으로 시작하는 형태의 특별한 문법을 사용하여 작성한다.
- 어노테이션은 컴파일러나 런타임 시점에서 소스 코드를 분석하고, 해당 정보를 이용하여 다양한 작업을 수행한다.
- 어노테이션을 사용하는 방법은 크게 세 가지
- 어노테이션을 이용하여 소스 코드에 부가 정보를 추가
- 컴파일러가 어노테이션 정보를 이용하여 코드를 검증
- 런타임 시점에서 어노테이션 정보를 이용하여 코드를 실행
11. Annotation 종류
어노테이션 | 설명 |
@Override | 컴파일러에게 오버라이딩하는 메서드 알림 |
@Deprecated | 앞으로 사용하지 않을 것을 권장 |
@SuppressWarnings | 컴파일러의 특정 경고 메시지 제거 |
@SafeVarargs | 지네릭스 타입의 가변인자에 사용 (JDK1.7) |
@FunctionalInterface | 함수형 인테페이스라는 것을 알림 (JDK1.8) |
@Native | native메서드에서 참조되는 상수 앞에 붙임 (JDK1.8) |
@Target | 어노테이션이 적용가능한 대상을 지정 |
@Documented | 어노테이션 정보가 javadoc으로 작성된 문서에 포함되게 한다. |
@Inherited | 어노테이션이 자손 클래스에 상속되도록 한다. |
@Retention | 어노테이션이 유지되는 범위를 지정하는데 사용 |
@Repeatable | 어노테이션을 반복해서 적용 (JDK 1.8) |
12. Meta Annotaion
12.1. @Target
- @Target으로 지정할 수 있는 어노테이션 적용대상 종류
대상 타입 | 의미 |
ANNOTATION_TYPE | 어노테이션 |
CONSTRUCTOR | 생성자 |
FIELD | 필드(멤버변수, enum 상수) |
LOCAL_VARIABLE | 지역변수 |
METHOD | 메서드 |
PACKAGE | 페키지 |
PARAMETER | 매개변수 |
TYPE | 타입 (클래스, 인터페이스, enum) |
TYPE_PARAMETER | 타입 매개변수(JDK 1.8) |
TYPE_USE | 타입이 사용되는 모든 곳 (JDK 1.8) |
- 예시
- FIELD : 기본형에 적용
- TYPE_USE: 참조형에 적용
@Target(FIELD, TYPE, TYPE_USE)
public @interface MyAnnotation {}
@MyAnnotation //TYPE
class MyClass {
@MyAnnotation // FIELD
int i;
@MyAnnotation // TYPE_USE
MyClass mc;
}
12.2. @Retetion
- @Retention policy 종류
유지 정책 | 의미 |
SOURCE | 소스 파일에만 존재, 컴파일러가 사용하는 어노테이션 |
RUNTIME | 실행시 reflection을 통해 처리하는 정보 |
CLASS | 컴파일러가 어노테이션의 정보를 클래스 파일에 저장 하지만, .class가 JVM에 로딩시 어노테이션 정보가 무시되어 실행되기 때문에 어노테이션 정보를 얻을 수 없다. |
12.3. @Document
- 어노테이션 정보를 javadoc으로 작성한 문서에 포함되도록 한다.
- @Override, @SuppressWarnings 제외하고 모두 해당 메타 어노테이션이 붙어 있다.
12.4. @Interited
@Interited
@interface SupperAnno {}
@SuperAnno
class Parent {}
class Child extends Parent {} // @SuperAnno가 붙은 것으로 인식
12.5. @Repeatable
- 같은 이름의 어노테이션이 여러 개가 하나의 대상에 적용될 수 있기 때문에, 이 어노테이션들을 하나로 묶어서 다룰 수 있는 어노테이션도 추가로 정의 되야한다.
@interface ToDos { // 여러 개의 ToDo 어노테이션을 담을 컨테이너 어노테이션
ToDo[] value(); // 이름은 반드시 value
}
@Repeatable(ToDos.class) // 괄호안에 컨테이너 어노테이션을 지정
@interface ToDo {
String value();
}
13. Annotation 정의 및 요소
- Annotation 정의
- 어노테이션은 주석, 주해, 메모라는 의미를 가지고 있다.
여러가지 어노테이션들이 존재하지만 각 어노테이션 마다 해당 어노테이션을 보고 compile 및 runtime 때 어노테이션 정보를 이용해서 특정 동작을 수행한다.
따라서, 해당 어노테이션이 어떤 프로그램에서는 의미가 있고 어떤 프로그램에서는 의미를 가지지 않게 된다. 의미를 가지지 않는 경우는 주석과 같이 존재하지 않는 것이나 다름없다. - 예시
@Test는 test 프로그램에게는 의미가 있지만 javadoc.exe에게는 주석과 다름없다. javadoc.exe에게는 @author, @since가 오히려 의미를 가지는 어노테이션이다.
- 어노테이션은 주석, 주해, 메모라는 의미를 가지고 있다.
- 규칙
- 요소로 추상 메서드만 가능
- 요소의 타입은 기본현, String, enum, 어노테이션, Class 만 허용
- 요소의 선언부 () 내부에 매개변수를 선언할 수 없다.
- 예외 선언 불가
- 요소를 타입 매개변수로 정의 불가 == 지네릭스 타입 불가
@interface 어노테이션이름 {
타입 요소이름1(); // 추상메서드로 선언
타입 요소이름2(); // 추상메서드로 선언
타입 요소이름3(); // 추상메서드로 선언
// 순서 상관 없다.
}
@어노테이션이름(요소이름3=값, 요소이름2=값, 요소이름1=값)
class Practice {
...
}
- Annotation 내부는 인터페이스와 동일하게 추상메서드 작성
- 상수는 작성 가능 static final int = 7;
- 디폴트 메서드 정의 불가
13.1. 어노테이션 사용 규칙
- default 값을 지정할 수 있다. - null은 불가
- 실제 사용 시 값 생략 가능
@interface TestInfo{
int count() default 1; // 기본값 1
}
@TestInfo // 생략가능
class Try{}
- 어노테이션 요소가 하나
- 요소의 이름 생략 작성 가능
@interface TestInfo{
int value();
}
@TestInfo(3) // @TestInfo(count = 3) -> 원래는 이렇게 해야함
class Try{}
- 어노테이션 요소가 여러 개이고 요소들이 default 값이 있어서 실제 사용 시 value 하나만 작성해야 할 때
- value는 요소이름 생략 가능
@interface test{
int str() default 1;
int i()default 3;
String[] value();
}
@test("vvv")
class Try {}
13. Annotation 조상
- 애너테이션은 상속이 허용되지 않는다.
- 하지만 모든 애너테이션의 조상인 Annotation은 일반 interface로 정의 되어있어서 조상이 될 수 있다.
- 그래서 모든 애너테이션은 조상 annotation의 추상 메서드를 상속받는다.
- 메서드 종류
public interface Annotation{
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
- 해당 메서드 호출하고 싶을 때. class를 붙여서 사용하면 된다.
@interface Test{}
@Test
class Try{
public static void main(String[] args) {
Test.class.toString
}
}
이전 발행글 : 11. Collection framework 개념
다음 발행글 : 13. 쓰레드