본문 바로가기

기술 서적/자바의 정석

[자바의 정석] ch12. Generics / enums / annotation

1. 지네릭스(Generics)

더보기

- 컴파일 시, 타입체크를 해주는 기능 => 타입안전성 상승

- 의도하지 않은 객체 저장 예방

- 잘못된 형변환 오류 예방

- 클래스와 메서드에 선언 가능

 

>>지네릭 클래스의 선언

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


>>지네릭스 용어

=> Box<String>과  Box<Integer>는 같은 클래스이다.

=> Box<T>에 서로 다른 타입을 대입하여 호출한 것일 뿐


>>지네릭스 제한

- static 멤버에 T사용 불가 :  모든 객체에 대해 동일해야 하는데 변수가 다르기 때문

- 지네릭 타입의 배열 x => new T[10]

==> new 연산자는 컴파일 시점에 타입 T가 무엇인지 알아야 하는데 T가 어떤 타입이 될지 알수 없음


>>지네릭 클래스의 객체 생성과 사용

 

 1) 참조변수 == 생성자에 대입된 타입

 

2) 상속관계일 때는 대입된 타입이 일치하는 것은 ok

ex) Box(부모) <= FruitBox(자손) 일 때

 

3) 대입된 타입과 다른 타입의 객체는 추가x

 

4) 대입된 타입의 자손 타입 객체는 추가0

Fruit
Apple Grape

 

ex)

package ch12;

import java.util.ArrayList;

public class FruitBoxEx1 {
    public static void main(String[] args) {
        Box<Fruit> fruitBox  = new Box<Fruit>();
        Box<Apple> appleBox = new Box<Apple>();
        Box<Toy> toyBox = new Box<Toy>();
//        Box<Grape> grapeBox = new Box<Apple>; => 에러 : 생성자와 타입변수 불일치

        fruitBox.add(new Fruit());
        fruitBox.add(new Apple());
        fruitBox.add(new Grape());


        appleBox.add(new Apple());
        appleBox.add(new Apple());
//        appleBox.add(new Toy()); => 에러 : 타입불일치

        toyBox.add(new Toy());
//        toyBox.add(new Apple()); => 에러 : 타입불일치

        System.out.println(fruitBox);
        System.out.println(appleBox);
        System.out.println(toyBox);
    }
}

class Fruit{public String toString(){return "Fruit";}}
class Apple extends Fruit{public String toString(){return "Apple";}}
class Grape extends Fruit{public String toString(){return "Grape";}}
class Toy {public String toString(){return "Toy";}}

class Box<T>{
    ArrayList<T> list = new ArrayList<T>();
    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();}
}


>>실행결과
[Fruit, Apple, Grape]
[Apple, Apple]
[Toy]

 


>>제한된 지네릭 클래스

: 지네릭 타입에 extends를 사용 => 특정 타입의 자손만 대입할 수 있게 제한

 

: 인터페이스의 경우에도 implements가 아닌 extends사용

ex)

public class FruitBoxEx1 {
    public static void main(String[] args) {
        FruitBox<Fruit> fruitBox  = new FruitBox<Fruit>();
        FruitBox<Apple> appleBox = new FruitBox<Apple>();
        FruitBox<Grape> grapeBox = new FruitBox<Grape>();

        //FruitBox<Toy> toyBox = new FruitBox<Toy>(); => Fruit 자손이 아니며 Eatable구현x


        fruitBox.add(new Fruit());
        fruitBox.add(new Apple());
        fruitBox.add(new Grape());
        appleBox.add(new Apple());
        grapeBox.add(new Grape());
//        appleBox.add(new Grape()); => Grape은 Apple의 자손이 아님

        System.out.println("fruitBox-"+ fruitBox);
        System.out.println("appleBox-" + appleBox);
        System.out.println("grapeBox-" + grapeBox);
    }
}

class Fruit implements Eatable {public String toString(){return "Fruit";}}
class Apple extends Fruit{public String toString(){return "Apple";}}
class Grape extends Fruit{public String toString(){return "Grape";}}
class Toy {public String toString(){return "Toy";}}

interface Eatable{};
class FruitBox<T extends Fruit & Eatable> extends Box<T> {};
class Box<T>{
    ArrayList<T> list = new ArrayList<T>();
    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();}
}

>>실행결과
fruitBox-[Fruit, Apple, Grape]
appleBox-[Apple]
grapeBox-[Grape]

>>와일드 카드

 - 여러 타입 대입 가능

 

ex)

package ch12;

import java.util.ArrayList;

public class FruitEx3 {
    public static void main(String[] args) {
        FruitBox<Fruit> fruitBox = new FruitBox<>();
        FruitBox<Apple> appleBox = new FruitBox<>();

        fruitBox.add(new Apple());
        fruitBox.add(new Grape());
        appleBox.add(new Apple());
        appleBox.add(new Apple());
        System.out.println(Juicer.makeJuice(fruitBox));
        System.out.println(Juicer.makeJuice(appleBox));

    }
}

class Fruit  {public String toString(){return "Fruit";}}
class Apple extends Fruit{public String toString(){return "Apple";}}
class Grape extends Fruit{public String toString(){return "Grape";}}


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

class FruitBox<T extends Fruit> extends Box<T>{}

class Box<T>{
    ArrayList<T> list = new ArrayList<T>();
    ArrayList<T> getList(){ return list;}
    void add(T item){list.add(item);}

    public String toString() {return list.toString();}
}

>>실행결과
Apple Grape Juice
Apple Apple Juice

>>지네릭 메서드

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

: static 메서드에는 지네릭 타입 선언이 가능

=> static 멤버는 불가능

 : 호출시, 타입변수에 타입을 대입해야 함

 

=> 매개변수 타입이 복잡할 때 유용

<before>
public static void printAll(ArrayList<? extends Product> list, ArratList<? extends Product> list2)

<after>
public static<T extends Product> void printAll(ArrayList<T> list, ArratList<T> list2)

 


>>지네릭 타입의 형변환

 

 1) 지네릭 타입 <=> 원시타입 형변환 (O)

: 다만 경고 발생

 2) 지네릭 타입 <=> 지네릭 타입 형변환 (X)

Box<Object> objBox = null;
Box<String> strBox = null;

objBox =(Box<Object>)strBox; //에러! Box<String> -> Box<Object> =>지네릭간 변환x
strBox =(Box<String>)objBox; //에러! Box<Object> -> Box<String> =>지네릭간 변환x

 

3) 와일드 카드의 경우는 지네릭 <=> 와일드카드 형변환 가능


>>지네릭 타입의 제거

 

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


2. Enums

더보기

- 서로 관련된 상수를 편리하게 사용하는 기능

- 값뿐만 아니라 타입도 관리 (C에서는 int로만 저장)

 

자바에서는 타입에 안전한 열거형

=> 실제 값이 같아도 타입이 다르면 컴파일 에러가 발생함

 

ex)

IF(Card.TWO ==Card.CLOVER) // TRUE
IF(CARD.KIND.CLOVER == CARD.VALUE.TWO) // FALSE =>타입이 다름

 

 

>>열거형의 정의와 사용

 

1) 열거형 정의 : enum [ 열거형 이름 ]

 

2) 열거형 타입 변수 사용

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

: 비교연산자(><)는 사용 불가

 

 

>> 메서드 : java.lang.Enum클래스에 정의됨

 

+) 컴파일러가 추가해주는 메서드들

static E values() 열거형의 모든 상수 반환
static E valueOf(String name) name을 가진 열거형 상수 반환

 

ex)

package ch12;

enum Direction{EAST, SOUTH, WEST, NORTH}

public class EnumEx1 {

    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 ?"+ (d2>d3)); => enum 상수끼리 비교연산자 사용 불가
        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 WEST:
                System.out.println("The direction is WEST");break;
            case SOUTH:
                System.out.println("The direction is SOUTH");break;
            case NORTH:
                System.out.println("The direction is NORTH");break;
            default:
                System.out.println("Invalid direction");break;
        }
        
        Direction[] drr= Direction.values(); //모든 enum상수 배열 반환
        for(Direction d : drr){
            System.out.printf("%s=%dn", 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

 


>> 열거형에 멤버 추가하기

- 불연속적인 열거형 상수의 경우, 원하는 값을 괄호와 함께 적음

+)  인스턴스 변수(멤버 변수)와 생성자를 새로 추가해주어야 함

 

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

ex)

package ch12;

enum Direct {
    EAST(1, ">"),SOUTH(2, "V"), WEST(3, "<"), NORTH(4, "^");

    private final int value;
    private final String symbol;
    Direct(int value, String symbol){
        this.value= value;
        this.symbol= symbol;
    }
    private static final Direct [] DIR_ARR= Direct.values();
    public int getValue(){return value;}
    public String getSymbol(){ return symbol;}

    public static Direct of(int dir){
        if(dir<1 || dir>4){
            throw new IllegalArgumentException("Invalid value : "+ dir);
        }

        return DIR_ARR[dir-1];
    }

    public Direct rotate(int num){
        num%=4;
        if(num<0) num+=4;

        return DIR_ARR[(value+num-1)%4];
    }

    public String toString(){
        return name()+getSymbol();
    }
}
public class EnumEx2 {
    public static void main(String[] args) {
        for(Direct d: Direct.values()){
            System.out.printf("%s=%d%n", d.name(), d.getValue());
        }
        Direct d1= Direct.EAST;
        Direct d2= Direct.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(Direct.EAST.rotate(1));
        System.out.println(Direct.EAST.rotate(2));
        System.out.println(Direct.EAST.rotate(-1));
        System.out.println(Direct.EAST.rotate(-2));
    }
}


>>실행결과
EAST=1
SOUTH=2
WEST=3
NORTH=4
d1=EAST, 1
d2=EAST, 1
SOUTHV
WEST<
NORTH^
WEST<

>>열거형에 추상 메서드 추가하기

 

- 열거형에 추상메서드를 선언하면 각 열거형 상수가 이 추상메서드를 반드시 구현해야 함

package ch12;

enum Transportation{
    //무조건 추상 메서드를 구현해야 함
    BUS(100) {int fare(int distance){return distance *BASIC_FARE;}},
    TRAIN(150) {int fare(int distance){return distance *BASIC_FARE;}},
    SHIP(100) {int fare(int distance){return distance *BASIC_FARE;}},
    AIRPLANE(300) {int fare(int distance){return distance *BASIC_FARE;}};

    protected final int BASIC_FARE;
    Transportation(int money){
        BASIC_FARE=money;
    }
    public int getBasicFare() { return BASIC_FARE;}
    abstract int fare(int distance);
}
public class EnumEx3 {
    public static void main(String[] args) {
        System.out.println("bus fare="+Transportation.BUS.fare(100));
        System.out.println("train fare="+Transportation.TRAIN.fare(100));
        System.out.println("ship fare="+Transportation.SHIP.fare(100));
        System.out.println("airplane fare="+Transportation.AIRPLANE.fare(100));
    }
}

>>실행결과
bus fare=10000
train fare=15000
ship fare=10000
airplane fare=30000

>>enum 이해하기

 

- enum이 다음처럼 선언이 되어 있을 때

 

- 내부적으로는 다음처럼 작성된 것과 유사함

=> EAST, SOUTH, WEST, NORTH는 static 상수이기 때문에 객체의 주소이고 바뀌지 않으므로 '=='로 비교 가능

 

 


3. 애너테이션(annotation)

더보기

- 주석처럼 프로그래밍 언어에 영향을 미치지 않으며 유용한 정보를 저장

- 모든 프로그램에게 의미가 있는 것x / 미리 정의된 종류와 형식으로 작성해야만 의미가 있음

ex) @Test : 이 메서드를 테스트해야함으로 테스트 프로그램에게 알림

 

>>표준 애너테이션


>> @Override

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

- 오버라이딩을 할 때, 부모 클래스에 같은 이름의 메소드가 있는지 체크

- 메서드 선언부 앞에 @Override를 붙임


>> @Deprecated

- 앞으로 사용핮 않을 것을 권장하는 필드나 메서드

- @Deprecated가 붙은 대상이 사용된 코드를 컴파일 시 => 경고 메시지 출력

package ch12;


class newClass {
    int newField;
    int getNewField() { return newField;}
    @Deprecated
    int oldField;

    @Deprecated
    int getOldField(){return oldField;}
}
public class AnnotationEx2 {
    public static void main(String[] args) {
        newClass nc = new newClass();
        nc.oldField=10;
        System.out.println(nc.getOldField());
    }
}

>>@FunctionalInterface

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

- 함수 인터페이스 : 하나의 추상 메서드만 가져야 한다는 제약


>>@SuppressWarnings

- 컴파일러의 경고메시지가 나타나지 않게 억제됨

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

deprecation Deprecated가 붙은 대상을 사용해서 발생하는 경고
unchecked 지네릭스 타입을 지정하지 않았을 때
rawtypes 지네릭스를 사용하지 않아서 발생하는 경고
varargs 가변인자의 타입이 지네릭 타입일 때 발생하는 경고를 억제할 때
package ch12;


import java.util.ArrayList;

class newClass {
    int newField;
    int getNewField() { return newField;}
    @Deprecated
    int oldField;

    @Deprecated
    int getOldField(){return oldField;}
}
public class AnnotationEx2 {
    @SuppressWarnings("deprecation")
    public static void main(String[] args) {
        newClass nc = new newClass();
        nc.oldField=10;
        System.out.println(nc.getOldField());
        
        @SuppressWarnings("unchecked")
        ArrayList<newClass> list = new ArrayList<>;
        list.add(nc);
    }
}

>> @SafeVarags : 가변 인자의 타입안전성

- 메서드에 선언된 가변인자의 타입이 non-reifiable타입일 경우 => non-reifiable(컴파일 후, 제거되는 타입)

- static이나 final이 붙은 메서드와 생성자에만 붙일 수 있음

- @SuppressWarnings("varargs")와 주로 함께 사용

package ch12;

import java.util.Arrays;

class MyArrayList<T>{
    T[] arr;

    @SafeVarargs
    @SuppressWarnings("varargs")
    MyArrayList(T...arr){
        this.arr= arr;
    }

    @SafeVarargs
    @SuppressWarnings("unchekced")
    public static <T> MyArrayList<T> asList(T...a){
        return new MyArrayList<>(a);
    }

    public String toString(){
        return Arrays.toString(arr);
    }
}
public class AnnotationEx4 {
    public static void main(String[] args) {
        MyArrayList<String> list = MyArrayList.asList("1", "2", "3");
        System.out.println(list);
    }
}

 

>>메타 애너테이션

 

>> @Target : 애너테이션의 적용대상 지정


>>@Retention :애너테이션이 유지되는 기간

-  SOURCE: 컴파일러에 의해 사용되는 애너테이션 유지 정책

- RUNTIME : 실행시에 사용 가능한 애너테이션의 정책

 


>>@Documented

: javadoc으로 작성된 문서에 포함

 

>> @Inherited

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


>>@Repeatable : 반복해서 붙일 수 있는 애너테이션을 정의할 때

- 반복 애너테이션을 묶어서 관리할 수 있는 다른 애너테이션도 추가로 정의해야 함

@interface ToDos{
	ToDo[] value();
}

>>애너테이션 타입 정의하기

- 애너테이션의 메소드는 추상메서드이며, 적용할 때 모두 지정해야 함

@TestInfo(
	count=3, testedBy="Kim",
    testTools={"JUnit", "AutoTester"},
    testType=TestType.FIRST,
    testDate=@DateTime(yymmdd="160101", hhmmss="235959")
    )

 

- default를 통해  기본값 지정 가능

- 요소가 하나뿐이며 이름이 value라면 이름 생략이 가능

- 요소 타입이 배열인 경우, 괄호{} 사용

@SuppressWarnings({"deprecation", "unchecked"})
=>요소 이름이 value이기 때문에 생략 가능

>> java.lang.annotation.Annotation

: 모든 애너테이션의 조상이지만 상속은 불가

: 인터페이스로 정의되어 있음

: 모든 애너테이션 객체에 equals(), hasCode(), toString()같은 메서드 호출이 가능

 

>>마커 애너테이션

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

>>애너테이션 요소 규칙

package ch12;
import org.w3c.dom.ls.LSOutput;

import java.lang.annotation.*;
enum TestType{FIRST, FINAL}

@Retention(RetentionPolicy.RUNTIME)
@interface TestInfo{
    int count() default 1;
    String testBy();
    String [] testTools()  default "JUnit";
    TestType testType() default TestType.FIRST;
    DateTime testDate();
}

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

@Deprecated
@SuppressWarnings("1111")
@TestInfo(testBy="aaa", testDate=@DateTime(yymmdd="160101", hhmmss="235959"))
public class AnnotationEx5 {
    public static void main(String[] args) {
        Class<AnnotationEx5> cls= AnnotationEx5.class;
        TestInfo anno =(TestInfo)cls.getAnnotation(TestInfo.class);
        System.out.println("anno.testedBy()="+ anno.testBy());
        System.out.println("anno.testDate().yymmdd()="+ anno.testDate().yymmdd());

        System.out.println("anno.testDate().hhmmss()="+ anno.testDate().hhmmss());

        for (String str : anno.testTools()){
            System.out.println("testTools="+str);
        }
        System.out.println();

        Annotation [] annoArr =cls.getAnnotations();
        for(Annotation a : annoArr){
            System.out.println(a);
        }

    }


}

>>실행결과
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"}, testBy="aaa", testDate=@ch12.DateTime(yymmdd="160101", hhmmss="235959"))