public enum ElementType {
// ... 이전과 동일 (생략)
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
타입에 애노테이션을 사용하기 위해 아래와 같은 ENUM 타입을 명시해야 함.
TYPE_PARAMETER : 타입 변수에만 사용할 수 있다.
TYPE_USE : 타입 변수를 포함해서 모든 타입 선언부에 사용할 수 있다.
위의 어노테이션들과는 다르게 선언 주석이 아닌 TYPE 주석이다.
선언 주석 : 주의사항, 사용방법, 사용처 등 설명
TYPE 주석 : 정수 값이 0보다 커야 한다, null이 아니다 와 같은 값에 대한 정보 제공함으로써 implements, throws, new 구절에 사용하거나 제네릭에 사용함으로써 외부의 프로젝트에도 적용할 수 있도록 확장한 범위
@Retention(RetentionPolicy.RUNTIME) // 이 애노테이션 정보를 언제까지 유지할 것인가
@Target(ElementType.TYPE_PARAMETER) // 이 애노테이션을 사용할 곳
public @interface Chicken {
}
public class App {
public static void main(@Chicken String[] args) throws @Chicken RuntimeException {
List<@Chicken String> names = Arrays.asList("Park");
}
// @Target(ElementType.TYPE_PARAMETER) parameter 만 사용
static class FeelsLikeChicken<@Chicken T> {
}
// @Target(ElementType.TYPE_USE) 사용
static class FeelsLikeChicken2<@Chicken T> {
// 전자의 <C> 는 타입 파라미터
// 후자의 C 는 타입
public static <@Chicken C> void print(@Chicken C c) {
System.out.println(c);
}
}
}
TYPE_PARAMETER와 TYPE_USE에 대해 더 자세히 알아보기 위해 다음 예제를 살펴보자.
TYPE_PARAMETER
타입 선언부에 사용이 가능
//annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_PARAMETER)
public @interface ParmeterEx {
}
//class에 사용
public class AnnotationStudy <@ParmeterEx T> {
public void print( T t){}
}
//method에 사용
public class AnnotationStudy {
public <@ParmeterEx T> void print( T t){}
}
//method에 사용
public class AnnotationStudy {
public <@ParmeterEx T> void print(T t) throws @ParameterEx SomthingException{}
}
컴파일하면서 해당 타입이 무슨 타입인지 분석하여 CLASS_TYPE_PARAMETER / METHOD_TYPE_PARAMETER 로 변환하는 것을 볼 수 있다.
타입 선언부에 사용이 가능한 Target이므로 아래와 같이 실제 사용하는 부분에는 적용할 수 없다.
public class AnnotationStudy {
public <T> void print(@ParmeterEx T a){}
}
TYPE_USE
선언부 뿐만이 아닌 타입 사용되는 모든 곳에 적용이 가능
(클래스/인터페이스/내부필드/파라미터/제네릭/지역변수 등)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_USE)
public @interface ParmeterEx {
}
@ParmeterEx
public class AnnotationStudy <@ParmeterEx T>{
@ParmeterEx
String name = "default";
@NonNull
public <@ParmeterEx R> void print(@ParmeterEx String t, @ParmeterEx R r){
@ParmeterEx
int a=1;
}
}
바이트 코드를 보면 TYPE_USE를 사용해도 컴파일 결과는 컴파일러가 적절한 어노테이션 TARGET으로 바꾸는 것을 볼 수 있다.
Lombok의 NonNull 어노테이션으로 해당 어노테이션도 Target범위가 TYPE_USE가 포함되어 있다.
1-2. 중복 사용할 수 있는 애노테이션을 만들기
중복 사용할 애노테이션 만들기
중복 애노테이션 컨테이너 만들기
컨테이너 애노테이션은 중복 애노테이션과 @Retention 및 @Target이 같거나 더 넓어야 한다.
1-2-1. 중복 사용할 애노테이션 생성
기존에는 애노테이션을 같은 범위에 중복해서 정의할 수 없었는데 java8 부터는 @Repeatable()이 추가되어 중복해서 사용이 가능해졌다.
Repeatable은 한개의 value를 가지고 있는데, 여기에 일종의 애노테이션 컨테이너 역할을 할 애노테이션 클래스를 넘겨주면 해당 컨테이너에 중복 사용한 애노테이션들을 담는 방식으로 동작하게 된다.
이때 주의할 점이 Repeatable의 value는 애노테이션의 컨테이너 역할이기 때문에 중복해서 사용할 어노테이션보다 생명주기(RetentionPolicy)가 길어야만 한다.
/*
@Target(ElementType.TYPE_PARAMETER)
- 제네릭을 사용할 때 타입 변수 앞에 애노테이션을 사용할 수 있다.
@Target(ElementType.TYPE_USE)
- 타입 변수를 포함해서 타입 선언부에서 애노테이션을 사용할 수 있다.
@Repeatable(컨테이너 성격의 애노테이션 class)
- 여러 개의 애노테이션을 가지고 있을 컨테이너 애노테이션을 설정해주면 반복해서 애노테이션 사용이 가능하다.
- 컨테이너의 애노테이션 설정(Retention, Target)은 반드시 자기자신이 감싸는 애노테이션의 범위보다 같거나 넓어야 한다.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_USE)
@Repeatable(ChickenContainer.class)
public @interface Chicken {
String value() default "후라이드";
}
1-2-2. 중복 애노테이션의 컨테이너 생성
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_USE)
public @interface ChickenContainer {
// 컨테이너 애노테이션은 자기 자신이 감쌀 애노테이션 배열을 가지고 있어야 한다.
Chicken[] value();
}
1-2-3. 컨테이너 애노테이션으로 중복 애노테이션 참조
@Chicken
@Chicken("양념")
@Chicken("마늘간장")
public class App {
public static void main(String[] args) {
// 1. 클래스에서 해당 애노테이션 타입으로 바로 읽어오는 방법
Chicken[] chickens = App.class.getDeclaredAnnotationsByType(Chicken.class);
Arrays.stream(chickens)
.forEach(c -> System.out.println(c.value()));
// 2. 컨테이너 애노테이션 타입으로 읽는 방법
ChickenContainer chickenContainer = App.class.getAnnotation(ChickenContainer.class);
Arrays.stream(chickenContainer.value())
.forEach(c -> System.out.println(c.value()));
}
}
@Chicken 은 default value가 없으면 에러가 발생한다.
// ERROR 발생
@Chicken
@Chicken("양념")
public class App() {
}
더 나아가서 main에서 2번째로 방법으로 컨테이너 애노테이션 타입으로 읽어들일 때 바이트 코드를 살펴보자.
ChickenContainer 애노테이션을 생성하여 Chicken 애노테이션들을 value로 할당하는 것을 볼 수 있다.
MethodHandles$Lookup라는 이름의 클래스로 innerClass가 만들어지는 것을 볼 수 있는데 이는 메서드 핸들을 생성하기 위한 factory class로 ChickenContainer의 value()인 Chicken[]에 접근하기 위한 class 이다.
배열 병렬 정렬
Arrays.parallelSort()가 추가되어 분산되어 정렬이 가능하다.
1. Arrays.parallelSort()
Fork/Join 프레임워크를 사용해서 배열을 병렬로 정렬하는 기능을 제공한다.
2. 병렬 정렬 알고리즘
배열을 둘로 계속 쪼갠다.
합치면서 정렬한다.
3. sort()와 parallelSort() 비교
알고리즘 효츌성은 같다.
시간복잡도 : O(n logN)
공간복잡도 : O(n)
단, 정렬하는 배열의 크기에따라 속도가 차이날 수 있다.
public class App {
public static void main(@Chicken String[] args) {
int size = 1500;
int[] numbers = new int[size];
Random random = new Random();
IntStream.range(0, size).forEach(i -> numbers[i] = random.nextInt());
// 싱글 쓰레드 사용 (일반 정렬 시간 측정)
long start = System.nanoTime();
Arrays.sort(numbers);
System.out.println("serial sorting took " + (System.nanoTime() - start));
// 멀티 쓰레드 사용 (병렬 정렬 시간 측정)
IntStream.range(0, size).forEach(i -> numbers[i] = random.nextInt());
start = System.nanoTime();
Arrays.parallelSort(numbers);
System.out.println("parallel sorting took " + (System.nanoTime() - start));
}
}
serial sorting took 1127801
parallel sorting took 612500
참고
size가 15000일 때 나오는 속도
serial sorting took 5023800
parallel sorting took 14654200
Arrays.sort()
Arrays.sort() 알고리즘은 기본적으로 DualPivotQuicksort를 사용한다.