본문 바로가기
Java/Java

[자바의 정석_기초편] Chapter12. 제네릭스(Generics), 열거형(Enumeration), 애너테이션(Annotation)_1

by HJ0216 2023. 7. 25.

이 글은 남궁성의 정석코딩 [자바의정석-기초편] 수강하며 정리한 글입니다.

 

 

🟣 기본 환경: IDE: Eclipse, Language: Java

 

 

 

지네릭스(Generics): 컴파일 시 타입을 체크해 주는 기능

: runtimeException을 compileException으로 변환

 

ArrayList<Tv> tvList = new ArrayList<Tv>;

<>를 통해서 ArrayList에 들어올 수 있는 객체 제한

ArrayList<>(참조변수)와 new ArrayList<>(생성자)가 일치해야 함

 

tvList.add(new Tv());

tvList.add(new Audio()); - compile error(ClassCastException 형변환 오류)

객체 타입의 안정성을 높이고 형변환의 번거로움을 줄여줌

 

Box<T> Generic class, T의 Box or T Box라고 읽음

T(타입 문자): 타입변수 또는 타입 매개변수

Box: 원시타입(raw type)

 

지네릭 클래스 선언

class Box<T> = class 원시타입<타입변수>

 

지네릭 클래스에서 객체 생성

Box<String> b = new Box<String>();

참조변수 생성자

 

<대입된 타입(parameterized type)>

다형성 원리와 달리 대입된 타입의 경우, 조상-자손관계일 때도 타입이 일치해야 함

ArrayList<Product> list = new ArrayList<Tv>(); // Error

 

단, 타입이 아닌 arraylist 등에서는 다형성 성립(지네릭 클래스간 다형성 성립)

List<Tv> list = new ArrayList<Tv()>; // ArrayList가 List 구현, OK

 

매개변수의 다형성 성립

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

list.add(new Product());

list.add(new Tv()); // OK

list.add(new Audio()); // OK

Product p = list.get(0); // 형변환 불요

Tv tv = (Tv) list.get(1); // 형변환 필요

 

Hash<K, V>

여러개의 타입 변수가 필요한 경우, ‘,’ 사용

 

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

class FruitBox<T extends Fruit> { // Fruit 자손만 타입으로 지정 가능

 

Interface인 경우에도 extends를 사용

interface Eatable{}

class FruitBox<T extends Fruit & Etable>

→ 인터페이스 여러개 상속 & 만 사용 가능

 

타입 변수에 대입은 인스턴스 별로 다르게 지정 가능

Box<Apple> appleBox = new Box<Apple>();

Box<Grape> grapeBox = new Box<Grape>();

 

static member(모든 인스턴스에서 공통이므로)에 타입변수 사용 불가

** static variable에 generic 사용 불가(static T nameX)

** static method에 generic 사용 불가

** generic method에 static 사용 가능

 

타입변수 배열 선언은 가능

T[] itemArr();

배열, 객체 생성 시 타입 변수 사용불가(= new T (X))

T[] toArray() {T[] tmpArr = new T[itemArr.length]} // Error

 

- 와일드 카드<?>

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

→ 하나의 참조변수로 서로 다른 타입이 대입된 여러 지네릭 객체를 다루기 위한 것

cf. 지네릭 메서드: 메서드를 호출할 때마다 다른 지네릭 타입을 대입할 수 있게 한 것

 

<? extends T> 와일드 카드의 상한 제한, T와 그 자손들만 가능 - 주로 사용

<? super T> 와일드 카드의 하한 제한, T와 그 조상들만 가능

<?> 제한 없음, 모든 타입 가능(=<? extends Object>)

 

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

static Juice makeJuice(FruitBox<? extends Fruit>) {}

→ Fruit, Apple 가능

 

 

Generic Method

지네릭 타입이 선언된 메서드(타입 변수는 메서드 내에서만 유효)

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

매서드를 호출할 때마다 타입을 대입해야 함(대부분 생략 가능)

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

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

println(Jucier.<Fruit>makeJuice(fruitBox));

println(Jucier.<Apple>makeJuice(appleBox));

→ Fruitbox에서 매개변수 타입<T>이 선언되었으므로 매서드 호출에서 사용되는 매개변수<T>는 대부분 생략 가능

 

메서드를 호출할때 타입을 생략하지 않을 때는 클래스 이름 생략 불가(같은 클래스 내 일지라도)

println(<Fruit>makeJuice(fruitBox)); // Error

println(this.<Fruit>makeJuice(fruitBox)); // OK

println(Jucier.<Fruit>makeJuice(fruitBox)); // OK

 

지네릭 타입과 원시 타입 간의 형변환은 바람직하지 않음

Box b = null;

Box<String> bStr = null;

b = (Box)bStr; // B<>→B 가능은 하지만 경고

bStr = (Box<String>)b; // B→B<> 가능은 하지만 경고

 

지네릭 타입의 제거

컴파일러는 지네릭 타입을 제거하고, 필요한 곳에 형변환을 넣는다.

Object→<T>를 다시 Object로 돌림

단, T type이 제한된 경우는 제한된 타입으로 변경

Box<T extends Fruits>→Fruits

→ 지네릭 타입 제거 후에 타입이 불일치하면 형변환을 추가한다.

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

→ fruit get(int i) {return fruit.list.get(i)}

list.get(i)가 object이므로 fruit으로 형변환 필요

→ 와일드 카드가 포함된 경우, 적절한 타입으로 형변환 추가

 

 

열거형(enum): 관련된 상수들을 같은 묶어놓은 것

enum Kind = {CLOVER, HEART, DIAMOND, SPADE}

enum Value = {TWO, THREE, FOUR}

-> enum으로 정의하면, CLOVER에 0부터 부여

 

타입의 안전한 열거형 제공(값, 타입 모두 확인)

C언어: Kind enum의 CLOVER와 Value enum의 TWO는 모두 0으로 만일 Card.CLOVER==Card.TWO, 하면 같다고 반환(의미는 Kind와 Value가 다르므로 같다고 나오면 안됨)

Java_enum: Card.kind.CLOVER == Card.Value.TWO 를 적으면 타입이 달라서 비교가 불가능하다는 컴파일 에러가 발생

 

열거형 정의

enum Direction {E, S, W, N}

 

열거형 타입의 변수 선언 및 사용법

class Unit {

unit x, y;

Direction dir; // 열거형 iv 선언(enum Direction {E, S, W, N}만 대입 가능)

void init()

dir = Direction.E; 초기화

}

 

열거형 상수의 비교에 ==와 compareTo() 사용 가능

if(dir==Direction.E) {

x++;

} else if(dir>Direction.E) { // Error - 열거형 상수=객체이므로 비교 연산자 사용 불가(equal, compareTo써야하지만, ==도 예외적으로 가능)

} else if(dir.compareTo(Direction.SOUTH)) { // CompareTo는 사용 가능

→ CompareTo: 왼:+1, 동:0, 오 +1(크기도 반환 가능)

}

 

java.lang.Enum

- String name(): 열거형 상수의 이름을 문자열로 반환

- int oridinal(): 열거형 상수가 정의된 순서를 반환(0부터 시작)

(단, 상수들이 실제로는 다른 값을 갖을 수 있기때문에 상수값과 순서값이 다를 수 있음)

- T valueOf(Class<T> enum Type, String name): 지정된 열거형에서 name과 일치하는 열거형 상수를 반환

컴파일러 자동 추가(values(),valueOf())

static E[] values()

Direction[] dArr = Direction.values(); // Direction의 상수를 배열로 반환

for(Direction d: dArr)

System.out.printf("%s=%d%n", d.name(), d.ordinal()); // 이름과 순서 반환

 

static E valueOf(String name)

Direction d = Direction.valueOf("W");

valueOf(열거형 상수 이름) -> 열거형 상수 반환

(Direction.W와 동일)

 

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

enum Direction {E(1), S(4), W(-1), N(7)}

* 여러개도 가능

enum Direction {east(1, “>”) south(5, “v”) west(-2, “<”) north(-8, “^”)}

 

→ 이러한 괄호를 사용하려면 인스턴스 변수와 생성자를 새로 추가해 줘야 한다.

enum Direction {

east(1, “>”) south(5, “v”) west(-2, “<”) north(-8, “^”)

private final int value; // 정수를 저장할 필드(인스턴스 변수 선언)를 추가

private final String symbol;

// esat(1, ">") -> east 객체의 1, ">" 생성자 호출 이므로

Direction(int value, String symbol) { // 생성자 추가

this.value = value

this symbol = symbol

}

cf. 생성자는 항상 private이므로 생략 가능→따로 표시가 없더라도 외부에서 객체 생성 불가

(Direction d = new Direction(1); // 열거형 생성자 private: 외부 호출 불가 -> Error)

public int getValue() {return value;}

public String getSymbol() {return symbol;}

}

 

 

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

/** ~ */ java.doc 주석(소스코드에 대한 설명)

 

@annotation

표준 애너테이션: 자바에서 제공하는 애너테이션

 

* @Override: 오버라이딩을 올바르게 했는지 컴파일러가 체크

class Parent {void parentMethod(){}}

class Child extends Parent {

@Override

void parentMethod() {}

}

 

* @Dreprecated: 앞으로 사용하지 않을 것을 권장하는 필드나 메서드

cmd 창에서 javac JavaFileName.java;

deprecated 경고 발생(eclipse는 자동 컴파일되므로 확인할 수 X)

javac -Xlint JavaFileName.java

상세한 경고 내용 확인 가능

 

* @Functionallterface

함수형 인터페이스에 붙이면 컴파일러가 올바르게 작성했는지 확인

함수형 인터페이스는 하나의 추상 메서드({}구현부가 없)는 메서드만 가져야 한다는 제약이 있어 그것을 확인해줌

일반 인터페이스는 여러 추상 메서드 보유 가능

interface testable {

void test();

void check();

}

일반 인터페이스 취급, 여러 추상 메서드 보유 가능

@FunctionnalInterface

interface testable {

void test();

void check();

}

1개 이상의 추상 메서드 구현으로 오류

 

* @SuppressWarnings

컴파일러의 경고메세지가 나타나지 않게 억제(해당 경고의 원인이 된 곳에 기재)

괄호()안에 억제하고자하는 경고의 종류를 문자열로 지정

@SuppressWarnings({”rawtypes”, ”deprecation”, ””})

→ 해당 경고를 내가 확인했다는 의미

→ 경고가 쌓이면서 놓치는 경우가 있을 수 있으므로 확인 한 경고는 SurpressWarnings에 기재해서 경고 삭제

*xlint옵션으로 cmd에서 컴파일하면 경고메시지 확인 가능

javac -Xlint AnnotionTest.java

-> [] 안의 경고가 SurpressWarnings에 들어가는 경고 이름

 

 

메타 애너테이션: 애너테이션을 만들 때 사용

 

- @Target

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

 

@Target({FIELD, TYPE, TYPE_USE}): 적용대상

ANNOTATION_TYPE: 애너테이션

field: 멤버 변수(IV, CV), ENUM 상수

type: class, interface, enum

type_use: 타입이 사용되는 모든 곳

 

애너테이션 만들기

@Target({FIELD, TYPE, TYPE_USE})

public @interface MyAnnotation {} // annottion 앞에 @interface 기재

@MyAnnotation // 적용대상: TYPE_USE

class MyClass {

@MyAnnotation // 적용대상: FIELD

int i;

@MyAnnotation // 적용대상: TYPE_USE

MyClass mc;

}

 

- @Retention: 애너테이션이 유지되는 기간을 지정하는데 사용

* 1. SOURCE: 소스파일에만 존재, 클래스 파일에는 X // @Override

2. RUNTIME: 클래스 파일에 존재, 실행 시 사용 가능 // @FuntionalInterface

3. CLASS: 클래스 파일에 존재, 실행 시 사용 불가 (기본값)

@Retention(RetentionPolicy.SOURCE)

public @interface Override

@Retention(RetentionPolicy.RUNTIME)

public @interface FunctionalInterface {}

 

- @Documented

javadoc으로 작성한 문서에 애너테이션 포함시키려고 할 때

 

- @Inherited

애너테이션을 자손 클래스에 상속하고자 할 때

@Inherited // SuperAnno가 자손까지 영향을 미치게

@interface SuperAnno{}

 

@SuperAnno

class Parent{}

 

class Child extends Parent {} // child에 @SuperAnno 애너테이션이 붙은 것으로 인식

 

- @Repeatable

반복해서 붙일 수 있는 애너테이션을 정의할 때 사용

 

@Repeatable(ToDos.class)

@interface Todo{String value();}

 

@ToDo()

@ToDo()

class MyClass

하나의 대상에 애너테이션을 반복해서 붙일 수 있음

 

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

@interface ToDos{ // 여러개의 애너테이션을 담을 컨테이너 애너테이션 생성

ToDo[] value(); // todo 애너테이션 배열타입의 요소를 선언, 이름이 반드시 value여야 함

}

 

애너테이션 타입 정의하기

@interface 애너테이션 이름{

타입 요소이름();

}

 

애너테이션 메서드는 추상 매서드(구현 필요 X)이며, 애너테이션을 적용할 때 모든 요소를 다 적어줘야 하며, 순서는 상관X

(단, null제외 기본값 지정 가능→기본값 지정시 애너테이션 구성요소 작성 생략 가능)

@interface TestInfo{

int count() default 1;

}

@TestInfo(

count =3

)

 

enum 사용 가능

enum TestType {F, S}

TestType testType();

 

자신이 아닌 다른 애너테이션 포함 가능

@interface DateTime{}

DateTime testDate();

 

요소가 하나이고, 이름이 value면 요소이름 생략 가능

@interface TestInfo{

String value();

}

@TestInfo(”Passed”) // @TestInfo(value=”Passed”)와 동일

class NewClass{}

 

요소 타입이 배열인 경우 괄호를 사용해야 함

@interface TestInfo{

String[] testTools();

}

 

@Test(testTools={”JUnit”, “AutoTester”})

@Test(testTools=”JUnit”) - 1개일 때는 생략 가능

@Test(testTools={}) - 값이 없을 때는 괄호{}가 반드시 필요

 

모든 애너테이션의 조상은 interface

→ extends를 사용해서 상속하는 표현 쓰지X, 자동 상속

→ 모든 annotation의 조상인 interface Annotation의 추상매서드들을 모두 구현하지 않고도 사용 가능(컴파일러가 자동 구현)

 

마커 애너테이션(Marker Annotation)

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

@Test, @Deprecated, @Override

 

애너테이션 요소 규칙

애너테이션 요소를 선언할 때, 아래의 규칙을 반드시 지켜야 함

1. 요소의 타입은 기본형, String, enum, Annotation, Class만 허용

2. 추상 메서드 괄호()안에 매개변수를 선언 할 수 X

3. 예외를 선언할 수X

4. 요소를 타입 매개변수<T>로 정의할 수 없음

 

@interface AnnoTest {

(static final) int id = 100; - 상수O, default method*는 안됨

String major(int i, int j); - 2

String minor() throws Exception; - 3

ArrayList<T> list(); - 4

}

* default method

인터페이스에 메서드를 추가할 경우, 해당 인터페이스를 구현하고 있던 모든 class에 오류 발생

이 때, 인터페이스 내부에 메서드 추가시 default를 앞에 붙이면 필요적으로 구현하지 않아도 됨

interface A {

default void b() {}; - 추상 메서드와 달리 빈 선언부가 있음

void c() {};

}

 

class D impletmens A {

void c() {}; - 필요적 구현

void b() {}; - 선택적 구현

}

 

 

 

소스 코드

🔗 HJ0216/java-practice

 

참고 자료

 

Java - Default Method (Interface)

인터페이스를 구현할 때 굳이 필요하지 않은 메서드까지 다 override 해야한다. 그렇기 때문에 인터페이스...

blog.naver.com