Java의 특색이라고 할 수 있는 제네릭, enum, 에노테이션에 대한 핵심을 정리해본다.
단기간 자바 정복 이라는 시리즈로 서술하고 있지만, Java는 절대 단기간에 정복할 수 없는 언어라는것이 느껴진다..
제네릭은 Java에서 소스코드 컴파일 시 타입을 체크하는 기능이다. 주로 여러가지 타입을 다루는 클래스나 메소드에 적용하여 사용하게 된다. 객체의 타입을 컴파일 시에 체크하게 되기 때문에 객체의 타입 안정성을 높일 수 있고, 형변환의 번거로움도 줄일 수 있다. 예를 들자면, 특정 collection에 들어갈 수 있는 객체의 타입을 지정하거나, 타입 파라미터 내부의 클래스 상속 표기를 통해 객체 타입 범위를 지정할 수도 있다.
제네릭을 사용하는 문법에 대해 간략하게 알아보자.
public class testClass{
public static voic main(String arg[]){
Box<Fruit> FruitBox = new Box<Fruit>();
Box<Grape> GrapeBox = new Box<Grape>();
Box<Apple> AppleBox = new Box<Grape>(); // 에러. 참조변수의 타입과 객체 생성시의 타입이 일치해야함
FruitBox.add(new Fruit());
GrapeBox.add(new Grape());
GrapeBox.add(new Apple()); // 에러. 해당 메소드의 파라미터는 클래스 타입 파라미터에서 지정된 타입만 받을 수 있다.
}
}
class Box<T>{
ArrayList<T> list = new ArrayList<T>();
void add(T item) { this.list.add(item);}
...(이하 생략)...
}
class Fruit {...}
class Apple extends Fruit{...}
class Grape extends Fruit{...}
이처럼, 제네릭 클래스를 통해 해당 클래스가 어떤 타입의 객체를 받아서 사용하게 되는지 정의할 수 있다. Box 클래스의 add 메소드에 나타나듯이, 제네릭 클래스의 지정 타입을 파라미터로 받게 되므로, GrapeBox의 add 메소드는 apple 객체를 파라미터로 받을 수 없게된다.
제네릭 타입 파라미터에 extends 키워드를 추가한다면 그 특정 타입의 자손들만 대입할 수 있게된다.
public class testClass{
public static voic main(String arg[]){
FruitBox<Apple> AppleBox = new FruitBox<Apple>();
FruitBox<Grape> GrapeBox = new FruitBox<Grape>();
FruitBox<Carrot> CarrotBox = new FruitBox<Carrot>(); // 에러. Carrot은 Fruit을 상속받지 않는다.
}
}
class FruitBox<T extends Fruit>{
ArrayList<T> list = new ArrayList<T>();
void add(T item) { this.list.add(item);}
...(이하 생략)...
}
class Fruit {...}
class Apple extends Fruit{...}
class Grape extends Fruit{...}
class Carrot{...}
제네릭 타입은 static 멤버의 선언에는 사용할 수 없으며, 제네릭 타입 배열의 생성도 허용되지 않는다. (특정 타입 배열을 저장하기 위한 제네릭 타입 배열 형의 참조변수 선언은 허용된다.)
와일드카드는 제네릭 타입에 대입하여 여러가지 타입이 올 수 있음을 나타내는 기호이다. 와일드카드를 사용하는 방식은 세가지가 있는데, 각각 다음과 같다.
<?>
: 모든 타입이 올 수 있다.<? extends T>
: T와 그 자손들만 대입 가능하다.<? super T>
: T와 그 조상들만 대입 가능하다.와일드카드의 사용 예시는 다음과 같다.
static Juice JuiceMaker(FruitBox<? extends Fruit> Box){
String FruitType = Box.getType();
return new Juice(FruitType)
}
위의 메소드는 Fruit타입이나 Fruit을 상속하는 타입의 Box가 파라미터로 들어갈 수 있다.
제네릭 메소드는 메소드 선언부의 리턴타입 앞에 제네릭 타입이 선언된 메소드이다. 이 때, 제네릭 메소드의 타입과(리턴 타입이 아닌 타입 파라미터) 제네릭 클래스의 타입 파라미터는 별개라는 사실을 주의하라.
제네릭 메소드를 쓰는 형식은 다음과 같다.
class Fruit<T>{
static <T> void sort(List<T> list, Comparator<? extends Fruit> c){...}
}
위와 같은 식으로 함수의 리턴타입 앞에 제네릭 타입을 선언하며, 제네릭 메소드에서 쓰이는 제네릭 타입은 모두 함수의 선언부에 쓰여진 타입 파라미터이다. (클래스의 타입 파리미터와 무관하다는 사실을 다시 한번 강조한다.)
만약 Juicer 라는 클래스 내부에 makeJuice라는 제네릭 메소드가 있을 경우, 그 메소드의 호출 방법은 다음과 같다. (이를 호출할 때는 메소드의 제네릭 타입을 직접 지정해주어야 한다. 단, 컴파일러가 대부분의 경우 타입을 추정하므로, 표기에 생략이 가능하다.)
System.out.println(Juicer.<Fruit>makeJuice(fruitBox));
System.out.println(Juicer.makeJuice(grapeBox)); // 이런식으로, 타입 추론이 가능할 경우 표기를 생략할 수 있다.
열거형이란 관련된 상수들을 묶어놓은 것을 의미하며, Java는 타입에 열거형을 제공해준다.
이를 사용하는 기본적인 방법은 다음과 같다.
class Card{
enum Kind {C, H, S, D}
enum Value { two, three, four} // enum 타입을 선언한다.
final Kind kind;
final Value value;
Card(){
kind = Kind.C; // enum의 value를 직접 지정하여 사용한다.
value = Value.two;
}
}
열거형에는 위처럼 기본값에 ()를 붙여서 특정한 값을 할당할 수 있다. 이 때, 인스턴스 변수와 생성자를 추가해줘야 한다. 구체적인 사용방법은 다음과 같다.
enum Direction {
EAST(1), SOUTH(5), WEST(-1), NORTH(10);
private final int value;
Direction(int v){this.value = v;}
public int getValue(){return value;}
}
이 때, 열거형의 생성자는 묵시적으로 private이므로 외부에서 호출할 수 없다.
열거형 데이터에 대해서는 compareTo를 사용할 수 있다.
어노테이션이란, Java에서 주석처럼 실제 프로그래밍 언어에는 영향을 미치지 않지만, 유용한 정보를 제공하는 일종의 인터페이스 문법이다.
어노테이션에는 다음의 3가지 종류가 있다.
어노테이션의 사용방법은 기본적으로 다음과 같다.
class ParentClass{ void testMethod(){}}
class ChildClass extends ParentClass{
@Override // 이것이 어노테이션이다.
void testMethod(){} // 컴파일러가 오버라이드 메소드를 체크한다.
}
Java가 기본적으로 제공하는 빌트인 어노테이션은 대표적으로 다음과 같은것들이 있다.
대표적인 메타 어노테이션은 다음과 같은 것들이 있다.
사용자가 직접 어노테이션을 만들어서 사용하는것도 가능하다. 어노테이션을 정의하기 위한 기본적인 문법은 다음과 같다.
@interface TestInfo{
int count();
String testedBy();
STring[] testTools();
TestType testType(); // enum TestType{FIRST, FINAL}
DataeTime testDate(); // 자신이 아닌 어노테이션을 포함할 수 있다.
}
interface DateTime{
String yymmdd();
String hhmmss();
}
커스텀 어노테이션에서 메소드는 모두 추상메소드이며, 어노테이션을 적용할 때 모두 지정해야한다. 다만, 지정하는 순서는 상관없다.
만약 어노테이션 요소 타입이 배열인 경우, 괄호 {}를 사용하여 지정해주어야 한다.
어노테이션을 선언할 때는 다음과 같은 규칙을 반드시 지켜야 한다.
https://dundung.tistory.com/274
https://devlog-wjdrbs96.tistory.com/201
https://st-lab.tistory.com/243
https://github.com/ksundong/backend-interview-question#%EC%96%B8%EC%96%B4-%EA%B4%80%EB%A0%A8
https://github.com/JaeYeopHan/Interview_Question_for_Beginner/tree/master/Java#jvm-%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C-gc-%EC%9D%98-%EC%9B%90%EB%A6%AC
https://thecodinglog.github.io/java/2020/12/15/java-generic-wildcard.html