반응형

 내용은 라울-게이브리얼 우르마, 마리오 푸스코, 앨런 마이크로프트의 "모던 자바 인 액션" 책을 보고 정리한 내용입니다. 저작권 보호를 위해 책의 내용은 요약되었습니다.

 

함수형 인터페이스란?

  • 함수형 인터페이스는 단 하나의 추상 메서드를 가지는 인터페이스로 하나의 추상메서드를 지정한다. 이러한 특징 때문에 함수형 인터페이스는 함수를 객체로 표현하기 위한 용도로 사용된다.
  • 람다표현식으로 함수형 인터페이스의 추상 메서드 구현을 직접 전달하여, 표현식을 함수형 인터페이스의 인스터스로 취급할 수 있다.

함수 디스크립터란?

  • 함수 디스크립터는 함수에 대한 정보를 나타내는 객체 또는 데이터 구조이다.
  • 함수형 인터페이스의 추상 메서드 시그니처는 람다 표현식의 시크니처르 ㄹ가리키고 이런 람다 표현식의 시그니처를 서술하는 메서드를 함수 디스크립터라 한다.
  • 함수 디스크립터는 함수의 이름, 매개변수, 반환값, 함수의 속성 등과 같은 함수에 관련된 다양한 정보를 포함한다.

실행 어라운드 패턴

자원을 처리하는 코드를 설정과 정리 두 과정이 둘러싸는 형태로, 이 패턴은 주로 리소스 관리나 예외 처리와 같이 중요한 보조 작업을 수행해야 할 때 유용하게 활용된다.

 

예시) 1. 함수형 인터페이스 정의

먼저 실행 어라운드 패턴을 구현하기 위한 함수형 인터페이스를 정의해야하는데 이 인터페이스는 주요 동작을 수행하는 메서드를 포함해야 한다.

@FunctionalInterface
interface FileOperation {
    void performFileOperation(BufferedReader br) throws IOException;
}

 

예시) 2. 실행 어라운드 메서드 작성
실행 어라운드 메서드를 작성하는데 이 메서드는 주요 동작을 람다 표현식으로 전달받아 실행한다. 

public void processFile(String filePath, FileOperation operation) {
    try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
        // 보조 작업 (리소스 할당, 예외 처리, 기타 작업)
        // ...

        operation.performFileOperation(br); // 주요 작업 (람다 표현식 실행)

    } catch (IOException e) {
        // 주요 작업에서 발생한 예외 처리
        System.err.println("파일 처리 중 예외 발생: " + e.getMessage());
    } finally {
        // 보조 작업 (리소스 해제, 정리, 기타 작업)
        // ...
    }
}

 

예시) 3. 실행 어라운드 패턴 사용

실행 어라운드 패턴을 사용하여 파일에서 텍스트를 읽는다.

public static void main(String[] args) {
        FileProcessor fileProcessor = new FileProcessor();
        String filePath = "파일경로.txt";

        // 실행 어라운드 패턴 사용
        fileProcessor.processFile(filePath, (br) -> {
            // 주요 작업 (파일에서 텍스트 읽기)
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        });
    }
}

 

1. Predicate<T> 인터페이스

Predicate 인터페이스는 단일 인자를 받아서 test 추상 메서드를 정의하고 boolean 값을 반환하는 함수를 나타내며, 주로 조건을 검사하거나 필터링을 수행하는 데 사용한다.

 

예시)

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

 

예시) 1~10 중 짝수만 고른다.

public static void main(String[] args) {
    // 1 ~ 10
    List<Integer> numbers = new ArrayList<>();
    for (int i = 1; i <= 10; i++) {
        numbers.add(i);
    }

    // Predicate를 사용하여 짝수를 필터링한다.
    Predicate<Integer> isEven = num -> num % 2 == 0;
    List<Integer> evenNumbers = filter(numbers, isEven);

    System.out.println("짝수: " + evenNumbers); // 출력 : 짝수: [2, 4, 6, 8, 10]
}
@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

// Predicate를 사용하여 리스트를 필터링하는 메서드
public static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
    List<T> result = new ArrayList<>();
    for (T item : list) {
        if (predicate.test(item)) {
            result.add(item);
        }
    }
    return result;
}

 

2. Consumer<T> 인터페이스

Consumer 인터페이스는 단일 인자를 받고 accept추상 메서드를 정의한다. 반환하지 않는 함수를 나타낸다. 주로 데이터를 소비하거나 처리할 때 사용한다. 

 

예시)

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}

 

예시) 이름을 순차적으로 출력한다.

public static void main(String ...args){
    List<String> names = new ArrayList<>();
    names.add("sjmoon");
    names.add("jjmoon");
    names.add("gdhong");
    forEach(names, (String s) -> System.out.print(s + " ")); // 출략 : sjmoon jjmoon gdhong
}

@FunctionalInterface
public interface Consumer<T>{
    void accept(T t);
}

public static <T> void forEach(List<T> list, Consumer<T> c){
    for(T t: list){
        c.accept(t);
    }
}

 

3. Function<T, R> 인터페이스

Function은 단일 인자를 받고, apply 추상 메서드를 정의한다. 다른 타입의 결과를 반환하는 함수를 나타내며, 주로 데이터 변환 및 매핑 작업에 사용된다.

 

예시)

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

 

예시) 이름의 길이를 구한다.

public static void main(String[] args) {
    List<String> members = new ArrayList<>();
    members.add("sjmoon");
    members.add("hi");
    members.add("hello");

    // Function을 사용하여 각문자의 길이를 구한다.
    Function<String, Integer> len = x -> x.length();
    List<Integer> strLength = map(members, len);

    System.out.println("문자 길이: " + strLength); // 출력 : 문자 길이: [6, 2, 5]
}

@FunctionalInterface
public interface Function<T,R> {
    R apply(T t);
}

// Function을 사용하여 리스트를 매핑하는 메서드
public static <T, R> List<R> map(List<T> list, Function<T, R> function) {
    List<R> result = new ArrayList<>();
    for (T item : list) {
        result.add(function.apply(item));
    }
    return result;
}

 

4. Supplier<T> 인터페이스

Supplier는 매개변수 없이 값을 제공하는 역할을 합니다. get 추상 메서드를 정의하고, 주로 데이터나 값을 생성하는 데 사용된다.

 

예시)

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

 

예시) 현재 시간을 반환한다.

public static void main(String[] args){
    Supplier<Date> getCurrentTime  = () -> new Date();

    Date currentTime = getCurrentTime .get();
    // 현재 시간을 반환
    System.out.println("현재 시간: " + currentTime); // 현재 시간: Sun Sep 24 16:17:58 KST 2023
}

 

5. UnaryOperator<T> 인터페이스

UnaryOperator 는 단일 인자를 받고 입력과 같은 타입을 반환하는 apply 추상 메서드를 정의한다.

 

예시)

@FunctionalInterface
public interface UnaryOperator<T> {
    T apply(T t);
}

 

예시) 대문자로 변환하여 출력

public static void main(String[] args) {
    UnaryOperator<String> toUpperCase = str -> str.toUpperCase();

    String name = "moon";
    String upperName  = toUpperCase.apply(name);

    System.out.println("name: " + name); // 출력 : name: moon
    System.out.println("upperName: " + upperName); // 출력 : upperName: MOON
}

 

6. BinaryOperator<T> 인터페이스

BinaryOperator는 두 개의 인자를 받아 같은 타입을 반환하는 apply 추상 메서드를 정의한다.

 

예시)

@FunctionalInterface
public interface BinaryOperator<T> {
    T apply(T t, T u);
}

 

예시) 덧셈 연산

public static void main(String[] args) {
    BinaryOperator<Integer> add = (a, b) -> a + b;

    int result = add.apply(2, 3);
    System.out.println("2 + 3 = " + result); // 출력 : 2 + 3 = 5
}

 

기본형 특화

  • 제네릭  함수형 인터페이스에서 사용하는 제네릭 파라미터참조형(Byte, Integer, List)만 사용할 수 있다.
  • 자바에서는 기본형(int, double, char)을 참조형으로 변환하는 오토 박싱 기능을 제공하는데 이런 변환 과정은 많은 메모리와 비용을 소모한다.
  • 자바 8에서는 오토방식을 피할 수 있는 특수한 함수형 인터페이스를 제공하는데 일반적으로 특정 형식의 입력을 받는 함수형 인터페이스 앞에 형식을 붙힌다.
함수형 인터페이스 함수 디스크립터 기본형 특화
Predicate<T> T → boolean IntPredicate, LongPredicate, DoublePredicate
Consumer<T> T → void IntConsumer, LongConsumer, DoubleConsumer
Function<T, R> T → R IntFunction<R>, IntToDoubleFunction, IntToLongFunction,
LongFunction, LongToDoubleFunction, IntToIntFunction,
DoubleFunction, DoubleToIntFunction, DoubleToLongFunction
Supplier<T> () → T  BooleanSupplier, IntSupplier, LongSupplier, DoubleSupplier
UnaryOperator<T> T → T IntUnaryOperator, LongUnaryOperator, DoubleUnaryOperator
BinaryOperator<T, T> (T, T) → T IntBinaryOperator, LongBinaryOperator, DoubleBinaryOperator

 

함수형 인터페이스와 예외 처리

  • 함수형 인터페이스는 확인된 예외를 던지는 동작이 허용되지 않기에 직접 정의하거나 람다를 try catch 블록으로 감싸야 한다.

예시) 인자가 숫자가 아니면 0을 반환

public static void main(String[] args) {
    Function<String, Integer> parseToInt = s -> {
        try {
            return Integer.parseInt(s);
        } catch (NumberFormatException e) {
            return 0; // 예외 발생 시 기본값으로 0을 반환
        }
    };

    String input = "123";
    int result = parseToInt.apply(input);
    System.out.println("결과값: " + result) // 출력 : 결과값 : 123;
}

형식검사

  • 형식 검사는 람다 표현식이 사용되는 컨텍스트에서 예상되는 형식과 일치하는지 확인한다.
  • 콘텍스트에서 기대되는 람다 표현식의 형식을 대상 형식이라고 한다.
  • 대상 형식이라는 특징 때문에 같은 람다 표현식이라도 다양한 함수형 인터페이스를 사용할 수 있다.

예시) 주어진 문자열이 대문자로만 이루어져 있는지를 검사

Predicate<String> isUpperCase1 = s -> s.equals(s.toUpperCase());
boolean result = isUpperCase.test("HELLO"); // 출력 : true

Function<String, Boolean> isUpperCase2 = s -> s.equals(s.toUpperCase());
boolean result = checkUpperCase.apply("HELLO"); // 출력 : true

형식추론

  • 바 8에서는 람다 표현식을 사용할 때 명시적으로 형식을 지정하지 않아도 컴파일러가 컨텍스트를 기반으로 형식을 추론할 수 있다.

예시) name의 형식을 포함하지 않아도 컴파일러가 String 형태를 추론할 수 있다.

ArrayList<String> names = new ArrayList<>();
names.add("sjmoon");
names.add("jjmoon");

names.forEach(name -> System.out.println(name));

지역변수 사용

  • 람다표현식도 익명함수 처럼 지역변수를 활용할 수 있고 이와 같은 동작을 람다 캡처링이라고 한다.
  • 람다는 인스턴스 변수와 정적 변수를 참조 할 수 있지만 지역변수는 명시적으로 final로 선언되어 있거나 한 번만 할당할 수 있는 변수여야한다.
  • 내부적으로 인스턴스 변수는 힙에 저장되는 반면 지역 변수는 스택에 위치하므로 람다에서 지역 변수에 접근할 때 람다가 스레드에서 실행 된다면 변수를 할당하 스레드가 사라져 원래 변수에 접근을 하지 못할 수 있다.

예시)

String name = "sjmoon";

Runnable runnable = () -> {
    System.out.println("Hello! " + name); // 출력 : Hello! sjmoon
};

runnable.run();
반응형

'Java' 카테고리의 다른 글

[자바8] 스트림  (0) 2023.12.30
[자바 8] 메서드 참조  (0) 2023.09.24
[자바 8] 람다 표현식  (0) 2023.09.18
[자바 8] 동작 파라미터화  (0) 2023.09.06
[자바] 자바 8  (0) 2023.09.01

+ Recent posts