[Java의 정석] 제너릭스, 열거형, 애노테이션(Generics, enum, annotation)

JH·2023년 6월 15일
post-thumbnail

📓 제너릭, 열거형, 애노테이션(Generic, enum, Annotation)

📕 제너릭스(Generics)

컴파일시 타입을 체크해 주는 기능 (compile-time type-check)

Array<TV> tvList = new ArrayList<TV>(); //<객체이름> - 제네릭스

tvList.add(new TV()); //ok
tvList.add(new Audio()); //에러!! TV객체만 허용

//제너릭스가 없다면 컴파일 에러가 나지 않는다!
//런타임에러(형변환 예외) 문제를 컴파일 에러로 끌고오는 효과

🔷 장점

  • 객체타입 안정성을 높이고 형변환의 번거로움 줄여줌
  • 타입체크와 형변환을 생략가능. - 코드가 간결해짐

🔷 타입변수

클래스를 작성할 때, Object타입 대신 타입 변수(E)를 선언

public class ArrayList extends AbstractList {
	private transient Object[] elementData;
    public boolean add(Object o) {}
    public Object get(int index) {}
}

// Object를 E로 변경!
public class ArrayList<E> extends AbstractList {
	private transient E[] elementData;
    public boolean add(E o) {}
    public E get(int index) {}
}

◼ 타입변수에 대입하기

객체 생성시, 타입변수(E)대신 실제 타입 지정

//E가 실제타입으로 변경됨  E → Integer
ArrayList<Integer> aList = new ArrayList<Integer>();

public class ArrayList<Integer> extends AbstractList {
	private transient Integer[] elementData;
    public boolean add(Integer o) {}
    public Integer get(int index) {}
}

// E → String
ArrayList<String> sList = new ArrayListd<String>();

public class ArrayList<String> extends AbstractList {
	private transient String[] elementData;
    public boolean add(String o) {}
    public String get(int index) {}
}

타입 변수 대신 실제타입이 지정되면, 형변환 생략가능

ArrayList<Integer> aList = new ArrayList<Integer>();

aList.add(10);
Integer i = aList.get(0); // (Integer)aList.get(0); 형변환생략됨 

🔷 제네릭스 용어

  • Box<T> : 제너릭 클래스. "T의 Box" 혹은 "T Box"라고 읽음
  • T : 타입 변수 또는 타입 매개변수. (T는 타입문자)
  • Box : 원시 타입(raw type)
class Box<T> {} // 제너릭 클래스.

Box<String> b = new Box<String>(); //제너릭 타입 호출.

🔷 제너릭 타입과 다형성

참조 변수생성자대입된 타입일치해야 한다.

class Product {}
class TV extends Product {}

ArrayList<TV> list = new ArrayList<TV>(); // ok
ArrayList<Product> list = new ArryList<TV>(); //에러!! 완전히 일치해야 한다

제너릭 클래스간다형성성립. (타입이 일치할 때만)

List<TV> list = new ArrayList<TV>(); // ok
List<TV> list = new LinkedList<TV>(); // ok

매개변수의 다형성성립

ArrayList<Product> list = new ArrayList<Product>();

list.add(new Product());
list.add(new TV());
Product p = list.get(1); //Product get(int index)이므로 자손도 받을수 있다

🔷 Iterator<E>

클래스를 작성할 때, Object타입 대신 T와 같은 타입 변수 사용

public interface Iterator<E> {
	boolean hasNext();
    E next();
    void remove();
}

Iterator<Student> it = list.iterator();
while(it.hasNext()){
	Student s = it.next(); //형변환 생략
}

🔷 HashMap <K,V>

여러 개타입 변수가 필요한 경우, 콤마(,)를 구분자로 선언

public class HashMap<K,V> extends AbstractMap<K,V> {
	...
    public V get(Object key) {}
    public V put (K key, V value) {}
    public V remove(Object key) {}
    ...
}

HashMap<String, Student> map = new HashMap<String, Student>(); //객체 생성

//다음과 같이 클래스의 타입 변경
public class HashMap<String,Student> extends AbstractMap<String,Student> {
	...
    public Student get(Object key) {}
    public Student put (String key, Student value) {}
    public Student remove(Object key) {}
    ...
}

Student s1 = map.get("1-1"); //마찬가지로 형변환 생략 가능

🔷 제한된 제너릭 클래스

extends로 대입할 수 있는 타입을 제한

class FruitBox<T extends Fruit> { //Fruit와 그의 자손들만 가능
	ArrayList<T> list = new ArrayList<T>();
}

FruitBox<Apple> aBox = new FruitBox<Apple>(); //ok
FruitBox<Toy> tBox = new FruitBox<Toy>(); //에러!! Toy는 Fruit의 자손 X

인터페이스인 경우에도 extends 사용

interface Eatable {}
class FruitBox<T extends Eatable> { ... } //implements가 아닌 extends!

🔷 제너릭스의 제약

타입 변수에 대입인스턴스 별다르게 가능

Box<Apple> appleBox = new Box<Apple>(); //Apple객체만 저장
Box<Grape> grapeBox = new Box<Grape>(); //Grape객체만 저장

static멤버에 타입 변수 사용 불가

class Box<T> {
	static T item; //에러!!
    static int compare(T t1, T t2) {} //에러!!
}

배열을 생성할 때 타입 변수 사용 불가. 타입 변수로 배열 선언은 가능

class Box<T> {
	T[] itemArr;
    
    T[] toArray() {
    	T[] tmpArr = new T(itemArr.length); //에러!! new T는 사용불가
    }
}

🔷 와일드 카드 <?>

하나의 참조 변수로 대입된 타입이 다른 객체를 참조 가능

  • <? extends T> : 와일드 카드의 상한 제한. T와 그 자손들만 가능
  • <? super T> : 와일드 카드의 하한 제한. T와 그 조상들만 가능.
  • <?> : 제한 없음. 모든 타입이 가능. <? extends Object>와 동일
ArrayList<? extends Product> list  = new ArrayList<TV>(); //ok
ArrayList<? extends Product> list = new ArrayList<Audio>(); //ok
ArrayList<Product> list = new ArrayList<TV>(); //에러!! 타입불일치

메소드의 매개변수에 와일드 카드를 사용가능

static Juice makeJuice(FruitBox<? extends Fruit> box) { 
	String tmp = "";
    for(Fruit f : box.getList()) tmp += f + " ";
    return new Juice(tmp);
}

System.out.println(makeJuice(new FruitBox<Fruit>));
System.out.println(makeJuice(new FruitBox<Apple>));

🔷 제너릭 메소드

제너릭 타입이 선언된 메소드 (메소드 내에서만 유효)

static <T> void sort(List<T> list, Comparator<? super T> c)

클래스의 타입 매개변수 <T>메소드의 타입 매개변수 <T>별개

class FruitBox<T> { //제너릭 클래스
	...
    static <T> void sort(List<T> list, Comparator<? super T> c) 
}

// FruitBox의 <T>와 sort의 매개변수 List<T>의 <T>는 서로 별개이다!

메소드호출할 때마다 타입을 대입 (대부분 생략 가능)
- 타입을 생략하지 않을 때는 클래스이름 생략 불가

static Juice makeJuice(FruitBox<T> box) { 
	String tmp = "";
    for(Fruit f : box.getList()) tmp += f + " ";
    return new Juice(tmp);
}

FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();

System.out.println(Juicer.makeJuice(fruitBox));
System.out.println(Juicer.makeJuice(appleBox));

//타입 생략하지 않으면 클래스이름 생략 불가
System.out.println(<Fruit>makeJuice(fruitBox)); //에러!!
System.out.println(this.<Fruit>makeJuice(fruitBox)); //ok
System.out.println(Juicer.<Fruit>makeJuice(fruitBox)); //ok

◼ 와일드 카드와 제너릭 메소드

  • 와일드 카드 : 하나의 참조변수서로 다른 타입이 대입된 여러 제너릭 객체를 다루기 위한 것
  • 제너릭 메소드 : 메소드를 호출할 때마다 다른 제너릭 타입을 대입할 수 있게 한 것

🔷 제너릭타입의 형변환

제너릭 타입원시타입 간형변환바람직 하지 않다. (경고 발생)

Box<Object> objBox = null;
Box box = (Box)objBox; //ok...경고발생
objBox = (Box<Object>)box; //ok...경고발생

와일드 카드가 사용된 제너릭 타입으로는 형변환 가능

Box<Object> objBox = (Box<Object>)new Box<String>(); //에러!! 형변환불가능
//Box<? extends Object> wBox = (Box<? extends Object>)new Box<String>();
Box<? extends Object> wBox = new Box<String>();//ok

🔷 제너릭 타입의 제거

컴파일러제너릭 타입을 제거하고, 필요한 곳에 형변환

① 제너릭타입의 경계를 제거

class Box<? extends T> {
	void add(T t) { ... }
}

//타입 경계 제거
class Box {
	void add(Fruit t) { ... }
}

② 타입이 불일치하면 형변환

T get(int i) { return list.get(i); }

//타입 불일치. 형변환
Fruit get(int i) { return (Fruit)list.get(i); } //Fruit로 형변환

③ 와일드카드는 적절하게 형변환

static Juice makeJuice(FruitBox<? extends Fruit> box) { 
	String tmp = "";
    for(Fruit f : box.getList()) tmp += f + " ";
    return new Juice(tmp);
}

//와일드 카드 형변환
static Juice makeJuice(FruitBox box) { 
	String tmp = "";
    Iterator it = box.getList().iterator();
    while(it.hasNext()){
    	tmp += (Fruit)it.next() + " "; //Fruit로 형변환
    }
    return new Juice(tmp);
}

📙 열거형(enum)

관련된 상수들을 같이 묶어놓은 것
enum 열거형이름 {상수명1, 상수명2, 상수명3, ...}

class Card {
	static final int CLOVER = 0;
    static final int HEART = 1;
    static final int DIAMOND = 2;
    static final int SPADE = 3;
    
    
    static final int TWO = 0;
    static final int THREE = 1;
    static final int FOUR = 2;
}

//열거형 사용
class Card {
	enum Kind {CLOVER, HEART, DIAMOND, SPADE}
    enum Value {TWO, THREE, FOUR}
}

🔷 열거형의 조상 - java.lang.Enum

모든 열거형은 Enum의 자손. 따라서 메소드를 상속받음

메소드설 명
Class<E> getDeclaringClass()열거형의 Class객체 반환
String name()열거형 상수의 이름을 문자열로 반환
int ordinal()열거형 상수가 정의된 순서를 반환(0부터 시작)
T valueOf(Class<T> enumType, String name)지정된 열거형에서 name과 일치하는 열거형 상수 반환

⁕ vaules(), valueOf()는 컴파일러가 자동으로 추가

🔷 열거형 비교

열거형 상수의 비교==compareTo()사용가능 (객체 취급)

if(dir==Diretion.EAST) { // ok
	x++;
} else if(dir > Direction.WEST) { //에러!! 열거형에 비교연산자 사용불가
	...
} else if(dir.compareTo(Direction.WEST) > 0) { // ok
	...
}

🔷 열거형에 멤버 추가하기

불연속적인 열거형 상수의 겅우, 원하는 값을 괄호()안에 적는다,

enum Direction {EAST(1), WEST(-1), SOUTH(5), NORTH(10)}

//여러개의 값을 가질 수 있다.
enum Direction {EAST(1, "→"), WEST(-1, "←"), SOUTH(5, "↑"), NORTH(10, "↓")}

괄호()를 사용하려면 변수에 생성자를 새로 추가해 줘야 한다.

enum Direction {
	EAST(1), WEST(-1), SOUTH(5), NORTH(10); //끝에 ';' 추가
    
    private final int value; // 정수를 저장할 필드
    Direction(int value) {this.value = value;} //생성자 추가
    
    public int getValue() {return value;}
}

열거형의 생성자는 묵시적으로 private이므로, 외부에서 생성불가

Direction d = new Direction(1); //에러. 생성자 외부에서 호출불가

📘 애노테이션(annotation)

주석처럼 프로그래밍 언어에 영향을 미치지 않으며, 유용한 정보 제공

@Test // 이 메소드가 테스트 대상임을 테스트 프로그램에게 알림.
public void method() {
	...
}

🔷 애노테이션의 조상 - java.lang.annotation.Annotation

Annotation은 모든 애노테이션의 조상이지만 상속 불가 (인터페이스)

public interface Annotation { //Annotation은 인터페이스이다.
	//추상메소드이지만, 구현하지 않아도 사용가능
	boolean equals(Object obj);
    int hashCode();
    String toString();
    
    Class<? extends Annotation> annotationType(); //애노테이션의 타입 변환
}

🔷 표준 애노테이션

Java에서 제공하는 애노테이션

에노테이션설명
@Override컴파일러에게 오버라이딩 메소드라는 것을 알림
@Deprecated앞으로 사용하지 않을 것을 권장하는 대상에 붙임
@SuppressWarnings컴파일러의 특정 경고메세지를 억제한다
@SafeVarargs제너릭 타입의 가변인자에 사용(JDK1.7)
@FunctionalInterface함수형 인터페이스라는 것을 알림(JDK1.8)
@Nativenative메소드에서 참조되는 상수 앞에 붙임(JDK1.8)
@Target*애노테이션이 적용가능한 대상을 지정하는데 사용
@Documented*애노테이션 정보가 javadoc으로 작성된 문서에 포함되도록 함
@inherited*애노테이션이 자손클래스에 상속되도록 함
@Retention*애노테이션이 유지되는 범위 지정에 사용
@Repeatable*애노테이션을 반복해서 적용가능

* 가 붙은 것은 메타 에노테이션

◼ @Override

오버라이딩을 올바르게 했는지 컴파일러가 체크하게 한다.

class Parent{
	void method() {}
}

class Child {
	@Override //애노테이션
    void Method() {} //에러!! 오버라이딩이 제대로 되지않음
}

◼ @Deprecated

앞으로 사용하지 않을 것을 권장하는 필드나 메소드에 붙임

@Deprecated
public int getDate() {
	return normalizer().getDayOfMonth();
}

컴파일시 경고가 뜸 (도스창에서 컴파일시 확인가능)


Note: XXX(자바 파일이름).java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.

◼ @FunctionalInterface

함수형 인터페이스에 붙이면, 컴파일러가 올바르게 작성했는지 체크
- 함수형 인터페이스에는 하나의 추상메소드를 가져야 함

@FunctionalInterface
public interface Runnable {
	public abstract void run();
}

@FunctionalInterface
public interface Runnable {
	//에러!! 추상메소드가 없음
}

@FunctionalInterface
public interface Runnable {
	public abstract void run();
	public abstract void stop();//에러!! 하나의 추상메소드만 허용
}

◼ @SuppressWarnings

컴파일러의 경고메세지나타나지 않게 해줌
- 괄호()안에 억제하고자하는 경고의 종류문자열로 지정

@SuppressWarnings ("unchecked") //unchecked 경고를 억제
ArrayList list = new ArrayList(); //제너릭 타입을 지정하지않음
list.add(obj); //경고발생 - 억제됨

@SuppressWarnings ("deprecation") // deprecation 경고를 억제
@Deprecated
public int getDate() {
	return normalizer().getDayOfMonth();
}

둘 이상의 경고를 억제중괄호{} 사용

@SuppressWarnings ({"deprecation", "unchecked", "varargs"})

🔷 메타 애노테이션

애노테이션만들 때 사용

애노테이션설명
@Target애노테이션이 적용가능한 대상을 지정하는데 사용
@Documented애노테이션 정보가 javadoc으로 작성된 문서에 포함되도록 함
@inherited애노테이션이 자손클래스에 상속되도록 함
@Retention애노테이션이 유지되는 범위 지정에 사용
@Repeatable애노테이션을 반복해서 적용가능

* 가 붙은 것은 메타 에노테이션

◼ @Target

애노테이션을 정의할 때, 적용대상 지정에 사용

@Target ({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTION, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
	String[] value;
}
  • 적용 대상 타입
대상 타입의미
ANNOTATION_TYPE애노테이션
CONSTRUCTOR생성자
FIELD필드(멤버변수, enum상수)
LOCAL_VARIABLE지역변수
METHOD메소드
PACKAGE패키지
PARAMETER매개변수
TYPE타입(클래스, 인터페이스, enum)
TYPE_PARAMETER타입 매개변수 (JDK1.8)
TYPE_USE타입이 사용되는 모든 곳(JDK1.8)

◼ @Retention

애노테이션이 유지(retention)되는 기간지정하는데 사용

유지 정책의미
SOURCE소스 파일에만 존재. 클래스파일에는 존재하지 않음
CLASS클래스 파일에 존재. 실행시에 사용불가. 기본값
RUNTIME클래스 파일에 존재. 실행시에 사용가능.
@Target ({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTION, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)//소스파일에만 존재
public @interface SuppressWarnings {
	String[] value;
}

◼ @Documented

javadoc으로 작성한 문서에 포함시킴

@Documented // javadoc 문서에 포함!!
@Retention(RetentionPolicy.RUNTIME) // 실행시 사용가능
@Target(ElementType.TYPE)
public @interface MyAnnotation {} // 애노테이션 정의

◼ @Inherited

애노테이션자손 클래스에 상속하고자 할 때 사용

@Inherited //애노테이션 상속을 위해 사용
@interface SuperAnno {}

@SuperAnno
class Parents{} //부모클래스에 @SuperAnno붙임

class Child extends Parents {} //@SuperAnno가 붙은것으로 인식

◼ @Repeatable

반복해서 붙일 수 있는 애노테이션 정의시 사용

@Repeatable(ToDos.class) // ToDo애노테이션을 여러번 반복해서 쓸 수 있게 함
@interface ToDo {
	String[] value;
}

@Repeatable이 붙은 애노테이션은 반복해서 붙일 수 있음.

@ToDo("delete test codes.")
@ToDo("override inherited method")
class MyClass {}

@Repeatable인 애노테이션을 하나로 묶을 컨테이너 애노테이션도 정의해야함

@interface ToDos {
	ToDo[] value(); // 이름이 반드시 value여야 함
}

🔷 애노테이션 타입 정의하기

애노테이션직접 정의할 수 있다.

@interface 애노테이션이름 {
	타입 요소이름(); //애노테이션의 요소를 선언
    ...
}

public @interface MyAnnotation{ //MyAnnotation 애노테이션 정의
	...
}

애노테이션의 메소드는 추상 메소드이며, 애노테이션을 적용할 때 지정

@interface TestInfo{
	int count();
    String testedBy();
    String[] testTools(); //배열
    TestType testType(); //enum
    DateTime testDate(); // 애노테이션도 포함 가능
}

🔷 애노테이션의 요소

애노테이션 적용시 값을 지정하지 않으면 사용되는 기본값 지정 가능
- null값은 사용X

@interface TestInfo {
	int count() default 1; //기본값을 1로 지정
}

@TestInfo //@TestInfo(count=1)과 동일
public class NewClass { ... }

요소가 하나이고 이름이 value일 때는 요소의 이름 생략가능

@interface TestInfo{
	String value();
}

@TestInfo("passed") //@TestInfo(value="passed")와 동일
class NewClass { ... }

요소의 타입이 배열인 경우 중괄호{}를 사용해야한다

@interface TestInfo {
	String[] testTools();
}

//다음과 같이 선언
@TestInfo(testTools = {"Junit", "AutoTester"})

@TestInfo(testTools="Junit") //하나일때는 괄호 생략 가능

@TestInfo(testTools={}) //값이 없을 때는 반드시 {} 사용

◼ 애노테이션 요소의 규칙

  • 요소의 타입은 기본형, String, enum, 애노테이션, Class만 허용
  • 괄호()안매개변수선언할 수 없다.
  • 예외선언할 수 없다.
  • 요소타입 매개변수정의할 수 없다.
//에러코드

@interface AnnoTest {
	int id = 100; //ok 기본형 타입 사용
    String major(int i, int j); //괄호 안 매개변수 선언 X
    String minor() throws Exception; //예외 선언 X
    ArrayList<T> list(); //타입 매개변수 정의 X
}

🔷 마커 애노테이션

요소하나도 정의되지 않은 애노테이션

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {} //마커 애노테이션. 정의된 요소가 없음.

@Override //이 메소드가 오버라이딩메소드라는 것을 알림
public int Method() {}
profile
모르는 것 정리하기 - https://kjh00.notion.site/devlog 로 이전

0개의 댓글