개발 메모장

[Java] Stream API(1) - 스트림 데이터 생성 본문

Java

[Java] Stream API(1) - 스트림 데이터 생성

yyyyMMdd 2023. 12. 12. 17:36
728x90

#. 서비스 중인 프로젝트가 기존 Java 7이었고 알다시피 Java 7은 22년 7월 29일부로 수명이 종료됐습니다.

 

#. 수명 종료로 인한 업데이트 및 보안 패치 미적용으로 자바 버전을 8로 올려야 했습니다.

 

#. 이에 따라 Java 8부터 추가된 Stream API에 대해 알아보려고 합니다.

 

#. Stream API는 크게 보면 데이터 생성, 데이터처리, 결과도출 방식으로 메서드를 호출하는데 데이터 생성에 대한 부분을 먼저 살펴보려 합니다.


  • Stream 사용의 장점

    - 코드를 간결히 작성할 수 있습니다.

    - 병렬 처리와 같은 기술로 처리 속도를 높일 수 있습니다.

    - Stream 내부적으로 반복된 작업을 처리하여 편합니다.

    - 여러 종류의 컬렉션들을 Stream으로 변환하여 처리할 수 있어  데이터 처리에 일관성이 생깁니다.

    - 한 메서드를 통해 연결된 여러 작업을 한 번에, 원활히 구성할 수 있게 합니다.(메서드 체이닝)

  • Stream 사용의 단점

    - 스트림 초기화, 메서드 호출 등의 비용으로 성능 오버헤드가 발생해 기존 방식보다 성능이 떨어지는 경우가 있습니다.

    - 코드가 추상화되기 때문에 실수할 가능성이 있습니다.

    - 일반적으로 순차처리 되는 소스보다 디버깅이 어렵습니다.

    - 스트림은 일회성이라 여러 작업을 수행해야 할 때 스트림을 다시 생성해야 할 수 있고 이로 인한 비용이 발생할 수 있습니다.

  • Stream 사용 전후 비교
// Stream 사용 전
String[] strArr = {"test1", "test2", "test3"};
List<String> testList = Arrays.asList(strArr);

Collections.sort(nameList);

for (String str : testList) {
  System.out.println(str);
}
// Stream 사용 후
String[] strArr = {"test1", "test2", "test3"};
List<String> testList = Arrays.asList(strArr);

Stream<String> testListStream = testList.stream();

testListStream.sorted().forEach(System.out::println);

 

  • Stream 인스턴스 생성

    - 스트림 데이터로 변환하기 위한 전처리라고 보면 좋을 것 같습니다.

    - 데이터는 스트림을 이용해 처리한 뒤 리스트형으로 변환하여 찍은 결과를 보여드릴 예정입니다.

    - 배열, 컬렉션, 스트림 of 등을 통해 만들어진 객체를 변환하는 방법은 아래와 같습니다.

  • 스트림 메서드 데이터 처리

    - Stream.of를 이용해 데이터를 넣어줍니다.
public static void streamTest() {
	Stream<String> streamData = Stream.of("bird", "fish", "plant");
	List<String> streamDataList = streamData.collect(Collectors.toList());
// [bird, fish, plant]
}

 


  • 배열 데이터 처리

    - 배열 데이터는 스트림으로 처리가 불가하므로 List로 변환 후 처리합니다.
public static void streamTest() {
	String[] strArr = {"seoul", "incheon", "bucheon", "suwon", "ansan"};
	List<String> strArrList = Arrays.asList(strArr);
	List<String> strArrStreamList = strArrList.stream()
                                        .filter(a -> a.startsWith("i") || a.startsWith("a"))
                                        .map(String::toUpperCase)
                                        .collect(Collectors.toList());
// [INCHEON, ANSAN]
}

 


  • 리스트 데이터 처리
public static void streamTest() {
	List<String> myList = Arrays.asList("january", "march", "july", "december");
	List<String> filteredList = myList.stream()
                                    .filter(a -> a.startsWith("j"))
                                    .map(String::toUpperCase)
                                    .collect(Collectors.toList());
// [JANUARY, JULY]
}

 


  • 셋 데이터 처리
public static void streamTest() {
	Set<String> mySet = new HashSet<>();
	mySet.add("orange");
	mySet.add("pear");
	mySet.add("kiwi");
	
	Set<String> startWithOList = mySet.stream()
    				    .filter(o -> o.startsWith("o"))
                                    .collect(Collectors.toSet());
// [orange]
}

 


  • 맵 데이터 처리
public static void streamTest() {
	Map<String, Object> myMap = new HashMap<>();
	myMap.put("animal", "lion");
	myMap.put("star", "orion");
	myMap.put("veg", "carrot");
		
	Map<String, Object> mapStream = myMap.entrySet().stream()
    					.filter(a -> a.getValue().toString().startsWith("o"))
                                        .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue()));
// {star=orion}
}

 


  • 타입형 Stream 인스턴스 생성

    - 변수 타입으로 많이 접해본 int, Long, Double, chars 등의 스트림 생성 방법입니다.

  • IntStream
IntStream is = IntStream.rangeClosed(20, 30);
int[] arrayIs = is.toArray();
System.out.println(Arrays.toString(arrayIs));
// [20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]

 

- 위처럼 IntStream을 사용하지 않으면 아래처럼 비교적 복잡한 방법을 사용해야 합니다.(Long, Double도 동일)

int startNum = 20;
int endNum = 30;

List<Integer> integerStream = Stream.iterate(startNum, i -> i <= endNum, i -> i + 1)
                                     .limit(endNum - startNum + 1)
                                     .collect(Collectors.toList());
System.out.println(integerStream);
// [20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]

 

 


  • LongStream
LongStream ls = LongStream.range(9, 12);
long[] arrayLs = ls.toArray();
System.out.println(Arrays.toString(arrayLs));
// [9, 10, 11]

 


  • DoubleStream
DoubleStream ds = DoubleStream.iterate(1, n -> n + 1).limit(10);
double[] arrayDs = ds.toArray();
System.out.println(Arrays.toString(arrayDs));
// [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]

 


  • iterate()

    - iterate() 인자 값에 리턴 계산식을 추가하여 값을 처리합니다.

    - limit 메서드를 사용해 반복될 횟수를 지정해 주어야 합니다.

    - 만약 지정하지 않으면 무한 반복되어 PC 소리가 커지면서 OOM이 발생하게 됩니다.
Stream<Integer> iter = Stream.iterate(5, n -> n + 2).limit(10);
System.out.println(iter.toList());
// [5, 7, 9, 11, 13, 15, 17, 19, 21, 23]

  • generate()

    - generate는 람다식에 넣은 값을 limit의 인자만큼 추가해 줍니다.

    - limit에 값을 정하지 않으면 무한으로 생성하므로 OOM이 발생합니다.

    - 람다식은 Supplier<? extends String> s와 같이 들어가고 이는 인자 없이 리턴값만 정해주면 됩니다.
Stream<String> generate = Stream.generate(() -> "test").limit(3);
System.out.println(generate.toList());
// [test, test, test]

  • concat()

    - 2개 이상의 스트림으로 생성한 값을 한 스트림으로 합치고자 할 때 사용합니다.

    - 기본적으로 중복 허용되나 distinct()를 이용해 중복 제거를 할 수 있습니다.

    - 인자로 들어가는 객체가 스트림이 아닐 경우 스트림 객체로 파싱 해줘야 합니다.( .stream() )
Stream<String> concat1 = Stream.of("test", "test1");
Stream<String> concat2 = Stream.of("test3", "test1");
List<String> concatList = Stream.concat(concat1, concat2).distinct().toList();
System.out.println(concatList);
// [test, test1, test3]

 


#. 이 밖에도 더 많은 메서드들이 있으나 하나하나 보기엔 조금 무리가 있고 기본적인 흐름은 위 정리한 바와 같습니다.

 

#. 자바 7을 사용했다 보니 조금 어색하고 눈에 잘 안 들어오긴 하나 익숙해지면 개발적인 부분이나 가독성 부분도 좋게 느껴질 것 같습니다.

 

 

===========================================================
틀린 내용이 있거나 이견 있으시면 언제든 가감 없이 말씀 부탁드립니다!

===========================================================

728x90