자바의정석 ch12

soso·2023년 2월 23일
0
post-thumbnail

Chapter12 지네릭스, 열거형, 애너테이션

지네릭스(Generics)

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

// Tv객체만 저장할 수 있는 ArrayList를 생성
ArrayList<TV> tvList = new ArrayList<TV>();

tvList.add(new Tv()); // OK
tvList.add(new Audio()); //컴파일 에러! Tv 외에 다른 타입은 지정불가

지네릭스의 장점
1. 타입 안정성을 제공한다.
2. 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해진다.

타입변수

클래스를 작성할 때, Object타입 대신 변수(E)를 선언해서 사용
ArrayList클래스의 선언에서 클래스 이름 옆의 < >'안에 있는 E를 '타입 변수(type variable)'라고 한다.

일반적으로는 'Type'의 첫 글자를 따서 T를 사용한다.
타입변수가 여러 개인 경우에는 Map<K,V>와 같이 콤마','를 구분자로 나열하면된다. K는 Key(키)를 의미하고, V는 Value(값)을 의미한다

타입변수에 대입하기

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

// 타입 변수 E 대신에 실제 타입Tv를 대입
ArrayList<Tv> tvList = new ArrayList<Tv>();

지네릭스 용어

Box〈T〉 지내릭 클래스.'T의 Box'또는 'T Box'라고 읽는다.
T 타입변수 또는 타입 매개변수.(T는 타입 문자)
Box 원시 타입(raw type)

타임 문자 T는 지네릭 클래스 BOX〈T〉의 타입 변수 또는 타입 매개변수라고 하는데, 메서드의 매개변수와 유사한 면이 있기 때문이다.
그래서 아래와 같이 타입 매개변수에 타입을 지정하는 것을 '지네릭 타입 호출'이라고 하고, 지정된 타입 'String'을 매개변수화된 타입 parameterized type)이라고 한다.

지네릭 타입과 다형성

지네릭 클래스의 객체를 생성할때, 참조 변수에 지정해준 지네릭 타입과 생성자에 지정해준 지네릭 타입은 일치해야 한다.

Iterator〈E〉

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

public interface Iterator〈E〉{
boolean hasNext();
E next();
void remove();
}

HashMap〈K,V〉

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

제한된 지네릭 클래스

▶지네릭 타입에'extends'를 사용하면, 특정 타입의 자손들만 대입할 수 있게 제한할 수 있다.
class FruitBox〈T extends Fruit〉{ // OK. Fruit의 자손만 타입으로 지정가능

▶add()의 매개변수의 타입T도 Fruit와 자손 타입이 될 수 있다.
FruitBox〈Fruit〉fruitBox =new FruitBox〈Fruit〉();
fruitBox.add(new Apple()); // Apple이 Fruit의 자손
fruitBox.add(new Grape()); // Grape Fruit의 자손

▶인터페이스의 경우에도 'implements'가 아닌, 'extends'를 사용한다.
interface Eatable{}
class FruitBox〈T extends Eatable〉{...}

지네릭스의 제약

지네릭 클래스 Box의 객체를 생성할때, 객체별로 다른 타입을지정하는것은 적절하다

▶static멤버에는 타입변수 T를 사용할 수 없다.
class Box〈T〉{
static T item; // 에러
static int compare(T t1, T t2) {...} // 에러

▶지네릭 타입의 배열 T[]를 생성하는 것은 허용되지 않는다.
class Box〈T〉{
T[] itemArr; // OK. T타입의 배열을 위한 참조변수
T[] toArray(){
T[] tmpArr = new T[itemArr.lengh]; // 에러! 지네릭 배열생성불가
return tmpArr;
}}

와일드 카드

와일드 카드는 기호 ?를 사용하는데 다음과 같이 'extends' 와 'super'로 상환(upper bound)과 하한(ower bound)을 제한할 수 있다.

▶지네릭 타입에 와일드 카드를 쓰면, 여러 타입을 대입가능하다
단, 와일드 카드에는〈? extends T & E〉와 같이 '&'사용불가

〈? extends T〉 와일드 카드의 상한 제한. T와 그 자손들만 가능
〈? super T〉와일드 카드의 하한 제한. T와 그 조상들만 가능
〈?〉 제한 없음. 모든 타입 가능〈? extends Object〉와 동일

지네릭 메서드

▶반환타입 앞에 지네릭 타입이 선언된 메서드

▶클래스의 타입 매개변수〈T〉와 메서드의 타입 매개변수〈T〉는 별개이다

▶지네릭 메서드를 호출할때, 타입 변수에 타입을 대입해야 한다.
(대부분 경우 추정이 가능하므로 생략할 수 있다.)

지네릭 타입의 형변환

▶지네릭 타입과 원시 타입간의 형변환은 불가능하다

▶와일드 카드가 사용된 지네릭 타입으로 형변환 가능하다.

▶〈? extends Object>를 줄여서 〈?〉쓸 수 있다.

지네릭 타입의 제거

컴파일러는 지네릭 타입을 타입을 제거하고, 필요한 곳에 형변환을 넣어준다.
즉, 컴파일된 파일(*. class)에는 지네릭 타입에 대한 정보가 없는 것이다.
이렇게 하는 주된 이유는 지네릭이 도입되기 이전(JDK1.5 이전)의 소스 코드와 의 호환성을 유지하기 위해서이다.

열거형(enum)

관련된 상수들을 같이 묶어 놓은것. Java는 타입에 안전한 열거형을 제공한다.

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;
final int kind;
final int num;

   ↓↓↓↓
   
class Card { //0,    1,      2,     3
enum Kind {CLOVER, HEART, DIAMOND, SPADE } // 열거형 Kind를 정의
enum Value { TWO, THREE, FOUR } //열거형 Value를 정의
final Kind kind; //타입이 int가 아닌 Kind임에 유의!!
final Value value;

열거형의 정의와 사용

▶열거형 정의하는 방법
enum 열거형이름 { 상수명1, 상수명2, ...}

▶열거형에 정의된 상수를 사용하는 방법은 '열거형이름. 상수명'이다. 클래스의 static변수를 참조하는 것과 동일하다.

class Unit {
	int x, y; //유닛의 위치
	Direction dir; //열거형 인스턴스 변수를 선언

void init() {
	dir = Direction.EAST; // 유닛의 방향을 EAST로 초기화
  }
}

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

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

열거형 조상 -java.lang.Enum

▶모든 열거형은 Enum의 자손이며, 아래의 메서드를 상속받는다.

▶컴파일러가 자동적으로 추가해주는 values()도 있다.

static E[] values ()
static E valueOf (String name)

▶values()는 열거형 Direction에 정의된 모든 상수를 출력하는데 사용된다.

Direction[] dArr = Direction. values();
for (Direction d : dArr) // for (Direction d: Direction.values ())
	System.out.printf("%s=%d%n", d.name(), d.ordinal());

▶그리고 valueof(String name)는 열거형 상수의 이름으로 문자열 상수에 대한 참조를 얻을 수 있게 해준다.

Direction d = Direction.valuedf ("WEST");
System.out.printIn(d); // WEST
System.out.printIn(Direction.WEST==Direction.valueOf("WEST")); // true

열거형에 멤버 추가하기

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

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

▶괄호()를 사용하려면, 인스턴스 변수와 생성자를 새로 추가해 줘야 한다.

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

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

Direction d = new Direction(1); // 에러, 열거형의 생성자는 외부에서 호출품가

애너테이션이란?

프로그램의 소스코드 안에 다른 프로그램을 위한 정보를 미리 약속된 형식으로 포함시킨것이 애너테이션이다.
애너테이션은 주석처럼 프로그래밍 언어에 영향을 미치지 않으며, 유용한 정보를 제공한다.

표준 애너테이션

자바에서 기본적으로 제공하는 애너테이션은 몇 개 없다.
그나마 이들의 일부는 '메타 애너테이션(meta annotation)'으로 애너테이션을 정의하는데 사용되는 애너테이션의 애너테이션이다.

@Override

▶버라이딩을 올 바르게 했는지. 컴파일러가 체크하게 한다.
▶오버라이딩할 때 메서드이름을 잘못적는 실수를 하는 경우가 많다.

class Parent {
	void parentMethod () {}
}
class Child extehds Parent {
	void parentmethod () {} //오버라이딩하려 했으나 실수로 이름을 잘못적음
}

▶오버라이딩할 때는 메서드 선언부 앞에 @Override를 붙이는것은 필수가 아니지만, 알아내기 어려운 실수를 미연에 방지해주므로 붙이도록 하자.

@Deprecated

▶앞으로 사용하지 않을 것을 권장하는 필드나 메서드에 붙인다.
▶@Deprecated의 사용 예, Date클래스 getDate()

int getDate ()
Deprecated.
As of JDK version 1.1, replaced by Calendar.get(Calendar. DAY_OF _MONTH).

▶@Deprecated가 붙은 대상이 사용된 코드를 컴파일하면 나타나는 메세지
Note: AnnotationEx2. java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.

@FunctionalInterface

▶함수형 인터페이스에 붙이면, 컴파일러가 올바르게 작성했는지 체크
▶함수형 인터페이스에는 하나의 추상메서드만 가져야 한다는 제약이 있다

@FunctionalInterface
public interface Runnable {
	public abstract void run(); // 추상 메서드
}

@SuppressWarnings

▶컴파일러의 경고메세지가 나타나지 않게 억제한다.
▶괄호()안에 억제하고자하는 경고의 종류를 문자열로 지정한다.

@SuppressWarnings ("unchecked") //지네릭스와 관련된 경고를 억제
ArrayList list = new ArrayList(); // 지네릭 타입을 지정하지 않았음.
list.add(obj); //여기서 경고가 발생하지만 억제됨

▶둘 이상의 경고를 동시에 억제하려면 다음과 같이 한다.
@SuppressWarnings ({"deprecation", "unchecked", "varargs"})

메타 애너테이션

▶애너테이션에 붙이는 애너테이션으로 애너테이션을 정의할 때 애너테이션의 적용대상(target)이나 유지기 간(retention)등을 지정하는데 사용된다.
▶메타 애너테이션은 java.lang,annotation패키지에 포함되어 있다.

@Target

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

@Target ({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL _VARIABLE})
@Retention (RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
	String[] value();
}

'@Target'으로 지정할 수 있는 애너테이션 적용대상의 종류

@Retention

▶애너테이션이 유지(retention)되는 기간을 지정하는데 사용한다.

▶컴파일러에 의해 사용되는 애너테이션의 유지 정책은 SOURCE이다.

@Target (ElementType .METHOD)
@Retention(RetentionPolicy. SOURCE) 
public @interface Override {}

▶실행시 사용 가능한 애너테이션의 정책은 RUNTIME이다.

@Documented
@Retention (RetentionPolicy .RUNTIME)
@Target (ElementType.TYPE)
public @interface FunctionalInterface {}

@Documented, @Inherited

@Documented

애너테이션에 대한 정보가 javadoc으로 작성한 문서에 포함되도록 한다.

@Documented
@Retention (RetentionPolicy.RUNTIME)
@Target (ElementType. TYPE) public @interface FunctionalInterface {}

@Inherited

애너테이션이 자손 클래스에 상속되도록 한다.

@Inherited			//@SuperAnno가 자손까지 영향 미치게
@interface SuperAnno {}

@SuperAnno
class Parent {}

classchild extends Parent () // Child에 애너테이션이 붙은 것으로 인식

@Repeatable

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

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

▶@Repeatable이 붙은 애너테이션은 반복해서 붙일 수 있다.

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

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

@interface ToDos {//여러 개의 TODo에너테이션을 담을 컨테이너 에너테이션 TODos
	ToDo[] value(); //ToDo에너테이션 배열타입의 요소를 선언. 이름이 반드시 value이어야 한다.
 } 

애너테이션 타입 정의하기

▶애너테이션을 직접 만들어 쓸 수 있다.
새로운 애너테이션을 정의하는 방법은 아래와 같다.

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

애너테이션의 요소

애너테이션 내에 선언된 메서드를 애너테이션의 요소(element)라고 하며, 아래에 선언된 Testinfo애너테이션은 다섯 개의 요소를 갖는다.

▶애너테이션의 메서드는 추상 메서드이며, 애너테이션을 적용할 때 지정한다(순서X)

@interface TestInfo {
  int count();
  String testedBy();
  String[] testTools ();
  TestType testType (); // enum TestType { FIRST, FINAL }
  Datetime testDate(); // 자신이 아닌 다른 애너테이션 (@DateTime)을 포함할 수 있다.
}
@interface DateTime {
  String yymmdd();
  String hhmmss ();
}

▶적용시 값을 지정하지 않으면, 사용될 수 있는 기본값 지정 가능(null제외)

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

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

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

@interface TestInfo {
String value ();
}

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

▶요소의 타입이 배열인 경우, 괄호{}을 사용해야 한다.

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

@Test(testTools={"JUnit", "AutoTester"}) //  값이 여러 개인 경우.
@Test (testTools="JUnit")  //값이 하나일 때는 괄호{} 생략가능
@Test (testTools={})  //값이 없을 때는 괄호 ()가 반드시 필요

모든 애너테이션의 조상

▶Annotation은 모든 애너테이션의 조상이지만 상속은 불가능하다

interface TestInfo extends Annotation { // 에러. 허용되지 않는 표현
  int count () ;
  String testedBy ();
   ...

▶사실은 Annotation은 인터페이스다.

package java. lang.annotation;

Public interface Annotation { //  Annotation자신은 인터페이스이다.
  boolean equals (Object obj); 
  int hashCode();
  String tostring();

Classs? extends Annotation, annotationrype(); // 애너테이션의 타임을 반환
}

마커 애너테이션

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

@Target (ElementType.METHOD)
@Retention(RetentionPolicy. SOURCE)
public @interface override {} // 마커 애너테이션. 정의된 요소가 하나도 없다.
@Target (ElementType. METHOD)
@Retention (RetentionPolicy .SOURCE)
public @interface Test {} // 마커 애너테이션. 정의된 요소가 하나도 없다.

애너테이션 요소의 규칙

▶애너체이션의 요소를 선언할 때 아래의 규칙을 반드시 지켜야한다.

  • 요소의 타입은 기본형, String, enum, 애너테이션, Class만 허용된다.
  • 괄호()안에 매개변수를 선언할수 없다.
  • 요소를 타입 매개변수로 정의할 수 없다.

▶아래의 코드에서 잘못된 부분은 무엇인지 생각해보자

@interface AnnoTest {
int id = 100; //OK. 상수 선언. static final int id = 100;
String major (int i, int j); //에러. 매개변수를 선언할 수 없음
String minor() throws Exception; // 에러. 예외를 선언할 수 없음
ArrayList<T> list(); //에러. 요소의 타입에 타입 매개변수 사용불가
}
profile
오늘의 기록

0개의 댓글