
컴파일시 타입을 체크해 주는 기능 (compile-time type-check)
Array<TV> tvList = new ArrayList<TV>(); //<객체이름> - 제네릭스
tvList.add(new TV()); //ok
tvList.add(new Audio()); //에러!! TV객체만 허용
//제너릭스가 없다면 컴파일 에러가 나지 않는다!
//런타임에러(형변환 예외) 문제를 컴파일 에러로 끌고오는 효과
- 객체의 타입 안정성을 높이고 형변환의 번거로움 줄여줌
- 타입체크와 형변환을 생략가능. - 코드가 간결해짐
클래스를 작성할 때, Object타입 대신 타입 변수(E)를 선언
public class ArrayList extends AbstractList {
private transient Object[] elementData;
public boolean add(Object o) {}
public Object get(int index) {}
}
// Object를 E로 변경!
public class ArrayList<E> extends AbstractList {
private transient E[] elementData;
public boolean add(E o) {}
public E get(int index) {}
}
객체 생성시, 타입변수(E)대신 실제 타입 지정
//E가 실제타입으로 변경됨 E → Integer
ArrayList<Integer> aList = new ArrayList<Integer>();
public class ArrayList<Integer> extends AbstractList {
private transient Integer[] elementData;
public boolean add(Integer o) {}
public Integer get(int index) {}
}
// E → String
ArrayList<String> sList = new ArrayListd<String>();
public class ArrayList<String> extends AbstractList {
private transient String[] elementData;
public boolean add(String o) {}
public String get(int index) {}
}
타입 변수 대신 실제타입이 지정되면, 형변환 생략가능
ArrayList<Integer> aList = new ArrayList<Integer>();
aList.add(10);
Integer i = aList.get(0); // (Integer)aList.get(0); 형변환생략됨
- Box<T> : 제너릭 클래스. "T의 Box" 혹은 "T Box"라고 읽음
- T : 타입 변수 또는 타입 매개변수. (T는 타입문자)
- Box : 원시 타입(raw type)
class Box<T> {} // 제너릭 클래스.
Box<String> b = new Box<String>(); //제너릭 타입 호출.
참조 변수와 생성자의 대입된 타입은 일치해야 한다.
class Product {}
class TV extends Product {}
ArrayList<TV> list = new ArrayList<TV>(); // ok
ArrayList<Product> list = new ArryList<TV>(); //에러!! 완전히 일치해야 한다
제너릭 클래스간의 다형성은 성립. (타입이 일치할 때만)
List<TV> list = new ArrayList<TV>(); // ok
List<TV> list = new LinkedList<TV>(); // ok
매개변수의 다형성도 성립
ArrayList<Product> list = new ArrayList<Product>();
list.add(new Product());
list.add(new TV());
Product p = list.get(1); //Product get(int index)이므로 자손도 받을수 있다
클래스를 작성할 때, Object타입 대신 T와 같은 타입 변수 사용
public interface Iterator<E> {
boolean hasNext();
E next();
void remove();
}
Iterator<Student> it = list.iterator();
while(it.hasNext()){
Student s = it.next(); //형변환 생략
}
여러 개의 타입 변수가 필요한 경우, 콤마(,)를 구분자로 선언
public class HashMap<K,V> extends AbstractMap<K,V> {
...
public V get(Object key) {}
public V put (K key, V value) {}
public V remove(Object key) {}
...
}
HashMap<String, Student> map = new HashMap<String, Student>(); //객체 생성
//다음과 같이 클래스의 타입 변경
public class HashMap<String,Student> extends AbstractMap<String,Student> {
...
public Student get(Object key) {}
public Student put (String key, Student value) {}
public Student remove(Object key) {}
...
}
Student s1 = map.get("1-1"); //마찬가지로 형변환 생략 가능
extends로 대입할 수 있는 타입을 제한
class FruitBox<T extends Fruit> { //Fruit와 그의 자손들만 가능
ArrayList<T> list = new ArrayList<T>();
}
FruitBox<Apple> aBox = new FruitBox<Apple>(); //ok
FruitBox<Toy> tBox = new FruitBox<Toy>(); //에러!! Toy는 Fruit의 자손 X
인터페이스인 경우에도 extends 사용
interface Eatable {}
class FruitBox<T extends Eatable> { ... } //implements가 아닌 extends!
타입 변수에 대입은 인스턴스 별로 다르게 가능
Box<Apple> appleBox = new Box<Apple>(); //Apple객체만 저장
Box<Grape> grapeBox = new Box<Grape>(); //Grape객체만 저장
static멤버에 타입 변수 사용 불가
class Box<T> {
static T item; //에러!!
static int compare(T t1, T t2) {} //에러!!
}
배열을 생성할 때 타입 변수 사용 불가. 타입 변수로 배열 선언은 가능
class Box<T> {
T[] itemArr;
T[] toArray() {
T[] tmpArr = new T(itemArr.length); //에러!! new T는 사용불가
}
}
<?>하나의 참조 변수로 대입된 타입이 다른 객체를 참조 가능
<? extends T>: 와일드 카드의 상한 제한. T와 그 자손들만 가능<? super T>: 와일드 카드의 하한 제한. T와 그 조상들만 가능.<?>: 제한 없음. 모든 타입이 가능.<? extends Object>와 동일
ArrayList<? extends Product> list = new ArrayList<TV>(); //ok
ArrayList<? extends Product> list = new ArrayList<Audio>(); //ok
ArrayList<Product> list = new ArrayList<TV>(); //에러!! 타입불일치
메소드의 매개변수에 와일드 카드를 사용가능
static Juice makeJuice(FruitBox<? extends Fruit> box) {
String tmp = "";
for(Fruit f : box.getList()) tmp += f + " ";
return new Juice(tmp);
}
System.out.println(makeJuice(new FruitBox<Fruit>));
System.out.println(makeJuice(new FruitBox<Apple>));
제너릭 타입이 선언된 메소드 (메소드 내에서만 유효)
static <T> void sort(List<T> list, Comparator<? super T> c)
클래스의 타입 매개변수 <T>와 메소드의 타입 매개변수 <T>는 별개
class FruitBox<T> { //제너릭 클래스
...
static <T> void sort(List<T> list, Comparator<? super T> c)
}
// FruitBox의 <T>와 sort의 매개변수 List<T>의 <T>는 서로 별개이다!
메소드를 호출할 때마다 타입을 대입 (대부분 생략 가능)
- 타입을 생략하지 않을 때는 클래스이름 생략 불가
static 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> appleBox = new FruitBox<Apple>();
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)); //ok
System.out.println(Juicer.<Fruit>makeJuice(fruitBox)); //ok
- 와일드 카드 : 하나의 참조변수로 서로 다른 타입이 대입된 여러 제너릭 객체를 다루기 위한 것
- 제너릭 메소드 : 메소드를 호출할 때마다 다른 제너릭 타입을 대입할 수 있게 한 것
제너릭 타입과 원시타입 간의 형변환은 바람직 하지 않다. (경고 발생)
Box<Object> objBox = null;
Box box = (Box)objBox; //ok...경고발생
objBox = (Box<Object>)box; //ok...경고발생
와일드 카드가 사용된 제너릭 타입으로는 형변환 가능
Box<Object> objBox = (Box<Object>)new Box<String>(); //에러!! 형변환불가능
//Box<? extends Object> wBox = (Box<? extends Object>)new Box<String>();
Box<? extends Object> wBox = new Box<String>();//ok
컴파일러는 제너릭 타입을 제거하고, 필요한 곳에 형변환
① 제너릭타입의 경계를 제거
class Box<? extends T> {
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); } //Fruit로 형변환
③ 와일드카드는 적절하게 형변환
static Juice makeJuice(FruitBox<? extends Fruit> box) {
String tmp = "";
for(Fruit f : box.getList()) tmp += f + " ";
return new Juice(tmp);
}
//와일드 카드 형변환
static Juice makeJuice(FruitBox box) {
String tmp = "";
Iterator it = box.getList().iterator();
while(it.hasNext()){
tmp += (Fruit)it.next() + " "; //Fruit로 형변환
}
return new Juice(tmp);
}
관련된 상수들을 같이 묶어놓은 것
enum 열거형이름 {상수명1, 상수명2, 상수명3, ...}
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;
}
//열거형 사용
class Card {
enum Kind {CLOVER, HEART, DIAMOND, SPADE}
enum Value {TWO, THREE, FOUR}
}
모든 열거형은 Enum의 자손. 따라서 메소드를 상속받음
| 메소드 | 설 명 |
|---|---|
| Class<E> getDeclaringClass() | 열거형의 Class객체 반환 |
| String name() | 열거형 상수의 이름을 문자열로 반환 |
| int ordinal() | 열거형 상수가 정의된 순서를 반환(0부터 시작) |
| T valueOf(Class<T> enumType, String name) | 지정된 열거형에서 name과 일치하는 열거형 상수 반환 |
⁕ vaules(), valueOf()는 컴파일러가 자동으로 추가
열거형 상수의 비교에 ==와 compareTo()사용가능 (객체 취급)
if(dir==Diretion.EAST) { // ok
x++;
} else if(dir > Direction.WEST) { //에러!! 열거형에 비교연산자 사용불가
...
} else if(dir.compareTo(Direction.WEST) > 0) { // ok
...
}
불연속적인 열거형 상수의 겅우, 원하는 값을 괄호()안에 적는다,
enum Direction {EAST(1), WEST(-1), SOUTH(5), NORTH(10)}
//여러개의 값을 가질 수 있다.
enum Direction {EAST(1, "→"), WEST(-1, "←"), SOUTH(5, "↑"), NORTH(10, "↓")}
괄호()를 사용하려면 변수에 생성자를 새로 추가해 줘야 한다.
enum Direction {
EAST(1), WEST(-1), SOUTH(5), NORTH(10); //끝에 ';' 추가
private final int value; // 정수를 저장할 필드
Direction(int value) {this.value = value;} //생성자 추가
public int getValue() {return value;}
}
열거형의 생성자는 묵시적으로 private이므로, 외부에서 생성불가
Direction d = new Direction(1); //에러. 생성자 외부에서 호출불가
주석처럼 프로그래밍 언어에 영향을 미치지 않으며, 유용한 정보 제공
@Test // 이 메소드가 테스트 대상임을 테스트 프로그램에게 알림.
public void method() {
...
}
Annotation은 모든 애노테이션의 조상이지만 상속 불가 (인터페이스)
public interface Annotation { //Annotation은 인터페이스이다.
//추상메소드이지만, 구현하지 않아도 사용가능
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType(); //애노테이션의 타입 변환
}
Java에서 제공하는 애노테이션
| 에노테이션 | 설명 |
|---|---|
| @Override | 컴파일러에게 오버라이딩 메소드라는 것을 알림 |
| @Deprecated | 앞으로 사용하지 않을 것을 권장하는 대상에 붙임 |
| @SuppressWarnings | 컴파일러의 특정 경고메세지를 억제한다 |
| @SafeVarargs | 제너릭 타입의 가변인자에 사용(JDK1.7) |
| @FunctionalInterface | 함수형 인터페이스라는 것을 알림(JDK1.8) |
| @Native | native메소드에서 참조되는 상수 앞에 붙임(JDK1.8) |
| @Target* | 애노테이션이 적용가능한 대상을 지정하는데 사용 |
| @Documented* | 애노테이션 정보가 javadoc으로 작성된 문서에 포함되도록 함 |
| @inherited* | 애노테이션이 자손클래스에 상속되도록 함 |
| @Retention* | 애노테이션이 유지되는 범위 지정에 사용 |
| @Repeatable* | 애노테이션을 반복해서 적용가능 |
* 가 붙은 것은 메타 에노테이션
오버라이딩을 올바르게 했는지 컴파일러가 체크하게 한다.
class Parent{
void method() {}
}
class Child {
@Override //애노테이션
void Method() {} //에러!! 오버라이딩이 제대로 되지않음
}
앞으로 사용하지 않을 것을 권장하는 필드나 메소드에 붙임
@Deprecated
public int getDate() {
return normalizer().getDayOfMonth();
}
컴파일시 경고가 뜸 (도스창에서 컴파일시 확인가능)
Note: XXX(자바 파일이름).java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
함수형 인터페이스에 붙이면, 컴파일러가 올바르게 작성했는지 체크
- 함수형 인터페이스에는 하나의 추상메소드를 가져야 함
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
@FunctionalInterface
public interface Runnable {
//에러!! 추상메소드가 없음
}
@FunctionalInterface
public interface Runnable {
public abstract void run();
public abstract void stop();//에러!! 하나의 추상메소드만 허용
}
컴파일러의 경고메세지가 나타나지 않게 해줌
- 괄호()안에 억제하고자하는 경고의 종류를 문자열로 지정
@SuppressWarnings ("unchecked") //unchecked 경고를 억제
ArrayList list = new ArrayList(); //제너릭 타입을 지정하지않음
list.add(obj); //경고발생 - 억제됨
@SuppressWarnings ("deprecation") // deprecation 경고를 억제
@Deprecated
public int getDate() {
return normalizer().getDayOfMonth();
}
둘 이상의 경고를 억제시 중괄호{} 사용
@SuppressWarnings ({"deprecation", "unchecked", "varargs"})
애노테이션을 만들 때 사용
| 애노테이션 | 설명 |
|---|---|
| @Target | 애노테이션이 적용가능한 대상을 지정하는데 사용 |
| @Documented | 애노테이션 정보가 javadoc으로 작성된 문서에 포함되도록 함 |
| @inherited | 애노테이션이 자손클래스에 상속되도록 함 |
| @Retention | 애노테이션이 유지되는 범위 지정에 사용 |
| @Repeatable | 애노테이션을 반복해서 적용가능 |
* 가 붙은 것은 메타 에노테이션
애노테이션을 정의할 때, 적용대상 지정에 사용
@Target ({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTION, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value;
}
| 대상 타입 | 의미 |
|---|---|
| ANNOTATION_TYPE | 애노테이션 |
| CONSTRUCTOR | 생성자 |
| FIELD | 필드(멤버변수, enum상수) |
| LOCAL_VARIABLE | 지역변수 |
| METHOD | 메소드 |
| PACKAGE | 패키지 |
| PARAMETER | 매개변수 |
| TYPE | 타입(클래스, 인터페이스, enum) |
| TYPE_PARAMETER | 타입 매개변수 (JDK1.8) |
| TYPE_USE | 타입이 사용되는 모든 곳(JDK1.8) |
애노테이션이 유지(retention)되는 기간을 지정하는데 사용
| 유지 정책 | 의미 |
|---|---|
| SOURCE | 소스 파일에만 존재. 클래스파일에는 존재하지 않음 |
| CLASS | 클래스 파일에 존재. 실행시에 사용불가. 기본값 |
| RUNTIME | 클래스 파일에 존재. 실행시에 사용가능. |
@Target ({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTION, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)//소스파일에만 존재
public @interface SuppressWarnings {
String[] value;
}
javadoc으로 작성한 문서에 포함시킴
@Documented // javadoc 문서에 포함!!
@Retention(RetentionPolicy.RUNTIME) // 실행시 사용가능
@Target(ElementType.TYPE)
public @interface MyAnnotation {} // 애노테이션 정의
애노테이션을 자손 클래스에 상속하고자 할 때 사용
@Inherited //애노테이션 상속을 위해 사용
@interface SuperAnno {}
@SuperAnno
class Parents{} //부모클래스에 @SuperAnno붙임
class Child extends Parents {} //@SuperAnno가 붙은것으로 인식
반복해서 붙일 수 있는 애노테이션 정의시 사용
@Repeatable(ToDos.class) // ToDo애노테이션을 여러번 반복해서 쓸 수 있게 함
@interface ToDo {
String[] value;
}
@Repeatable이 붙은 애노테이션은 반복해서 붙일 수 있음.
@ToDo("delete test codes.")
@ToDo("override inherited method")
class MyClass {}
@Repeatable인 애노테이션을 하나로 묶을 컨테이너 애노테이션도 정의해야함
@interface ToDos {
ToDo[] value(); // 이름이 반드시 value여야 함
}
애노테이션을 직접 정의할 수 있다.
@interface 애노테이션이름 {
타입 요소이름(); //애노테이션의 요소를 선언
...
}
public @interface MyAnnotation{ //MyAnnotation 애노테이션 정의
...
}
애노테이션의 메소드는 추상 메소드이며, 애노테이션을 적용할 때 지정
@interface TestInfo{
int count();
String testedBy();
String[] testTools(); //배열
TestType testType(); //enum
DateTime testDate(); // 애노테이션도 포함 가능
}
애노테이션 적용시 값을 지정하지 않으면 사용되는 기본값 지정 가능
- null값은 사용X
@interface TestInfo {
int count() default 1; //기본값을 1로 지정
}
@TestInfo //@TestInfo(count=1)과 동일
public class NewClass { ... }
⁕ 요소가 하나이고 이름이 value일 때는 요소의 이름 생략가능
@interface TestInfo{
String value();
}
@TestInfo("passed") //@TestInfo(value="passed")와 동일
class NewClass { ... }
요소의 타입이 배열인 경우 중괄호{}를 사용해야한다
@interface TestInfo {
String[] testTools();
}
//다음과 같이 선언
@TestInfo(testTools = {"Junit", "AutoTester"})
@TestInfo(testTools="Junit") //하나일때는 괄호 생략 가능
@TestInfo(testTools={}) //값이 없을 때는 반드시 {} 사용
- 요소의 타입은 기본형, String, enum, 애노테이션, Class만 허용됨
- 괄호()안에 매개변수를 선언할 수 없다.
- 예외를 선언할 수 없다.
- 요소를 타입 매개변수로 정의할 수 없다.
//에러코드
@interface AnnoTest {
int id = 100; //ok 기본형 타입 사용
String major(int i, int j); //괄호 안 매개변수 선언 X
String minor() throws Exception; //예외 선언 X
ArrayList<T> list(); //타입 매개변수 정의 X
}
요소가 하나도 정의되지 않은 애노테이션
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {} //마커 애노테이션. 정의된 요소가 없음.
@Override //이 메소드가 오버라이딩메소드라는 것을 알림
public int Method() {}