Notice
Recent Posts
Recent Comments
Link
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

블로그 이름 뭐하지

[JAVA] 모던 자바 : 람다식과 스트림 본문

JAVA

[JAVA] 모던 자바 : 람다식과 스트림

가는말이고우면오는말은come 2024. 9. 26. 21:19

모던자바

시장의 변화에 따라 자바 8부터는 새로운 요구사항을 맞이하게 되었는데,

대표적으로 병렬처리와 함수형 프로그래밍의 도입이 있다.

병렬처리는 스트림, 함수형 프로그래밍은 람다식에 도입했다.

 

1) 병렬처리

빅데이터의 처리와, 병렬 프로그래밍 방식이 가능한 장비들이 보급됨에 따라 필요성이 증가했다.

병렬처리란 여러 개의 작업을 동시에 실행하여 효율을 높이는 것을 의미한다.

 

여러 쓰레드를 사용하는 방식과 여러 프로세스를 사용하는 방식이 있는데,

각각 멀티쓰레드, 멀티 프로세싱이라 불린다.

순차적인 직렬 프로그램을 분할하고, 분할된 단위를 동시에 병렬로 수행하여 성능을 향상시킨다.

 

데이터 병렬화(백터화)

어떠한 데이터 집합을 분해한 뒤 각 프로세서에 할당하여 동일한 연산을 수행하는 패턴

테스크 병렬화

수행할 작업들을 기능별로 분해한 뒤 각 프로세서에 할당하여 서로 다른 기능을 동시에 수행하는 패턴

 

 

2) 함수형 프로그래밍

자바의 객체지향 프로그래밍처럼 프로그래밍 패러다임의 한 부분이다.

프로그래밍 종류 핵심 효용
객체지향 프로그래밍 프로그램을 객체들의 협력과 상호작용으로 바라보고 구현 코드의 재사용성이 높아짐
코드를 유지보수, 확장하기 수월함
코드를 신뢰성 있게 사용하기 좋음
함수형 프로그래밍 프로그램을 순수함수의 모음으로 바라보고 구현.
(순수 함수: 특정한 데이터에 의존하지 않고, 관련없는 데이터를 변경하지 않으며, 결과 값이 오직 입력 값에만 영향을 받는 함수)
검증이 쉬움(검증이 필요한 부분만 검증)
성능 최적화가 쉬움(특정 input에 대한 output 재사용가능)
동시성 문제 해결하기 쉬움(함수는 다른 값 변경x)

 

람다식

메서드를 하나의 식으로 표현한 것.

하나의 식으로 표현해 간략하고, 메서드의 이름과 반환값이 없어져 '익명함수'라고도 불린다.

return문의 여부에 따라 {}도 생략이 가능하다.

함수를 값으로 전달하는 데, 어딘가에 구현하지 않고 간단히 구현해 넘기고 싶을 때 이용한다.

//일반적인 메서드
반환타입 메서드명 (매개변수){
	코드
}

//람다식
(매개변수) -> {코드}

 

//(1)
public int toLamdaMethod(int a, int b){
	return a + b;
}

(a, b) -> {a + b};

//(2)
public int toLamdaMethod(){
	return 100;
}

() -> {100};

//(3)
public void toLamdaMethod(){
	System.out.println("Hello");
}

() -> System.out.println("Hello")

 

람다식은 일급객체로 취급된다.

일급객체 : 변수나 데이터, 함수의 파라미터, 리턴 값으로 사용될 수 있다.

//1) 변수나 데이터에 담을 수 있다.
public class Main {
    public static void main(String[] args) {
    	// 람다식을 인터페이스 타입 변수에 할당
        Consumer<String> c = (t) -> System.out.println(t); 
        c.accept("Hello World");
    }
}


//2) 함수의 파라미터(매개변수)로 전달할 수 있다.
public class Main {
   
    public static void print(Consumer<String> c, String str) {
        c.accept(str);
    }
	// 메소드 매개변수로 람다 함수를 전달
    public static void main(String[] args) {
        print((t) -> System.out.println(t) ,"Hello World");
    }
}

//3) 함수의 리턴값으로 사용할 수 있다.
import java.util.function.Consumer;

public class Main {
    public static Consumer<String> hello() {
        // 람다 함수 자체를 리턴함
        return (t) -> {
            System.out.println(t);
        };
    }

    public static void main(String[] args) {
        Consumer<String> c = hello();
        c.accept("Hello World");
    }
}

 

함수형 인터페이스와 람다

함수형 인터페이스: 구현해야 할 추상 메서드가 하나만 정의된 인터페이스

@FunctionalInterface를 위에 명시하여 선언하며, 두 개 이상의 메서드가 선언되면 오류가 발생한다.

// 함수형 인터페이스 선언
@FunctionalInterface
interface Math {
    public int Calc(int first, int second);
}

// 추상메서드 구현 및 함수형 인터페이스 사용(람다식)
public static void main(String[] args){

   Math plusLambda = (first, second) -> first + second;
   System.out.println(plusLambda.Calc(4, 2)); //결과 : 6

   Math minusLambda = (first, second) -> first - second;
   System.out.println(minusLambda.Calc(4, 2)); //결과 : 2

}

 

스트림

자료구조(List, Map, Set...)의 흐름을 객체로 제공해주고, 그 흐름동안 사용할 수 있는 메서드를 api로 제공한다.

 

특징

1) 원본의 데이터를 변경하지 않는다.(=clone())

: 자바 컬렉션으로부터 스트림(해당 컬렉션의 흐름)을 받아 한 번 사용한다.

2) 일회용이다.

: 한 번 사용한 스트림은 어디에도 남지 않는다.(컬렉션은 재사용 가능)

3) 작업을 내부 반복으로 처리한다(작업이 간결함)

:반복문을 메서드 내부에 숨길 수 있다.

4) 지연된 연산(최종 연산에서 일괄 실행) 방식을 사용한다.

//스트림 생성
Stream<String> strStream1 = strList.stream();
Stream<String> strStream2 = Arrays.stream(strArr);

//스트림 생성시에
//스트림에 직접 접근하면 of()
//배열에 접근하면 stream()을 사용한다.

String strArr01 = new String[4];
Stream<String> stream01 = Stream.of(strArr01)

String strArr02 = new String[5];
Stream<String> stream02 = Arrays.stream(strArr02)
---------------------------------------------------

for(String str : strList){
    	System.out.println(str);
}
//스트림 사용
stream.forEach(System.out::println); // 메서드 참조
stream.forEach(System.out.println(str)); // 람다식

 

 

연산

stream 연산은 중간 연산과 최종 연산으로 나뉜다.

중간 연산은 반복적으로 여러 번 적용할 수 있고 연산 결과가 stream이다.

최종 연산은 연산의 끝에 한 번만 적용할 수 있고, 연산 결과가 stream이 아니다.

지연된 연산방식을 적용해 최종 연산에 진입하기 전까지는 연산이 수행되지 않는다.

중간 연산 설명
.distinct() 중복 제거
.filter(Predicate<T> p) 조건에 맞지 않는 요소 제외
.limit(long maxSize) 스트림의 일부를 잘라냄
.skip(long n) 스트림의 일부를 건너뜀
.peek(Consumer<T> action) 스트림의 요소에 작업 수행
.sorted()
.sorted(comparator<T> c)
스트림의 요소를 정렬
.map(Function<T, R> mapper)
.mapToDouble( ToDoubleFunction<T> mapper)
...
.flatmap(Function<T, Stream<R>> mapper)
.flatmap(Function<T, DoubleStream<R>> mapper)
...
스트림 요소를 변환

 

최종 연산 설명
.forEach(Consumer<? super T> action)
.forEachOrdered(Consumer<? super T>action)
각 요소에 지정된 작업 수행
.count() 스트림 요소의 개수 반환
.max(Comparator<? super T> c)
.min(Comparator<? super T> c)
스트림의 최댓값, 최솟값 반환
.findAny() //아무거나
.findFirst() //첫번째 요소
스트림의 요소 하나를 반환
.allMatch(Predicate<T> p) // 모두 만족
.anyMatch(Predicate<T> p) //하나라도 만족
.noneMatch(Predicate<T> p) // 모두 만족하지 않음
주어진 조건에 대한 만족여부
.toArray()
.toArray(IntFunction<A[]> generator)
스트림의 모든 요소를 배열로 반환
.reduce(BinaryOperator<T> accumulator)
.reduce(T identify, BinaryOperator<T> accumulator)
.reduce(U identify, BiFunction<U, T, V>,accumulator, BinaryOperator<T> combiner )
스트림의 요소를 하나씩 줄여가며 계산
.collect(Collector<T, A, R> c) 스트림의 요소를 수집

 

// members 변수는 여러 Member 객체를 담은 컬렉션이다.
// teenager 변수는 20살 아래는 Member 객체를 담을 컬렉션이다.

List<Member> teenagers = new ArrayList<>();
for (Member member : members) {
    if(member.getAge() < 20) {
        teenagers.add(member);
    }
}

List<String> teenagerNames = new ArrayList<>();
for (Member teenager : teenagers) {
    teenagerNames.add(teenager.getName());
}

-----------------스트림 변환

List<String> teenagerNames = members.stream()
        .filter(member -> member.getAge() < 20) // 내부 반복
        .map(Member::getName) // 파이프라이닝이 가능하다. 내부 반복
        .collect(toList());

 

병렬스트림

스트림의 장점으로 병렬처리가 수월하다.

병렬 처리를 한다고, 항상 빠른 결과를 얻을 수 있는 것은 아니다.

parallel() : 병렬화

sequential() : 병렬화 해제

public class ParalleCode{
	public static void main(String[] args){
    
    List<String> list = Arrays.asList("1", "2", "3");
    
    //스트림
    Stream<String> stream = list.stream();
    stream.forEach(ParellelCode::print);
    
    //스트림 병렬처리
    Stream<String> parallelStream = list.parallelStream();
    parallelStream.forEach(ParallelCode::print);
    
    public static void print(String s){
    	System.out.println(s + " : " + Thread.currentThread().getName());
    }
    //결과
    1: main
    2: main
    3: main
    1: ForkJoinPool.commonPool-worker-1
    2: ForkJoinPool.commonPool-worker-2
    3: ForkJoinPool.commonPool-worker-3
    }
}

참고한 링크

 

병렬처리

과거에는 슈퍼컴퓨터에만 적용되어 왔던 병렬컴퓨터 아키텍처가 현재 일반 PC에도 범용적으로 채택되어 컴퓨터의 성능 향상에 많은 기여를 한다. 현재 멀티코어 시대에서는 프로세서 간의 협력

velog.io

 

 

재미로 읽어보는 ‘병렬처리’

역사가 오래된 분야이자 작업 효율을 높여주어 최근 각광 받고 있는 주제 ‘병렬처리’에 대해 간단하게 소개해드리고자 합니다.

medium.com

 

 

스트림(stream)이란 무엇인가?

자바에서는 파일이나 콘솔의 입출력을 직접 다루지 않고, 스트림(stream)이라는 흐름을 통해 다룹니다. 스트림이란 실제의 입력이나 출력이 표현된 데이터의 이상화된 흐름을 의미합니다. 즉, 스

velog.io

 

'JAVA' 카테고리의 다른 글

[JAVA] 쓰레드  (0) 2024.09.25
[JAVA] 제네릭  (0) 2024.09.24
[JAVA] 예외 처리  (0) 2024.09.24
[JAVA] 추상 클래스와 인터페이스  (0) 2024.09.23
[JAVA] 상속  (0) 2024.09.23