1. Generics
1.1 제네릭스란?
: 컴파일 시에 타입을 확인하는 기능. => 타입 안정성을 높이고 형변환의 번거로움을 줄인다.
1.2 제네릭 클래스의 선언
class Box<T> {
T item;
void setItem(T item) { this.item = item; }
T getItem() { return this.item; }
}
Box<String> b = new Box<>();
b.setItem(new Object()); // error
b.setItem("abc"); // ok
// 이전 코드 호환을 위해 아래도 가능
Box b = new Box();
b.setItem(new Object()); // ok, but warning
b.setItem("abc"); // ok, but warning
- Box<T> : 제네릭 클래스 / Box : 원시타입(raw type) / T : 타입변수 또는 타입 매개변수
- static 변수는 타입변수를 사용할 수 없다. (인스턴스 vs 클래스)
- 제네릭 타입 배열을 사용할 수 없다. new 연산자나 instanceof 연산자는 컴파일 시점에 타입을 정확히 알아야한다. 제네릭 타입 배열을 꼭 생성해야할 때에는 java reflection 의 newInstance() 를 이용하거나, Object 배열을 생성한 후에 복사 후 형변환(T[])하는 방법을 사용한다.
1.4 제한된 제네릭 클래스
: 특정한 여러 종류의 클래스로 제한하고 싶을 때 사용한다.
class FruitBox<T extends Fruit> { // Fruit의 자손만 타입으로 지정가능
ArrayList<T> list = new ArrayList<T>();
/* ... */
}
- 만약 클래스가 아니라 인터페이스를 구현해야한다는 제약이 필요하다면, 이때도 extends 를 사용한다.
- Fruit 의 자손이면서 Eatable 인터페이스도 구현해야한다면 아래와 같이 & 기호로 연결한다.
class FruitBox<T extends Fruit & Eatable> {...}
1.5 와일드 카드
: 제네릭 클래스를 파라미터로 넘겨 받을 때의 문제를 해결한다.
class Juice {
/* 아래의 매서드는 메서드 오버로딩이 성립하지 못해 에러
static Juice makeJuice(FruitBox<Fruit> box) {...}
static Juice makeJuice(FruitBox<Apple> box> {...}
*/
static Juice makeJuice(FruitBox<? extends Fruit> box> {...}
}
- <?> : 제한이 없다. 모든 타입이 가능.
- <? extends T> 와일드 카드의 상한 제한. T와 자손들만 가능
- <? super T> 와일드 카드의 하한 제한. T와 조상들만 가능
1.6 제네릭 메서드
- 제네릭 메서드는 제네릭 클래스가 아닌 일반 클래스에도 사용가능하다.
- 제네릭 클래스에 정의된 타입 매개변수와, 메서드에 정의된 타입매개변수는 별개이다.
class Juice {
// static Juice makeJuice(FruitBox<? extends Fruit> box) {...}
static <T extends Fruit> Juice makeJuice(FruitBox<T> box) {...}
}
FruitBox<Apple> appleBox = new FruitBox<>();
Juicer.<Apple>makeJuice(appleBox);
Juicer.makeJuice(appleBox); // 생략 가능
- 매개변수 타입이 복잡한 경우에 유용하게 사용될 수 있다.
public static void printAll(ArrayList<? extends Product> list,
ArrayList<? extends Product> list2) {...}
public static <T extends Product> void printAll(ArrayList<T> list,
ArrayList<T> list2) {...}
1.7 제네릭 타입의 형변환
Box<Object> objBox = new Box<String>(); // error
Box<? extends Object> = new Box<string>(); // ok
Optional<?> EMPTY = new Optional<>(); // 가능
// == Optional<?> EMPTY = new Optional<Object>();
// == Optional<? extends Object> EMPTY = new Optional<Object>();
Optional<?> EMPTY = new Optional<?>(); // 불가능, 미확인 타입의 객체는 생성불가
Optional<T> result = (Optional<T>) EMPTY; // 가능
1.8 제네릭 타입의 제거
: 컴파일러는 제넥 타입을 이요해서 소스파일을 체크해 필요한 곳에 현변환을 넣어준다. 그리고 제네릭 타입을 제거한다. 결과적으로 컴파일된 파일(.class)에는 제네릭 타입에 대한 정보가 없다.
- 제네릭 타입의 경계를 제거한다.
제네릭 타입이 <T extends Fruit>라면 T는 Fruit로 치환된다. <T> 인 경우는 Object 로 치환된다.
class Box<T extends Fruit> {
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); // List.get 은 Object 타입을 반환한다.
}
2. 열거형 (enums)
2.1 열거형이란?
: jdk 1.5부터 추가되었다. c보다도 더 우수한 기능을 갖추었다. c에서는 타입이 달라도 값이 같으면 true 로 평가되었지만, java 에서는 typesafe enum 이다. 또 기존에는 상수의 값이 바뀌면 해당 상수를 참조하는 모든 소스를 다시 컴파일해야 되지만, 열거형 상수는 다시 컴파일하지 않아도 된다.
2.2 열거형의 정의와 사용
enum 열거형이름 { 상수명1, 상수명2, ... }
- 열거형 상수간에는 == 비교 연산자를 사용할 수 있다.
- <, > 같은 비교 연산자는 사용할 수 없고, compartTo() 를 사용해야한다.
- switch 문에도 적용할 수 있는데, 이 때 열거형 타입은 case 에 적지 않는다.
- 컴파일러가 자동으로 values(), valueOf(String name) 같은 메소드를 지원한다.
2.3 열거형에 멤버 추가하기
enum Direction {
EAST(1), SOUTH(5), WEST(-1), NORTH(10); // 끝에 ; 추가
private final int value; // 값을 저장할 변수
Direction(int value {this.value = value;} // constructor, 외부에서 호출 불가
public int getValue() { return value; }
}
- 멤버 변수를 추가할 수 있다
- 생성자는 암묵적으로 private 이다.
- 변수를 여러개의 순서쌍으로 지정할 수 있다. 이 경우에는 그에 맞는 멤버변수를 추가해주어야한다.
- 아래와 같이 메서드를 선언할 수 있다. 특히 추상메서드를 선언하는 경우에는 각 상수에서 구현해야한다.
enum Transportation {
BUS(100) {int fare(int distance) {return distnace*BASIC_FARE;}},
TRAIN(150) {int fare(int distance) {return distnace*BASIC_FARE;}},
SHIP(100) {int fare(int distance) {return distnace*BASIC_FARE;}},
AIRPLAIN(300) {int fare(int distance) {return distnace*BASIC_FARE;}},
abstract int fare(int distance); // 거리에 따라 요금을 계산하는 추상 메서드
protected final int BASIC_FARE; // private 으로 하면 각 상수에서 접근불가능
Transportation(int basicFare) {
BASIC_FARE = basicFare;
}
public int getBasicFare() { return BASIC_FARE; }
}
2.4 열거형의 이해
: 순수 java 클래스로 구현할 수 있다. 거의 동일하다고 보면 될듯!
abstract class MyEnum<T extends MyEnum<T>> implemnets Comparable<T> {
static int id = 0; // 객체에 붙일 일련번호
int ordinal;
string name = "";
public int ordinal() {return oridinal;}
MyEnum(String name) {
this.name = name;
ordinal = id++; // 객체 하나를 생성할 때마다 id 의 값을 증가시킨다.
}
public int compareTo(T t) {
return ordinal = t.ordinal(); // MyEnum<T> 라고 했으면 t.ordinal() 호출 시 오류
}
}
//추상메서드가 있으면 클래스도 추상클래스가 되어야함.
abstract class Direction extends MyEnum {
static final Direction EAST = new Direction("EAST") {/*익명클래스*/};
static final Direction WEST = new Direction("WEST") {/*익명클래스*/};
static final Direction SOUTH = new Direction("SOUTH") {/*익명클래스*/};
static final Direction NORTH = new Direction("NORTH") {/*익명클래스*/};
private String name;
private Direction(String name) {this.name = nema;}
abstract Point move(Point p);
}
3. 애너테이션
3.1 애너테이션
- 소스코드의 주석에 소스코드에 대한 정보를 저장하고, 소스코드의 주석으로부터 html 문서를 생성해내는 프로그램(javadoc)을 만들었다.
- 이 주석에 @ 기호로 시작하는 태그들이 있는데, 미리 약속된 형태로 사용된다.
- 다른 프로그래머에게 유용한 정보를 제공하기 위함이다.
- jdk에서 제공하는 표준 어노테이션은 주로 컴파일러를 위한 것.
- 새로운 어노테이션을 만들때 필요한 메타 어노테이션도 있다.
3.2 표준 어노테이션
- @Override
- @Deprecated
- @FunctionalInterface : 함수형 인터페이스인지 컴파일러가 확인하도록 한다.
- @SuppressWarnings
- @SafeVarargs : non-reifiable 타입 가변인자를 사용할 때, unchecked 경고가 발생하는 것을 막는다.
3.3 메타 어노테이션
- @Target
: 어노테이션이 적용될 수 있는 대상을 지정한다. - @Retention
: 어노테이션이 유지되는 기간을 지정한다. source - 소스파일에만 존재하고 클래스파일에는 존재하지 않음. class - 클래스 파일에 존재하고 실행 시에는 사용불가, runtime - 클래스파일에 존재하고 실행시에 사용가능하다. @Override 처럼 컴파일러가 사용하는 어노테이션은 source, @FunctionalInterface 는 컴파일러가 체크해주기도 하지만 실행시에도 사용되므로 runtime, class 는 디폴트값이지만 잘 사용되지 않는다. 클래스 파일에는 존재하지만 jvm 에 로딩될 때는 적용되지 않기 때문이다. 또 지역 변수에 붙은 어노테이션은 컴파일러만 인식할 수 있어서, runtime으로 설정하더라도 실행중에는 인식할 수 없다. - @Documented
: javadoc 으로 작성한 문서에 포함되도록 한다. 표준 어노테이션 중에 override 와 suppressWarnings 를 빼고 다 붙어있다. - @Inherited
: 어노테이션이 자손 클래스에 상속되도록 한다. - @Repeatable
: 보통은 한 대상에 한 종류의 어노테이션만 붙이는데 여러번 붙일 수 있도록 한다. 이 어노테이션을 묶어주는 컨테이너 어노테이션을 지정해주어야한다. - @Native
: 네이티브 메서드에 의해 참조되는 상수 필드에 붙이는 어노테이션이다. 네이티브 메서드는 os의 메서드를 말한다. 보통 c언어로 작성되어 있어서 자바에서는 메서드의 선언부만 정의하고 구현은 하지 않는다. Object 클래스의 메서드들은 대부분 네이티브 메서드이다. java 메서드 선언과 os 메소드 호출을 연결하는 작업은 JNI(JAVA Native Interface) 가 한다.
3.4 어노테이션 타입 정의하기
@interface TestInfo {
int count();
String testedBy();
String[] testTools();
TestType testType(); // enum 가능
DateTime testDate(); // 다른 어노테이션 포함 가능
}
@interface DateTime {
String yymmdd();
String hhmmss();
String value(); // element 이름이 value 이면 적용할 때 이름 생략 가능
}
- element 타입은 기본현, 문자열, 열거형, 어노테이션, 클래스만 허용
- () 안에 매개변수 선언 불가
- 예외 선언 불가
- 제네릭으로 선언 불가
'Java > Java의 정석' 카테고리의 다른 글
[Java의 정석] 14. 람다와 스트림 (0) | 2022.05.10 |
---|---|
[Java의 정석] 11. Collections Framework (0) | 2022.03.23 |
[Java의 정석] 09. java.lang 패키지 (0) | 2022.03.17 |
[Java의 정석] 08. 예외 처리 (0) | 2022.03.17 |
[Java의 정석] 7.2 메소드 오버라이딩 시 예외 선언 (0) | 2022.03.14 |