본문 바로가기
개발 언어/Java

Java8 Functional Interface 간단한 예제로 알아보기

by 정권이 내 2023. 6. 4.

Java8 특징과 추가된 개념들, 간단한 예제

 

Java8의 주요 개념

java8에서 추가된 주요 개념들은 아래와 같은데 각 개념에 대해 자세하게 알아보기 전에 간단한 예제들로 java8 문법 맛보기를 해보겠습니다.

  • 람다 표현식
  • 함수형 인터페이스
  • 스트림 API
  • 병렬 스트림
  • 기본 메서드

 

목표

  • 1~10 숫자를 모두 문자열 형태로 연결하고 각 숫자 사이에는 " : " 콜론으로 이어지게 한다.

 

1. java8 문법 미사용

public static void main(String[] args) {
    final List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

    final StringBuilder stringBuilder = new StringBuilder();
    final int size = numbers.size();

    for (int i = 0; i < size; i++) {
        stringBuilder.append(numbers.get(i));
        if (i != size - 1){
            stringBuilder.append(" : ");
        }
    }
    System.out.println("stringBuilder = " + stringBuilder);
}

java8 이전 방식으로 위 문제를 해결한다면 1~10의 리스트에 대해 루프를 활용하여 내부에서 문자열 형태로 바꾸고 구분자로 지정한 " : " 문자열을 직접 append 하는 방식으로 해결할수 있습니다.

루프를 사용하기위해 리스트의 사이즈를 계산해야 하며 마지막 값에는 구분자 값이 들어가면 안되기 때문에 그에 대한 처리 역시 필요하여 번거로운 방법입니다.

 

2. java8 문법 사용

public static void main(String[] args) {
    final List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

    final String result = numbers.stream()
            .map(String::valueOf)
            .collect(Collectors.joining(" : "));
    System.out.println("result = " + result);
}

java8 적용 방식은 적용 이전 방식과 다르게 코드가 간결해졌고 어떤 역할을 하는지 알아 보기 쉬워졌고 루프문을 직접적으로 사용하지 않기때문에 루프에서 발생가능한 무한루프와 같은 실수가 방지할수 있는 방식입니다.

이 코드에서는 Stream API를 이용하였는데 사용한 방식들을 살펴보겠습니다.

  • stream(): 리스트를 stream 형태로 변환.
  • map(): stream 내부의 값들을 특정 데이터 타입으로 변환.
  • collect(): stream의 값들을 하나로 병합하는데 " : " 라는 문자열을 구분자로 지정한다.

 

Java8 적용 예제

사칙연산을 수행하는 코드를 만들어보고 함수형 인터페이스 방식도 적용하여 어떤 차이점이 있는지 알아보겠습니다.

public class OopExample {
    public static void main(String[] args) {
        CalculatorService calculatorService = new CalculatorService();

        final int sum = calculatorService.calculate('+', 2, 2);
        System.out.println("sum = " + sum);
        final int subtract = calculatorService.calculate('-', 4, 2);
        System.out.println("subtract = " + subtract);
        final int multiple = calculatorService.calculate('*', 2, 2);
        System.out.println("multiple = " + multiple);
        final int divide = calculatorService.calculate('/', 4, 2);
        System.out.println("divide = " + divide);
    }
}

class CalculatorService {
    public int calculate(char calculation, int num1, int num2) {
        if (calculation == '+') {
            return num1 + num2;
        } else if (calculation == '-') {
            return num1 - num2;
        } else if (calculation == '*') {
            return num1 * num2;
        } else if (calculation == '/') {
            return num1 / num2;
        } else {
            throw new IllegalArgumentException("Unknown calculation: " + calculation);
        }
    }
}

우선 기존 방식으로 작성한 사칙연산 코드인데 Java의 OOP 관점에서 이 코드의 문제점은 SRP 원칙을 위배 한다는 것입니다. CalculatorService 라는 클래스에서 덧셈, 뺄셈, 나눗셈, 곱하기 총 4개의 작업을 수행하는데 SRP 원칙에 따라 하나의 클래스는 한가지 역할만 수행 해야하지만 네가지 역할을 수행 하고있습니다.

 

위 코드를 보완하여 인터페이스를 이용한 방식을 만들어보겠습니다.

public class OopExample2 {
    public static void main(String[] args) {
        CalculatorService2 addService = new CalculatorService2(new Addition());
        CalculatorService2 subtractService = new CalculatorService2(new Subtract());
        CalculatorService2 multipleService = new CalculatorService2(new Multiple());
        CalculatorService2 divideService = new CalculatorService2(new Divide());

        final int sum = addService.calculate(2, 2);
        System.out.println("sum = " + sum);
        final int subtract = subtractService.calculate(2, 2);
        System.out.println("subtract = " + subtract);
        final int multiple = multipleService.calculate(2, 2);
        System.out.println("multiple = " + multiple);
        final int divide = divideService.calculate(2, 2);
        System.out.println("divide = " + divide);
    }
}

interface Calculation {
    int calculate(int num1, int num2);
}

class Addition implements Calculation {
    @Override
    public int calculate(int num1, int num2) {
        return num1 + num2;
    }
}

class Subtract implements Calculation {
    @Override
    public int calculate(int num1, int num2) {
        return num1 - num2;
    }
}

class Multiple implements Calculation {
    @Override
    public int calculate(int num1, int num2) {
        return num1 * num2;
    }
}

class Divide implements Calculation {
    @Override
    public int calculate(int num1, int num2) {
        return num1 / num2;
    }
}

class CalculatorService2 {
    private final Calculation calculation;

    public CalculatorService2(Calculation calculation) {
        this.calculation = calculation;
    }

    public int calculate(int num1, int num2) {
        return calculation.calculate(num1, num2);
    }
}

인터페이스를 사용하면 SRP원칙은 지킬수 있지만 역할별로 인터페이스를 상속받는 클래스를 만들다 보니 실제 수행하는 역할에 비해서 코드가 너무 길어졌다는 문제가 있습니다.

 

이번엔 java8의 개념중 하나인 함수형 인터페이스(Functional Interface) 방식으로 리팩토링 해보겠습니다.

package org.example;

public class OopExample3 {
    public static void main(String[] args) {
        CalculatorService3 addService = new CalculatorService3((a, b) -> a + b);
        CalculatorService3 subtractService = new CalculatorService3((a, b) -> a - b);
        CalculatorService3 multipleService = new CalculatorService3((a, b) -> a * b);
        CalculatorService3 divideService = new CalculatorService3((a, b) -> a / b);

        final int sum = addService.calculate(2, 2);
        System.out.println("sum = " + sum);
        final int subtract = subtractService.calculate(2, 2);
        System.out.println("subtract = " + subtract);
        final int multiple = multipleService.calculate(2, 2);
        System.out.println("multiple = " + multiple);
        final int divide = divideService.calculate(2, 2);
        System.out.println("divide = " + divide);
    }
}

@FunctionalInterface
interface FpCalculation {
    int calculate(int num1, int num2);
}

class CalculatorService3 {
    private final FpCalculation FpCalculation;

    public CalculatorService3(FpCalculation FpCalculation) {
        this.FpCalculation = FpCalculation;
    }

    public int calculate(int num1, int num2) {
        return FpCalculation.calculate(num1, num2);
    }
}

java는 기본적으로 일급객체(First-class-citizen) 개념이 지원되지 않는 언어지만 함수형 인터페이스를 사용함으로 인해 함수를 일급함수(first-class-function) 라는 개념으로 대체하여 쓸수 있게 되는것입니다.

일급 객체에 대한 자세한 내용은 아래 글을 참고해주시기 바랍니다.

 

[Java] 함수형 프로그래밍(Functional Programming)의 개념 #순수함수 #1급객체

함수형 프로그래밍(Functional Programming) 일반적인 프로그래밍에서는 명령의 순서대로 호출됨에 따라 프로그램이 실행되는 명령형 프로그래밍이 대부분의 언어에서 사용하는 방식이었습니다. 함

ksr930.tistory.com

 

함수형 인터페이스에 대해 간단히 설명하면 단일 추상 메서드(SAM)를 가지는 인터페이스를 뜻하며 람다표현식이나 메서드 참조등 함수형 프로그래밍에서 사용하는 인터페이스 입니다.

위 코드에서 CalculatorService3 클래스 함수형 인터페이스를 인자로 받는 생성자가 있고 main 메서드에서 클래스 인스턴스를 만들때 람다표현식으로 메서드를 전달하는 것입니다.

 

간단한 예제를 통해서 java8에서 추가된 기본 개념들인 람다표현식, 스트림, 함수형 인터페이스 등에 대해 알아보았고 다음 글에서는 java8의 주요 Functional Interface인 Function, Consumer, Supplier, Predicate에 대해서 정리해보겠습니다.

반응형

댓글