Recent Posts
Recent Comments
Link
06-30 12:53
Today
Total
관리 메뉴

삶 가운데 남긴 기록 AACII.TISTORY.COM

자바 stream 본문

DEV&OPS/Java

자바 stream

ALEPH.GEM 2022. 5. 17. 14:29

스트림 stream

자바7이전에는 컬렉션 순차처리를 위해 Iterator를 사용했지만 자바8부터 추가된 컬렉션(배열)을 람다식으로 처리할 수 있도록 해줍니다.

BaseStream을 부모로하는 IntStream, LongStream, DoubleStream, Stream 들로 구성되어 있습니다.

import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Stream;

public class IteratorStreamEx {

	public static void main(String[] args) {
		List<String> list = Arrays.asList("홍길동", "고길동","김길동");
		
		//Java7 이전
		Iterator<String> iterator = list.iterator();
		while(iterator.hasNext()) {
			String name = iterator.next();
			System.out.println(name);
		}
		
		//Java8 이후
		Stream<String> stream = list.stream();
		stream.forEach(name -> System.out.println(name));
	}

}

 

람다식으로 객체 컬렉션 처리

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

class Student{
	private String name;
	private int score;
	public Student(String name, int score) {
		this.name = name;
		this.score = score;
	}
	public String getName() {return name;}
	public int getScore() {return score;}
}

public class LambdaEx {

	public static void main(String[] args) {
		List<Student> list = Arrays.asList(
				new Student("홍길동", 80),
				new Student("고길동", 92)
				);
		Stream<Student> stream = list.stream();
		stream.forEach( s -> {
			String name = s.getName();
			int score = s.getScore();
			System.out.println(name+":"+score);
		});
	}

}

 

병렬처리시에도 반복 작업을 스트림 내부에서 처리하기 때문에 코드가 간결하며 안전합니다.

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class ParallelEx {
	public static void print(String str) {
		System.out.println(str+":"+Thread.currentThread().getName());
	}

	public static void main(String[] args) {
		List<String> list = Arrays.asList("홍길동","고길동","김길동","이길동","박길동");
		
		//순차처리
		Stream<String> stream = list.stream();
		stream.forEach(ParallelEx :: print);	//람다식 메소드 참조
		System.out.println();
		
		//병렬처리
		Stream<String> parallelStream = list.parallelStream();
		parallelStream.forEach(ParallelEx :: print);
	}

}

 

import java.util.Arrays;
import java.util.List;

public class MapAndReduceEx {

	public static void main(String[] args) {
		List<Student> studentList = Arrays.asList(
				new Student("홍길동", 10),
				new Student("고길동", 20),
				new Student("김길동", 35)
				);
		
		double avg = studentList.stream().mapToInt(Student :: getScore).average().getAsDouble();
		System.out.println("평균: "+avg);
	}

}

 

스트림을 얻는 기본 방법

import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class FromCollectonEx {

	public static int sum;
	
	public static void main(String[] args) {
		//컬렉션으로부터 스트림 얻기
		List<Student> studentList = Arrays.asList(new Student("홍길동", 20), new Student("고길동", 30), new Student("김길동", 35));
		Stream<Student> stream = studentList.stream();
		stream.forEach(s->System.out.println(s.getName()));
		
		//배열로부터 스트림 얻기
		String[] strArray = {"홍길동", "고길동", "김길동"};
		Stream<String> strStream = Arrays.stream(strArray);
		strStream.forEach(a->System.out.print(a+","));
		System.out.println();
		
		//숫자 범위로부터 스트림 얻기
		IntStream intStream = IntStream.rangeClosed(1,100);
		intStream.forEach(a -> sum+=a);
		System.out.println("합계:"+sum);
	}

}

 

파일(폴더)로부터 스트림 얻기

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;

public class FromFileEx {

	public static void main(String[] args) throws IOException {
		Path path = Paths.get("src/패키지경로/test.txt");	//텍스트파일의 경로 프로젝트의 소스폴더의 패키지 경로 예
		Stream<String> stream;
		
		//Files.lines()
		stream = Files.lines(path, Charset.defaultCharset());
		stream.forEach(System.out :: println);	//메소드 참조
		System.out.println();
		
		//BufferedReader
		File file = path.toFile();
		FileReader fileReader = new FileReader(file);
		BufferedReader br = new BufferedReader(fileReader);
		stream = br.lines();
		stream.forEach(System.out :: println);
		
		//디렉토리를 소스로 하는 스트림
		Path path2 = Paths.get("D:/절대경로/");	//경로 구분자가 역슬래시가 아니라 슬레시
		Stream<Path> stream2 = Files.list(path2);
		stream2.forEach(p->System.out.println(p.getFileName()));
	}

}

 

스트림 파이프라인

스트림 인터페이스에는 필터링, 매핑, 정렬 등 중간 처리 메소드가 있는데 이 메소드들은 중간 처리된 스트림을 리턴합니다.

그리고 이 스트림은 다시 다른 중간 처리 메소드를 호출해서 순차적으로 처리하는 파이프라인을 형성하게됩니다.

메소드의 리턴타입이 stream이라면 중간처리 메소드인 것입니다.

import java.util.Arrays;
import java.util.List;

class Member{
	public static int MALE = 0;
	public static int FEMALE = 1;
	private String name;
	private int gender;
	private int age;
	
	public Member(String name, int gender, int age) {
		this.name = name;
		this.gender = gender;
		this.age = age;
	}
	
	public int getGender() {return gender;}
	public int getAge() {return age;}
}

public class StreamPipelinesEx {

	public static void main(String[] args) {
		List<Member> list = Arrays.asList(
				new Member("김철수", Member.MALE, 30),
				new Member("김영희", Member.FEMALE, 20),
				new Member("안철수", Member.MALE, 45),
				new Member("고영희", Member.FEMALE, 37)
				);
		//스트림 파이프 라인
		double ageAvg = list.stream().filter(m->m.getGender()==Member.MALE).mapToInt(Member :: getAge).average().getAsDouble();
		
		System.out.println("남자 평균 나이: "+ageAvg);
	}

}

 

스트림 필터링

import java.util.Arrays;
import java.util.List;

public class FilteringEx {

	public static void main(String[] args) {
		List<String> names = Arrays.asList("김철수","나철수","안철수","이철수","박철수","안철수");
		
		//중복제거
		names.stream().distinct().forEach(n->System.out.println(n));
		System.out.println();
		
		//필터링
		names.stream().filter(n->n.startsWith("안")).forEach(n->System.out.println(n));
		System.out.println();
		
		//중복 제거 후 필터링
		names.stream().distinct().filter(n->n.startsWith("안")).forEach(n->System.out.println(n));
	}

}

 

스트림 매핑

원래의 스트림 요소가 복수개의 요소로 대체하는 예제

import java.util.Arrays;
import java.util.List;

public class FlatMapEx {

	public static void main(String[] args) {
		List<String> inputList1 = Arrays.asList("java8 lambda", "stream mapping");
		inputList1.stream().flatMap(data->Arrays.stream(data.split(" "))).forEach(word->System.out.println(word));
		System.out.println();
		List<String> inputList2 = Arrays.asList("10, 20, 30, 40, 50, 60, 70");
		inputList2.stream().flatMapToInt(data->{
			String[] strArr = data.split(",");
			int[] intArr = new int[strArr.length];
			for(int i=0; i<strArr.length;i++) {
				intArr[i] = Integer.parseInt(strArr[i].trim());
			}
			return Arrays.stream(intArr);
		}).forEach(number->System.out.println(number));
	}

}

 

아래 예제의 Student는 위(람다식으로 객체 컬렉션 처리)에서 정의했던 Student class 입니다.

같은 패키지에 정의되어 있다면 다시 정의할 필요가 없습니다.

public class MapEx {

	public static void main(String[] args) {
		List<Student> studentList = Arrays.asList(
				new Student("김철수",10),
				new Student("이철수",20),
				new Student("박철수",30),
				new Student("안철수",40)
				);
		studentList.stream().mapToInt(Student :: getScore).forEach(score->System.out.println(score));
	}
}

 

아래는 정수 데이터를 실수로 대체하고 박싱도 해보는 예제입니다. 

import java.util.Arrays;
import java.util.stream.IntStream;

public class AsDoubleStreamEx {

	public static void main(String[] args) {
		int[] intArray = {5,4,3,2,1};
		IntStream intStream = Arrays.stream(intArray);
		intStream.asDoubleStream().forEach(d->System.out.println(d));
		System.out.println();
		intStream = Arrays.stream(intArray);
		intStream.boxed().forEach(obj->System.out.println(obj.intValue()));
	}

}

 

스트림 정렬

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.IntStream;

class SortableStudent extends Student implements Comparable<SortableStudent>{

	public SortableStudent(String name, int score) {
		super(name, score);
	}

	@Override
	public int compareTo(SortableStudent o) {
		return Integer.compare(getScore(), o.getScore());
	}
	
}

public class SortingEx {

	public static void main(String[] args) {
		IntStream intStream = Arrays.stream(new int[] {5,2,1,4,3});
		intStream.sorted().forEach(n->System.out.print(n + ","));
		System.out.println();
		
		List<SortableStudent> studentList = Arrays.asList(
				new SortableStudent("김철수", 30),
				new SortableStudent("이철수", 20),
				new SortableStudent("박철수", 10),
				new SortableStudent("안철수", 40)
				);
		studentList.stream().sorted().forEach(s->System.out.print(s.getScore() + ","));
		System.out.println();
		
		studentList.stream().sorted(Comparator.reverseOrder()).forEach(s->System.out.print(s.getScore()+","));
	}

}

 

스트림 루핑 peek(), forEach()

스트림 루핑은 요소 전체를 반복처라하는 것을 말하는데, peek()은 중간처리 메소드이지만 forEach()는 최종 처리 메소드의 차이가 있습니다.

peek()은 최종 처리 메소드가 호출될때까지 지연되기 때문에 반드시 최종처리 메소드를 호출해야만 합니다.

import java.util.Arrays;

public class LoopingEx {

	public static void main(String[] args) {
		int[] intArr = {1,2,3,4,5,6,7};
		System.out.println("peek()을 마지막에 호출한 경우. 동작하지 않음.(중간처리메소드)");
		Arrays.stream(intArr).filter(a->a%2==0).peek(n->System.out.println(n));	//동작하지 않음
		System.out.println("최종처리 메소드를 마지막에 호출한 경우.");
		int total = Arrays.stream(intArr).filter(a->a%2==0).peek(n->System.out.println(n)).sum();
		System.out.println("합계:"+total);
		System.out.println("forEach()를 마지막에 호출한경우");
		Arrays.stream(intArr).filter(a->a%2==0).forEach(n->System.out.println(n));
	}

}

 

스트림 매칭 allMatch(), anyMatch(), noneMatch()

매칭 여부를 판단해서 boolean을 리턴해주는 메소드입니다.

import java.util.Arrays;

public class MathchEx {

	public static void main(String[] args) {
		int[] intArr = {2,4,6,8};
		boolean result = Arrays.stream(intArr).allMatch(a->a%2==0);
		System.out.println("모두 2의 배수? "+result);
		result = Arrays.stream(intArr).anyMatch(a->a%3==0);
		System.out.println("하나라도 3의 배수? "+result);
		result = Arrays.stream(intArr).noneMatch(a->a%3==0);
		System.out.println("3의 배수가 없음? "+result);
		
	}

}

 

기본 집계 메소드 sum(), count(), average(), max(), min()

import java.util.Arrays;

public class AggregateEx {

	public static void main(String[] args) {
		long count = Arrays.stream(new int[] {1,2,3,4,5,6}).filter(n->n%2==0).count();
		System.out.println("2의 배수의 개수:"+count);
		long sum = Arrays.stream(new int[] {1,2,3,4,5,6}).filter(n->n%2==0).sum();
		System.out.println("2의 배수의 합:"+sum);
		double avg = Arrays.stream(new int[] {1,2,3,4,5,6}).filter(n->n%2==0).average().getAsDouble();
		System.out.println("2의 배수의 평균:"+avg);
		int max = Arrays.stream(new int[] {1,2,3,4,5,6}).filter(n->n%2==0).max().getAsInt();
		System.out.println("2의 배수 최대값:"+max);
		int min = Arrays.stream(new int[] {1,2,3,4,5,6}).filter(n->n%2==0).min().getAsInt();
		System.out.println("2의 배수 최소값:"+min);
		int first = Arrays.stream(new int[] {1,2,3,4,5,6}).filter(n->n%3==0).findFirst().getAsInt();
		System.out.println("첫번째 3의 배수 값:"+first);
	}

}

 

Optional 클래스

컬렉션의 요소는 동적으로 추가되는 경우가 대부분입니다.

만약 동적으로 요소가 없는 상황에서 평균 값을 구하는 메소드를 실행할 경우 예외가 발생합니다.

이런경우를 대비해 디폴트 값을 지정해줄 수 있습니다.

import java.util.ArrayList;
import java.util.List;
import java.util.OptionalDouble;

public class OptionalEx {

	public static void main(String[] args) {
		
		List<Integer> list = new ArrayList<>();
		//list에 값이 없는 경우 평균 구하기
		OptionalDouble optional = list.stream().mapToInt(Integer::intValue).average();
		if(optional.isPresent()) {
			System.out.println("평균1: "+optional.getAsDouble());
		}else {
			System.out.println("평균1(기본값): "+0.0);
		}
		
		double avg = list.stream().mapToInt(Integer::intValue).average().orElse(0.0);
		System.out.println("평균2: "+avg);
		
		list.stream().mapToInt(Integer::intValue).average().ifPresent(a->System.out.println("방법3: "+a));
	}

}

 

커스텀 집계 reduce()

import java.util.Arrays;
import java.util.List;

public class ReductionEx {

	public static void main(String[] args) {
		List<Student> studentList = Arrays.asList(
				new Student("홍길동", 92),
				new Student("고길동", 95),
				new Student("김길동", 88));
		
		int sum1 = studentList.stream().mapToInt(Student::getScore).sum();
		int sum2 = studentList.stream().map(Student::getScore).reduce((a,b)->a+b).get();
		int sum3 = studentList.stream().map(Student::getScore).reduce(0,(a,b)->a+b);
		
		System.out.println(sum1);
		System.out.println(sum2);
		System.out.println(sum3);
	}

}

 

수집 collect()

필터링 또는 매핑한 요소들을 수집하는 기능입니다.

필요한 요소만 컬렉션에 담을 수 있고 요소들을 그룹핑한 후 집계(reduction) 할 수 있습니다.

import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

class Student2{
	public enum Sex{MALE, FEMALE}
	public enum City{Seoul, Pusan}
	private String name;
	private int score;
	private Sex sex;
	private City city;
	
	public Student2(String name, int score, Sex sex) {
		this.name = name;
		this.score = score;
		this.sex = sex;
	}
	
	public Student2(String name, int score, Sex sex, City city) {
		this.name = name;
		this.score = score;
		this.sex = sex;
		this.city = city;
	}
	
	public String getName() {return name;}
	public int getScore() {return score;}
	public Sex getSex() {return sex;}
	public City getCity() {return city;}
}

public class ToListEx {

	public static void main(String[] args) {
		List<Student2> totalList = Arrays.asList(
				new Student2("김철수", 10, Student2.Sex.MALE),
				new Student2("김영희", 6, Student2.Sex.FEMALE),
				new Student2("안철수", 8, Student2.Sex.MALE),
				new Student2("이영희", 10, Student2.Sex.FEMALE)
				);
		
		//남자만 수집하여 새로운 컬렉션(list) 생성
		List<Student2> maleList = totalList.stream().filter(s->s.getSex()==Student2.Sex.MALE).collect(Collectors.toList());
		maleList.stream().forEach(s->System.out.println(s.getName()));
		System.out.println();
		
		//여자만 수집하여 새로운 컬렉션(HashSet) 생성
		Set<Student2> femaleSet = totalList.stream().filter(s->s.getSex() == Student2.Sex.FEMALE).collect(Collectors.toCollection(HashSet::new));
		femaleSet.stream().forEach(s->System.out.println(s.getName()));
	}

}

 

사용자 정의 컨테이너에 수집

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

class MaleStudent{
	public List<Student2> list;
	public MaleStudent() {
		list = new ArrayList<Student2>();
	}
	//요소를 수집하는 메소드
	public void accumulate(Student2 student) {
		list.add(student);
	}
	//두 MaleStudent를 결합하는 메소드
	public void combine(MaleStudent other) {
		list.addAll(other.getList());
	}
	public List<Student2> getList(){
		return list;
	}
}

public class MaleStudentEx {

	public static void main(String[] args) {
		List<Student2> totalList = Arrays.asList(
				new Student2("김철수", 10, Student2.Sex.MALE),
				new Student2("김영희", 6, Student2.Sex.FEMALE),
				new Student2("안철수", 8, Student2.Sex.MALE),
				new Student2("이영희", 10, Student2.Sex.FEMALE)
				);
		
		MaleStudent maleStudent = totalList.stream().filter(s->s.getSex()==Student2.Sex.MALE)
				.collect(MaleStudent::new, MaleStudent::accumulate, MaleStudent::combine );
		
		maleStudent.getList().stream().forEach(s->System.out.println(s.getName()));
	}

}

 

요소를 그룹핑해서 수집

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class GroupingEx {

	public static void main(String[] args) {
		List<Student2> totalList = Arrays.asList(
				new Student2("김철수", 10, Student2.Sex.MALE, Student2.City.Seoul),
				new Student2("김영희", 6, Student2.Sex.FEMALE, Student2.City.Pusan),
				new Student2("안철수", 8, Student2.Sex.MALE, Student2.City.Pusan),
				new Student2("이영희", 10, Student2.Sex.FEMALE, Student2.City.Seoul)
				);
		
		Map<Student2.Sex, List<Student2>> mapBySex = totalList.stream().collect(Collectors.groupingBy(Student2::getSex));
		
		System.out.print("[남자]");
		mapBySex.get(Student2.Sex.MALE).stream().forEach(s->System.out.print(s.getName() + " "));
		System.out.println();
		System.out.print("[여자]");
		mapBySex.get(Student2.Sex.FEMALE).stream().forEach(s->System.out.print(s.getName() + " "));
		System.out.println();
		
		Map<Student2.City, List<String>> mapByCity = totalList.stream()
				.collect(Collectors.groupingBy(Student2::getCity,Collectors.mapping(Student2::getName, Collectors.toList())));
		System.out.print("[서울]");
		mapByCity.get(Student2.City.Seoul).stream().forEach(s->System.out.print(s + " "));
		System.out.println();
		System.out.print("[부산]");
		mapByCity.get(Student2.City.Pusan).stream().forEach(s->System.out.print(s + " "));
	}

}

 

그룹핑 후 집계

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class GroupingAndReductionEx {

	public static void main(String[] args) {
		List<Student2> totalList = Arrays.asList(
				new Student2("김철수", 10, Student2.Sex.MALE, Student2.City.Seoul),
				new Student2("김영희", 6, Student2.Sex.FEMALE, Student2.City.Pusan),
				new Student2("안철수", 8, Student2.Sex.MALE, Student2.City.Pusan),
				new Student2("이영희", 20, Student2.Sex.FEMALE, Student2.City.Seoul)
				);
		
		//성별로 평균 점수를 저장하는 Map
		Map<Student2.Sex, Double> mapBySex = totalList.stream().collect(Collectors.groupingBy(Student2::getSex, Collectors.averagingDouble(Student2::getScore)));		
		System.out.println("남자 평균:"+mapBySex.get(Student2.Sex.MALE));
		System.out.println("여자 평균:"+mapBySex.get(Student2.Sex.FEMALE));
		
		//성별로 이름을 쉼표로 구분하여 저장하는 Map
		Map<Student2.Sex, String> mapByName = totalList.stream()
				.collect(Collectors.groupingBy(Student2::getSex, Collectors.mapping(Student2::getName, Collectors.joining(","))));
		System.out.println("남자 전체 이름: "+mapByName.get(Student2.Sex.MALE));
		System.out.println("여자 전체 이름: "+mapByName.get(Student2.Sex.FEMALE));
	}

}

 

스트림 병렬처리

import java.util.Arrays;
import java.util.List;

public class TimeTest {

	//요소 처리
	public static void work(int value) {
		try {
			Thread.sleep(100);
		}catch(Exception e) {
			
		}
	}
	
	//순차 처리
	public static long testSequencial(List<Integer> list) {
		long start = System.nanoTime();
		list.stream().forEach((a)->work(a));
		long end = System.nanoTime();
		long runTime = end - start;
		return runTime;
	}
	
	//병렬 처리
	public static long testParallel(List<Integer> list) {
		long start = System.nanoTime();
		list.stream().parallel().forEach((a)->work(a));
		long end = System.nanoTime();
		long runTime = end - start;
		return runTime;
	}
	
	public static void main(String[] args) {
		//컬렉션
		List<Integer> list = Arrays.asList(0,1,2,3,4,5,6,7,8,9);
		
		long timeSequencial = testSequencial(list);
		long timeParallel = testParallel(list);
		
		System.out.println(timeSequencial);
		System.out.println(timeParallel);
	}

}

 

 

 

 

 

 

 

 

 

 

728x90

'DEV&OPS > Java' 카테고리의 다른 글

파일 입출력  (0) 2022.05.18
자바 콘솔 입출력  (0) 2022.05.17
Stack과 Queue  (0) 2022.05.10
컬랙션 검색, 병렬처리, 동기화  (0) 2022.05.09
Map 컬렉션  (0) 2022.05.09