[Section 1] Annotation, Lambda, Stream

Kim·2022년 9월 19일
0

Boot Camp

목록 보기
17/64
post-thumbnail

Annotation

애너테이션은 정보 전달이란 목적이 있는데, 주석과는 다르게 다른 프로그램에게 유용한 정보를 제공하기 위해 만들어졌다.
컴파일러에게 문법 에러를 체크하고, 프로그램을 build 할 때 코드를 자동 생성할 수 있게 하고, 런타임에 특정 기능을 실행하도록 정보를 제공한다.

@Test //아래 메서드가 테스트 대상임을 프로그램에게 알림
public void run() {...}
public void stop() {...}

주석과 마찬가지로 해당 테스트를 수행하는 프로그램 외의 다른 프로그램에는 아무런 영향을 미치지 않는다.

종류

*표준 애너테이션 : 자바에서 기본적으로 제공하는 애너테이션

표준 애너테이션설명
@Override컴파일러에게 메서드를 오버라이딩하는 것이라고 알림
@Deprecated앞으로 사용하지 않을 대상임을 알림
@FunctionalInterface함수형 인터페이스임을 알림
@SuppressWarning컴파일러가 경고 메세지를 나타내지 않음
  • 메타 애너테이션 : 애너테이션에 붙이는 애너테이션, 애너테이션을 정의하는 데 사용
메타 애너테이션설명
@Target애너테이션을 정의할 때 적용 대상을 지정하는데 사용
@Documented에너테이션 정보를 javadoc으로 작성된 문서에 포함
@Inherited애너테이션이 하위 클래스에 상속되도록 함
@Retention애너테이션이 유지되는 기간을 정하는데 사용
@Repeatable애너테이션을 반복해서 적용할 수 있게 함
  • 사용자 정의 애너테이션 : 사용자가 직접 정의하는 애너테이션

Lambda

람다식은 함수형 프로그래밍 기법을 지원하는 자바의 문법 요소다. 메서드를 하나의 식(expression)으로 표현한 것으로, 코드를 매우 간결하고 명확하게 표현할 수 있다.

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

//람다식
() -> System.out.println("Hello!")

람다식은 기본적으로 반환타입과 이름을 생략할 수 있다. 람다함수를 종종 이름이 없는 함수라는 뜻의 익명 함수라고 부르기도 한다.

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

//람다식
(int num1, int num2) -> { //반환타입과 메서드명 제거 + 화살표 추가
	num1 + num2 //반환값이 있는 메서드의 경우 return문과 세미콜론 생략 가능
}

특정 조건이 충족되면 아래와 같이 더욱 축약할 수 있다.

(int num1, int num2) -> num1 + num2

(num1, num2) -> num1 + num2

메서드 바디에 문장이 실행문이 하나만 존재하는 경우 중괄호를 생략할 수 있다.
또한, 매개변수의 타입을 쉽게 유추할 수 있는 경우 매개변수의 타입을 생략할 수 있다.

함수형 인터페이스

람다식은 객체다. 이름이 없어 익명 클래스라 할 수 있다.

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

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

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

public class LamdaEx {
	public static void main(String[] args) {
    	Object obj = new Object() {
        	int sum(int num1, int num2) {
    			return num1 + num2;
    		}
        };
    }
}		//람다식 Object obj = (num1, num2) => num1 + num2

자바에서 함수형 프로그래밍을 하기 위해 새로운 문법 요소를 도입하는 대신, 기존의 인터페이스 문법을 활용해 람다식을 다루는 것이다.
람다식도 하나의 객체이므로 인터페이스에 정의된 추상 메서드를 구현할 수 있다.

메서드 레퍼런스

메서드 참조는 람다식에서 불필요한 매개변수를 제거할 때 주로 사용한다. 람다식으로 간단해진 익명 객체를 더욱 간단하게 사용하고 싶은 개발자의 산물이다.

//람다식
(left, right) -> Math.max(left, right);

//메서드 참조
Math :: max //클래스명 :: 메서드명

메서드 참조는 정적 메서드와 인스턴스 메서드, 생성자도 참조 가능하다.

  • 정적 메서드를 참조할 경우
클래스 :: 메서드
  • 인스턴스 메서드를 참조할 경우
참조변수 :: 메서드
  • 생성자 참조
(a, b) -> {return new 클래스(a, b);};

클래스 :: new //생성자 참조

Stream

스트림은 배열고 ㅏ컬렉션의 저장 요소를 하나씩 참조해 람다식으로 처리할 수 있게 하는 반복자다. 다양한 데이터 소스로부터 스트림을 만들 수 있고, 이를 표준화된 방법으로 다룰 수 있다.

특징

스트림을 사용하면 선언형으로 데이터 소스를 처리할 수 있다.
선언형 프로그래밍이란 무엇을 수행하는지에 관심을 두는 프로그래밍 패러다임이다.

자바7 전가지는 List 컬렉션에서 요소를 순차적으로 처리하기 위해 아래와 같이 사용했다.

import java.util.List;

public class ImperativeProgrammingExample {
    public static void main(String[] args){
        //List에 있는 숫자들 중에서 4보다 큰 짝수의 합계 구하기
        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;
            }
        }

        System.out.println("# 명령형 프로그래밍 : " + sum);
    }
}

이 코드를 스트림을 사용해 변경하면 아래와 같다.

import java.util.List;

public class DeclarativeProgramingExample {
    public static void main(String[] args){
        //List에 있는 숫자들 중에서 4보다 큰 짝수의 합계 구하기
        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();

        System.out.println("# 선언형 프로그래밍: " + sum);
    }
}

두 코드를 비교해보면 스트림을 사용한 코드가 훨씬 단순하고 가독성이 높음을 알 수 있다.

병렬 처리

스트림은 내부 반복자를 사용해 병렬 처리가 쉽다.

내부 반복자를 사용하면 어떻게 요소를 반복시킬 것인가는 컬렉션에게 맡기고, 개발자는 요소 처리 코드에만 집중할 수 있다. 또한, 요소들을 분배시켜 병렬 작업을 할 수 있기 때문에 하나씩 처리하는 외부 반복자보다 효율적으로 요소를 반복시킬 수 있다.
병렬 스트림을 사용하려면 스트림의 parallel() 메서드를 사용하면 된다.

중간 연산과 최종 연산

스트림은 컬렉션의 요소에 대해 중간 연산과 최종 연산을 수행할 수 있다.
중간 연산에서는 매핑, 필터링, 정렬 등을 수행하고 최종 연산에서는 반복, 카운팅, 평균, 총합 등의 집계를 수행할 수 있다.

파이프라인 구성

스트림은 중간 연산과 최종 연산을 파이프라인으로 해결한다.
파이프라인은 여러개의 스트림이 연결되어 있는 구조를 말한다. 파이프라인에서 최종 연산을 제외하고 모두 중간 연산 스트림이다.

중간 스트림이 생성될 때 요소들이 바로 중간 연산되는 것이 아닌, 최종 연산이 시작되기 전까지 지연된다. 최종 연산이 시작되면 비로소 컬렉션의 요소가 하나씩 중간 스트림에서 연산되고 최종 연산까지 오게 된다.

스트림 인터페이스에는 많은 중간 연산 메소드가 있는데, 이 메소드들은 중간 연산된 스트림을 리턴한다. 그리고 이 스트림에서 다시 중간 연산 메소드를 호출해 파이프라인을 형성한다.

Optional<T>

null값으로 인해 에러가 발생하는 현상을 객체 차원에서 효율적으로 방지하고자 도입되었다.

Optional 클래스는 모든 타입의 객체를 담을 수 있는 Wrapper 클래스다.

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

Optional 객체를 생성하려면 of() 또는 ofNullable()을 사용한다. 참조변수의 값이 null일 가능성이 있다면 ofNullable()을 사용한다.

Optional<String> opt1 = Optional.ofNullable(null);
Optional<String> opt2 = Optional.ofNullable("123");
System.out.println(opt1.isPresent()); //Optional 객체의 값이 null인지 여부를 리턴
System.out.println(opt2.isPresent());

Optional 타입의 참조변수를 기본값으로 초기화하려면 empty() 메서드를 사용한다.

Optional<String> opt3 = Optional.<String>empty();

Optional 객체에 저장된 값을 가져오려면 get() 메서드를 사용한다. 참조변수의 값이 null일 가능성이 있으면 orElse() 메서드를 통해 디폴트 값을 지정할 수 있다.

Optional<String> optString = Optional.of("User");
System.out.println(optString);
System.out.println(optString.get());

String nullName = null;
String name = Optional.ofNullable(nullName).orElse("Kim");
System.out.println(name);

참고자료

Java8 - 함수형 인터페이스(Functional Interface) 이해하기
Package java.util.function

스트림(Stream)
Interface Stream<T>

Class Optional<T>

이미지 출처

gwichanlee.log

0개의 댓글