이번 글은 Collector의 분할에 대해서 정리해보겠습니다.

분할 함수는 맵의 key로 Boolean을 반환합니다.

참 아니면 거짓 값을 갖는 두 그룹으로 분류돼 맵이 결과로 반환되게 됩니다.

예제를 통해 확인해보겠습니다.

먼저 예제에 쓰일 games 객체 리스트를 생성합니다.

List<Game> games = Arrays.asList(
		 new Game("CALL 1", 20000, "Shooting")
		,new Game("DIA 3", 30000, "RPG")
		, new Game("DIA 2", 40000, "RPG")
		, new Game("DIA 1", 50000, "RPG")
);

 

partitioningBy

games 객체 리스트에서 RPG 라는 타입으로 분할하여 분류하는 예제입니다. 

Boolean 타입의 각각 false, true의 key 값이 생성되고 partitioningBy에 인수로 넣은 프레디케이트 값에 따라 분류됩니다. 

Map<Boolean, List<Game>> partitionedGame = games.stream()
.collect(partitioningBy(game -> game.getType().equals("RPG")));

partitionedGame.forEach((k,v) -> {
	System.out.println("<"+k+">");
	v.forEach(game -> System.out.println(game.getName()));
	System.out.println();
});

결과값

RPG 타입과 Shooting 타입으로 분류됩니다.

<false>
CALL 1

<true>
DIA 3
DIA 2
DIA 1

 

다음 예제는 앞에서 정리한 groupingBy과 partitioningBy 같이 활용한 예제입니다.

Map<Boolean, Map<String, List<Game>>> rpgGameByType = games.stream()
	.collect(partitioningBy(game -> game.getType().equals("RPG")
	, groupingBy(Game::getType)));
		
rpgGameByType.forEach((k,v) -> {
	System.out.println(k + " >");
	v.forEach((key, value) -> {
		System.out.println(key + " >");
		value.forEach(game -> System.out.println(game.getName()));
	});
	System.out.println();
});

결과값

false >
Shooting >
CALL 1

true >
RPG >
DIA 3
DIA 2
DIA 1

 

다음은 partitioningBy을 사용하여 소수를 분류하는 예제를 확인해보겠습니다.

먼저 소수를 판단하는 리턴하는 정적 메서드를 작성하겠습니다.

앞에서 배운 rangeClosed 메서드로 범위를 정하고 noneMatch 메서드를 사용하여 어떤 값들을 

찾을 것인지 조건을 정합니다.

public static boolean isPrime(int num) {
	int root = (int) Math.sqrt((double) num);
	return IntStream.rangeClosed(2, root).noneMatch(i -> num % i == 0);
}

앞에서 만든 isPrime 메서드를 활용하여 소수와 소수가 아닌 수로 분류하는 스트림을 작성합니다.

public static Map<Boolean, List<Integer>> partitionPrimes(int num) {
	return IntStream.rangeClosed(2, num).boxed()
				.collect(partitioningBy(x -> isPrime(x)));
}

partitionPrimes 메서드를 사용하여 분류하는 테스트를 해봅니다.

1~10까지 소수를 분류하여 출력합니다.

System.out.println(partitionPrimes(10));

결과값

{false=[4, 6, 8, 9, 10], true=[2, 3, 5, 7]}

 

마치며

스트림과 컬렉터를 쭉 살펴보면서 정리하다 보니 기존 DB 레벨에서 사용하던 SQL 처리 기능들을 

많이 Java 코드로 처리할 수 있게 많이 고민을 한 것 같습니다.

이전 글에 groupingBy, maxBy, counting는 SQL에 groupBy, max, count와 매칭이 됩니다.

현업에 있으면서 여러 회사를 거치면서 DB 프로시저로만 모든 비즈니스 로직 구현한 곳도 봤고

프로시저 없이 SQL과 Java를 적절히 섞어서 비즈니스 로직이 구현된 곳을 가장 많이 본듯합니다.

점점 JPA가 자리 잡으면서 예전처럼 Mybatis 프레임워크 위에서 SQL를 직접 구현하는 경우는 점점 줄고 있는데요.

물론 case by case로 적절히 기술을 적용하고 사용해야겠지만 이제는 DB 프로시저나 SQL을 직접 비즈니스 로직

구현하는 경우는 거의 없어지지 않을까 싶습니다.

물론 아직도 개발자가 거의 없거나 개발팀이 없는 경우 최소한에 인력으로 운영하면서 리팩터링에 대한 시간을

아까워하는 곳이 많이 있기 때문에 어느 정도 수요는 유지될 것으로 생각됩니다.

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

Collector 그룹화  (0) 2022.11.01
Collector 요약  (0) 2022.10.28
스트림 생성  (0) 2022.10.27
기본형 특화 스트림  (0) 2022.10.27
리듀싱  (0) 2022.10.26

이번 글에서는 Collector의 그룹화에 대해서 알아보겠습니다.

먼저 그동안 사용했던 games 객체 리스트를 조금 수정해보겠습니다.

 

type 속성을 추가하고, getter/setter 메서드를 추가했습니다.

그리고 생성자는 기존 것 외에 type을 추가하여 3가지 매개변수를 받는 생성자를 추가했습니다.,

package demo;

public class Game {
	private String name;
	private int price;
	private String type;
	
	public Game(String name, int price) {
		this.name = name;
		this.price = price;
	}
	
	public Game(String name, int price, String type) {
		this.name = name;
		this.price = price;
		this.type = type;
	}
	
	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;
	}

	public String getType() {
		return type;
	}

	public void setType(String type) {
		this.type = type;
	}
}

 

groupingBy

데이터를 하나 이상의 특성으로 분류하여 그룹화는 연산을 지원합니다.

아라 예제는 groupingBy를 활용하여 game 타입 별 그룹 연산을 하고 출력하여 결과를 확인해봅니다.

Map<String, List<Game>> gamesByType = games.stream().collect(groupingBy(Game::getType));
gamesByType.get("RPG").stream().forEach(game -> System.out.println(game.getName()));
gamesByType.get("Shooting").stream().forEach(game -> System.out.println(game.getName()));

결과값

DIA 3
DIA 2
DIA 1
CALL 1

 

인수로 메서드 참조 대신 람다 표현식으로 구현도 가능합니다. 

결과 값은 동일합니다.

Map<String, List<Game>> rpgGame = games.stream().collect(groupingBy(game -> {
	if(game.getType().equals("RPG")) return "RPG"; else return "Shooting";
}));

rpgGame.get("RPG").stream().forEach(game -> System.out.println(game.getName()));
gamesByType.get("Shooting").stream().forEach(game -> System.out.println(game.getName()));

 

그룹화 이후 연산이 하고 싶다면 filtering 등을 이용할 수 있지만 groupingBy 전에 사용하게 되면

특정 결과만 도출하고 반대되는 그룹 값들은 없어지게 된다.

예를 들면 RPG를 filtering 하고 groupingBy 하게 되면 RPG 결과값만 남고 Shooting 값은 사라지게 됩니다.

다음 예제와 같이 groupingBy에 인수로 filtering을 사용하면 비어있는 그룹의 값으로 결과 값이 반환됩니다.

40,000 초과하는 가격을 가진 게임 목록에 대해서 filtering 하고 그룹화합니다.

Map<String, List<Game>> priceGameByType = games.stream()
	collect(groupingBy(Game::getType, filtering(game -> game.getPrice() > 40000, toList())));
    
priceGameByType.forEach((k,v) -> {
	System.out.print(k + " : ");
	v.forEach(game -> System.out.println(game.getName()));
	System.out.println();
});

결과값

Shooting 목록은 해당되는 요소가 없지만 그렇다고 해당 그룹 자체 없이 결과 값이 반환되지는 않는 것이 포인트입니다.

Shooting : 
RPG : DIA 1

 

filtering 외에 mapping 등 다양한 응용이 가능합니다.

다음 예제는 앞에서 정리했던 joining 메서드를 사용하여 각 요소들을  "," 구분자로 합쳐서 결과 값을 그룹화하여

출력해보겠습니다.

Map<String, String> gameNameByType = games.stream()
	.collect(groupingBy(Game::getType, mapping(Game::getName, joining(","))));
    
gameNameByType.forEach((k,v) -> {
	System.out.print(k + " : " + v);
	System.out.println();
});

결과값

Shooting : CALL 1
RPG : DIA 3,DIA 2,DIA 1

 

다음 예제는 counting 메서드를 사용하여 그룹화된 값들에 대해 합계를 구하여 결과를 반환합니다.

Map<String, Long> typesCount = games.stream().collect(groupingBy(Game::getType, counting()));

typesCount.forEach((k,v) -> {
	System.out.print(k + " : " + v);
	System.out.println();
});

결과값

Shooting : 1
RPG : 3

 

다음은 2번 groupingBy를 사용하여 여러 번 그룹화를 적용해보겠습니다.

Map<String, Map<String, List<Game>>> gameMultiGrouping = games.stream().collect(groupingBy(Game::getType,
		groupingBy(game -> {
			if(game.getPrice() > 20000) {
				return "RPG";
			} else {
				return "Shooting";
			}
		})
	)
);
		
gameMultiGrouping.forEach((k,v) -> {
	System.out.println("<"+k+">");
	v.get(k).forEach(game -> System.out.println(game.getName()));
	System.out.println();
});

결과값

<Shooting>
CALL 1

<RPG>
DIA 3
DIA 2
DIA 1

 

collectingAndThen

다음 예제는 maxBy를 사용하여  각각 그룹에 가장 비싼 게임을 결과값을 반환하겠습니다.

여기서 결과 값은 collectingAndThen을 사용하여 다른 형식으로 변환합니다.

참고할 점은 리듀싱 컬렉터의 경우에는 Optional.empty()를 반환하지 않으므로 안전합니다.

Map<String, Game> mostPriceBytype = games.stream().collect(groupingBy(Game::getType,
collectingAndThen(maxBy(Comparator.comparingInt(Game::getPrice)), Optional::get)));

결과값

Shooting : CALL 1
RPG : DIA 1

 

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

Collector 분할  (0) 2022.11.08
Collector 요약  (0) 2022.10.28
스트림 생성  (0) 2022.10.27
기본형 특화 스트림  (0) 2022.10.27
리듀싱  (0) 2022.10.26

Collector 인터페이스 구현은 스트림의 요소를 어떤 식으로 도출할지 결정.

예를 들면 Collector 인터페이스의 구현으로 toLost는 각 요소들을 리스트로 만들어줍니다.

미리 정의된 컬렉터의 기능과 커스텀해서 만드는 기능에 대해서 정리해보려고 합니다.

크게 Collector 는 요약, 그룹화, 분할로 나눌 수 있습니다.

이번 글에서는 요약에 대해서 정리해보겠습니다.

 

먼저 예제에서 사용할 Game 리스트를 생성해보겠습니다.

List<Game> games = Arrays.asList(
		new Game("CALL 1", 20000)
		, new Game("DIA 3", 30000)
		, new Game("DIA 2", 40000)
		, new Game("DIA 1", 50000)
);

 

counting

아래 예제는 리스트 모든 요소들을 카운팅 하는 예제입니다.

long countGames = games.stream().collect(Collectors.counting());

다음과 같이 Colletors 클래스를 import 하면 아래와 같이 더욱더 간단하게 작성이 가능합니다.

이제부터 살펴볼 예제는 Collectors 클래스를 import 한 상태로 진행하겠습니다.

import static java.util.stream.Collectors.*;
long countGames = games.stream().collect(counting());

 

maxBy와 minBy

최대값 최솟값을 구하는 예제를 살펴보겠습니다.

Comparator<Game> gamesComparator = Comparator.comparingInt(Game::getPrice);
Optional<Game> maxPriceGame = games.stream()
				.collect(maxBy(gamesComparator)); 
                
Optional<Game> minPriceGame = games.stream()
				.collect(minBy(gamesComparator)); 
		
System.out.println(maxPriceGame.get().getPrice());
System.out.println(minPriceGame.get().getPrice());

결과값

50000
20000

 

summingInt

객체를 int로 맵핑하는 함수를 인수로 받아 객체를 int 로 맵핑하여 컬렉터를 반환하는 메서드.

int totalPriceGame = games.stream().collect(summingInt(Game::getPrice));
System.out.println(totalPriceGame);

결과값

140000

 

averagingInt

평균값을 계산하는 메서드.

double avgPriceGame = games.stream().collect(averagingInt(Game::getPrice));
System.out.println(avgPriceGame);

결과값

35000.0

 

summarizingInt

요소를 카운팅 한 숫자, 합계, 평균, 최댓값, 최솟값을 연산하는 메서드.

IntSummaryStatistics priceGameStat = games.stream().collect(summarizingInt(Game::getPrice));
System.out.println(priceGameStat);

결과값

IntSummaryStatistics{count=4, sum=140000, min=20000, average=35000.000000, max=50000}

 

joining

각 객체의 toString 메서드를 호출해서 추출한 문자열들을 하나의 문자열로 합쳐서 반환.

String gameName = games.stream()
		.map(game -> game.getName().split(" ")[0]) // 문자열을 쪼개서 첫번째 문자열을 생성.
		.collect(joining(",")); // 각 첫번째 문자열을 합친다. 구분자는 ","
        
System.out.println(gameName);

결과값

CALL,DIA,DIA,DIA

 

reducing

리듀싱 구현 기능을 제공하는 메서드.

아래는 price 합을 구하는 예제를 reducing 메서드를 이용해 구현.

int totalPriceGame = games.stream().collect(reducing(0, Game::getPrice, (i, j) -> i + j));
System.out.println(totalPriceGame);

첫 번째 인수는 시작 값 또는 인수가 없을 때 반환 값.

두 번째 인수는 price 반환 함수

세 번째 인수는 두 개의 항목을 하나로 합하는 BinaryOperator 이다.

 

결과값

140000

 

다음은 price 최대 값을 구하는 예제.

Optional<Game> maxGamePrice = games.stream().collect(Collectors.reducing((i, j) -> i.getPrice() > j.getPrice() ? i : j));
System.out.println(maxGamePrice.get().getPrice());

결과값

50000

 

 

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

Collector 분할  (0) 2022.11.08
Collector 그룹화  (0) 2022.11.01
스트림 생성  (0) 2022.10.27
기본형 특화 스트림  (0) 2022.10.27
리듀싱  (0) 2022.10.26

일반 값들, 배열, 파일, 함수를 이용해서 무한 스트림 등 다양한 방식으로 스트림을 생성 가능하다.

 

값으로 스트림 생성

이전 글들에서 계속 예제를 위해 생성했던 Game List 도 아래와 같이 Stream.of 메서드를 이용해서 생성할 수 있다.

아래는 대문자로 요소를 변환해서 각각 출력하는 예제이다.

Stream<String> st = Stream.of("Dia1", "Dia2", "DIA3", "Call 1", "Call 2");
st.map(String::toUpperCase).forEach(System.out::println);
Stream<String> emStream = Stream.empty();
emStream.map(String::toUpperCase).forEach(System.out::println);

결과값

DIA1
DIA2
DIA3
CALL 1
CALL 2

 

Null 이 될 수 있는 객체로 생성

환경 변수를 가져올 때 값이 없으면 null 이 될 수 있는데 ofNullable 메서드를 사용하여 처리 가능하다.

Stream<String> values = Stream.of("config", "home", "user")
						.flatMap(key -> Stream.ofNullable(System.getProperty(key)));

 

배열로 스트림 생성

int[] numArr = {2,3,4,5};
int res = Arrays.stream(numArr).sum();
System.out.println(res);

결과값

14

 

파일로 스트림 생성

파일을 열어 고유 단어 수를 계산하는 예제이다.

long words =0;
try (Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())) {
	words = lines.flatMap(line -> Arrays.stream(line.split(" "))).distinct().count(); 	
} catch (IOException e) {
	e.printStackTrace();
}

 

함수로 무한 스트림 생성

Stream의 iterate, genetate 메서드를 사용하여 무한 스트림을 만들 수 있다.

무한 스트림은 고정된 컬렉션에 고정된 크기가 아니라 크기가 고정되지 않은 스트림이다.

 

아래 iterate 예제는 초기값 1로 시작하여 1씩 누적 연산해 나가는 예제이다.

첫 번째 코드는 무한 생성되는 것을 확인 가능하다. 

Stream.iterate(1, n -> n+1).forEach(System.out::println); // 무한이 생성.
Stream.iterate(1, n -> n+1).limit(10).forEach(System.out::println); // 10개로 제한 생성.

 

아래 generate 예제이다.

iterate 와는 달리 연속적으로 값을 계산하지 않고 인수로 Supplier <T>를 받아 새로운 값을 생성한다.

Stream.generate(Math::random).forEach(System.out::println);
Stream.generate(Math::random).limit(10).forEach(System.out::println);

 

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

Collector 그룹화  (0) 2022.11.01
Collector 요약  (0) 2022.10.28
기본형 특화 스트림  (0) 2022.10.27
리듀싱  (0) 2022.10.26
검색과 매칭  (0) 2022.10.26

자바 8 에서는 박싱 비용을 피할 수 있게  IntStream, DoubleStream, LongStream 기본형 특화 스트림을 제공한다. 

스트림을 특화 스트림으로 변환할 때 mapToInt, mapToDouble, mapToLong 세 가지 메소드를 지원해준다.

map 같은 기능이지만 Stream<T>대신 특화된 스트림을 반환한다.

 

Game 객체 리스트를 생성.

List<Game> games = Arrays.asList(
		new Game("CALL 1", 20000)
		, new Game("DIA 3", 30000)
		, new Game("DIA 2", 40000)
		, new Game("DIA 1", 50000)
);

 

기본형 특화 스트림

숫자 스트림으로 매핑했다가 다시 객체 스트림으로 복원.

sum, max, min 같은 메서드 등 리듀상 연산 수행 메서드도 제공한다.

int price = games.stream().mapToInt(Game::getPrice).sum(); // 특화 스트림으로 변환해서 sum 메서드 사용
System.out.println(price);
		
IntStream intStream = games.stream().mapToInt(Game::getPrice); 
Stream<Integer> stream = intStream.boxed(); // 객체 스트림으로 복원

기본값이 없는 경우 Optional 객체 역시 기본형 특화 스트림이 제공된다.

OptionalInt, OptionalDouble, OptionalLong

OptionalInt maxPrice = games.stream().mapToInt(Game::getPrice).max();
int max = maxPrice.orElse(0); // 기본값 명시적 설정

 

 

숫자 범위

자바 8 에서는 IntStream 과 LongStream에서 range와 rangeClosed 라는 정적 메서드 제공.

두 메서드 모두 첫 번째 인수로 시작값, 두 번째 인수로 종료 값을 갖는다.

차이점은 range 메서드는 결과에 시작값과 종료값이 포함되지 않고 rangeClosed는 포함된다는 점이 다르다.

아래는 피타고라스 정리를 1 ~ 100 범위에서 가능한 조합을 결과 값으로 반환하는 코드이다.

Stream<int[]> pythago = IntStream.rangeClosed(1, 100).boxed()
				.flatMap(a -> IntStream.rangeClosed(a, 100)
				.filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)
				.mapToObj(b -> new int[]{a, b, (int)Math.sqrt(a*a + b*b)}));
                
pythago.limit(5).forEach(t -> System.out.println(t[0] + "," + t[1] + "," + t[2]));

결과값

limt 으로 5개만 출력하게 제한을 걸었다.

3,4,5
5,12,13
6,8,10
7,24,25
8,15,17

 

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

Collector 요약  (0) 2022.10.28
스트림 생성  (0) 2022.10.27
리듀싱  (0) 2022.10.26
검색과 매칭  (0) 2022.10.26
map과 flatMap  (0) 2022.10.17

스트림 모든 스트림 요소를 처리해서 값으로 도출 하는 것을 지원하는 reduce 메서드를 제공.

 

예제에서 사용할 숫자 객체 리스트 생성하고 각 요소들을 합산하는 예제를 작성해보았다.

List<Integer> numbers = Arrays.asList(1,2,3,4,5);
int sum = 0;
for(int t : numbers) {
	sum += t;
}

결과값

15

 

누적 반복 연산을 하는 것을 reduce 메서드를 사용해서 간단하게 처리 가능하다.

reduce 메서드의 첫번째 인수는 초기값, 두 번째 인수는 두 요소를 조합해서 새로운 값을 만드는 BinaryOperator<T> 

int sum = numbers.stream().reduce(0, (a,b) -> a+b);

결과값

15

 

초기값 없이 처리도 가능하나 이때 반환 값이 Optional 객체이다.

Optional<Integer> sum = numbers.stream().reduce((a, b) -> (a+b));	
System.out.println(sum.get());

 

최댓값과 최솟값도 reduce를 사용해서 다음과 같이 쉽게 처리가 가능하다.

Optional<Integer> min = numbers.stream().reduce(Integer::min);
System.out.println(min.get());

결과값

1

 

Optional<Integer> max = numbers.stream().reduce(Integer::max);
System.out.println(max.get());

결과값

5

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

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

+ Recent posts