본문 바로가기
IT

[Do it! 자바 프로그래밍 입문] 13 내부 클래스, 람다식, 스트림

by csongin 2022. 6. 10.
728x90
내부 클래스
람다식
스트림

내부 클래스

내부 클래스 요약

종류 구현 위치 사용할 수 있는 외부 클래스 변수 생성 방법
인스턴스 내부 클래스 외부 클래스 멤버 변수와 동일 외부 인스턴스 변수
외부 전역 변수
외부 클래스를 먼저 만든 후 내부 클래스 생성
정적 내부 클래스  외부 클래스 멤버 변수와 동일 외부 전역 변수 외부 클래스와 무관하게 생성
지역 내부 클래스  메서드 내부에 구현 외부 인스턴수 변수
외부 전역 변수
메서드를 호출할 때 생성
익명 내부 클래스  메서드 내부에 구현
변수에 대입하여 직접 구현
외부 인스턴스 변수
외부 전역 변수
메서드를 호출할 때 생성되거나, 인터페이스 타입 변수에 대입할 때 new 예약어를 사용하여 생성

람다식(lambda expression)

자바에서 함수형 프로그래밍(functional programming)을 구현하는 방식

자바 8부터 지원

클래스를 생성하지 않고 함수의 호출만으로 기능을 수행

 

함수형 프로그래밍

순수 함수(pure function)를 구현하고 호출함으로써 외부 자료에 부수적인 영향을 주지 않고 매개 변수만을 사용하도록 만든 함수

함수를 기반으로 구현

입력받은 자료를 기반으로 수행되고 외부에 영향을 미치지 않으므로 병렬 처리 등에 가능

안정적인 확장성 있는 프로그래밍 방식

 

람다식 구현하기

익명 함수 만들기

매개 변수와 매개 변수를 활용한 실행문으로 구현 (매개변수)->{실행문;}

// 두 수를 입력받아 더하는 add()함수

int add(int x, int y) {
    return x + y;
}

// 람다식
(int x, int y) -> {return x + y;}

함수 이름 반환 형을 없애고 ->를 사용

{ }까지 실행문을 의미

 

람다식 문법

// 매개변수가 하나인 경우 자료형과 괄호 생략하기
str -> {System.out.println(str);}

// 매개변수가 두개인 경우 괄호를 생략할 수 없음
x, y -> {System.out.println(x + y);} // 잘못된 형식

// 중괄호 안의 구현부가 한 문장인 경우 중괄호 생략
str -> System.out.println(str);

// 중괄호 안의 구현부가 한 문장이라도 return문은 중괄호를 생략할 수 없음
str -> return str.length(); // 잘못된 형식

//중괄호 안의 구현부가 반환문 하나라면 return과 중괄호 모두 생략
(x, y) -> x + y // 두 값을 더하여 반환함
str -> str.length() // 문자열의 길이를 반환함

람다식 사용하기 예제

두 수 중 더 큰 수를 반환하는 람다식 구현 활용

// 함수형 인터페이스 선언하기
public interface MyNumber {
    int getMax(int num1, int num2); // 추상 메서드 선언
}

// 람다식 구현과 호출
public class TestMyNumber {
    public static void main(String[] args) {
        MyNumber max = (x, y) -> (x >= y) ? x : y; // 람다식을 인터페이스형 max 변수에 대입
        System.out.println(max.getMax(10, 20)); // 인터페이스형 변수로 메서드 호출
    }
}

함수형 인터페이스

람다식을 선언하기 위한 인터페이스

익명 함수와 매개 변수만으로 구현되므로 단 하나의 메서드만을 가져야 함

(두 개 이상의 메서드인 경우 어떤 메서드의 호출인지 모호해 짐)

 

@FunctionalInterface 애노테이션

함수형 인터페이스라는 의미, 여러 개의 메서드를 선언하면 에러남

@FunctionalInterface
public interface MyNumber { // 메서드가 2개이므로 오류 발생

    int getMax(int num1, int num2);
    int add(int num1, int num2);
}

익명 객체를 선언하는 람다식

자바는 객체 지향 언어로 객체를 생성해야 메서드가 호출됨

람다식으로 메서드를 구현하고 호출하면 내부에서 익명 클래스가 생성됨

StringConcat concat3 = new StringConcat() {
    @Override
    public void makeString(String s1, String s2) {
        System.out.println( s1 + "," + s2);
    }
}

람다식에서 외부 메서드의 지역변수는 상수로 처리됨(지역 내부 클래스와 동일한 원리)

 

함수를 변수처럼 사용하는 람다식

프로그램에서 변수의 사용은

변수를 사용하는 경우 예시
특정 자료형으로 변수 선언 후 값 대입하여 사용하기 int a = 10;
매개변수로 전달하기 int add(int x, int y);
메서드의 반환 값으로 반환하기 return num;
// 인터페이스형 변수에 람다식 대입

interface PrintString {
    void showString(String str);
}

s -> System.out.println(s)
PrintString lambdaStr = s -> System.out.println(s); // 인터페이스형 변수에 람다식 대입
lambdaStr.showString("hello lambda_1");
// 매개변수로 전달하는 람다식

interface PrintString {
    void showString(String str);
}

public class TestLambda {
    public static void main(String[] args) {
        PrintString lambdaStr = s -> System.out.println(s);
        lambdaStr.showString("hello lambda_1");
        // 람다식을 인터페이스에 대입하고 그 변수를 사용해 람다식 구현부 호출
        showMyString(lambdaStr); // 메서드의 매개변수로 람다식을 대입한 변수 전달
    }
    
    public static void showMyString(PrintString p) { // 매개변수를 인터페이스형으로 받음
        p.showString("hello lambda_2);
    }
}

 

// 반환값으로 쓰이는 람다식

interface PrintString {
    void showString(String str);
}

public class TestLambda {
    public static void main(String[] args) {
        ...
        PrintString reStr = returnString(); // 변수로 반환받기
        reStr.showString("hello "); // 메서드 호출
    }
    
    public static void showMyString(PrintString p) {
        p.showString("hello lambda_2");
    }
    
    // 람다식을 반환하는 메서드
    public static PrintString returnString() {
        return s -> System.out.println(s + "world"); 
    }
}

스트림(stream)

 

자료의 대상과 관계없이 동일한 연산을 수행

배열, 컬렉션을 대상으로 동일한 연산을 수행함

일관성 있는 연산으로 자료의 처리를 쉽고 간단하게 함

 

한번 생성하고 사용한 스트림은 재사용할 수 없음

자료에 대한 스트림을 생성하여 연산을 수행하면 스트림은 소모됨

다른 연산을 위해서는 새로운 스트림을 생성함

 

스트림 연산은 기존 자료를 변경하지 않음

자료에 대한 스트림을 생성하면 별도의 메모리 공간을 사용하므로 기존 자료를 변경하지 않음

 

스트림 연산은 중간 연산과 최종 연산으로 구분됨

스트림에 대해 중간 연산은 여러 개 적용될 수 있지만 최종 연산은 마지막에 한 번만 적용됨

최종 연산이 호출되어야 중간 연산의 결과가 모두 적용됨

이를 '지연 연산'이라고 함

 

스트림 생성하고 사용하기

// 정수 배열에 스트림 생성하고 사용하기

public class IntArrayTest{
    public static void main(Stringp[] args) {
        int[] arr = {1,2,3,4,5};
        
        int sumVal = Array.stream(arr).sum(); // sum() 연산으로 arr 배열에 저장된 값을 모두 더함
        int count = (int)Arrays.stream(arr).count(); // count() 연산으로 arr 배열의 요소의 개수를 반환함
        // count() 메서드의 반환 값이 long이므로 int형으로 변환
        
        System.out.println(sumVal);
        System.out.println(count);
    }
}
ArrayList에 스트림 생성하고 사용하기
public class ArrayListStreamTest {
    public static void main(String[] args) {
        List<String> sList = new ArrayList<String>();
        sList.add("Tomas");
        sList.add("Edward");
        sList.add("Jack");
        
        Stream<String> stream = sList.stream(); // 스트림 생성
        stream.forEach(s->System.out.print(s + " ")); // 배열의 요소를 하나씩 출력
        System.out.println();
        
        sList.stream().sorted().forEach(s->System.out.println(s));
    } // 스트림 새로 생성, 정렬, 요소를 하나씩 꺼내어 출력
}

reduce() 연산

정의된 연산이 아닌 프로그래머가 직접 지정하는 연산을 적용

최종 연산으로 스트림의 요소를 소모하며 연산 수행

배열의 모든 요소의 합을 구하는 reduce() 연산

Arrays.stream(arr).reduce(0, (a, b) -> a + b); // 각 요소가 수행해야 할 기능

두 번째 요소로 전달되는 람다식에 따라 다양한 기능을 수행

 

reduce() 연산 예제

배열에 여러 문자열이 있을 때 길이가 가장 긴 문자열 찾기

import java.util.Arrays;
import java.util.function.BinaryOperator;

// BinaryOperator를 구현한 클래스 정의
class CompareString implements BinaryOperator<String> {
    @Override
    public String apply(String s1, String s2) {
        if(s1.getBytes().length >= s2.getBytes().length) return s1;
        else return s2;
    } // reduce() 메서드가 호출될 때 불리는 메서드, 두 문자열 길이들 비교
}
public class ReduceTest {
    public static void main(String[] args) {
        String[] greetings = {"안녕하세요~~~", "hello", "Good morning", "반갑습니다^^"};
        System.out.println(Arrays.stream(greetings).reduce("", (s1, s2) -> {
                            if(s1.getBytes().length >= s2.getBytes().length)
                             return s1;
                            else return s2;})); // 람다식을 직접 구현하는 방법
        String str = Arrays.stream(grettings).reduce(new CompareString()).get();
        System.out.println(str); // BinaryOperator를 구현한 클래스 사용
    }
}

 

반응형