지네릭스
: 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에
컴파일 시의 타입 체크 (Compile-time type check) 기능이다.
타입 안정성 제공, 타입체크와 형변환을 생략할수 있어 코드가 간결해지는 장점이 있다.
public class Box<T> {
T item;
void setItem(T item) {
this.item = item;
}
T getItem(){
return item;
}
}
객체 생성 시 타입 변수 T에 따로 타입을 지정한 상태에서 지네릭 클래스를 사용하여야 한다.
Box<Object> b = new Box<Object>();
b.setItem("ABC");
b.setItem(new Object());
지네릭스의 제한된 역할로 Box를 생성할 경우, 모든 객체에 대해 동일하게 동작하는 static 멤버의 경우 타입 변수를 사용할 수 없다.
또한 Generic 타입의 배열을 선언할 수 있는 것과는 별개로 new 연산자를 사용해 T 변수 타입의 배열을 생성하는 것은 불가능하다.
class Box<T>{
T[] itemArr;
T[] toArray{
T[] tempArr = new T[itemArr.length];
return tmpArr;
}
}
T라는 타입 변수는 컴파일 시점에서 어떤 타입이 될지 전혀 알 수 없다.
상속을 사용해서 지정될 수 있는 지네릭 클래스를 제한할 수 있다.
class FruitBox<T extends Fruit>{
ArrayList<T> list = new ArrayList<T>; // Fruit 타입의 자손으로만 지정 가능
}
와일드 카드
지네릭 타입이 다른 것만으로 오버로딩이 성립되지 않는다.
따라서 매개변수에 아래와 같이 지네릭 타입을 갖는 요소가 들어온 경우,
class Jucier{
static Juice makeJuice(FruitBox<Fruit> box){
String tmp = '';
//Code......
}
}
static 함수에 Fruit가 아닌 다른 클래스 타입의 매개변수가 들어온 경우
다른 클래스의 타입의 매개변수를 가지고 있는 함수를 계속ㅁㅆ해서 생성해주어야 하는 문제가 있다.
이 경우 와일드 카드 기능을 이용할 수 있는데,
static Juice makeJuice(FruitBox<? extends Fruit> box){
// ... Code
}
Fruit의 자손 클래스들을 모두 데려와 함수의 매개변수로 사용할 수 있게 되었다.
? extends T 를 이용해 T의 자손만 사용할 수 있도록 제한,
? super T 를 이용해 T와 그 조상만 사용할 수 있도록 제한,
? 를 이용해 제한 없이 모든 타입이 사용가능하도록 할 수 있다.
지네릭 메서드
메서드의 선언부에 지네릭 타입이 선언된 메서드
class FruitBox<T> { // 지네릭 클래스
static<T> void sort(List<T> list, Comparator<? super T> c) // 지네릭 메서드
}
클래스에 정의된 타입 매개변수와 지네릭 메서드에 정의된 타입 매개변수는 전혀 다르다.
메서드에 선언된 지네릭 타입은 지역 변수를 선언한 것과 같다.
public static <T extends Comparable<T>> void sort(List<T> list);
여기서는 List<T>의 요소가 Comparable의 인터페이스를 구현한 것이어야 한다는 의미이다.
public static <T extends Comparale<? super T>> void sort(List<T> list);
위 메서드 선언부는 아래와 같이 해석할 수 있다.
타입 T를 요소로 하는 리스트를 매개변수로 받고,
타입 T는 Comparable을 구현한 클래스이며 T 또는 그 조상의 타입을 비교하는 Comparable 구현체여야 한다.
지네릭 타입의 형변환
Box<Object> objBox = new Box<String>(); // ERROR
대입된 타입이 다른 지네릭 타입 간에는 형변환이 불가능하다.
Box<? extends Object> wBox = new Box<String>();
Box<String> 타입은 Box<? extends Object>로 형변환이 가능하다.
즉 Object라는 클래스는 String의 조상 타입이므로 Box<? extends Object>로 형변환이 가능한 것이다.
열거형
서로 관련된 상수를 편리하게 선언하기 위해 사용하는 것으로,
아래와 같이 정의하여 사용한다.
class Card{
enum Kind { CLOVER, HEART, DIAMOND, SPADE }
enum Value { TWO, THREE, FOUR }
final Kind kind; // Kind 타입의 상수 변수
final Value value; // Value 타입의 상수 변수
}
CLOVER은 0, HEART는 1과 같이 순서대로 값이 부여되어,
상수명으로 상수를 사용할 수 있게 된다.
자바의 열거형은 TypesafeEnum으로, 실제 값이 같아도 타입이 다르면 컴파일 에러가 발생한다.
java.lang.Enum 클래스는 모든 열거형의 조상이다.
메서드를 정리해보면 다음과 같다.
- getDeclaringClass() : 열거형의 Class객체를 반환한다.
- name() : 열거형 상수의 이름을 문자열로 반환한다.
- ordinal() : 열거형 상수가 정의된 순서를 반환한다.
- valueOf(Class<T> enumType, String name) : 지정된 열거형에서 name과 일치하는 열거형 상수를 반환한다.
열거형의 내부 구현
enum Direction { EAST, SOUTH, WEST, NORTH }
여기서 열거형 하나하나가 Direction의 객체이다.
class Direction {
static final Direction EAST = new Direction("EAST");
static final Direction SOUTH = new Direction("SOUTH");
static final Direction WEST = new Direction("WEST");
static final Direction NORTH = new Direction("NORTH");
private String name;
private Direction(String name){
this.name = name;
}
}
각 static 상수의 값은 객체의 주소이고, 바뀌지 않는 값이므로 '==' 비교가 가능하다.
Enum 클래스 안에 추상 메서드를 추가하는 경우 클래스 앞에도 abstarct를 붙여줘야 하고,
아래처럼 각 static 상수도 추상 메서드를 구현해주어야 한다.
abstract class Direction {
static final Direction EAST = new Direction("EAST"){
Point move(Point p){
// 동쪽으로 이동하는 코드
}
}
static final Direction SOUTH = new Direction("SOUTH"){
Point move(Point p){
// 남쪽으로 이동하는 코드
}
}
static final Direction WEST = new Direction("WEST"){
Point move(Point p){
// 서쪽으로 이동하는 코드
}
}
static final Direction NORTH = new Direction("NORTH"){
Point move(Point p){
// 북쪽으로 이동하는 코드
}
}
private String name;
private Direction(String name){
this.name = name;
}
abstract Point move(Point p);
}
이렇게 객체마다 수행해야하는 동작이 다른 경우 abstract함수를 서로 다르게 구현하여 다르게 동작하도록 할 수 있다.
애너테이션(annotation)
프로그램의 소스코드 안에 다른 프로그램을 위한 정보를 미리 약속된 형식으로 포함시킨 것
주석과 비슷한 기능을 하며 프로그램에게 코드에 대한 정보를 제공해준다.
@Override
조상 클래스의 함수를 오버라이딩할 때 오버라이딩 함수의 상단에 사용한다.
@Deprecated
기존의 기능을 대체한 새로운 기능이 나왔을 때, 기존의 것을 더이상 사용하지 않는다는 의미로 사용한다.
class NewClass{
int newField;
int getNewField(){
return newField;
}
@Deprecated
int oldField;
@Deprecated
int getOldField(){
return oldField;
}
인텔리제이에서 자동으로 Deprecated된 함수나 멤버에 표시를 해주는 것을 확인하였다.
@FunctionalInterface
함수형 인터페이스를 선언할 때, 컴파일러가 함수형 인터페이스를 올바르게 선언했는지 확인한다.
@FunctionalInterface
public interface Runnable{
public abstract void run();
}
@SuppressWarnings
컴파일러가 보여주는 경고메세지가 나타나지 않게 억제한다.
의도치않게 경고 문구가 나올 수 밖에 없게끔 프로그램을 설계했을 경우 관련 경고가 발생하지 않도록 해준다.
@SafeVarargs
메서드의 가변인지의 타입이 비구체화 타입, 즉 지네릭 타입변수 등의 상황에서 타입이 구체화되지 안항ㅆ을 때
해당 메서드를 선언하는 부분과 호출하는 부분에서 unchecked 오류가 발생하게 된다.
이때 코드에 문제가 없다면 경고를 억제해주는 역할을 한다.
메타 애너테이션
애너테이션을 위한 애너테이션,
애너테이션을 정의할 때 적용 대상이나 유지 기간등을 지정할 때 사용한다.
@Target
애너테이션이 적용 가능한 대상을 지정하는 데 사용된다.
애너테이션 적용 대상의 종류
@Retention
애너테이션이 유지(retention)되는 기간을 의미한다.
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {
}
유지 정책을 RUNTIME으로 하여 실행 시에 reflection을 통해 클래스 파일에 저장된 애너테이션의 정보를 읽어서 처리.
@Documented
애너테이션에 대한 정보가 javadoc으로 작성한 문서에 포함되도록 한다.
@Inhertied
애너테이션이 자손 클래스에 상속되도록 한다.
@Repeatable
이 애너테이션이 붙은 애너테이션은 여러 번 반복해서 사용할 수 있다.
@Native
네이티브 메서드에 의해 참조되는 상수 필드에 붙이는 애너테이션이다.
네이티브 메서드는 JVM이 설치된 OS의 메서드인데, Object클래스들의 메서드들이 대표적인 예시이다.
'Langauge > Java' 카테고리의 다른 글
자바의 정석 Chapter13. 쓰레드(Thread) (0) | 2022.05.03 |
---|---|
Java의 정석 Chapter10. 날짜와 시간 & 형식화 / Chapter11. 컬렉션 프레임워크 (0) | 2022.03.29 |
Java의 정석 Chapter9. java.lang 패키지와 유용한 클래스 (0) | 2022.03.21 |
Java의 정석 Chapter8. 예외처리(Exception Handling) (0) | 2022.03.21 |
Java InputStream에서 File 객체로 변환하기 (0) | 2022.03.21 |