My Melody Is Cute JAVA 심화 애너테이션 , 람다 , 스트림
본문 바로가기

개발공부🌷/JAVA

JAVA 심화 애너테이션 , 람다 , 스트림

애너테이션

소스 코드가 컴파일 되거나 실행될 때 컴파일러 및 다른 프로그램에게 필요한 정보를 전달해주는 문법요소

기능주입도 가능

 

표준 애너테이션 : JDK에 내장된 일반적인 애너테이션

메타 애너테이션 : 다른 애너테이션을 정의할 때 사용, 애너테이션을 직접 정의해서 사용

사용자정의 애너테이션

 

표준 애너테이션

@Override

메서드 앞에만 붙일 수 있음

오버라이딩(재정의) 하거나 추상메서드를 구현하는 메서드

 

오버라이딩한 메서드란것을 애너테이션으로 알려주면

파일러가 상위클래스에 동일한 메서드가 존재하는지 검사를 해주는데 

혹시나 오타로 이름이 잘못 입력되었다면 에러를 알려줌.

 

@Deprecated

기존에 사용하던 기술이 다른 기술로 대체되어 기존기술을 적용한 코드를 더이상 사용하지 않도록 유도

기존의 코드를 다른 코드와의 호환성 문제로 삭제하기 곤란해서 남겨두어야 하지만 더이상 사용을 권장하지 않을때

 

@SuppressWarnings

컴파일 경고 메시지가 나타나지 않도록 경고가 예상되는 상황임에도 묵인해야할때

 

@FuntionalInterface

함수형 인터페이스를 선언할때 컴파일러가 바르게 선언된지 확인하도록 함

함수형 인터페이스는 단 하나의 추상메서드를 가져야한다

 

메타 애너테이션

애너테이션을 정의하는데에 사용되는 애너테이션

 

@Target

애너테이션을 적용할 대상을 지정

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

애너테이션에 대한 정보가 javadoc으로 작성한 문서에 포함되도록 하는 설정

@Override와 @SuppressWarnings 제외 모두 @Documented가 적용

 

@Inherited

하위 클래스가 애너테이션을 상속받도록 함

상위클래스에 붙이면 하위클래스도 상위클래스에 붙은 애너테이션이 동일하게 적용

 

@Retention

애너테이션의 지속시간을 결정

유지정책(유지되는 기간 지정하는 속성) 종류 : SOURCE, CLASS, RUNTIME

 

@Repeatable

애너테이션을 하나의 대상에 여러번 붙일 수 있도록 허용

여러개를 하나로 묶어주는 애너테이션을 별도로 작성해야한다

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

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

사용자 정의 애너테이션

사용자가 직접 애너테이션을 정의해서 사용

인터페이스를 정의하는것과 비슷

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

java.lang.annotation인터페이스를 상속받기 때문에

다른 클래스나 인터페이스를 상속받을 수 없다

 

람다

함수형 프로그래밍 기법을 지원하는 자바의 문법요소

메서드를 하나의 식으로 표현 코드를 매우 간결하면서 명확하게 표현힐 수 있다

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

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

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

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

반환타입과 이름을 생략하고 코드 블록 사이에 화살표 추가

익명함수(anonymous function)라고 부르기도 한다

 

조건이 성립되면 더 축약이 가능하다

 

1. 메서드바디에 실행문 하나일때 : 중괄호와 return문 생략가능(세미콜론도 생략)

// 기존 방식
int sum(int num1, int num2) {
	return num1 + num2;
}

// 람다식
(int num1, int num2) -> {
	num1 + num2
}
//바디에 실행문이 하나일때 조건이 성립
(int num1, int num2) -> num1 + num2
//매개변수타입을 함수형 인터페이스를 통해 유추할 수 있는 경우
//매개변수타입 생략가능
(num1, num2) -> num1 + num2

2. 매개변수 타입을 함수형 인터페이스를 통해 유추할 수 있는 경우 : 매개변수의 타입 생략가능

 

함수형 인터페이스

 

람다식 또한 익명객체

익명클래스 : 객체의 선언과 생성을 동시에 해서 오직 하나의 객체를 생성하고 단 한번만 사용되는 일회용 클래스

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

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

객체에 접근하고 사용하려면 참조변수가 필요하다 그런데 Object클래스에는 sum 메서드가 없으므로

참조변수에 담아도 sum을 사용할 수 없다

이걸 해결해주는 자바의 문법요소가 함수형 인터페이스(Funtional Interface) 이다

 

함수형 인터페이스에는 단 하나의 추상메서드만 선언될 수 있는데

람다식과 인터페이스의 메서드가 1:1로 매칭되어야하기 때문

public class LamdaExample1 {
    public static void main(String[] args) {
        /* Object obj = new Object() {
              int sum(int num1, int num2) {
                   return num1 + num2;
         }
        };
         */
        //참조변수 exampleFunction에 람다식 할당
        ExampleFunction exampleFunction = (num1, num2) -> num1 + num2;
        System.out.println(exampleFunction.sum(10,15)); //sum 메서드 호출 가능
    }
}
@FunctionalInterface//컴파일러가 인터페이스가 바른지 확인하게 함
    ////함수형 인터페이스 ExampleFunction에 추상메서드 sum()정의
interface ExampleFunction {
    int sum(int num1, int num2);
}
//출력값 25

매개변수와 리턴값이 없는 람다식

더보기
@FunctionalInterface
interface MyFunctionalInterface {
    void accept();
}

public class MyFunctionalInterfaceExample {
    public static void main(String[] args) throws Exception {
        MyFunctionalInterface example = () -> System.out.println("accept() 호출");
        example.accept();
    }
}

// 출력값
accept() 호출

매개변수가 있는 람다식

더보기
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

리턴값이 있는 람다식

더보기
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문만 있으면, 중괄호 {}와 return문 생략 가능
        int result3 = example.accept(2, 5);
        System.out.println(result3);
       

        example = (x, y) -> sum(x, y);
        //return문만 있으면, 중괄호 {}와 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

스트림(Stream)

배열 및 컬렉션의 저장요소를 하나씩 참조해서 람다식으로 처리할 수 있도록 하는 반복자

선언형프로그래밍으로 데이터를 처리할 수 있음 "무엇"에 집중

내부동작 원리를 모르더라도 어떤 코드가 어떤역할을 하는지 직관적으로 이해

명령형프로그래밍 "어떻게"

람다식을 사용하여 데이터 처리

 

데이터 소스가 무엇이냐에 관계 없이 같은 방식으로 데이터를 가공/처리 할 수 있다

하나의 통합된 방식으로 데이터를 다룰 수 있게 되었다

 

스트림의 생성

배열 스트림 생성

Array 클래스의 stream()

Stream클래스의 of()

 

컬렉션 스트림 생성

Collection에 정의된 stream()메서드

List와 Set을 구현한 컬렉션 클래스

 

임의의 수 스트림 생성

 

 

스트림의 중간 연산

스트림의 중간 연산자의 결과는 스트림을 반환하기 때문에 여러개의 연산자를 연결

 

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

조건에 맞는 데이터들만을 정제

distinct() 중복제거

filter() 조건에 맞는 데이터만 정제해서 더 작은 컬렉션 만들어냄 매개값을 조건(Predicate)을 주고

참이되는 요소만 필터링 조건은 람다식을 사용하여 정의

 

매핑(maping) - map()

원하는 필드만 추철하거나 특정형태로 변환할 때 사용

값을 변환하기 위한 조건을 람다식으로 정의

flatMap()

중첩구조를 제거하고 단일컬렉션으로 만들어준다

 

 

정렬(sorting) - sorted()

정렬할 때 사용

 

skip() 일부 요소를건너뜀

limit() 일부를 자름

peek() forEach()와 마찬가지로 요소를 순회하며 특정작업을 수행

차이는 중간연산자인지 여부 중간연산자여서 여러번 연결하여 사용가능

종종 디버깅 용도로 활용됨

 

 

 

스트림의 최종 연산

지연된 연산

forEach() 최종적으로 사용되고 나면 해당 스트림은 닫히고 모든 연산이 종료된다

 

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

숫자와 관련된 기본적인 집계

double average = Arrays.stream(intArray).average().getAsDouble(); 
//기존 코드 getAsDouble()의 용도는???

//재작성한 코드
import java.util.Arrays;
import java.util.OptionalDouble;

public class TerminalOperationExample {
    public static void main(String[] args) {
        // int형 배열 생성
        int[] intArr = {1,2,3,4,5};

        // 평균값을 구해 Optional 객체로 반환
        OptionalDouble average = Arrays.stream(intArr).average();
        System.out.println(average);

        // 기본형으로 변환 getAsDouble()
        double result = average.getAsDouble();
        System.out.println("전체 요소의 평균값 " + result);

    }
}

//출력값
OptionalDouble[3.0]
전체 요소의 평균값 3.0

OptionalDoule클래스 래퍼클래스

null값으로 인해서 NullPointerException (NPE)에러가 발생하는 현상을 객체차원에서

효율적으로 방지하기위한 목적 연산결과를 Optional객체 안에 담아서 반환하면

if문을 사용한 조건문으로 반환된 결과가 null인지 여부를 체크하지 않아도 에러가 발생하지 않게 코드를 작성 가능하다

 

range(0,n) 은 0부터 n-1 까지의 범위

rangeClosed(0,n)은 0부터 n까지의 범위의 IntStream 을 생성

 

 

getAsDouble(), getAsInt()

객체로 반환된 값을 다시 기본형으로 변환하기 위해 사용되는 메서드로 스트림파이프라인과 관계가 없다

 

매칭 - allMatch() , anyMatch() , noneMatch()

match() 메서드 3종류

allMatch() : 모든 요소가 조건을 만족하는지 여부 판단

noneMatch() -  모든 요소가 조건을 만족하지 않는지 여부 판단

anyMatch() - 하나라도 조건을 만족하는 요소가 있는지 여부 판단

 

요소 소모 - reduce()

reduce() 스트림의 요소를 줄여나가면서 연산을 수행하고 최종적인 결과를 반환

먼저 첫번째 두번째를 연산 수행 후 세번째를 가지고 다시 연산 수행하는 식으로 끝날때까지 반복

 

Optional<T> reduce(BinaryOperator<T> accumulator)

 

최대 3개까지 매개변수를 받을 수 있음

T reduce(T identity, BinaryOperator<T> accumulator)

위에서 첫 번째 매개변수 identity는 특정 연산을 시작할 때 설정되는 초기값

 두 번째 accumulator는 각 요소를 연산하여 나온 누적된 결과값을 생성하는 데 사용하는 조건식

 

요소 수집 - collect()

스트림의 요소들을 List Set Map 등 다른 타입의 결과로 수집하고 싶으 ㄴ경우에 사용

 

Collector 인터페이스 타입의 인자를 받아서 처리 할 수 있다

요소 그룹핑 및 분할 등 다른 기능 제공