[JS] 일급객체, 일급함수, Java의 람다 표현식...

Shy·2024년 5월 13일

JavaScript

목록 보기
3/5

일급 객체(First-Class Object)

프로그래밍 언어에서 특정 유형의 값이나 객체가 다른 값들과 동등하게 취급될 수 있다는 개념

사용할 때, 다른 요소들과 차별점이 없이 동등하다!

특징

1. 변수에 할당

일급 객체는 변수에 저장할 수 있다.

const x = 42;  // 숫자 42를 변수 x에 할당
const sayHello = function() { console.log("Hello!"); };  // 함수를 변수에 할당

2. 데이터 구조에 저장

일급 객체는 배열이나 객체 등의 데이터 구조에 저장할 수 있다.

const array = [1, 2, sayHello];  // 함수가 배열에 저장됨
const obj = { number: 42, greet: sayHello };  // 함수가 객체의 프로퍼티로 저장됨

3. 함수의 인자로 전달 가능

일급 객체는 함수의 인자로 사용할 수 있다.

function execute(func) {
    func();
}
execute(sayHello);  // 함수가 다른 함수의 인자로 전달됨

4. 함수의 반환값으로 사용 가능

일급 객체는 함수의 반환값으로 사용 가능하다.

function createGreeter() {
    return function() { console.log("Hello there!"); };
}
const greeter = createGreeter();  // 함수가 반환됨
greeter();  // "Hello there!" 출력

5. 동적인 프로퍼티를 가짐

일급 객체는 런타임에 동적으로 프로퍼티를 추가하거나 변경할 수 있다.

sayHello.language = "English";
console.log(sayHello.language);  // "English" 출력

예시(JavaScript)

자바스크립트에서의 함수는 일급 객체의 모든 특성을 갖고 있다. (즉, 함수가 일급객체로서의 특징을 가지므로 일급 함수가 된다! 일급 함수에 관한건 밑에서...)

// 1. 변수에 할당
const greet = function() {
    console.log("Hello, world!");
};

// 2. 데이터 구조에 저장
const functions = [greet, function() { console.log("Hi!"); }];

// 3. 함수의 인자로 전달
function callFunction(func) {
    func();
}
callFunction(greet);  // "Hello, world!" 출력

// 4. 함수의 반환값으로 사용
function createFunction() {
    return function() {
        console.log("This is a returned function.");
    };
}
const newFunction = createFunction();
newFunction();  // "This is a returned function." 출력

// 5. 동적으로 프로퍼티 추가
greet.language = "English";
console.log(greet.language);  // "English" 출력

일급 함수(First-Class Function)

일급 함수(First-Class Function)는 프로그래밍 언어에서 함수를 일급 객체로 다룬다는 것을 의미한다. 즉 함수가 다른 데이터 타입과 마찬가지로 변수에 할당되거나 데이터 구조에 저장될 수 있으며, 함수의 인자나 반환값으로 사용될 수 있다는 개념이다.

일급 함수의 개념은 함수형 프로그래밍을 가능하게 하고, 코드의 유연성과 재사용성을 크게 향상시킨다.

특징

함수가 일급객체의 특성을 갖는다는 것을 의미하므로, 즉, 함수가 다른 데이터 타입(예: 숫자, 문자열, 객체)과 동일하게 취급된다는 것을 의미한다. 이를통해 함수가 매우 유연하고 강력한 프로그래밍 도구로 사용 될 수 있다.

  1. 변수에 할당할 수 있다.
  2. 데이터 구조에 저장할 수 있다.
  3. 함수의 인자로 전달할 수 있다.
  4. 함수의 반환값으로 사용할 수 있다.
  5. 동적으로 프로퍼티를 가질 수 있다.

활용

일급 함수의 개념을 활용하면, 고차 함수, 콜백 함수, 클로저와 같은 강력한 프로그래밍 패턴을 구현할 수 있다. 이러한 패턴들은 코드의 유연성을 높이고, 재사용성을 증대시키며, 유지보수성을 향상시킨다.

1. 고차 함수(Higher-Order Functions)

고차함수는 다른 함수를 인자로 받거나 함수를 반환하는 함수이다.

function map(array, func) {
    const result = [];
    for (let i = 0; i < array.length; i++) {
        result.push(func(array[i]));
    }
    return result;
}
const numbers = [1, 2, 3];
const doubled = map(numbers, function(x) {
    return x * 2;
});
console.log(doubled);  // [2, 4, 6]

2. 콜백 함수(Callback Functions)

콜백 함수는 다른 함수의 인자로 전달되어 특정 작업이 완료된 후 호출되는 함수이다

function fetchData(callback) {
    setTimeout(function() {
        const data = "Some data";
        callback(data);
    }, 1000);
}
fetchData(function(data) {
    console.log(data);  // "Some data" 출력 (1초 후)
});

3. 클로저(Closures)

클로저는 함수가 선언될 때의 환경(lexical scope)을 기억하여, 그 함수가 다른 환경에서 호출되더라도 그 환경에 접근할 수 있게 한다.

function makeCounter() {
    let count = 0;
    return function() {
        count++;
        return count;
    };
}
const counter = makeCounter();
console.log(counter());  // 1
console.log(counter());  // 2

Java의 람다 함수

자바에서는 기본적으로 함수(메소드)가 일급 객체는 아니지만, 자바 8 버전에서 도입된 람다 표현식(lambda expressions)을 통해 함수를 일급 객체처럼 다룰 수 있게 되었다. 람다 표현식은 함수형 프로그래밍의 특징을 도입하여, 메소드를 하나의 식(expression)으로 표현할 수 있게 해준다.

이는 메소드를 일반 객체처럼 변수에 할당하거나 다른 메소드의 인자로 전달할 수 있게 해주는 기능을 제공한다.

자바에서 람다 표현식은 주로 함수형 인터페이스(functional interface)를 통해 사용된다. 함수형 인터페이스는 하나의 추상 메소드만을 가진 인터페이스로. 람다 표현식은 이 추상 메소드의 구현체로 사용된다.

람다 표현식은 익명 함수(anonymous function)를 간결하게 표현하는 방법으로, 이를 통해 자바에서도 함수를 변수에 할당하거나, 함수의 인자로 전달하거나, 함수의 반환값으로 사용할 수 있게 되었다.

함수형 인터페이스 (Functional Interface)

람다 표현식을 사용하려면 함수형 인터페이스가 필요하다. 함수형 인터페이스는 하나의 추상 메소드를 가지는 인터페이스이다.
@FunctionalInterface 어노테이션을 사용하여 함수형 인터페이스임을 명시할 수 있다.

@FunctionalInterface
interface MyFunction {
    void apply();
}

람다 표현식의 문법

(parameters) -> expression
(parameters) -> { statements; }

예시

  1. 기본적인 람다 표현식
MyFunction myFunction = () -> System.out.println("Hello, world!");
myFunction.apply();  // "Hello, world!" 출력
  1. 파라미터를 받는 람다 표현식
@FunctionalInterface
interface MyFunctionWithParam {
    void apply(int x);
}
MyFunctionWithParam myFunctionWithParam = (x) -> System.out.println("Value: " + x);
myFunctionWithParam.apply(5);  // "Value: 5" 출력
  1. 값을 반환하는 람다 표현식
@FunctionalInterface
interface MyFunctionWithParam {
    void apply(int x);
}
MyFunctionWithParam myFunctionWithParam = (x) -> System.out.println("Value: " + x);
myFunctionWithParam.apply(5);  // "Value: 5" 출력

람다 표현식의 활용

자바의 표준 라이브러리에는 이미 많은 함수형 인터페이스가 포함되어 있다. 가장 대표적인 예로 java.util.function패키지 내의 인터페이스들이 있다.

  • Predicate: 인자를 받아 boolean을 반환하는 함수
  • Function: 인자를 받아 다른 타입의 값을 반환하는 함수
  • Supplier: 인자를 받지 않고 값을 반환하는 함수
  • Consumer: 인자를 받아 어떤 동작을 수행하고 값을 반환하지 않는 함수
  • BiFunction: 두 인자를 받아 값을 반환하는 함수

예시: Predicate

Predicate인터페이스는 주어진 인자를 테스트하여 boolean값을 반환하는 함수형 인터페이스이다. 이를 통해 필터링 작업을 간편하게 수행할 수 있다.

import java.util.function.Predicate;

public class LambdaExample {
    public static void main(String[] args) {
        Predicate<Integer> isEven = (x) -> x % 2 == 0;

        System.out.println(isEven.test(4));  // true
        System.out.println(isEven.test(3));  // false
    }
}

예시: Function

Function인터페이스는 하나의 인자를 받아 다른 타입의 값을 반환하는 함수형 인터페이스이다.

import java.util.function.Function;

public class LambdaExample {
    public static void main(String[] args) {
        Function<String, Integer> stringLength = (s) -> s.length();

        System.out.println(stringLength.apply("Hello"));  // 5
        System.out.println(stringLength.apply("Lambda"));  // 6
    }
}

예시: Supplier

Supplier인터페이스는 인자를 받지 않고 값을 반환하는 함수형 인터페이스이다. 주로 데이터를 공급하는 역할을 한다.

import java.util.function.Supplier;

public class SupplierExample {
    public static void main(String[] args) {
        // Supplier를 사용하여 문자열을 반환하는 람다 표현식
        Supplier<String> supplier = () -> "Hello, Supplier!";
        
        // Supplier를 호출하여 값을 얻음
        String result = supplier.get();
        System.out.println(result);  // "Hello, Supplier!" 출력
    }
}

예시: Consumer

Consumer인터페이스는 인자를 받아 어떤 동작을 수행하고 값을 반환하지 않는 함수형 인터페이스이다. 주로 입력값에 대한 어떠한 처리를 수행할 때 사용된다.

import java.util.function.Consumer;

public class ConsumerExample {
    public static void main(String[] args) {
        // Consumer를 사용하여 문자열을 출력하는 람다 표현식
        Consumer<String> consumer = (s) -> System.out.println(s);
        
        // Consumer를 호출하여 문자열을 출력
        consumer.accept("Hello, Consumer!");  // "Hello, Consumer!" 출력
    }
}

예시: BiFunction

BiFunction 인터페이스는 두 인자를 받아 값을 반환하는 함수형 인터페이스이다. 두 개의 입력값을 반아서 결과값을 생성하는 경우에 사용된다.

import java.util.function.BiFunction;

public class BiFunctionExample {
    public static void main(String[] args) {
        // BiFunction을 사용하여 두 정수를 더하는 람다 표현식
        BiFunction<Integer, Integer, Integer> add = (x, y) -> x + y;
        
        // BiFunction을 호출하여 두 정수의 합을 구함
        int result = add.apply(3, 5);
        System.out.println(result);  // 8 출력
    }
}

전체 예시

import java.util.function.Predicate;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.function.Consumer;
import java.util.function.BiFunction;

public class LambdaCombinedExample {
    public static void main(String[] args) {
        // Predicate 예시: 문자열의 길이가 5 이상인지 검사
        Predicate<String> isLongerThanFive = (s) -> s.length() >= 5;
        System.out.println(isLongerThanFive.test("Hello"));  // true
        System.out.println(isLongerThanFive.test("Hi"));     // false

        // Function 예시: 문자열을 정수로 변환
        Function<String, Integer> stringToLength = (s) -> s.length();
        System.out.println(stringToLength.apply("Hello"));  // 5

        // Supplier 예시: 고정된 문자열을 반환
        Supplier<String> stringSupplier = () -> "Generated String";
        System.out.println(stringSupplier.get());  // "Generated String"

        // Consumer 예시: 문자열을 출력
        Consumer<String> stringConsumer = (s) -> System.out.println("Consumed: " + s);
        stringConsumer.accept("Test String");  // "Consumed: Test String"

        // BiFunction 예시: 두 정수를 더한 후 결과를 문자열로 변환
        BiFunction<Integer, Integer, String> addAndConvert = (x, y) -> "Result: " + (x + y);
        System.out.println(addAndConvert.apply(4, 7));  // "Result: 11"

        // 모든 기능을 합친 예시
        String input = "Example";

        // Predicate를 사용하여 문자열의 길이가 5 이상인지 검사
        if (isLongerThanFive.test(input)) {
            // Function을 사용하여 문자열의 길이를 구함
            int length = stringToLength.apply(input);

            // BiFunction을 사용하여 길이에 10을 더하고 결과를 문자열로 변환
            String result = addAndConvert.apply(length, 10);

            // Consumer를 사용하여 결과를 출력
            stringConsumer.accept(result);  // "Consumed: Result: 17"
        }

        // Supplier를 사용하여 문자열을 생성하고 Consumer로 출력
        stringConsumer.accept(stringSupplier.get());  // "Consumed: Generated String"
    }
}

스트림 API와 람다 표현식

람다 표현식은 스트림 API와 함께 사용될 때 매우 강력하다. 스트림 API는 컬렉션 데이터를 처리하는 다양한 방법을 제공한다. 예를 들어, 필터링, 매핑, 집계 등의 작업을 람다 표현식과 함께 간결하게 구현할 수 있다.

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.function.Consumer;
import java.util.function.BiFunction;
import java.util.stream.Collectors;

public class LambdaStreamExample {
    public static void main(String[] args) {
        // Sample data
        List<String> names = Arrays.asList("John", "Jane", "Jack", "Doe", "Jill", "Bob");

        // Predicate: 이름의 길이가 3 이상인지 검사
        Predicate<String> lengthGreaterThanThree = (s) -> s.length() > 3;

        // Function: 이름을 대문자로 변환
        Function<String, String> toUpperCase = String::toUpperCase;

        // Supplier: 기본 메시지 제공
        Supplier<String> defaultMessage = () -> "No names found";

        // Consumer: 이름을 출력
        Consumer<String> printName = (s) -> System.out.println("Name: " + s);

        // BiFunction: 두 문자열을 합쳐서 반환
        BiFunction<String, String, String> concatenate = (s1, s2) -> s1 + " & " + s2;

        // 스트림을 사용하여 필터링, 변환, 출력 수행
        List<String> result = names.stream()
            .filter(lengthGreaterThanThree)  // 길이가 3 초과인 이름 필터링
            .map(toUpperCase)                // 이름을 대문자로 변환
            .collect(Collectors.toList());   // 결과를 리스트로 수집

        // 결과가 비어있으면 기본 메시지를 출력하고, 그렇지 않으면 이름들을 출력
        if (result.isEmpty()) {
            System.out.println(defaultMessage.get());
        } else {
            result.forEach(printName);
        }

        // BiFunction을 사용하여 리스트의 첫 두 이름을 결합하여 출력
        if (result.size() >= 2) {
            String combined = concatenate.apply(result.get(0), result.get(1));
            System.out.println("Combined: " + combined);
        }
    }
}

// Name: JOHN
// Name: JANE
// Name: JACK
// Name: JILL
// Combined: JOHN & JANE
  • 스트림을 사용하여 names 리스트의 각 요소를 처리한다.
  • filter 메소드를 사용하여 Predicate를 적용하여 길이가 3 초과인 이름들만 남긴다.
  • map 메소드를 사용하여 Function을 적용하여 이름을 대문자로 변환한다.
  • 변환된 결과를 collect 메소드를 사용하여 리스트로 수집한다.
  • 결과 리스트가 비어있으면 Supplier를 사용하여 기본 메시지를 출력한다.
  • 그렇지 않으면 Consumer를 사용하여 각 이름을 출력한다.
  • 결과 리스트의 첫 두 이름을 BiFunction을 사용하여 결합하고 출력한다.

위와 같이 스트림 API와 함수형 인터페이스를 결합하여 데이터를 처리하면 코드의 가독성과 유지보수성을 높일 수 있다.

profile
신입사원...

0개의 댓글