우주먼지
article thumbnail

💡 Annotation

 

컴파일러에게 문법 에러를 체크하도록 정보 제공
프로그램 빌드 시 코드 자동 생성할수 있도록 정보 제공
런타임에 특정 기능 실행하도록 정보 제공

 

표준 Annotation

자바에서 기본적으로 제공하는 애너테이션입니다.

Annotation Name Description
@Override 컴파일러에게 메서드를 오버라이딩하는 것이라고 알림
@Deprecated 앞으로 사용하지 않을 대상을 알릴 때 사용
@FunctionalInterface 함수형 인터페이스라는 것을 알
@SuppressWarning 컴파일러가 경고메세지를 나타내지 않음

 

메타 Annotation

애너테이션에 붙이는 애너테이션으로, 애너테이션을 정의하는 데에 사용됩니다.

Annotation Name Description
@Target 애너테이션을 정의할 때 적용 대상을 지정하는데 사용한다.
@Documented 애너테이션 정보를 javadoc으로 작성된 문서에 포함시킨다.
@Inherited 애너테이션이 하위 클래스에 상속되도록 한다.
@Retention 애너테이션이 유지되는 기간을 정하는데 사용한다.
@Repeatable 애너테이션을 반복해서 적용할 수 있게 한다.

 

Custom Annotation

사용자가 직접 정의하는 애너테이션입니다.


표준 Annotation

@Override

메소드 앞에만 붙일수 있는 에너테이션
선언한 메소드가 상위클래스의 메소드를 오버라이딩하는 메소드라는것을 컴파일러에게 전
에러를 발생시켜서 휴먼에러 방지

 

@Deprecated

오래된 JDK 버전으로 인해 사용안하는 필드&메소드를 사용하지 않는 것을 권장표시

 

@SuppressWarnings()

컴파일 에러메시지 표시X

 

@FunctionalInterface

- 함수형 인터페이스를 선언할때, 컴파일러가 선언이 잘되었는지 확인
- 함수형 인터페이스는 단 하나의 추상메소드만 가져야하는 제약이 있음
- 이걸 붙이지 않아도 함수형 인터페이스 선언이 가능하지만 휴먼에러 방지&확인용 Annotation임
@FunctionalInterface
public interface Runnable {
	public abstract void run (); // 하나의 추상 메서드
}

메타 Annotation (root Annotation)

 

@Target

적용할 대상 지정, java.lang.annotation.ElementType 열거형에 정의되어 있음
ANNOTATION_TYPE 애너테이션
CONSTRUCTOR 생성자
FIELD 필드(멤버변수, 열거형 상수)
LOCAL_VARIABLE 지역변수
METHOD 메서드
PACKAGE 패키지
PARAMETER 매개변수
TYPE 타입(클래스, 인터페이스, 열거형)
TYPE_PARAMETER 타입 매개변수
TYPE_USE 타입이 사용되는 모든 대상
import static java.lang.annotation.ElementType.*; 
//import문을 이용하여 ElementType.TYPE 대신 TYPE과 같이 간단히 작성할 수 있습니다.

@Target({FIELD, TYPE, TYPE_USE})	// 적용대상이 FIELD, TYPE
public @interface CustomAnnotation { }	// CustomAnnotation을 정의

@CustomAnnotation	// 적용대상이 TYPE인 경우
class Main {
    
		@CustomAnnotation	// 적용대상이 FIELD인 경우
    int i;
}

 

@Documented

- Annotation에 대한 정보가 javadoc으로 작성한 문서에 포함되도록 설정
- java에서 제공하는 표준&메타 Annotation중 @Override , @SuppressWarnings 를 제외, 전부 @Documented 적용
@Documented
@Target(ElementType.Type)
public @interface CustomAnnotation { }

 

@Inherited

- 하위 클래스가 Annotation을 상속받도록 함
- 상위 클래스에 붙이면, 하위 클래스도 상위에 붙은 Annotation들과 동일하게 적용됨
@Inherited // @SuperAnnotation이 하위 클래스까지 적용
@interface SuperAnnotation{ }

@SuperAnnotation
class Super { }

class Sub extends Super{ } // Sub에 애너테이션이 붙은 것으로 인식

 

@Retention

- Annotation의 지속시간 결정
- 관련한 유지정책의 종류에는 아래 3가지가 있다

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE) 
//오버라이딩이 제대로 되었는지 컴파일러가 확인하는 용도 
//클래스 파일에 남길 필요 없이 컴파일시에만 확인하고 사라짐
public @interface Override(){ }

위의 예제에서 Override 애너테이션은 컴파일러가 사용하면 끝나기 때문에, 실행 시에는 더이상 사용되지 않음을 의미함

 

@Repeatable

- Annotation을 여러번 붙일수 있도록 허용
@Repeatable(Works.class) // ToDo 애너테이션을 여러 번 반복해서 쓸 수 있게 한다.  
@interface Work{  
    String value();  
}

사용자 타입의 애너테이션 ToDo 를 정의하고, @Repeatable 애너테이션을 사용하여 이것을 여러번 사용할 수 있도록 함

@Work("코드 업데이트")  
@Work("메서드 오버라이딩")  
class Main{  
	... 생략 ...
}

위와 같이 일반적인 애너테이션과 다르게 ToDo 애너테이션을 하나의 대상에 여러번 적용하는 것이 가능함

@interface Works {  // 여러개의 ToDo애너테이션을 담을 컨테이너 애너테이션 ToDos
    Work[] value(); 
}

@Repeatable(Works.class) // 컨테이너 애너테이션 지정 
@interface Work {
	String value();
}

 

일반적인 애너테이션과 달리 같은 이름의 애너테이션이 여러번 적용될 수 있기 때문에,
이 애너테이션들을 하나로 묶어주는 애너테이션도 별도로 작성

 

Custom Annotation

@interface 애너테이션명 { // 인터페이스 앞에 @기호만 붙이면 애너테이션을 정의할 수 있습니다. 
	타입 요소명(); // 애너테이션 요소를 선언
}

한 가지 유의할 점은,애너테이션은 java.lang.annotation 인터페이스를 상속받기 때문에 
다른 클래스나 인터페이스를 상속 받을 수 없다


💡 Lambda

 

- 함수형 프로그래밍을 지원하는 문법요소
- 메소드를 하나의 식(expression)으로 표현한 것으로 코드의 간결성,명확성을 목표로 함
- 익명의 객체이기 때문에 java의 문법요소를 해치지 않으면서 함수형 인터페이스를 사용가능함
- 기본적으로 반환타입과 이름을 생략 가능하며, 익명 함수(anonymous function) 라고 불림

 

Lambda의 기본형식

//기존 메서드 표현 방식
void sayhello() {
	System.out.println("HELLO!")
}

//위의 코드를 람다식으로 표현한 식
() -> System.out.println("HELLO!")

 

메소드를 Lambda식으로 만드는 예시

int sum(int num1, int num2) {
	return num1 + num2;
}

↓↓↓↓↓

(int num1, int num2) -> { // 반환타입과 메서드명 제거 + 화살표 추가
	return num1 + num2;
}

 

다른 예시

// 기존 방식
void example1() {
	System.out.println(5);
}

// 람다식
() -> {System.out.println(5);}
// 기존 방식
int example2() {
	return 10;
}

// 람다식
() -> {return 10;}
// 기존 방식
void example3(String str) {
	System.out.println(str);
}

// 람다식
(String str) -> {	System.out.println(str);}

 

특정 조건이 충족되면 더욱 축약 가능

메소드 바디에 실행문이 하나만 존재할 경우 중괄호 생략

// 람다식
(int num1, int num2) -> {
	num1 + num2
}
(int num1, int num2) -> num1 + num2

 

매개변수 타입을 유추할 수 있는 경우 매개변수 타입 생략

(num1, num2) -> num1 + num2

💡 함수형 인터페이스

 

- Java에서 함수는 반드시 클래스 내부에 정의되어야 하기 때문에 독립적인 메소드가 존재할 수 없다
- Lambda 또한 객체이며, 이름이 없기 때문에 익명 클래스임
- 단 하나의 추상메소드만 선언될 수 있다
- (Lambda식 : 인터페이스의 메소드)  가 1:1로 매칭되어야 함

 

위에서 봤던 sum() 예시를 다시 보면 sum()도 익명 클래스이다.
익명 클래스 = 객체의 선언&생성을 동시에 하여 고유한 객체로 여기며, 단 한번만 사용되는 일회용 클래스임

// sum 메서드 람다식
(num1, num2) -> num1 + num2

// 람다식을 객체로 표현
new Object() {
	int sum(int num1, int num2) {
		return num1 + num1;
	}
}

위의 코드 예제에서 익명 객체를 생성하여 참조변수 obj 에 담아준다 하더라도
sum 메서드를 사용할 수 있는 방법이 없다.
이 같은 문제를 해결하기 위해 사용하는 자바의 문법 요소가 바로 자바의 함수형 인터페이스이다.

 

함수형 인터페이스의 적용

public class LamdaExample1 {
    public static void main(String[] args) {
		   /* Object obj = new Object() {
            int sum(int num1, int num2) {
                return num1 + num1;
            }
        };
			*/ 
		ExampleFunction exampleFunction = (num1, num2) -> num1 + num2
		System.out.println(exampleFunction.sum(10,15))
}

@FunctionalInterface // 컴파일러가 인터페이스가 바르게 정의되었는 지 확인할 수 있도록
interface ExampleFunction {
		public abstract int sum(int num1, int num2);
}

// 출력값
25

 

매개변수&리턴값이 없는 Lambda식

 

매개변수 & 리턴값이 없는 추상메소드를 가진 함수형 인터페이스 예시

@FunctionalInterface
public interface MyFunctionalInterface {
    public void accept();
}

↓↓↓↓↓

MyFunctionalInterface example = () -> { ... };

// example.accept();

 

public class MyFunctionalInterfaceExample {
	public static void main(String[] args) throws Exception {
		MyFunctionalInterface example;
		example = () -> {
			String str = "첫 번째 메서드 호출!";
			System.out.println(str);
		};
		example.accept();

		example = () -> System.out.println("두 번째 메서드 호출!");
		//실행문이 하나라면 중괄호 { }는 생략 가능
		example.accept();
	}
}

// 출력값
첫 번째 메서드 호출!
두 번째 메서드 호출!

 

매개변수가 있는 Lambda식

@FunctionalInterface
public interface MyFunctionalInterface {
    public void accept(int x);
}

↓↓↓↓↓

public class MyFunctionalInterfaceExample {
    public static void main(String[] args) throws Exception {
        MyFunctionalInterface example;
        example = (x) -> {
            int result = x * 5;
            System.out.println(result);
        };
        example.accept(2);

        example = (x) -> System.out.println(x * 5);
        example.accept(2);
    }
}

// 출력값
10
10

 

리턴값이 있는 Lambda식

@FunctionalInterface
public interface MyFunctionalInterface {
    public int accept(int x, int y);
}

↓↓↓↓↓

public class MyFunctionalInterfaceExample {
    public static void main(String[] args) throws Exception {
        MyFunctionalInterface example;
        example = (x, y) -> {
            int result = x + y;
            return result;
        };
        int result1 = example.accept(2, 5);
        System.out.println(result1);
        

        example = (x, y) -> { return x + y; };
        int result2 = example.accept(2, 5);
        System.out.println(result2);
       

	      example = (x, y) ->  x + y;
				// 실행문이 한 줄인 경우, 중괄호 {}와 return문 생략가능
        int result3 = example.accept(2, 5);
        System.out.println(result3);
       

        example = (x, y) -> sum(x, y);
				// 실행문이 한 줄인 경우, 중괄호 {}와 return문 생략가능
        int result4 = example.accept(2, 5);
        System.out.println(result4);
 
    }

    public static int sum(int x, int y){
        return x + y;
    }
}

//출력값
7
7
7
7

 

메소드 레퍼런스

- 불필요한 매개변수 제거를 위해 사용
- 정적 & 인스턴스 메소드 & 생성자 참조 가능 

 

정적 메소드 참조 방식

클래스 :: 메서드

 

인스턴스 메소드 참조 방식 (객체 먼저 생성해야함)

참조 변수 :: 메서드

 

생성자 참조 = 객체생성

클래스 :: new

 


💡 Stream

 

- 배열,컬렉션의 요소를 하나씩 참조해서 Lambda식으로 처리할 수 있도록 하는 반복자
- List,Set,Map,배열 등 다양한 데이터 소스로부터 스트림 생성 가능하며, 표준화된 방법으로 다룰 수 있다
- 데이터의 선언형 처리 (어떻게 < 무엇을)
- 내부반복자를 사용하므로 병렬처리가 쉽다

 

 

 

Stream 사용 예시

        List<Integer> numbers = List.of(1, 3, 6, 7, 8, 11);
        int sum = 0;

        for(int number : numbers){
            if(number > 4 && (number % 2 == 0)){
                sum += number;

↓↓↓↓↓

        List<Integer> numbers = List.of(1, 3, 6, 7, 8, 11);

        int sum =
                numbers.stream()
                        .filter(number -> number > 4 && (number % 2 == 0))
                        .mapToInt(number -> number)
                        .sum();

파이프라인 구성 (.)

파이프라인에서 최종 연산을 제외하면 모두 중간 연산 스트림이다
회원 컬렉션에서 남자를 필터링하는 중간스트림 연결 후, 다시 나이로 매핑하는 스트림 연결, 최종적으로 평균 나이를 집계하면 이 그림처럼 파이프라인이 형성됨

 

코드로 표현한 파이프라인

Stream<Member> maleFemaleStream = list.stream();
Stream<Member> maleStream = maleFemaleSTream.filter(m -> m.getGender() == Member.MALE);
IntStream ageStream = maleStream.mapToInt(Member::getAge);
OptionalDouble opd = ageStream.average();
double ageAve = opd.getAsDouble();
  • .filter(m-> m.getGender() == Member.MALE) 는 남자 Member 객체를 요소로 하는 새로운 스트림을 생성
  • .mapToInt(Member::getAge) 는 Member 객체를 age 값으로 매핑해서 age를 요소로 하는 새로운 스트림을 생성
  • average() 메소드는 age 요소의 평균을 OptionalDouble에 저장.
  • OptionalDouble에 저장된 평균 값을 읽으려면 getAsDouble() 메소드를 호출 하면 됨

 

위 예시 코드에서 로컬변수를 생략하고 연결하면 다음과 같은 형태의 파이프라인 코드가 남는다

double ageAve = list.stream() //오리지널 스트림
.filter(m-> m.getGender() == Member.MALE) //중간 연산 스트림
.mapToInt(Member::getAge) //중간 연산 스트림
.average() //최종 연산
.getAsDouble();

 

Stream 생성

- Collection 인터페이스에 stream()이 정의되있고, Collection을 구현한 객체들은 모두 이 메소드로 생성
- stream()을 사용하면 해당 Collection의 객체를 소스로 하는 Stream 반환

배열의 원소들을 소스로하는 Stream을 생성하기 위해서는 Stream의 of 메서드 또는 Arrays의 stream 메서드를 사용

// List로부터 스트림을 생성
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> listStream = list.stream();
listStream.forEach(System.out::prinln); //스트림의 모든 요소를 출력.

객체를 위한 Stream 외에도 int와 long 그리고 double과 같은 원시 자료형들을 사용하기 위한
특수한 종류의 Stream(IntStream, LongStream, DoubleStream)들도 사용할 수 있으며,
Intstream은 range()함수를 사용하여 기존의 for문을 대체한다.

// 배열로부터 스트림을 생성
Stream<String> stream = Stream.of("a", "b", "c"); //가변인자
Stream<String> stream = Stream.of(new String[] {"a", "b", "c"});
Stream<String> stream = Arrays.stream(new String[] {"a", "b", "c"});
Stream<String> stream = Arrays.stream(new String[] {"a", "b", "c"}, 0, 3); //end 범위 미포함

스트림은 주로 컬렉션과 배열에서 얻지만, 다음과 같은 소스로부터 스트림 구현 객체를 얻을 수도 있다

// 4이상 10미만의 숫자를 갖는 IntStream
IntStream stream = IntStream.range(4, 10);

 

 

리턴 타입 메소드(매개 변수) 소스
Stream java.util.Collection.Stream(), java.util.Collection.parallelSream( ) 컬렉션
Stream, IntStream,
LongStream,
DoubleStream
Arrays.stream(T[]), Arrays.stream(int[]),
Arrays.stream(long[]), Arrays.stream(double[]),
Stream.of(T[]), IntStream.of(int[]) LongStream.of(long[]),
DoubleStream.of(double[])
배열
IntStream IntStream.range(int, int), IntStream.rangeClosed(int, int) int 범위
LongStream LongStream.range(long, long), LongStream.rangeClosed(long, long) long 범위
  • 스트림은 데이터 소스로부터 데이터를 읽기만 할 뿐 변경 X (Read-only)
  • 스트림은 일회용입니다. 한 번 사용하면 닫히므로, 필요하다면 새로운 스트림을 만들어야 함

중간 연산

- 연산 결과를 스트림으로 반환하기 때문에, 연속해서 여러번 수행 가능

 

Stream의 주요 중간 연산 메소드

 

필터링(filter(), distinct())

  • distinct() : Stream의 요소들에 중복된 데이터가 존재하는 경우, 중복을 제거하기 위해 사용합니다.
  • filter() : Stream에서 조건에 맞는 데이터만을 정제하여 더 작은 컬렉션을 만들어냅니다. filter() 메서드에는 매개값으로 조건(Predicate)이 주어지고, 조건이 참이 되는 요소만 필터링합니다.

매핑 map()

  • map() map은 기존의 Stream 요소들을 대체하는 요소로 구성된 새로운 Stream을 형성하는 연산입니다.

 

정렬 sorted()

  • Stream의 요소들을 정렬하기 위해서는 sorted를 사용해야 하며, 파라미터로 Comparator를 넘길 수도 있음
  • Comparator 인자 없이 호출할 경우에는 오름차순 정렬, 내림차순으로 정렬하기 위해서는 Comparator의 reverseOrder를 이용함

 

연산 결과 확인 peek()

  • peek(), forEach()는 요소를 하나씩 돌면서 출력한다는 기능에서는 동일하지만, 동작 방식은 다름
  • peek은 중간 연산 메서드이고, forEach는 최종 연산 메서드임
  • forEach는 스트림의 요소를 소모하므로 한 번만 호출할 수 있지만(재호출하려면 새로운 스트림을 생성해야함),
    peek은 중간 연산이므로 하나의 스트림에 여러 번 사용할 수 있음
  • peek()은 주로 연산 중간에 결과를 확인하여 디버깅하고자 할 때 사용

최종 연산

- 연산 결과가 스트림이 아니므로, 한번만 연산 가능

 

 

Stream의 주요 최종 연산 메소드

 

연산 결과 확인 forEach()

  • forEach는 최종 연산 메서드이기 때문에 파이프라인 마지막에서 요소를 하나씩 연산함
  • forEach값을 출력할 때도 사용하지만, 이메일 발송, 스케줄링 등 리턴 값이 없는 작업에서도 많이 사용함

 

매칭 match()

- Stream의 요소들이 특정한 조건을 충족하는지 검사하고 싶은 경우에는 match() 메서드를 이용할 수 있다.
- match() 메서드는 함수형 인터페이스 Predicate를 받아서 해당 조건을 만족하는지 검사하고,검사 결과를 boolean으로 반환함.
- match() 메서드에는 크게 다음 3 가지가 있다.
  • allMatch() : 모든 요소들이 매개값으로 주어진 Predicate의 조건을 만족하는지 조사
  • anyMatch() : 최소한 한 개의 요소가 매개값으로 주어진 Predicate의 조건을 만족하는지 조사
  • noneMatch() : 모든 요소들이 매개값으로 주어진 Predicate의 조건을 만족하지 않는지 조사

 

 

기본 집계(sum(), count(), average(), max(), min())

  • 집계는 최종 연산 기능으로 요소들을 카운팅, 합계, 평균값, 최대값, 최소값 등으로 연산하여 하나의 값으로 산출

 

 

reduce()

-앞서 본 집계 메서드인 sum, average, count, max, min 외에도 다양한 집계 결과물을 만들 수 있는 reduce 메서드가 있다
- reduce는 누적하여 하나로 응축(reduce)하는 방식으로 동작합니다. 앞의 두 요소의 연산 결과를 바탕으로 다음 요소와 연산
- reduce 메서드는 최대 3개의 매개변수를 받을 수 있다
  • Accumulator: 각 요소를 계산한 중간 결과를 생성하기 위해 사용
  • Identity: 계산을 수행하기 위한 초기값
  • Combiner: 병렬 스트림(Parlallel Stream)에서 나누어 계산된 결과를 하나로 합치기 위한 로직

 

 

collect()

  • Stream의 요소들을 List나 Set, Map, 등 다른 종류의 결과로 수집하고 싶은 경우에는 collect 메서드를 이용 가능
  • collect 메서드는 어떻게 Stream의 요소들을 수집할 것인가를 정의한 Collector 타입을 인자로 받는다
  • 이는 Collector 인터페이스를 구현한 클래스임
  • 일반적으로 List로 Stream의 요소들을 수집하는 경우가 많다
  • 자주 사용하는 작업은 Collectors 객체에서 static 메서드로 제공하고 있고, 원하는 것이 없는 경우에는 Collector 인터페이스를 직접 구현하여 사용할 수도 있다

💡 Optional<T>

 

- Optional은 NullPointerException(NPE),
- 즉 null 값으로 인해 에러가 발생하는 현상을 객체 차원에서 효율적으로 방지하고자 도입되었다
- 연산 결과를 Optional에 담아서 반환하면, 조건문을 따로 작성안해도 NPE가 발생하지 않도록 코드작성 가능
- Optional 객체를 생성하려면 of() , ofNullabel() 사용하며 참조변수 값이 null일 가능성이 있으면 ofNullable()

 

Optional 클래스는모든 타입의 객체를 담을 수 있는 래퍼(Wrapper) 클래스

public final class Optional<T> {
	private final T value; // T타입의 참조변수
}

💡 InputStream & OutputStream

 

- 데이터 단방향 전송 / 입출력을 동시에 하려면 각각의 스트림 필요
- 다루는 대상에 따라 종류가 다름
  (파일 = FileInputStream / FileOutputStream, 프로세스 = PipedInputStream / PipedOutputStream) 사용

 

FileInputStream

- BufferedInputStream이라는 보조 스트림을 사용하면 성능이 향상되기 때문에, 대부분의 경우 이를 사용함
- 버퍼란 바이트 배열로서, 여러 바이트를 저장하여 한 번에 많은 양의 데이터를 입출력 하도록 도와주는 임시 저장 공간
import java.io.FileInputStream;
import java.io.BufferedInputStream;
  
public class FileInputStreamExample {
    public static void main(String args[])
    {
        try {
            FileInputStream fileInput = new FileInputStream("codestates.txt");
						BufferedInputStream bufferedInput = new BufferedInputStream(fileInput);
            int i = 0;
            while ((i = bufferedInput.read()) != -1) {
                System.out.print((char)i);
            }
            fileInput.close();
        }
        catch (Exception e) {
            System.out.println(e);
        }
    }
}

 

FileOutputStream

import java.io.FileOutputStream;
  
public class FileOutputStreamExample {
    public static void main(String args[]) {
        try {
            FileOutputStream fileOutput = new FileOutputStream("codestates.txt");
            String word = "code";

            byte b[] = word.getBytes();
            fileOutput.write(b);
            fileOutput.close();
        }
        catch (Exception e) {
            System.out.println(e);
        }
    }
}

위 코드를 실행하면, 같은 디렉토리 내에 code라는 문자열이 입력된 codestates.txt 파일이 생성됨을 확인할 수 있음

 

'Languages > Java' 카테고리의 다른 글

ObjectMapper  (0) 2023.01.09
Method 공부  (0) 2022.09.28
Exception & Collection Framework  (2) 2022.09.14
Enum & Generic & Wrapper  (0) 2022.09.13
객체지향 프로그래밍 2 (다형성 & 추상화)  (0) 2022.09.07
profile

우주먼지

@o귤o

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

검색 태그