1. 지네릭스(Generics)
1.1 지네릭스란?
Def) 지네릭스: 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입 체크를 해주는 기능
- 자료형을 제한해주는 기능이다.
- 타입 안정성이 높아지고 형변환의 번거로움이 줄어들어 간결해진다.
- 의도하지 않은 타입의 객체가 저장되는 것을 막는다.
1.2 지네릭 클래스의 선언
- 클래스와 메서드에 선언할 수 있다.
Ex) 지네릭 클래스 Box가 선언되어 있을 때
- Box<T>: 지네릭 클래스, 'T의 Box' 또는 'T Box'라고 있는다.
- T: 타입 변수 또는 타입 매개변수(T는 타입 문자)
- Box: 원시 타입(raw type)
Note) 지네릭스의 제한
- 지네릭 타입의 배열을 생성할 수 없다.
- static 멤버에 타입 변수 T를 사용할 수 없다.
1.3 지네릭 클래스의 객체 생성과 사용
Ex)
<hide/>
import java.util.ArrayList;
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";}}
public class ArrayListEx1 {
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<Grape>(); // 에러 타입 불일치
fruitBox.add(new Fruit());
fruitBox.add(new Apple()); // OK. void add(Fruit item)
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 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();
}
}
Note) 실행결과
- appleBox.add(new toy()); 에러 -> Box<Apple>에는 Apple만 담을 수 있다.
- Grape, Apple은 Fruit의 자손이므로 상속 관계에 있다.
-> fruitBox에 Apple을 추가할 수 있다.
1.4 제한된 지네릭 클래스
- 지네릭 타입에 'extends'를 사용하면 특정 타입의 자손들만 대입할 수 있도록 제한한다.
- 타입 매개 변수 T에 Object를 대입하면 모든 종류의 객체를 저장 가능하다.
Ex)
<hide/>
import java.util.ArrayList;
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 {}
public class FruitBoxEx2 {
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>(); 에러
fruitBox.add(new Fruit());
fruitBox.add(new Apple()); // OK. void add(Fruit item)
fruitBox.add(new Grape());
appleBox.add(new Apple());
// appleBox.add(new Grape()); 에러
grapeBox.add(new Grape());
System.out.println( "fruitBox-"+ fruitBox);
System.out.println( "appleBox-" + appleBox);
System.out.println("grapeBox"+ grapeBox);
}
}
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();}
}
Note) 실행결과
- implements가 아닌 'extends'를 이용해서 Eatable인터페이스를 구현한다.
1.5 와일드카드
<? extends T> : 와일드 카드의 상한 제한. T와 그 자손들만 가능
<? super T> : 와일드 카드의 하한 제한. T와 그 조상들만 가능
<?> : 제한 없음. 모든 타입이 가능하다. < ? extends Object >와 동일하다.
Ex)
<hide/>
import java.util.*;
class Fruit {
String name;
int weight;
Fruit(String name, int weight){
this.name = name;
this.weight = weight;
}
public String toString() {return name + "(" + weight + ")" ; }
}
class Apple extends Fruit{
Apple(String name, int weight){
super(name, weight);
}
}
class Grape extends Fruit{
Grape(String name, int weight){
super(name, weight);
}
}
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;
}
}
class FruitComp implements Comparator<Fruit>{
public int compare(Fruit t1,Fruit t2) {
return t1.weight - t2.weight;
}
}
public class FruitBoxEx2 {
public static void main(String[] args) {
FruitBox<Apple> appleBox = new FruitBox<Apple>();
FruitBox<Grape> grapeBox = new FruitBox<Grape>();
appleBox.add(new Apple("GreenApple", 300));
appleBox.add(new Apple("GreenApple", 100));
appleBox.add(new Apple("GreenApple", 200));
grapeBox.add(new Grape("GreenGrape", 400));
grapeBox.add(new Grape("GreenGrape", 300));
grapeBox.add(new Grape("GreenGrape", 200));
Collections.sort(appleBox.getList(), new AppleComp());
Collections.sort(grapeBox.getList(), new GrapeComp());
System.out.println(appleBox);
System.out.println(grapeBox);
System.out.println();
Collections.sort(appleBox.getList(), new FruitComp());
Collections.sort(grapeBox.getList(), new FruitComp());
System.out.println(appleBox);
System.out.println(grapeBox);
}
}
class FruitBox<T extends Fruit> 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);
}
ArrayList<T> getList() { return list;}
int size() {
return list.size();
}
public String toString(){
return list.toString();
}
}
Note) 실행결과
- Collections.sort()를 이용해서 appleBox와 grapeBox에 담긴 과일을 무게 별로 정렬한다.
- Comparator<? super apple>: Comparator의 타입 매개변수로 Apple과 그 조상이 가능하다는 뜻.
1.6 지네릭 메서드
- 지네릭 타입이 선언된 메서드(타입 변수는 메서드 내에서만 유효)
-> 반환 타입 바로 앞에 지네릭 타입을 선언한다.
- 클래스의 타입 매개변수 <T>와 메서드의 타입 매개변수 <T>는 별개
- 메서드를 호출할 때마다 타입을 대입해야 (대부분 생략 가능)
- 메서드를 호출할 때, 타입을 생략하지 않을 때는 클래스 이름 생략 불가
1) 타입 T를 요소로 하는 List를 매개변수로 허용한다.
2) 'T'는 Comparable을 구현한 클래스여야 한다.( <T extends Comparable> )
-> 'T'또는 그 조상의 타입을 비교하는 Comparable이어야한다는 것( Comparable<? super T>)를 의미
-> T가 Student이고 Person의 자손이라면 <? super T>는 Student, Person, Object가 모두 가능하다.
1.7 지네릭 타입의 형변환
- 지네릭 타입과 원시 타입(raw type) 간의 형변환은 바람직하지 않다. (경고 발생)
- 대입된 타입이 다른 지네릭 타입 간에는 형변환이 불가능하다.
- 와일드 카드가 사용된 지네릭 타입끼리도 형변환 가능한 경우가 있다. (경고 발생)
1.8 지네릭 타입의 제거
- 컴파일러는 지네릭 타입을 제거하고 필요한 곳에 형변환을 넣는다.
- 제거 과정
1) 지네릭 타입의 경계(Bound)를 제거
2) 지네릭 타입 제거 후에 타입이 불일치하면, 형변환을 추가한다.
-> 와일드 카드가 포함된 경우, 적절한 타입으로 형변환 추가한다.
2. 열거형(enums)
2.1 열거형이란?
Def) 열거형: 관련된 상수들을 같이 묶어 놓은 것. Java는 타입에 안전한 열거형을 제공한다.
2.2 열거형의 정의와 사용
- 열거형 정의: enum 열거형이름 { 상수명1, 상수명2, ... }
- 열거형 타입의 변수를 선언하고 사용하는 방법
- 열거형 상수의 비교에 '==' 와 compareTo() 사용 가능하다. (부등호는 사용불가)
-> compareTo() : 같으면 0 / 왼쪽이 크면 (+) / 오른쪽이 크면 (-) 반환
- 모든 열거형의 조상 - java.lang.Enum
-> values(), valueOf()는 컴파일러가 자동으로 추가
-> values():열거형의 모든 상수를 배열에 담아 반환한다.
Ex)
<hide/>
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 ? " + (d1 > d3)); 에러
System.out.println("d1.compareTo(d3) ? " + (d1.compareTo(d3)));
System.out.println("d1.compareTo(d2) ? " + (d1.compareTo(d2)));
switch(d1) {
case EAST: // Direction.EAST 라고 쓸 수 있다.
System.out.println("The direction is EAST."); break;
case SOUTH: // Direction.EAST 라고 쓸 수 있다.
System.out.println("The direction is SOUTH."); break;
case WEST: // Direction.EAST 라고 쓸 수 있다.
System.out.println("The direction is WEST."); break;
case NORTH: // Direction.EAST 라고 쓸 수 있다.
System.out.println("The direction is NORTH."); break;
default:
System.out.println("Invalid direction.");
}
Direction[] dArr = Direction.values();
for(Direction d: dArr) {
System.out.printf("%s = %d%n" , d.name(), d.ordinal());
}
}
}
Note) 실행결과
2.3 열거형에 멤버 추가하기
- ordinal()이 열거형 상수가 정의된 순서를 반환하지만 사용하지 않는 게 좋다.
- 불연속적인 열거형 상수의 경우, 원하는 값을 괄호()안에 적는다.
- 정수를 저장한 필드(인스턴스 변수)를 추가해야한다.
- 열거형의 생성자는 묵시적으로 private이므로 외부에서 객체 생성 불가.
- 열거형의 생성자는 외부에서 호출 불가능
Ex)
<hide/>
enum Direction {
EAST(1, ">"), SOUTH(2, "V"), WEST(3, "<"), NORTH(4, "^");
private static final Direction[] DIR_ARR = Direction.values();
private final int value; // 정수를 저장할 필드(인스턴스 변수)를 추가한다.
private final String symbol;
Direction(int value, String symbol){ // 생성자를 추가한다. private 생략됨
this.value = value;
this.symbol = symbol;
}
public int getValue() {return value;}
public String getSymbol() {return symbol;}
public static Direction of(int dir) {
if(dir < 1 || dir > 4) {
throw new IllegalArgumentException("Invalid value :" + dir);
}
return DIR_ARR[dir - 1];
}
public Direction 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 EnumEx2 {
public static void main(String[] args) {
for(Direction d: Direction.values())
System.out.printf("%s = %d %n", d.name(), d.getValue());
Direction d1 = Direction.EAST;
Direction d2 = Direction.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(Direction.EAST.rotate(1));
System.out.println(Direction.EAST.rotate(2));
System.out.println(Direction.EAST.rotate(-1));
System.out.println(Direction.EAST.rotate(-2));
}
}
Note) 실행결과
- 하나의 열거형 상수에 여러 값을 지정할 수 있다.
- rotate(): 방향을 회전시키는 메서드, num의 값만큼 90도씩 시계 방향으로 회전한다.
-> num이 음수일 때는 시계 반대 방향으로 회전한다.
2.4 열거형의 이해
- 열거형 상수 하나 하나가 Direction객체이다.
- Direction클래스의 static상수 EAST, SOUTH, WEST, NORTH 값은 객체의 주소이다.
-> 이 값은 바뀌지 않으므로 == 로 비교가 가능하다.
-> 객체가 생성될 때마다 번호를 붙여서 인스턴스변수 ordinal에 저장한다.
Ex) 컴파일 에러 발생하는데 오류는
<hide/>
abstract class MyEnum< T extends MyEnum<T>> implements Comparable {
static int id = 0;
int ordinal;
String name = "";
public int ordinal() { return ordinal;}
MyEnum(String name){
this.name = name;
ordinal = id++;
}
public int compareTo(T t) {
return ordinal - t.ordinal();
}
}
abstract class MyTransportation extends MyEnum{
static final MyTransportation BUS = new MyTransportation("BUS", 100) {
int fare(int distance) {return distance * BASIC_FARE; }
};
static final MyTransportation TRAIN = new MyTransportation("TRAIN", 150) {
int fare(int distance) {return distance * BASIC_FARE; }
};
static final MyTransportation SHIP = new MyTransportation("SHIP", 100) {
int fare(int distance) {return distance * BASIC_FARE; }
};
static final MyTransportation AIRPLANE = new MyTransportation("AIRPLANE", 300) {
int fare(int distance) {return distance * BASIC_FARE; }
};
abstract int fare(int distance); //추상메서드
protected final int BASIC_FARE;
private MyTransportation(String name, int basicFare) {
super(name);
BASIC_FARE = basicFare;
}
public String name() {return name;}
public String toString() {return name;}
}
public class EnumEc4 {
public static void main(String[] args) {
MyTransportation t1 = MyTransportation.BUS;
MyTransportation t2 = MyTransportation.BUS;
MyTransportation t3 = MyTransportation.TRAIN;
MyTransportation t4 = MyTransportation.SHIP;
MyTransportation t5 = MyTransportation.AIRPLANE;
System.out.printf("t1 = %s, %d%n", t1.name(), t1.ordinal());
System.out.printf("t2 = %s, %d%n", t2.name(), t2.ordinal());
System.out.printf("t3 = %s, %d%n", t3.name(), t3.ordinal());
System.out.printf("t4 = %s, %d%n", t4.name(), t4.ordinal());
System.out.printf("t5 = %s, %d%n", t5.name(), t5.ordinal());
System.out.println("t1==t2 ? "+ (t1 == t2));
System.out.println("t1.compareTo(t3) = " + t1.compareTo(t3));
}
}
Note) 실행결과
- 열거형에 추상 메서드를 추가하면 각 열거형 상수가 추상 메서드를 구현해야한다.
3. 애너테이션(annotation)
3.1 애너테이션이란?
Def) 애너테이션: 프로그램의 소스 코드안에 다른 프로그램을 위한 정보를 미리 약속된 형식으로 포함시킨 것이다.
- 주석처럼 프로그래밍 언어에 영향을 미치지 않으며 유용한 정보를 제공
3.2 표준 애너테이션
- Java에서 제공하는 애너테이션
- @Override: 오버라이딩을 바르게 했는지 컴파일러가 체크하도록 한다.
-> 오버라이딩할 때 메서드 이름을 잘못 적는 실수를 하는 경우가 많다.
-> 메서드 선언부 앞에 붙인다.
- @Deprecated: 앞으로 사용하지 않을 것을 권장하는 필드나 메서드에 붙인다.
ex) Date클래스의 getDate()
- @FunctionalInterface: 함수형 인터페이스에 붙이면 컴파일러가 올바르게 작성했는지 체크
-> 함수형 인터페이스에는 하나의 추상메서드만 가져야 한다는 제약이 있다.
- @SuppressWarnings: 컴파일러의 경고 메시지가 나타나지 않게 억제한다.
-> 괄호() 안에 억제하고자한는 경고의 종류를 문자열로 지정
-> 둘 이상의 경고를 동시에 억제하려면 괄호 안에 쭉 쓴다.
-> 'Xlint'옵션으로 컴파일하면 경고메시지를 확인할 수 있다.
- @SafeVarargs: 메서드에 선언된 가변인자 타입이 non-refiable 타입일 경우, 해당 메서드를 선언하는 부분과 호출하는 부분에서 "unchecked" 경고가 발생한다. 해당 코드에 문제가 없다면 경고를 억제시키기 위해 사용한다.
-> @SafeVarargs로 "unchecked"는 억제할 수 있지만 "varargs"경고는 억제할 수 없기 때문에
-> @SuppressWarnings("varargs")를 같이 붙인다.
3.3 메타 애너테이션
- 메타 애너테이션은 '애너테이션을 위한 애너테이션'
- 애너테이션의 적용 대상이나 유지 지간을 지정하는데 사용한다.
- @Target: 애너테이션을 정의할 때, 적용 대상 지정에 사용
-> TYPE: 타입을 선언할 때, 애너테이션을 붙일 수 있다.
-> TYPE_USE: 해당 타입의 변수를 선언할 때 붙일 수 있다.
- @Retention: 애너테이션이 유지되는 기간을 지정하는데 사용
-> 컴파일러에 의해 사용되는 애너테이션의 유지 정책은 SOURCE이다.
-> 실행 시에 사용가능한 애너테이션의 정책은 RUNTIME이다.
- @Documented: javadoc으로 작성한 문서에 포함시키려면 붙인다.
- @Inherited: 에너태이션을 자손 클래스에 상속하고자 할 때 붙인다.
-> 조상 클래스에 붙이면 자손 클래스도 붙인 것처럼 인식된다.
- @Repeatable: 반복해서 붙일 수 있는 애너테이션을 정의할 때 사용
-> @Todo를 하나로 묶을 컨테이너 애너테이션도 정의해야한다.
- @Native: 네이티브 메서드에 의해 참조되는 상수 필드에 붙이는 애너테이션이다.
3.4 애너테이션 타입 정의하기
- 애너테이션의 메서드는 추상 메서드이며 애너테이션을 적용할 때 지정한다. (순서 X)
- 에너테이션의 요소
-> 반환값이 있고 매개변수를 없는 추상메서드의 형태를 가진다.
-> 적용시 값은 지정하지 않으면, 사용될 수 있는 기본값 지정 가능(Null 제외)
-> 요소가 하나이고 이름이 value일 때, 요소의 이름 생략가능하다.
-> 요소의 타입이 배열인 경우, 괄호{}를 사용한다. (값이 하나일 때는 생략 가능, 값이 없을 때는 빈 괄호)
- 모든 애너테이션의 조상
-> Annotation은 모든 애너테이션의 조상이지만 상속은 불가하다.
-> 사실 Annotation은 인터페이스이다.
- java.lang.annotation.Annotation
- 마커 애너테이션: 요소가 하나도 정의되지 않은 애너테이션
- 애너테이션 요소의 규칙
1) 요소의 타입은 기본형, String, enum, 애너테이션, class만 허용된다.
2) 괄호 안에 매개 변수를 선언할 수 없다.
3) 예외를 선언할 수 없다.
4) 요소를 타입 매개 변수로 정의할 수 없다.
Ex) AnnoEx5
<hide/>
@Deprecated
@SuppressWarnings("1111")
@TestInfo(testedBy = "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.testedBy());
System.out.println("anno.testedDate().yymmdd() = " + anno.testDate().yymmdd());
System.out.println("anno.testedDate().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);
}
}
@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 }
Note) 실행결과
- getAnnotation(): 매개 변수로 정보를 얻는다.
- getAnnotations(): 모든 애너테이션을 배열로 받아온다
'Java > Java의 정석' 카테고리의 다른 글
Chapter 15 입출력 I/O (0) | 2022.03.07 |
---|---|
Chapter 14 람다와 스트림 (0) | 2022.03.04 |
Chapter 11 컬렉션 프레임웍(Collection Framework) (0) | 2022.03.02 |
Chapter 10 날짜와 시간 & 형식화 date, time and formatting (0) | 2022.02.28 |
Chapter 09 java.lang 패키지와 유용한 클래스 (0) | 2022.02.24 |