제네릭, enum, 에노테이션

박기범·2022년 1월 7일
0

단기간 Java 정복

목록 보기
3/3

Java의 특색이라고 할 수 있는 제네릭, enum, 에노테이션에 대한 핵심을 정리해본다.
단기간 자바 정복 이라는 시리즈로 서술하고 있지만, Java는 절대 단기간에 정복할 수 없는 언어라는것이 느껴진다..

Generic

1. Generic class

제네릭은 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 멤버의 선언에는 사용할 수 없으며, 제네릭 타입 배열의 생성도 허용되지 않는다. (특정 타입 배열을 저장하기 위한 제네릭 타입 배열 형의 참조변수 선언은 허용된다.)

2. wild card

와일드카드는 제네릭 타입에 대입하여 여러가지 타입이 올 수 있음을 나타내는 기호이다. 와일드카드를 사용하는 방식은 세가지가 있는데, 각각 다음과 같다.

  • <?> : 모든 타입이 올 수 있다.
  • <? extends T> : T와 그 자손들만 대입 가능하다.
  • <? super T> : T와 그 조상들만 대입 가능하다.

와일드카드의 사용 예시는 다음과 같다.

static Juice JuiceMaker(FruitBox<? extends Fruit> Box){
	String FruitType = Box.getType();
	return new Juice(FruitType)
}

위의 메소드는 Fruit타입이나 Fruit을 상속하는 타입의 Box가 파라미터로 들어갈 수 있다.

3. Generic method

제네릭 메소드는 메소드 선언부의 리턴타입 앞에 제네릭 타입이 선언된 메소드이다. 이 때, 제네릭 메소드의 타입과(리턴 타입이 아닌 타입 파라미터) 제네릭 클래스의 타입 파라미터는 별개라는 사실을 주의하라.

제네릭 메소드를 쓰는 형식은 다음과 같다.

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)); // 이런식으로, 타입 추론이 가능할 경우 표기를 생략할 수 있다.


Enum

열거형이란 관련된 상수들을 묶어놓은 것을 의미하며, 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를 사용할 수 있다.

Annotation

어노테이션이란, Java에서 주석처럼 실제 프로그래밍 언어에는 영향을 미치지 않지만, 유용한 정보를 제공하는 일종의 인터페이스 문법이다.

어노테이션에는 다음의 3가지 종류가 있다.

  • built-in annotation : 자바 SDK에 포함되어 제공되는 기본적인 어노테이션이다.
  • meta annotation : 어노테이션을 위한 어노테이션이다. 특정 어노테이션이 적용되는 시점이나 대상 등을 정의할 수 있다.
  • custom annotation : 사용자가 직접 커스텀하는 어노테이션이다.

어노테이션의 사용방법은 기본적으로 다음과 같다.

class ParentClass{ void testMethod(){}}

class ChildClass extends ParentClass{

    @Override // 이것이 어노테이션이다.
    void testMethod(){} // 컴파일러가 오버라이드 메소드를 체크한다.
    
}

1. built-in annotation

Java가 기본적으로 제공하는 빌트인 어노테이션은 대표적으로 다음과 같은것들이 있다.

  • @Override
    • 해당 어노테이션을 붙인 메소드는 제대로 오버라이드가 되어있는지 컴파일러가 체크하게 된다.
    • 물론 해당 어노테이션을 붙이지 않아도 오버라이드는 가능하지만, 간혹가다 메소드 이름을 잘못 적었는데도 오버라이드 한것으로 착각하는 경우가 있다.
      @Override를 사용하면 이러한 경우를 컴파일러를 이용해 막을 수 있다.
  • @Deprecated
    • 앞으로 사용하지 않거나 지원을 종료할 메소드에 붙이는 어노테이션이다.
    • 해당 어노테이션이 붙은 메소드를 사용하면 컴파일러가 경고 메시지를 띄워준다.
  • @FunctionalInterface
    • 함수형 메소드에 붙일 수 있는 어노테이션이다.
    • 이 어노테이션을 사용하면 컴파일러가 제대로 작성한 함수형 메소드인지를 검사한다.
    • 함수형 메소드는 하나의 추상 메소드만 가질 수 있다는 특징이 있다.

2. meta annotation

대표적인 메타 어노테이션은 다음과 같은 것들이 있다.

  • @Target
    • 특정 어노테이션을 적용할 수 있는 대상을 지정하는데 사용한다.
  • @Retention
    • 특정 어노테이션을 사용할 수 있는 시점을 지정하는데 사용한다.
    • 사용 시점이란 다음과 같다.
      • SOURCE : 컴파일러에 의해 사용되는 어노테이션
      • RUNTIME : 실행 시에(런타임에) 사용되는 어노테이션
  • @Documented
    • javadoc으로 작성한 문서에 포함시키기 위한 어노테이션
  • @Inherited
    • 어노테이션을 자손 클래스에 상속시키기 위해 사용하는 어노테이션
  • @Repeatable
    • 여러번 붙일 수 있는 어노테이션에 대해 추가하는 어노테이션이다
  • @Native
    • native 메소드에 의해 참조되는 상수에 붙이는 어노테이션이다.

3. custom annotation

사용자가 직접 어노테이션을 만들어서 사용하는것도 가능하다. 어노테이션을 정의하기 위한 기본적인 문법은 다음과 같다.

@interface TestInfo{
    int count();
    String testedBy();
    STring[] testTools();
    TestType testType(); // enum TestType{FIRST, FINAL}
    DataeTime testDate(); // 자신이 아닌 어노테이션을 포함할 수 있다.

}

interface DateTime{
    String yymmdd();
    String hhmmss();
}

커스텀 어노테이션에서 메소드는 모두 추상메소드이며, 어노테이션을 적용할 때 모두 지정해야한다. 다만, 지정하는 순서는 상관없다.
만약 어노테이션 요소 타입이 배열인 경우, 괄호 {}를 사용하여 지정해주어야 한다.

어노테이션을 선언할 때는 다음과 같은 규칙을 반드시 지켜야 한다.

  • 요소의 타입은 기본형, String, enum, 어노테이션, Class만 허용된다.
  • 괄호 () 안에 매개변수를 선언할 수 없다.
  • exception을 통해 예외를 선언할 수 없다.
  • 요소를 타입 매개변수로 정의할 수 없다.

마무리

  • 제네릭 : Java에서 컴파일 시에 타입을 체크하는 기능이다. 클래스와 메소드에 대해 사용이 가능하며, 제네릭을 사용하면 해당 객체나 메소드가 사용하는 타입을 자유롭게 설정할 수 있다. 또한 제네릭 타입이 특정 클래스와 연관된 타입만 가질 수 있도록 제한할 수 있다. 이를 통해 객체의 타입 안정성을 향상시킬 수 있으며, 개발자가 직접 번거로운 형변환을 해주지 않아도 된다.
  • 열거형 (enum) : Java에서 관련된 상수들을 묶어놓은것을 의미한다. 열거형에는 기본값에 ()를 붙여서 특정 값을 할당할 수 있는데, 이를 위해서는 인스턴스 변수와 생성자를 직접 선언해주어야 한다. 다만, 이 생성자는 묵시적으로 private이므로 외부에서 호출할 수 없다.
  • 어노테이션 : Java에서 실제 프로그래밍 언어에는 영향을 주지 않지만, 주석처럼 유용한 정보를 제공해주는 일종의 인터페이스 문법이다. Java에서 직접 제공하는 built-in annotation과 어노테이션을 위한 어노테이션으로써, 해당 어노테이션이 사용되는 시점이나 대상을 지정해주는 meta-annotation, 사용자가 직접 커스텀하여 사용하는 custom-annotation이 존재한다.

참고자료

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

profile
원리를 좋아하는 개발자

0개의 댓글