[Java] Stream

โœ”๏ธ Stream์ด๋ž€?

๐Ÿ’ก ์ •์˜

  • ์ŠคํŠธ๋ฆผ์€ Java 8์—์„œ ์ถ”๊ฐ€๋œ ๊ธฐ๋Šฅ
  • ์ŠคํŠธ๋ฆผ์€ ๋ฐ์ดํ„ฐ์˜ ํ๋ฆ„, ์ปฌ๋ ‰์…˜ ๋ฐ์ดํ„ฐ๋ฅผ ์„ ์–ธํ˜•์œผ๋กœ ์‰ฝ๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Œ
  • ๋ฃจํ”„๋ฌธ/๋ฃจํ”„๋ฌธ ์ค‘์ฒฉ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•„๋„ ๋จ
  • ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ๋ฅผ ๋ณ„๋„์˜ ๋ฉ€ํ‹ฐ์“ฐ๋ ˆ๋“œ ์—†์ด ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Œ

 

๐Ÿ˜ฎ ์žฅ์ 

  • ์„ ์–ธํ˜•์ด๊ธฐ ๋•Œ๋ฌธ์— ์ฝ”๋“œ๊ฐ€ ๊ฐ„๊ฒฐํ•ด์ง€๊ณ  ๊ฐ€๋…์„ฑ์ด ์ข‹์•„์ง
  • ์œ ์—ฐ์„ฑ์ด ์ข‹์•„์ง
  • ๋ณ‘๋ ฌํ™”๋กœ ์ธํ•ด ์„ฑ๋Šฅ์ด ์ข‹์•„์ง

 


 

โœ”๏ธ ์ŠคํŠธ๋ฆผ์€ ํฌ๊ฒŒ 3๊ฐ€์ง€๋กœ ๋‚˜๋ˆŒ ์ˆ˜ ์žˆ๋‹ค.

  • ์ƒ์„ฑํ•˜๊ธฐ : ์ŠคํŠธ๋ฆผ ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ
  • ๊ฐ€๊ณตํ•˜๊ธฐ : ํ•„ํ„ฐ๋ง ๋ฐ ๋งตํ•‘ ๋“ฑ ์›ํ•˜๋Š” ๊ฒฐ๊ณผ๋ฅผ ๋งŒ๋“ค์–ด๊ฐ€๋Š” ์ค‘๊ฐ„ ์ž‘์—…
  • ๊ฒฐ๊ณผ ๋งŒ๋“ค๊ธฐ : ์ตœ์ข…์ ์œผ๋กœ ๊ฒฐ๊ณผ๋ฅผ ๋งŒ๋“ค์–ด๋‚ด๋Š” ์ž‘์—…

 


 

โœ”๏ธ ์ƒ์„ฑํ•˜๊ธฐ

๋ฐฐ์—ด ์ŠคํŠธ๋ฆผ

์ŠคํŠธ๋ฆผ์€ ๋ฐฐ์—ด ๋˜๋Š” ์ปฌ๋ ‰์…˜ ์ธ์Šคํ„ด์Šค๋ฅผ ์ด์šฉํ•ด์„œ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค. ๋ฐฐ์—ด์€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด Arrays.stream ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

String[] alphabet = new String[]{"A", "B", "C"};

Stream<String> stream = Arrays.stream(alphabet);
Stream<String> streamOfPart = Arrays.stream(alphabet, 0, 2); // 0~1 ์š”์†Œ [A, B]

 

์ปฌ๋ ‰์…˜ ์ŠคํŠธ๋ฆผ

์ปฌ๋ ‰์…˜ ํƒ€์ž…์€ ์ธํ„ฐํŽ˜์ด์Šค์— ์ถ”๊ฐ€๋œ ๋””ํดํŠธ ๋ฉ”์„œ๋“œ stream()์„ ์ด์šฉํ•ด์„œ ์ŠคํŠธ๋ฆผ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

List<String> alphabet = Arrays.asList("A", "B", "C");

Stream<String> stream = alphabet.stream();
Stream<String> parallelStream = alphabet.parallelStream();

 

๋น„์–ด์žˆ๋Š” ์ŠคํŠธ๋ฆผ

๋นˆ ์ŠคํŠธ๋ฆผ์€ ์š”์†Œ๊ฐ€ ์—†์„ ๋•Œ null ๋Œ€์‹  ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

public Stream<String> streamOf(List<String> list) {
  return list == null || list.isEmpty() 
    ? Stream.empty() 
    : list.stream();
}

 

Stream.builder()

๋นŒ๋”๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ŠคํŠธ๋ฆผ์— ์ง์ ‘์ ์œผ๋กœ ์›ํ•˜๋Š” ๊ฐ’์„ ๋„ฃ์„ ์ˆ˜ ์žˆ๋‹ค. ๋งˆ์ง€๋ง‰์— build() ๋ฉ”์„œ๋“œ๋กœ ์ŠคํŠธ๋ฆผ์„ ๋ฆฌํ„ดํ•œ๋‹ค.

Stream<String> builderStream = 
	Stream.<String>builder()
    	.add("A").add("B").add("C")
    	.build(); // [A, B, C]

 

Stream.generate()

generate() ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•˜๋ฉด Supplier<T>์— ํ•ด๋‹นํ•˜๋Š” ๋žŒ๋‹ค๋กœ ๊ฐ’์„ ๋„ฃ์„ ์ˆ˜ ์žˆ๋‹ค. Supplier<T>๋Š” ์ธ์ž๋Š” ์—†๊ณ  ๋ฆฌํ„ด๊ฐ’๋งŒ ์žˆ๋Š” ํ•จ์ˆ˜ํ˜• ์ธํ„ฐํŽ˜์ด์Šค๋‹ค. ๋žŒ๋‹ค์—์„œ ๋ฆฌํ„ดํ•˜๋Š” ๊ฐ’์ด ๋“ค์–ด๊ฐ„๋‹ค.

Stream<String> generatedStream = 
	Stream.generate(() -> "A").limit(3); // [A, A, A]

 

Stream.iterate()

iterate() ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•˜๋ฉด ์ดˆ๊ธฐ๊ฐ’๊ณผ ํ•ด๋‹น๊ฐ’์„ ๋‹ค๋ฃจ๋Š” ๋žŒ๋‹ค๋ฅผ ์ด์šฉํ•ด์„œ ์ŠคํŠธ๋ฆผ์— ๋“ค์–ด๊ฐˆ ์š”์†Œ๋ฅผ ๋งŒ๋“ ๋‹ค. ๋‹ค์Œ ์˜ˆ์ œ์—์„œ๋Š” ์ดˆ๊ธฐ๊ฐ’์ด 1์ด๊ณ , ๊ฐ’์ด 2์”ฉ ์ฆ๊ฐ€ํ•˜๊ฒŒ ๋œ๋‹ค. ์ŠคํŠธ๋ฆผ์˜ ์‚ฌ์ด์ฆˆ๋Š” ๋ฌดํ•œ์ด๊ธฐ ๋•Œ๋ฌธ์— limit()์œผ๋กœ ์ œํ•œํ•ด์•ผ ํ•œ๋‹ค.

Stream<Integer> iteratedStream = 
	Stream.iterate(1, n -> n + 2).limit(5); // [1, 3, 5, 7, 9]

 

์ŠคํŠธ๋ฆผ ์—ฐ๊ฒฐํ•˜๊ธฐ

Stream.concat() ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด ๋‘ ๊ฐœ์˜ ์ŠคํŠธ๋ฆผ์„ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.

Stream<String> stream1 = Stream.of("1", "2", "3");
Stream<String> stream2 = Stream.of("A", "B", "C");
Stream<String> concat = Stream.concat(stream1, stream2); // [1, 2, 3, A, B, C]

 


 

โœ”๏ธ ๊ฐ€๊ณตํ•˜๊ธฐ

์ „์ฒด ์š”์†Œ ์ค‘ ๋‹ค์Œ๊ณผ ๊ฐ™์€ API๋ฅผ ์ด์šฉํ•ด ์›ํ•˜๋Š” ๊ฒƒ๋งŒ ๋ฝ‘์•„๋‚ผ ์ˆ˜ ์žˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋‹จ๊ณ„๋ฅผ ์ค‘๊ฐ„ ์ž‘์—…์ด๋ผ ํ•˜๋Š”๋ฐ, ์ŠคํŠธ๋ฆผ์„ ๋ฆฌํ„ดํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์—ฌ๋Ÿฌ ์ž‘์—…์„ ์ด์–ด ๋ถ™์—ฌ์„œ(chaining) ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

List<String> alphabet = Arrays.asList("A", "B", "C"); // [A, B, C]

 

Filtering

ํ•„ํ„ฐ๋Š” ์ŠคํŠธ๋ฆผ ๋‚ด ์š”์†Œ๋“ค์„ ํ•˜๋‚˜์”ฉ ํ‰๊ฐ€ํ•˜๋ฉฐ ๊ฑธ๋Ÿฌ๋‚ด๋Š” ์ž‘์—…์ด๋‹ค. ์ธ์ž๋กœ ๋ฐ›๋Š” Predicate๋Š” boolean์„ ๋ฆฌํ„ดํ•˜๋Š” ํ•จ์ˆ˜ํ˜• ์ธํ„ฐํŽ˜์ด์Šค๋กœ ํ‰๊ฐ€์‹์ด ๋“ค์–ด๊ฐ€๊ฒŒ ๋œ๋‹ค.

Stream<String> stream = 
	alphabet.stream()
    .filter(a -> a.contains("A")); // [AAA, Aa, aaA]

 

์ŠคํŠธ๋ฆผ์˜ ๊ฐ ์š”์†Œ์— ๋Œ€ํ•ด ํ‰๊ฐ€์‹์„ ์‹คํ–‰ํ•˜๊ฒŒ ๋˜๊ณ  'A'๊ฐ€ ๋“ค์–ด๊ฐ„ ๋‹จ์–ด๋งŒ ๋ฆฌํ„ดํ•œ๋‹ค.

 

Mapping

๋งต์€ ์ŠคํŠธ๋ฆผ ๋‚ด ์š”์†Œ๋“ค์„ ํ•˜๋‚˜์”ฉ ํŠน์ • ๊ฐ’์œผ๋กœ ๋ณ€ํ™˜ํ•œ๋‹ค. ์ด ๋•Œ ๊ฐ’์„ ๋ณ€ํ™˜ํ•˜๊ธฐ ์œ„ํ•œ ๋žŒ๋‹ค๋ฅผ ์ธ์ž๋กœ ๋ฐ›๋Š”๋‹ค.

Stream<String> stream = 
	alphabet.stream()
    .map(String::toLowerCase); // [a, b, c]

 

์ŠคํŠธ๋ฆผ ๋‚ด String์˜ toLowerCase() ๋ฉ”์„œ๋“œ๋ฅผ ์‹คํ–‰ํ•ด ๋Œ€๋ฌธ์ž๋กœ ๋ณ€ํ™˜ํ•œ ๊ฐ’๋“ค์ด ๋‹ด๊ธด ์ŠคํŠธ๋ฆผ์„ ๋ฆฌํ„ดํ•œ๋‹ค.

์ด์™ธ์—๋„ ์กฐ๊ธˆ ๋” ๋ณต์žกํ•œ flatMap() ๋ฉ”์„œ๋“œ๊ฐ€ ์žˆ๋‹ค.

 

List<List<String>> list = 
	Arrays.asList(Arrays.asList("A"), Arrays.asList("B"));

 

๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ค‘์ฒฉ๋œ ๋ฆฌ์ŠคํŠธ๋ฅผ flatMap()์„ ์‚ฌ์šฉํ•ด ์ค‘์ฒฉ ๊ตฌ์กฐ๋ฅผ ์ œ๊ฑฐํ•  ์ˆ˜ ์žˆ๋‹ค.

 

List<String> flatList = 
	list.stream()
    .flatMap(Collection::stream)
    .collect(Collectors.toList()); // [A, B]

 

Sorting

์ •๋ ฌ์€ Comparator์„ ์ด์šฉํ•œ๋‹ค. ์ธ์ž ์—†์ด ํ˜ธ์ถœํ•  ๊ฒฝ์šฐ ์˜ค๋ฆ„์ฐจ์ˆœ์œผ๋กœ ์ •๋ ฌํ•œ๋‹ค.

IntStream.of(1, 2, 3, 4, 5)
  .sorted()
  .boxed()
  .collect(Collectors.toList()); // [1, 2, 3, 4, 5]

 

๋‹ค์Œ์€ ์ธ์ž๋ฅผ ๋„˜๊ธฐ๋Š” ๊ฒฝ์šฐ์ด๋‹ค.

List<String> alphabet = 
    Arrays.asList("A", "B", "C", "D", "E", "F");

alphabet.stream()
	.sorted()
    .collect(Collectors.toList()); // [A, B, C, D, E, F]

alphabet.stream()
    .sorted(Comparator.reverseOrder())
    .collect(Collectors.toList()); // [F, E, D, C, B, A]

 

Comparatord์˜ compare() ๋ฉ”์„œ๋“œ๋Š” ๋‘ ์ธ์ž๋ฅผ ๋น„๊ตํ•ด ๊ฐ’์„ ๋ฆฌํ„ดํ•œ๋‹ค.

alphabet.stream()
    .sorted(Comparator.comparingInt(String::length))
    .collect(Collectors.toList()); // [apple, banana, bluberry]

alphabet.stream()
    .sorted((s1, s2) -> s2.length() - s1.length())
    .collect(Collectors.toList()); // [bluberry, banana, apple]

 


 

โœ”๏ธ ๊ฒฐ๊ณผ ๋งŒ๋“ค๊ธฐ

๊ฐ€๊ณตํ•œ ์ŠคํŠธ๋ฆผ์œผ๋กœ ์‚ฌ์šฉํ•  ๊ฒฐ๊ณผ๊ฐ’์„ ๋งŒ๋“ค์–ด๋‚ด๋Š” ๋‹จ๊ณ„์ด๋‹ค. ๋”ฐ๋ผ์„œ ์ŠคํŠธ๋ฆผ์„ ๋๋‚ด๋Š” ์ตœ์ข… ์ž‘์—…์ด๋‹ค.

 

Calculating

์ŠคํŠธ๋ฆผ API๋Š” ๋‹ค์–‘ํ•œ ์ตœ์ข… ์ž‘์—…์„ ์ œ๊ณตํ•œ๋‹ค. ์ตœ๋Œ€, ์ตœ์†Œ, ํ•ฉ, ํ‰๊ท  ๋“ฑ ๊ธฐ๋ณธํ˜• ํƒ€์ž…์œผ๋กœ ๊ฒฐ๊ณผ๋ฅผ ๋งŒ๋“ค์–ด๋‚ผ ์ˆ˜ ์žˆ๋‹ค.

long count = IntStream.of(1, 3, 5, 7, 9).count();
long sum = LongStream.of(1, 3, 5, 7, 9).sum();

 

๋งŒ์•ฝ ์ŠคํŠธ๋ฆผ์ด ๋น„์–ด์žˆ๋Š” ๊ฒฝ์šฐ count()์™€ sum()์€ 0์„ ์ถœ๋ ฅํ•˜๋ฉด ๋œ๋‹ค. ํ•˜์ง€๋งŒ ์ตœ๋Œ€, ์ตœ์†Œ, ํ‰๊ท ์˜ ๊ฒฝ์šฐ ํ‘œํ˜„ํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— Optional์„ ์ด์šฉํ•ด ๋ฆฌํ„ดํ•œ๋‹ค.

OptionalInt min = IntStream.of(1, 3, 5, 7, 9).min();
OptionalInt max = IntStream.of(1, 3, 5, 7, 9).max();

 

์ŠคํŠธ๋ฆผ์—์„œ ๋ฐ”๋กœ ifPresent ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด Optional์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

DoubleStream.of(1.1, 2.2, 3.3, 4.4, 5.5)
	.average()
	.ifPresent(System.out::println);

 

Collecting

collect() ๋ฉ”์„œ๋“œ๋Š” ๋˜ ๋‹ค๋ฅธ ์ตœ์ข… ์ž‘์—…์ด๋‹ค. Collector ํƒ€์ž…์˜ ์ธ์ž๋ฅผ ๋ฐ›์•„์„œ ์ฒ˜๋ฆฌํ•œ๋‹ค.

๋‹ค์Œ ๋ฆฌ์ŠคํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ collect() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

List<Product> productList = 
	Arrays.asList(new Product(1, "apple"),
    		new Product(2, "banana"),
            	new Product(3, "orange"),
            	new Product(4, "bluberry");

 

Collectors.toList()

์ŠคํŠธ๋ฆผ์—์„œ ์ž‘์—…ํ•œ ๊ฒฐ๊ณผ๋ฅผ ๋‹ด์€ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. map()์œผ๋กœ ๊ฐ ์š”์†Œ์˜ ์ด๋ฆ„์„ ๊ฐ€์ ธ์˜จ ํ›„ Collectors.

List<String> collectorCollection =
	productList.stream()
    	.map(Product::getName)
    	.collect(Collectors.toList()); // [apple, banana, orange, bluberry]

 

Collectors.joining()

์ŠคํŠธ๋ฆผ์—์„œ ์ž‘์—…ํ•œ ๊ฒฐ๊ณผ๋ฅผ ํ•˜๋‚˜์˜ ์ŠคํŠธ๋ง์œผ๋กœ ์ด์–ด๋ถ™์ผ ์ˆ˜ ์žˆ๋‹ค.

String listToString = 
     productList.stream()
     	.map(Product::getName)
		.collect(Collectors.joining()); // applebananaorangebluberry

 

Collectors.groupingBy()

ํŠน์ • ์กฐ๊ฑด์œผ๋กœ ์š”์†Œ๋“ค์„ ๊ทธ๋ฃนํ™”ํ•  ์ˆ˜ ์žˆ๋‹ค.

Map<Integer, List<Product>> collectorMapOfLists =
     productList.stream()
          .collect(Collectors.groupingBy(Product::getAmount));

 

Calculating

์ŠคํŠธ๋ฆผ API๋Š” ๋‹ค์–‘ํ•œ ์ตœ์ข… ์ž‘์—…์„ ์ œ๊ณตํ•œ๋‹ค. ์ตœ๋Œ€, ์ตœ์†Œ, ํ•ฉ, ํ‰๊ท  ๋“ฑ ๊ธฐ๋ณธํ˜• ํƒ€์ž…์œผ๋กœ ๊ฒฐ๊ณผ๋ฅผ ๋งŒ๋“ค์–ด๋‚ผ ์ˆ˜ ์žˆ๋‹ค.

 

Matching

๋งค์นญ์€ ์กฐ๊ฑด์‹ ๋žŒ๋‹ค Predicate๋ฅผ ๋ฐ›์•„ ํ•ด๋‹น ์กฐ๊ฑด์„ ๋งŒ์กฑํ•˜๋Š” ์š”์†Œ๊ฐ€ ์žˆ๋Š”์ง€ ์ฒดํฌํ•œ ๊ฒฐ๊ณผ๋ฅผ ๋ฆฌํ„ดํ•œ๋‹ค. ๋‹ค์Œ๊ณผ ๊ฐ™์€ 3๊ฐ€์ง€ ๋ฉ”์„œ๋“œ๊ฐ€ ์žˆ๋‹ค.

  • anyMatch() : ์กฐ๊ฑด์„ ๋งŒ์กฑํ•˜๋Š” ์š”์†Œ๊ฐ€ ์žˆ๋Š”์ง€
  • allMatch() : ๋ชจ๋“  ์กฐ๊ฑด์„ ๋งŒ์กฑํ•˜๋Š”์ง€
  • noneMatch() : ๋ชจ๋“  ์กฐ๊ฑด์„ ๋งŒ์กฑํ•˜์ง€ ์•Š๋Š”์ง€
List<String> fruits = Arrays.asList("apple", "banana", "blueberry");

boolean anyMatch = fruits.stream()
    .anyMatch(fruit -> fruit.contains("a")); // true
    
boolean allMatch = fruits.stream()
    .allMatch(fruit -> fruit.length() > 3); // true
    
boolean noneMatch = fruits.stream()
    .noneMatch(fruit -> fruit.endsWith("s")); // true

 

 

Iterating

forEach() ์š”์†Œ๋ฅผ ๋Œ๋ฉด์„œ ์‹คํ–‰๋˜๋Š” ์ตœ์ข… ์ž‘์—…์ด๋‹ค. ๋ณดํ†ต println() ๋ฉ”์„œ๋“œ๋ฅผ ๋„˜๊ฒจ์„œ ๊ฒฐ๊ณผ๋ฅผ ์ถœ๋ ฅํ•  ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.

alphabets.stream().forEach(System.out::println);

 

 

์œ„ ๋‚ด์šฉ์€ ๋‹ค์Œ ๋ธ”๋กœ๊ทธ๋ฅผ ์ฐธ๊ณ ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

https://futurecreator.github.io/2018/08/26/java-8-streams/

 

Java ์ŠคํŠธ๋ฆผ Stream (1) ์ด์ •๋ฆฌ

์ด๋ฒˆ ํฌ์ŠคํŠธ์—์„œ๋Š” Java 8์˜ ์ŠคํŠธ๋ฆผ(Stream)์„ ์‚ดํŽด๋ด…๋‹ˆ๋‹ค. ์ด ๋‘ ๊ฐœ์˜ ํฌ์ŠคํŠธ๋กœ, ๊ธฐ๋ณธ์ ์ธ ๋‚ด์šฉ์„ ์ด์ •๋ฆฌํ•˜๋Š” ์ด๋ฒˆ ํฌ์ŠคํŠธ์™€ ์ข€ ๋” ๊ณ ๊ธ‰ ๋‚ด์šฉ์„ ๋‹ค๋ฃจ๋Š” ๋‹ค์Œ ํฌ์ŠคํŠธ๋กœ ๋‚˜๋‰˜์–ด์ ธ ์žˆ์Šต๋‹ˆ๋‹ค. Java ์ŠคํŠธ

futurecreator.github.io

 

'Language > Java' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[Java] JVM ๋™์ž‘ ๊ณผ์ •  (0) 2024.12.04
[Java] enum  (0) 2023.11.10
[Java] ์ผ๊ธ‰ ์ปฌ๋ ‰์…˜  (2) 2023.10.26