정의

스트림이란?

데이터 처리 연산을 지원하는 추출된 연속된 요소.

자바 8 컬렉션에 stream 메서드 및 java.util.steam.Stream 에 인터페이스 정의가 추가되었다.

 

특징

1. 주제

컬렉션과 마찬가지로 스트림은 특정 요소 형식의 연속된 값 집합 인터페이스 제공.

컬렉션은 주제가 데이터이면 스트림은 데이터의 계산입니다.

 

2. 소스

스트림은 컬렉션, 배열, I/O 자원 등 데이터 제공 소스로부터 데이터를 소비.

정렬된 컬렉션으로 스트림을 생성하면 정렬이 그대로 유지.

 

3. 연산

일반적인 함수형 프로그래밍 언어 및 DB 비슷한 연산 기능 지원.

filter, map, reduce, find, match, sort 등 데이터를 조작. 

연산을 순차적, 병렬적으로 실행 가능.

 

4. 파이프라이닝

스트림 연산은 연산 끼리 연결하여 파이프 라이닝 구성 가능.

 

5. 내부 반복

외부에서 명시적으로 반복하는 컬렉션과 달리 스트림은 내부 반복 지원.

 

스트림과 컬렉션

1. 데이터 계산

컬렉션은 현재 자료구조가 포함하는 모든 값을 메모리에 저장하는 구조.

반면 스트림은 이론적으로는 요청할 때만 요소를 계산하는 구조.

 

2. 탐색

반복자와 마찬가지로 스트림도 소비되고 나면 반복 사용할 수가 없습니다.

 

3. 데이터 반복 처리

컬렉션은 요소를 사용자가 for 문 등을 사용하여 직접 요소를 꺼내어 연산하고 다시 요소를 넣는 외부 반복을 이 필요.

반면 스트림은 반복은 내부에서 알아서 처리하고 어떤 연산을 할지만 지정하면 처리가 가능.

 

스트림 연산

1. 중간 연산

filter나 sorted 같은 중간 연산은 다른 스트림을 반환.

중간 연산을 연결하여 파이프 라이닝 가능.

 

2. 최종 연산

스트림 파이프라인에서 List, Integer 등 스트림 이외의 결과를 반환.

 

비교 예제

기존 외부 반복을 통한 연산과 스트림을 통한 연산을 비교해보기 위한 간략한 예제.

1. Game 이라는 게임명, 가격을 가진 객체를 생성.

2. 30,000원 초과하는 가격의 게임의 게임명 리스트를 반환.

public class Game {
	private String name;
	private int price;
	
	public Game(String name, int price) {
		this.name = name;
		this.price = price;
	}
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getPrice() {
		return price;
	}
	public void setPrice(int price) {
		this.price = price;
	}
}

 

 

package demo;

import java.util.ArrayList;
import java.util.List;
import static java.util.stream.Collectors.toList;

public class Main {
	public static void main(String[] args) {
		
        // 1. 데이터 초기화
		List<Game> gameList = new ArrayList<>();
		gameList.add(new Game("Hell Gate", 39900));
		gameList.add(new Game("Heaven Gate", 49900));
		gameList.add(new Game("DIA 1", 10000));
		gameList.add(new Game("DIA 2", 20000));
		gameList.add(new Game("DIA 3", 30000));
		
        // 2. 외부반복을 통한 처리
		List<String> highPriceGameList = new ArrayList<>(gameList.size()); 
		
		for(Game game : gameList) {
			if (game.getPrice() > 30000) {
				highPriceGameList.add(game.getName());
			}
		}
		System.out.println(highPriceGameList);
	}
}

 

package demo;

import java.util.ArrayList;
import java.util.List;
import static java.util.stream.Collectors.toList;

public class Main {
	public static void main(String[] args) {
		
        // 1. 데이터 초기화
		List<Game> gameList = new ArrayList<>();
		gameList.add(new Game("Hell Gate", 39900));
		gameList.add(new Game("Heaven Gate", 49900));
		gameList.add(new Game("DIA 1", 10000));
		gameList.add(new Game("DIA 2", 20000));
		gameList.add(new Game("DIA 3", 30000));
		
        // 스트림 연산을 통한 처리
		List<String> highPriceGameList = gameList.stream()
							.filter(game -> game.getPrice() > 30000)
							.map(Game::getName)
							.limit(gameList.size())
							.collect(toList());
		System.out.println(highPriceGameList);
	}
}

 

마치며

비교적 간단한 예제라서 코드 길이는 큰 차이가 없어 보이지만 파이프 라이닝과 외부 반복 연산 없이

내부 연산을 통해 가독성이 좋게 처리가 가능하다.

 

'Java > Stream' 카테고리의 다른 글

기본형 특화 스트림  (0) 2022.10.27
리듀싱  (0) 2022.10.26
검색과 매칭  (0) 2022.10.26
map과 flatMap  (0) 2022.10.17
필터링 및 슬라이싱  (0) 2022.10.10

최근 이직을 하면서 인수인계와 면접 일정이 겹치면서 게시물 업로드를 하지 못했다.

인프런을 통해 JPA를 공부 중이지만 여러 가지 일정이 겹치니 게시물을 정리해서 업로드를 할 시간조차 부족했다.

회사의 사업계획이 바뀌면서 순식간에 팀원들이 빠져나가면서 어쩌다 보니 맨 마지막에 퇴사를 하게 되었고

내가 퇴사할 시점 개발자는 회사에 한 명도 남지 않았다.

수도권에서도 외곽에 거주하다 보니 회사를 선택하는데 극히 제한적일 수밖에 없었다.

조금만 서울 쪽으로 나가면 편도 2시간 이상 출퇴근 시간이 걸린다.

사실 기존 회사의 처우가 좋은 편은 아니지만 좋은 개발자 동료 분들이 있어서 고민하던 차에 

이번 일들이 트리거가 되었다.

 

이직 시 고려한 점은 크게 아래와 같다

 

1. 연봉 및 처우

- 기존 연봉 및 처우가 열악하여 이제는 다른 비슷한 중소 신입~4년 차와 크게 연봉 차이가 나지 않았다.

  여러 가지 돈 들어가는 일이 많아졌기 때문에 신입 때와는 다르게 가장 중요하게 생각하는 부분이었다.

 

2. 회사의 비전

- 업계 성장성 및 회사의 자본력을 확인했다.

  결국 좋은 아이템을 가지고 있어도 투자가 없으면

  아무리 시장 선점한 기업도 그 자리를 뺏기는 것을 확인했다.

 

3. 출퇴근 거리

- 기존 1~2시간의 출퇴근 거리에 따른 시간과 체력 소모가 상당히 아까웠다.

   하루에 1~2시간이면 운동도 할 수 있고 책이라도 한 페이지 더 볼 수 있는 시간이었다.

 

4. 성장 환경

- 회사에서 개발자 동료들이 같이 일하면서 성장할 수 있다.

  코드 리뷰를 하는 곳이면 좋고 그렇지 않아도 개발자들이 많으면

  어떻게든 같이 협업하면서 배우는 점이 많았다.

  만약 그런 동료들이 없다면 개인적으로 강의, 책, 연습해보면서 노력하는 수밖에 없다.

  다만 한계점은 있다. 

 

이러한 점을 고려해서 여러 군데를 면접을 보고 합격한 곳 중에 좋은 가장 좋은 제안을 받은 곳으로 고민 끝에 최종 결정을 하였다.

 

 

'생각정리' 카테고리의 다른 글

블로그 이전  (0) 2023.11.05
코딩 테스트 인터뷰 리뷰  (0) 2023.01.11
리프레시 & 코로나  (0) 2022.11.24
인앱 결제 진행 중 생각정리  (0) 2022.05.26
코딩테스트  (0) 2022.03.30

람다 표현식?

람다 표현식은 간략하게 표현하면 익명 함수의 단순화입니다.

 

특징

1. 익명(이름 없음)으므로 작성할 코드량이 줄어듭니다.

2. 특정 클래스에 종속되지 않으므로 메서드 대신 함수라고 부르지만

    메서드 처럼 파라미터 리스트, 바디, 반환 형식, 예외 리스트를 포함합니다.

3. 람다 표현식은 메서드 인수로 전달하거나 변수에 저장 가능합니다.

 

기존 익명함수 사용한 코드

new Thread(new Runnable() {

    @Override
    public void run() {
        System.out.println("Thread Start");		
    }
}).start();
Comparator<Integer> num = new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o1.compareTo(o2);
    }
};

람다 표현식을 사용한 코드

new Thread(() -> System.out.println("Thread Start")).start();
Comparator<Integer> ramDaExpNum1 = (o1, o2) -> o1.compareTo(o2);
Comparator<Integer> ramDaExpNum2 = (Integer o1, Integer o2) -> o1.compareTo(o2);

람다 파라미터는 위와 같이 생략이 가능합니다.

 

함수형 인터페이스

함수형 인터페이스는 오직 하나의 추상 메서드만 가지는 인터페이스입니다.

public class Main {
	public static void main(String[] args) {
    		Concat c = (a, b) -> System.out.println(a+b);	
		c.concat("홍", "길동");
		c.concat("11월", "11일");
    	}
}

@FunctionalInterface 어노테이션

interface에 추가해주면 컴파일 시점에 해당 인터페이스가 규칙을 지키는지 검증합니다.

@FunctionalInterface
public interface Concat {
	void concat(String a, String b);
}

 

기본 제공 함수형 인터페이스 

Interface Descripter Abstract Method
Predicate T -> boolean boolean test(T t)
Consumer T -> void void accept(T t)
Supplier () -> T T get()
Function<T, R> T -> R R apply(T t)
Comparator (T, T) -> int int compare(T o1, T o2)
Runnable () -> void void run()
Callable () -> T V call()

 

함수형 인터페이스 사용

함수형 인터페이스의 추상 메서드의 시그니처는 람다 표현식의 시그너처를 나타냅니다.

람다 표현식의 시그니처를 서술하는 메서드를 함수 디스크립터(descriptor)라고 하고

추상 메서드의 시그니처와 함수 디스크립터가 같다면 함수형 인터페이스를 활용 가능합니다.

 

public class Main {
	public static void main(String[] args) {
		Runnable r1 = () -> System.out.println("Runnable" + 1);
		Runnable r2 = () -> System.out.println("Runnable" + 2);
		print(r1);
		print(r2);
	}
	
	public static void print(Runnable r) {
		 r.run();
	}
}

위와 같이 자주 변경되는 요구사항이 있다면 매번 메서드를 생성할 것이 아니라 변경하는 부분만 람다 함수로 만들어

활용이 가능합니다.

 

메서드 참조

메서드 참조를 사용하여 가독성을 높일 수 있습니다.

 

메서드 참조를 사용하지 않은 코드

public class Main {
	public static void main(String[] args) {
		List<String> list = Arrays.asList("a","b","A","B");
		list.sort((a, b) -> a.compareToIgnoreCase(b));
		for(String str : list) {
    		System.out.print(str);
		}
  	}
}

 

메서드 참조를 사용 코드

public class Main {
	public static void main(String[] args) {
		List<String> list = Arrays.asList("a","b","A","B");
		list.sort(String::compareTo);
		list.stream().forEach(System.out::print); // 스트림 사용
  	}
}

 

지역변수의 사용

외부에 정의된 변수를 사용할 수 있습니다. 이를 람다 캡쳐링이라고 합니다.

public class Main {
	public static void main(String[] args) {
		int num = 12345;
		Runnable r = () -> System.out.println(num);
        // 아래 주석 해제 시 에러 발생.
        // num = 123;
  	}
}

여기서 지역 변수는 final로 선언하거나 final 변수처럼 사용해야 합니다.

람다가 실행되는 스레드에서 변수를 할당한 스레드가 해제되었는데도 접근하려 할 수 있기 때문에

이 같은 제약이 있다고 합니다.

 

 

import java.util.Arrays;
import java.util.Scanner;

public class TestDemo {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int N = scanner.nextInt();

        int[] arr = new int[N+1];
        int[] dp = new int[N+1];
        
        for (int i=0; i<N; i++) {
        	arr[i] = scanner.nextInt();
        }
        
        // 10, 20, 10, 30, 20, 50 
        // 10, 20, 10, 25, 20, 43
        
        dp[0] = 1;
        for (int i=1; i<N; i++) {
        	dp[i] = 1;
        	for (int j=0; j<i; j++) {
        		if(arr[i] > arr[j] && dp[j]+1 > dp[i]) {
        			dp[i] = dp[j] + 1;
        		}
        	}
        }
        Arrays.sort(dp);
        System.out.println(dp[N]);

       // OptionalInt ans = Arrays.stream(dp).max();
       // System.out.println(ans.getAsInt());
        
        scanner.close();
    }
}

문제

https://www.acmicpc.net/problem/2579

분석/설계

다이나믹 프로그래밍으로 풀어봤습니다.

점화식을 아래와 같이 만들어봤습니다. 

dp[n] = Math.max(dp[n-3] + arr[n-1], dp[n-2]) + arr[n]

dp[1], dp[2], dp[3] 같은 예외 사항들은 직접 식을 정의해주고 처리했습니다.

구현 코드

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int N = scanner.nextInt();

        int[] arr = new int[301];
        int[] dp = new int[301];
        for (int i = 1; i <= N; i++) {
        	arr[i] = scanner.nextInt();
        }
        	
        dp[1] = arr[1];
        dp[2] = arr[1] + arr[2];
        dp[3] = Math.max(arr[1], arr[2]) + arr[3];

        for (int n = 4; n <= N; n++) {
            dp[n] = Math.max(dp[n - 3] + arr[n - 1], dp[n - 2]) + arr[n];
        }

        System.out.println(dp[N]);
        scanner.close();
    }
}

정리

바텀업이 익숙해서인지 탑다운으로는 풀이를 잘 안하게됩니다.

탑다운으로 푸는 방식들이 많이 있으니 혹시 안풀릴 경우 검색해보시거나 아래 링크를 참고하면 도움이됩니다.

다이나믹은 바텀업의 경우 예시를 들어놓고 점화식으로 정리할경우 매우 빠르게 풀수있습니다.

익숙해지려면 많이 문제 유형을 만나보고 점화식을 정의할수 있게 고민해야 봐야할 것 같습니다.

참고

https://www.acmicpc.net/problem/2579

https://st-lab.tistory.com/132

문제

https://www.acmicpc.net/problem/1149

분석/설계

입력 숫자의 범위가 2~1000 이므로 다이나믹 프로그래밍으로 풀어봤습니다.

점화식을 아래와 같이 만들어봤습니다. 

 

dp[N][0] = min( dp[N-1][1], dp[N-1][2] ) + dp[N][0]

dp[N][1] = min( dp[N-1][0], dp[N-1][2] ) + dp[N][1]

dp[N][2] = min( dp[N-1][0], dp[N-1][1] ) + dp[N][2]

구현 코드

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int N = scanner.nextInt();
        int arr[][] = new int[N+1][3];
        int dp[][] = new int[N+1][3];

        for (int i=1; i<=N; i++) {
            for (int j=0; j<3; j++) {
                arr[i][j] = scanner.nextInt(); 
            }
        }

        for (int i=1; i<=N; i++) {
            dp[i][0] = Math.min(dp[i-1][1], dp[i-1][2]) + arr[i][0];
            dp[i][1] = Math.min(dp[i-1][0], dp[i-1][2]) + arr[i][1];
            dp[i][2] = Math.min(dp[i-1][0], dp[i-1][1]) + arr[i][2];
        }

        System.out.println(Math.min(Math.min(dp[N][0], dp[N][1]), dp[N][2]));
        scanner.close();
    }
}

정리

다이나믹 프로그램은 탑다운과 바텀업 방식이 있습니다.

이 문제는 점화식을 만들고 바텀업 방식으로 풀었고, 문제만 보고 점화식이 잘 정의가 안되면

예시를 여러 개 만들고 패턴을 찾아내어 점화식을 만들고 알고리즘을 적용하여 코드를 작성하면 

수월합니다.

참고

https://www.acmicpc.net/problem/1149

+ Recent posts