January 04, 2022
지네릭스는 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스의 컴파일 시의 타입 체크(compile - time type check)를 해주는 기능이다. 객체의 타입을 컴파일 시에 체크하기 때문에 객체의 타입 안정성을 높이고 형변환의 번거로움이 줄어든다.
class Box{
Object item;
void setItem(Object item) {this.item = item;}
Object getItem() { return item;}
}
위와 같은 클래스가 있을 때 이를 지네릭 클래스로 변경하면 클래스 옆에 <T>를 붙여주고 ‘Ojbect’를 모두 T로 바꾸면 된다.
class Box<T>{
T item;
void setItem(T item) {this.item = item;}
T getItem() { return item;}
}
무조건 T를 쓸 필요 없고, 다른 문자를 사용할 수 있다. 또한 Map처럼 타입 변수가 두개인 경우에는 Map<K, V>이런식으로 ’,’ 를 통해 구분이 가능하다.
위와 같이 지네릭 클래스를 선언하면 < >를 사용하여 타입을 지정할 수 있다.
Box<String> b = new Box<String>();
b.setItem(new Object()); // 에러 발생 : 타입을 String으로 선언했기 때문
b.setItem("ABC"); // String 타입이므로 선언이 가능하다.
위와 같이 <String>으로 선언을 하면 이는 지네릭 타입 T 대신에 String이 들어간 것과 같다.
class Box<T>{}
// Box<T> : 지네릭 클래스, T Box 라고 읽음
// T : 타입변수 또는 타입 매개변수(T는 타입 문자)
// Box : 원시 타입 (raw type)
지네릭 클래스는 모든 객체에 동일하게 동작해야하는 static멤버의 타입 변수 T를 사용할 수 없다.
지네릭 타입의 배열 변수를 선언하는 것이 불가하다. ex) new T[10] 불가
아래의 예시를 보며 어떻게 사용하는지 쉽게 알 수 있다.
import java.util.ArrayList;
class Fruite { 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 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()); // add함수가 Fruit item인 상태이기에 가능
appleBox.add(new Apple());
//appleBox.add(new Toy()); // 에러 Box<Apple>에는 Apple만 담을 수 있음
}
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(); }
}
}
지네릭 타입에 ‘extends’를 사용하면, 특정 타입의 자손들만 대입할 수 있게 제한할 수 있다.
class FruitBox<T extends Fruit>{ // Fruit의 자손만 타입으로 지정가능
ArrayList<T> list = new ArrayList<T>();
}
인터페이스를 구현해야 할 때 제약이 필요할 때도 extends를 사용한다.
interface Eatable{}
class FruitBox<T extends Eatable> {...}
// 다음과 같이 & 연산자를 사용해서 여러 조건을 걸어줄 수 도 있다.
class FruitBox<T extends Fruit & Eatable> {...} //Fruit의 자손이면서 Eatable도 구현해야한다.
지네릭 타입이 다른 것만으로는 오버로딩이 성립하지 않는다 ex)
static Juice makeJuice(FruitBox<Fruit> box){}
static Juice makeJuice(FruitBox<Apple> box){}
위의 두 함수는 지네릭 타입만 다르기 때문에 오버로딩이 성립하지 않아 컴파일 에러가 발생한다.
-> 따라서 이럴때 사용하기위해 고안된 것이 와일드 카드이며 ’?’로 표현한다
<? extends T> : 와일드 카드의 상한 제한. T와 그 자손들은 가능
<? super T> : 와일드 카드의 하한 제한. T와 그 조상들만 가능
<?> : 제한없이 모든 타입이 가능 <? extends Object>와 동일
Collections.sort()의 선언부는 다음과 같다
static <T> void sort(List<T> list, Comparator<? super T> c)
여기서 sort함수는 정렬할 방법이 정의된 Comparator가 있다. 클래스 Fruit를 상속한게 Apple, Grape라고 하면
class AppleComp implements Comparator<Apple>{
public int compare(Apple t1, Apple t2){
return t2.weight - t1.weight;
}
}
class GrapeComp implements Comparator<Grape>{
public int compare(Grape t1, Grape t2){
return t2.weight - t1.weight;
}
}
위의 두 함수는 완전히 같지만, Fruit의 자손이 생길 때마다 위와 같은 코드를 반복해서 만들어야하는 불편함이 있다. -> 다음과 같이 선언하면 자손이 생겨도 한번에 처리할 수 있다.
class FruitComp implements Comparator<Fruit>{
public int compare(Fruit t1, Fruit t2){
return t1.weight - t2.weight;
}
}
Comparator<? super Apple>, Comparator<? super Grape>로 되어있기 때문에 결국 Comparator에(sort함수의 두번째 파라미터에)Comparator
메서드의 선언부에 지네릭 타입이 선언된 메서드를 지네릭 메서드라고 한다.
static juice makeJuice(FruitBox<? extends Fruit> box){
return new juice();
};
// 위의 함수는 아래와 같이 쓸 수 있다.
static <T extends Fruit> juice makeJuice(FruitBox<T> box){
return new juice();
};
/////////////////////////////////////////////////////////////////////////////
public static void printAll(ArrayList<? extends Product> list, ArrayList<? extends Product> list2){
System.out.println("hi");
}
// 위의 복잡한 매개변수의 타입을 아래와 같이 변경이 가능하다.
public static <T extends Product> void printAll(ArrayList<T> list, ArrayList<T> list2){
System.out.println("hi");
}
자바의 열거형은 값 뿐만 아니라 타입도 체크하다 (typesafe enums)
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 = -1;
static final int THREE = 3;
static final int FOUR = 2;
final int kind;
final int num;
}
// 위의 클래스를 아래와 같이 간단하게 줄일 수 있다.
class Card{
enum Kind {CLOVER, HEART, DIAMOND SPADE}
enum Num {TWO(-1), THREE(3), FOUR(2)}
final Kind kind; // 타입이 int가 아니라 Kind이다
final Num num;
}
열거형은 다음과 같은 함수들을 내장하고 있다.
Class<E> getDeclaringClass() // 열거형의 Class객체를 반환한다.
String name() //열거형의 상수의 이름을 문자열로 반환한다.
int ordinal() // 열거형 상수가 정의된 순서를 반환한다
T valueOf(Class<T> enumType, String name) // 지정된 열거형에서 name과 일치하는 열거형 상수를 반환한다.
’>’, ’<’ 비교연산자는 compareTo()를 사용한다.
enum Transportation{
BUS(100) { int fare(int distance) { return distance*BASIC_FARE; }},
TRAIN(100) { int fare(int distance) { return distance*BASIC_FARE; }},
SHIP(100) { int fare(int distance) { return distance*BASIC_FARE; }},
AIRPLANE(100) { int fare(int distance) { return distance*BASIC_FARE; }};
protected final int BASIC_FARE; // private는 불가 public 은 가능
Transportation(int basicFare){
BASIC_FARE = basicFare;
}
public int getBasicFare(){ return BASIC_FARE; }
// 열거형의 추상메서드
abstract int fare(int distance); // 거리에 따른 요금 계산
}
public class EnumEx2 {
public static void main(String[] args){
System.out.println("bus fare="+Transportation.BUS.fare(100));
System.out.println("train fare="+Transportation.TRAIN.fare(150));
System.out.println("ship fare="+Transportation.SHIP.fare(100));
System.out.println("airplane fare="+Transportation.AIRPLANE.fare(100));
}
}
Transportation이라는 열거형의 변수 하나하나가 결국 Transportation 객체이다.
애너테이션이란 프로그램의 소스코드안에 다른 프로그램을 위한 정보를 미리 약속된 형식으로 포함시킨 것이 바로 애너테이션이다. 애너테이션은 주석(comment)처럼 프로그래밍 언어에 영향을 미치지 않으면서도 다른 프로그램에게 유용한 정보를 제공할 수 있다.
자바에서 기본적으로 제공하는 에너테이션이다.
조상의 메서드를 오버라이딩 하는 것임을 컴파일러에게 알리는 역할을 한다.
Note: \${@Deprecated 를 사용한 파일이름} uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details
함수형 인터페이스를 선언할 때 이 애너테이션을 붙이면 컴파일러가 직접 함수형 인터페이스를 올바르게 선언했는지 확인하고, 잘못된 경우 에러를 발생시킨다.
컴파일러가 보여주는 경고메시지가 나타나지 않게 억제해준다.
주로 사용되는 것은
메타 애너테이션은 ‘애너테이션을 위한 애너테이션’이다. 즉 애너테이션에 붙이는 애너테이션으로 애너테이션을 정의할 때 애너테이션의 적용대상(targe)이나 유지기간(retention)등을 지정하는데 사용된다.
@interface 이름 {
타입 요소이름(); //애너테이션의 요소를 선언한다.
}
//@Override는 애너테이션이고, Override는 애너테이션의 타입이다.
@기호를 붙이는 것을 제외하면 인터페이스를 정의하는 것과 동일하다
에너테이션을 선언한 예시는 다음과 같다
@interface TestInfo{
int count();
String testedBy();
String[] testTools();
TestType testType(); // enum TestType {FIRST, FINAL}
DateTime testDate(); // 자신이 아닌 다른 애너테이션을 포함할 수 있다. @DateTime
}
@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 myClass{
...
}