[JAVA] Chapter 12 지네릭스, 열거형, 애너테이션 - 2

WOOK JONG KIM·2022년 9월 29일

자바의 정석

목록 보기
17/25
post-thumbnail

ch 12-14 지네릭 메서드

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

ex) Collections.sort()

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

static멤버에는 타입 매개변수를 사용 할 수 없지만, 메서드에 지네릭 타입은 가능!
-> 지역변수와 같다고 생각하자!

class FruitBox<T>
{
	...
	static <T> void sort(List<T> list, Comparator<? super T> c){
    ...
 	}
}

앞에 < T >쓰는 이유 예시

public static ArrayList<? extends Product> merge(
		ArrayList<? extends Product> list, ArrayList<? extends Product> list2)
{
	ArrayList<? extends product> newList = new ArrayList<>(list);
	
	newList.addAll(list2);
	
	return newList
}

/* 둘은 같은 코드 즉 ArrayList의 제네릭 타입을 제한해주고 코드를
간결히 해준다고 생각하자! */

public static <T extends Product> ArrayList<T> merge(ArrayList<T> list, ArrayList<T> list2)
{
	ArrayList<T> newList = new ArrayList<>(list);
	
	newList.add
}
static <T extends Fruit> 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> fruitBox = new FruitBox<Apple>();
	...
// 원래라면 Juicer.<Fruit>makeJuice(fruitBox)
// 컴파일러가 대입된 타입을 추정할 수 있기에 생략 가능
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));
System.out.println(Juicer.<Fruit>makeJuice(fruitBox));

ch 12-15 지네릭 타입의 형변환

지네릭 타입과 원시 타입 간의 형변환

Box box = null;
Box<Object> objBox = null;

// 가능하지만 경고 발생
box = (Box)objBox; 지네릭 -> 원시
objBox = (Box<Object>)box; 원시 -> 지네릭
Box<Object> objBox = null;
Box<String> strBox = null;

// 에러 발생
objBox = (Box<Object>)strBox;
strBox = (Box<String>)objBox;

//이유
Box<Object> objBox = (Box<Object>)new Box<String>(); // 에러, 형변환 불가

// Box<String> -> Box<? extends Object> 형 변환 가능
Box<? extends Object> wBox = new Box<String>();

ch 12-16 지네릭 타입의 제거

컴파일러는 지네릭 타입을 이용해서 소스파일 체크, 필요한 곳에 형변환 넣어준 뒤 지네릭 타입 제거

  1. 지네릭 타입의 경계(bound)를 제거한다.
/* 지네릭 타입이 만약 < T extends Fruit > 라면 T는 Fruit으로 치환됨
< T >의 경우 -> Object */

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

change


class Box{
	void add(Fruit t){
    ...
   	}
}
  1. 지네릭 타입을 제거한 후에 타입이 일치하지 않으면 형변환을 추가
T get(int i){
	return list.get(i)
}

change

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

ch 12-17, 18 열거형(enum)

여러 상수를 선언해야 할 때 편리하게 선언하는 방법

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;
}
// 0 부터 시작하는 정수값이 자동으로 할당
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;
}
if(Card.CLOVER == Card.Two) // true 이지만 false가 의미상 맞음

// 열거형을 이용해서 상수 정의한 경우 값 비교 전 타입을 먼저 비교하므로 에러 발생 
if(Card.Kind.CLOVER == Card.Value.TWO) //컴파일 에러

정의 및 사용

enum 열거체 이름{ 상수명1, 상수명 2, ...}
enum Direction { EAST, SOUTH, WEST, NORTH}

Class Unit{
	int x, y;
    Direction dir; // 열거형 인스턴스 변수 선언
    
    void int(){
    	dir = Direction.EAST; // EAST로 초기화
    }
}

/* 열거형 상수 간 비교시 == 사용 가능, 반면 <, > 같은 연산은 불가하지만 compareTo는 
사용 가능 */

if(dir==Direction.EAST){
	x++;
} else if (dir > Direcion.West){ // 에러
	....
} else if(dir.compareTo(Direction.WEST) > 0){
	...
}

ch 12-19,20 열거형의 조상 및 예제

모든 열거형의 조상은 java.lang.Enum -> 여러 메서드 제공

// 열거형의 Class객체를 반환
Class<E> getDeclaringClass()

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

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

// 지정한 열거형에서 name과 일치하는 열거형 상수 반환
T valueOf(Class<T> enumType, String name)

// 열거형에 정의된 모든 상수 출력
static E[] values()

// 열거형 상수의 이름으로 문자열 상수에 대한 참조를 얻을 수 있게 해줌
static E valueOf(String name)
package ch12;

enum Direction{ EAST, SOUTH, WEST, NORTH}

public class Ex12_5 {

	public static void main(String[] args) {
		Direction d1 = Direction.EAST;
		Direction d2 = Direction.valueOf("WEST");
		Direction d3 = Enum.valueOf(Direction.class, "EAST");
		
		System.out.println("d1="+d1);
		System.out.println("d2="+d2);
		System.out.println("d3="+d3);
		
		System.out.println("d1==d2 ?" + (d1==d2));
		System.out.println("d1==d3 ?" + (d1== d3));
		System.out.println("d1.equals(d3)" + d1.equals(d3));
//		System.out.println("d2 > d3? " + (d1 > d3)); //에러
		System.out.println("d1.compareTo(d3) ?" +(d1.compareTo(d3)));
		System.out.println("d1.compareTo(d2) ?" +(d1.compareTo(d2)));
		
		switch(d1) {
			case EAST:
				System.out.println("The direction is East."); break;
			case SOUTH:
				System.out.println("The direction is East."); break;
			case WEST:
				System.out.println("The direction is East."); break;
			case NORTH:
				System.out.println("The direction is East."); break;
			default:
				System.out.println("Invalid direction. "); break;
				
		}
		
		Direction[] dArr = Direction.values();
		
		for(Direction d : dArr) // for (Direction d: Direction.values())
			System.out.printf("%s=%d%n", d.name(), d.ordinal());
	
	}

}
d1=EAST
d2=WEST
d3=EAST
d1==d2 ?false
d1==d3 ?true
d1.equals(d3)true
d1.compareTo(d3) ?0
d1.compareTo(d2) ?-2
The direction is East.
EAST=0
SOUTH=1
WEST=2
NORTH=3

ch 12-21,22 열거형에 멤버 추가

열거형 상수의 값이 불규칙한 경우

enum Direction{
	EAST(1), SOUTH(5), WEST(-1), NORTH(10); // 끝에 세미콜론 잊지 말자
    
    //멤버 추가
    private final int value; // 정수를 저장할 iv 추가
    Direction(int value) { this.value = value;} // 생성자 추가
    
    public int getValue() { return value;}
}
Direction d = new Direction(1) // 에러. 열거형의 생성자는 외부에서 호출 불가

// 열거형의 생성자는 묵시적으로 private
enum Direction{
	...
    Direction(int value) { 
    // private Direction(int value)와 동일
    ...
}
package ch12;

enum Direction2
{ 
	EAST(1, ">"), SOUTH(2, "V"), WEST(3, "<"), NORTH(4,"^");
	
	private static final Direction2[] DIR_ARR = Direction2.values();
	private final int value;
	private final String symbol;
	
	Direction2(int value, String symbol)
	{ // private 있다는 것 생각하기
		this.value = value;
		this.symbol = symbol;
	}
	
	public int getValue() { return value; }
	public String getSymbol() { return symbol;}
	
	public static Direction2 of(int dir)
	{
		if(dir < 1 || dir > 4)
			throw new IllegalArgumentException("Invalied value : " + dir);
		return DIR_ARR[dir - 1];
	}
	// 방향 회전 메서드, num의 값만큼 90도씩 시계방향으로 회전 
	public Direction2 rotate(int num)
	{
		num = num % 4;
		
		if(num < 0) num+= 4; // num이 음수일때는 시계반대 방향으로 회전
		
		return DIR_ARR[(value-1+num) % 4];
	}
	
	public String toString() {
		return name() + getSymbol();
	}

}

public class Ex12_6 {

	public static void main(String[] args) {
		for(Direction2 d: Direction2.values())
			System.out.printf("%s=%d%n", d.name(), d.getValue());
		
		Direction2 d1 = Direction2.EAST;
		Direction2 d2 = Direction2.of(1);
		
		System.out.printf("d1=%s, %d%n", d1.name(), d1.getValue());
		System.out.printf("d2=%s, %d%n", d2.name(), d2.getValue());
		System.out.println(Direction2.EAST.rotate(1));
		System.out.println(Direction2.EAST.rotate(2));
		System.out.println(Direction2.EAST.rotate(-1));
		System.out.println(Direction2.EAST.rotate(-2));
		

	}

}

ch 12-23,24 애너테이션

과거에는 소스코드와 문서를 따로 관리하였는데 서로 간의 불일치가 발생하였는데 이를 해결하기 위해 소스코드와 문서를 하나로 합치는 javadoc.exe를 만들었다.

/**로 시작하는 주석 안에 소스코드에 대한 설명들이 있고 @이 붙은 태그가 있는데 이를 애너테이션이라고 한다.

애너테이션은 주석처럼 프로그래밍 언어에 영향을 미치지 않으면서 컴파일러에게 유용한 정보를 제공한다.

@Test // 이 메서드가 테스트 대상임을 테스트 프로그램에게 알린다.

public void method(){
	...
}

모든 프로그램에게 의미가 있는 것은 아니고, 해당 프로그램에 미리 정의된 종류와 형식으로 작성해야 의미가 있다!


ch12-25~28 표준 애너테이션

  1. @Override
    메서드 앞에 붙일 수 있는 애너테이션으로 메서드를 오버라이딩 하는 것이라고 컴파일러에게 알려주는 역할
class Child extends Parent {
	@Override
    void parentMethod()
}
  1. @Deprecated
    앞으로 사용하지 않을 것을 권장하는 대상에 붙인다.
    하위호환성을 고려하여 기존의 것들을 함부로 삭제하지 않고 @Duprecated를 사용하는데 이를 사용할 경우 컴파일 시 경고 메시지가 출력된다.
class NewClass {
	@Deprecated
    int oldField;
    
    @Deprecated
    int getOldField(){
    	return oldField;
    }
}
  1. @FunctionalInterface
    함수형 인터페이스라는 것을 알리는 애너테이션으로 하나의 추상 메서드만 정의한다. 필수는 아니지만 붙이면 실수 방지 가능
// 함수형 인터페이스는 추상 메서드가 하나여야 한다!!!!!!

public interface Runnable {
	public abstract void run();
}
  1. @SuppressWarnings
    특정 경고메시지가 나타나지 않게 해준다.
  • deprecation : @Deprecated가 붙은 대상을 사용해서 발생하는 경고
  • unchecked : 지네릭스로 타입을 지정하지 않았을 때 발생하는 경고
  • rawtypes : 지네릭스를 사용하지 않아서 발생하는 경고
  • varargs : 가변인자의 타입이 지네릭 타입일 때 발생하는 경고 억제
@SuppressWarnings("unchecked")
ArrayList list = new ArrayList(); // 지네릭 타입 지정 X
list.add(obj); // 여기서 경고 발생하지만 억제됨!

여러 개 억제도 가능하다!


ch12 -29~33 메타 애너테이션

애너테이션의 애너테이션

새로운 애너테이션을 정의할 때 애너테이션의 적용대상이나 유지기간 등을 지정하는데 사용된다(java.lang.annotation에 포함)

  1. @Target
    애너테이션이 적용가능한 대상을 지정하는데 사용

위 표의 값들은 java.lang.annotation.ElementType 이라는 열겨형에 정의

// SuppressWarnings 애너테이션의 실제 코드

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings{
	String[] value();
}
import static java.lang.annotation.ElementType.*;

@Target({FIELD, TYPE, TYPE_USE})
public @interface MyAnnotation { }

@MyAnnotation // 적용 대상이 TYPE인 경우(클래스)
class MyClass{
	@MyAnnotation // 적용 대상이 FIELD인 경우(멤버 변수)
    int i;
    
    @Myannotation // 적용 대상이 TYPE_USE인 경우
    MyClass mc;
}
  1. @Retention

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

/* 
@Override 나 @SuppressWarnings처럼 컴파일러가 사용하는 애너테이션은 
유지 정책이 SOURCE -> 컴파일러를 직접 작성할 것이 아니면, 유지 정책 필요 X
*/

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

/*
@FunctionalInterface 는 Override 처럼 컴파일러가 체크하지만, 실행 시에도
사용 되므로 유지 정책이 RUNTIME
*/

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface{}
  1. @Documented
    애너테이션에 대한 정보가 javadoc으로 작성된 문서에 포함되도록 한다
    Override 와 SuppressWarning 제외하고 다 붙어 있음
  1. @Inherited
    애너테이션이 자손 클래스에 상속되도록 한다.
    @Inherited가 붙은 애너테이션을 조상 클래스에 붙이면, 자손 클래스에도 붙은 걸로 인식
@inherited
#interface SuperAnno{}

@SuperAnno
class Parent{}

// @SuperAnno가 붙은 것과 마찬가지
class Child extends Parent{}
  1. @Repeatable
    애너테이션을 여러 번 붙일 수 있게 해준다
@Repeatble(ToDos.class) // ToDO애너테이션을 여러 번 반복해서 쓸수 있게 해줌
@ interface ToDO{
	String value();
}

// 밑에 처럼 가능 하다는 것
@ToDo("delete test codes.")
@ToDo("override inherited methods")
class MyClass{
	...
}

/* 같은 이름의 애너테이션이 하나의 대상에 여러번 적용 될 수 있기에,  
하나로 묶어서 다룰 수 있는 애너테이션도 추가로 정의해야 한다 */

@interface ToDos{ // 여러 개의 ToDo애너테이션을 담을 컨테이너 애너테이션 ToDos
	Todo[] value(); // 이름이 반드시 value여야 함
}

ch 12- 34 애너테이션 타입 정의하기

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

@Override에서 그 자체는 애너테이션이고 Override는 애너테이션 타입이다


ch 12- 35 애너테이션의 요소

애너테이션 내에 선언된 메서드를 요소라고 부른다

@interface TestInfo{
	// 애너테이션 요소는 반환값이 있고, 매개변수는 없는 추상 메서드 형태
    // 요소 값을 빠짐없이 지정해주어야함
	int count();
    String testedBy();
    String[] testTools();
    TestType testType();
    DateTime testDate();
}

@interface DateTime
{
	String yymmdd();
    String hhmmss();
}

//지정 예시
@TestInfo{
	count = 3, testedBy = "kim",
    testTools = {"JUnit", "Autotester"},
    testType = TestType.FIRST,
    testDAte = @DateTime(yymmdd="160101", hhmmss="235959")
}
public class NewClass{...}
  • default를 통해 기본 값 지정 가능

  • 애너테이션 요소가 오직 value 인 경우, 적용 시 요소의 이름을 생략하고 값만 적어도 됨
    -> 그래서 애너테이션 괄호 안에 바로 { } 배열 값들만 넣어도 되는 것!

  • 괄호로 여러 개의 값기본값 지정 가능!


ch 12- 36 모든 애너테이션의 조상

Annotation
-> 명시적으로 조상 지정 (extends)불가, 일반적인 인터페이스로 정의 되어있음

package java.lang.annotation;

public interface Annotation { // Anotation 자신은 인터페이스이다.
	boolean equals(Object obj);
    int hashCode();
    String toString();
    
    Class<? extends Annotation> annotationType(); // 애너테이션 타입을 반환
    
    // 여기에 4개의 추상 메서드는 모든 애너테이션이 물려받음
    // 추상메서드지만 구현 안해주어도 사용할 수 있다. -> 컴파일러가 자동으로 구현
}

ch 12- 37 마커 애너테이션

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

명칭정도만 있다

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

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

//즉 @Override, @Test 처럼 앞에 붙여 사용시 적어주는 요소가 없다!!!

ch 12- 38, 39 애너테이션 요소의 규칙

  1. 요소의 타입은 기본형, String, enum, 애너테이션,Class(설계도 객체) 만 가능하다!

  2. () 안에 매개변수 선언 불가

  3. 예외를 선언할 수 없다.

  4. 요소를 타입 매개변수로 정의할 수 없다.

@interface AnnoTest
{
	int id = 100; // OK. 디폴트 메서드 불가. 인터페이스처럼 상수선언
    // static final int id = 100;
    String major(int i, int j); // 에러 매개변수 X
    String minor() throws Exception; // 예외 선언 불가
    ArrayList<T> list(); // 타입 매개변수 사용불가
}
package ch12;

import java.lang.annotation.*;

@Deprecated
@SuppressWarnings("1111") // 유효하지 않은 애너테이션은 무시 
// 디폴트 값은 안적어도됨!
@TestInfo(testedBy = "aaa", testDate=@DateTime(yymmdd = "160101", hhmmss = "235959"))

public class Ex12_8 {

	public static void main(String[] args) {
		// Ex12_8의 Class 객체를 얻는다
		Class<Ex12_8> cls = Ex12_8.class;
		
		TestInfo anno = cls.getAnnotation(TestInfo.class);
		System.out.println("anno.testedBy()="+anno.testedBy());
		System.out.println("anno.testDAte().yymmdd()=" + anno.testDate().yymmdd());
		System.out.println("anno.testDate().hhmmss()=" + anno.testDate().hhmmss());
		
		//testTools 읽어오기.
		for(String str : anno.testTools())
			System.out.println("testTools =" + str);
		
		System.out.println();
		
		// Ex12_8에 적용된 모든 애너테이션을 가져온다
		Annotation[] annoArr = cls.getAnnotations();
		
		for(Annotation a : annoArr)
			System.out.println(a);

	}

}

@Retention(RetentionPolicy.RUNTIME) // 실행 시에 사용 가능하도록 지정
@interface TestInfo{
	int 		count()			 default 1;
	String 		testedBy();
	String[] 	testTools() 		default  "JUnit";
	TestType 	testType() 			default TestType.FIRST;
	DateTime 	testDate();
}

@Retention(RetentionPolicy.RUNTIME)
@interface DateTime{
	String yymmdd();
	String hhmmss();
}

enum TestType { FIRST, FINAL }
anno.testedBy()=aaa
anno.testDAte().yymmdd()=160101
anno.testDate().hhmmss()=235959
testTools =JUnit

@java.lang.Deprecated(forRemoval=false, since="")
@ch12.TestInfo(count=1, testType=FIRST, testTools={"JUnit"}, testedBy="aaa", testDate=@ch12.DateTime(yymmdd="160101", hhmmss="235959"))
profile
Journey for Backend Developer

0개의 댓글