이번 글에서는 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

스트림 API는 특정 속성이 데이터 집합에 존재 여부를 검색하는데 유용한

allMatch, anyMatch, noneMatch, findFirst, findAny 등의 메서드를 제공.

 

예제를 작성하기에 앞서 공통으로 사용할 games 리스트를 생성.

// 게임 데이터를 담는 리스트 생성.
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)
);

 

anyMatch 

적어도 한 요소와 프레디케이트와 일치하는지 확인하는 메서드.

// 적어도 하나라도 일치하는지 불리언 반환하므로 최종연산
if(games.stream().anyMatch(game -> game.getName().startsWith("CALL"))) {
	System.out.println("The game is CALL series");
}

 

결과값

CALL 1이 일치하는 요소 이므로 아래의 결과값 출력.

The game is CALL series

 

allMatch

모든 요소가 주어진 프레디케이트와 일치하는지 검사.

// 모든 요소와 일치하는지 검사
if(games.stream().allMatch(game -> game.getName().startsWith("CALL"))) {
	System.out.println("The game is CALL series");
}

 

결과값

CALL 1 외에는 전부 DIA 시리즈이므로 일치하는 것이 없어 출력되는 것이 없음.

 

noneMatch

모든 요소가 주어진 프레디케이트와 일치하는지 않는지 검사.

if(games.stream().noneMatch(game -> game.getName().startsWith("STAR"))) {
	System.out.println("There is no Star series on this list of games");
}

 

결과값

요소 중에 STAR 시리즈는 없으므로 아래와 같은 결과가 출력.

There is no Star series on this list of games

 

 

findAny

현재 스트림의 임의의 요소를  Optional 객체로 반환.

Optional은 자바 8 에서 추가 된 기능으로 간단하게 활용도를 설명하면 null 확인 관련 버그를 피하는데 유용하다.

Optional에 대해서는 추후에 따로 글을 작성해보겠습니다.

Optional<Game> result = games.stream()
			.filter(game -> game.getName().startsWith("DIA"))
			.findAny();
System.out.println(result.get().getName());

 

결과값

요소 중에 랜덤으로 출력.

 

findFirst

첫번째 요소를 찾아서 Optional 객체로 반환.

Optional<Game> result = games.stream()
			.filter(game -> game.getName()
			.startsWith("DIA")).findFirst();
System.out.println(result.get().getName());

 

결과값

첫번째 요소는 DIA 3 이므로 아래와 같은 결과 출력.

DIA 3

 

 

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

기본형 특화 스트림  (0) 2022.10.27
리듀싱  (0) 2022.10.26
map과 flatMap  (0) 2022.10.17
필터링 및 슬라이싱  (0) 2022.10.10
정의 및 특징  (0) 2022.10.03

+ Recent posts