Java 개념 정리V

9 분 소요

Java 개념 정리

람다와 스트림 (Lambda & Stream)

1. 람다(Lambda)

1-1. 람다식(Lambda Expression)이란;

  • 함수(메서드)를 간단한 ‘식(Expression)’ 으로 표현한는 방법
      int max(int a, int b){
          return a > b  a : b;  
      }
              
      (a,b) -> a > b ? a : b
    
  • 익명 함수(이름이 없는 함수, annonymous function)
      int max(int a, int b){
          return a > b ? a : b;
      }
              
      (int a, int b) -> {
          return a > b ? a : b;
      }
    
  • 함수와 메서드의 차이
    • 근본적으로 동일. 함수는 일반적 용어, 메서드는 객체지향개념 용어
    • 함수는 클래스에 독립적 ,메서드는 클래스에 종속적

1-2. 람다식 작성하기

  • 메서드의 이름과 반환타입을 제거하고 ‘->’를 볼록{} 앞에 추가한다.
      int max(int a, int b){
          return a > b ? a : b;
      }
              
      (int a, int b) -> {
          return a > b ? a : b;
      }
    
  • 반환 값이 있는 경우, 식이나 값만 적고 return문 생략 가능 ( 끝에 ‘ ;’ 안 붙암)
      (int a, int b)->{
          return a > b ? a : b;
      }
                  
      (int a, int b) -> a > b ? a : b
    
  • 매개변수의 타입이 추론 가능하면 생략가능(대부분의 경우 생략 가능)
      (int a, int b) -> a > b ? a : b
                  
      (a,b) -> a > b ? a : b
    
  • 주의 사항
  • 매개변수가 하나인 경우, 괄호() 생략 가능 (타입이 없을 때만)
      a -> a * a //OK
      int a -> a * a //에러 (int a)->a*a
    
  • 블록 안의 문장이 하나뿐 일 때, 괄호{} 생략 가능(끝에 ‘;’ 안 붙임)
      (int i) -> System.out.println(i)
    
  • 단, 하나뿐인 문장이 return문이면 괄호{} 생략 불가
      (int a, int b) -> {return a > b ? a : b ; }
    

1-3. 함수형 인터페이스

  • 람다식은 익명 함수? 사실은 익명 객체
      new Object(){
          int max(int a, int b){
              return a > b ? a : b;
          }
      }
    
  • 람다식(익명 객체)을 다루기 위한 참조변수가 필요. 참조변서의 타입은?
      Object obj = new Object(){
          int max(int a, int b){
              return a > b ? a : b;
          }
      };
      int value = obj.max(3,5);  //에러. Object클래스에 max()가 없음
    
  • 함수형 인터페이스 - 단 하나의 추상 메서드만 선언된 인터페이스
      interface MyFunction{
          public abstract int max(int a , int b);
      }
    
      MyFunction f = new MyFunction(){
          public int max(int a, int b){
              return a > b ? a : b;
          }
      };
    
      int value = f.max(3,5);  //OK. MyFunction에 max()가 있음
    
  • 함수형 인터페이스 타입의 참조변수로 람다식을 참조할 수 있음 (단, 함수형 인터페이스의 메서드와 람다식의 매개변수 개수와 반환타입이 일치해야 함.)
      MyFunction f = (a,b) -> a > b ? a : b;
      int value = f.max(3,5);  //실제로는 람다식(익명 함수)이 호출됨
    
  • 익명 객체를 람다식으로 대체
      List<String> list = Arrays.asList("abc","aaa","bbb","ddd","aaa");
      Collections.sort(list,(s1,s2)-> s2.compareTo(s1));
    

1-4. java.util.function패키지

  • 자주 사용되는 다양한 함수형 인터페이스를 제공 함수형인터페이스
      Predicate<String> isEmptyStr = s-> s.length()==0;
      String s = "";
    
      if(isEmptyStr.test(s)) //if(s.length()==0)
          System.out.println("This is an empty String.");
    
  • 매개변수가 2개인 함수형 인터페이스 함수형인터페이스2

      @FunctionalInterface
      interface TriFunction<T,U,V,R>{
          R apply(T t, U u, V v);
      }
    
  • 매개변수의 타입과 반환타입이 일치하는 함수형 인터페이스 함수형인터페이스3
      @FunctinalInterface
      public interface UnaryOperator<T> extends Function<T,T>{
          static <T> UnaryOperator<T> identity(){
              return t -> t;
          }
      }
    
  • 함수형 인터페이스를 사용하는 컬렉션 프레임웍의 메서드 함수형인터페이스4
      list.forEach(i->System.out.print(i+",")); //list의 모든 요소를 출력
      list.removeIf(x->x%2==0||x%3==0); //2또는 3의 배수를 제거
      list.remplace(i->i&10);  //모든 요소에 10을 곱한다.
      //map의 모든 요소를 {k,v}의 형식으로 출력
      amp.forEach((k,v)->System.out.println("{"+k+","+v+"},"));
    
  • 기본형을 사용하는 함수형 인터페이스 함수형인터페이스5
      IntSupplier s = () -> (int)(Math.random()*100)+1;
      static void makeRandomList(IntSupplier s, int[] arr){
          for(int i=0;i<arr.length;i++)
              arr[i]= s.getAsInt();
      }
    

1-5. Function의 합성

  • Function 타입의 두 람다식을 하나로 합성 - addThen()
      Function<String, Integer> f = (s) -> Integer.parseInt(s,16); //s를 16진 정수로 변환
      Function<Integer,String> g = (i) -> Integer.toBinaryString(i); //2진 문자열로 변환
      Function<String, String> h = f.addThen(g); //f+g -> h
    
  • Function 타입의 두 람다식을 하나로 합성 - compose()
      Function<Integer, String> g = (i) -> Integer.toBinaryString(i); //2진 문자열로 변환
      Function<String, Integer> f = (s) -> Integer.parseInt(s,16); // s를 16진 정수로 변환
      Function<Integer, Integer> h = f.compose(g);  //g+f -> h
    

1-6. Predicate의 결합

  • and(), or(), negate()로 두 Predicate를 하나로 결합(default 메서드)
      Predicate<Integer> p = i -> i < 100;
      Predicate<Integer> q = i -> i < 200;
      Predicate<Integer> r = i -> i % 2 == 0;
    
      Predicate<Integer> notP = p.negate(); //i>=100
      Predicate<Integer> all = notP.and(q).or(r); //100<=i && i<200|| i%2==0
      Predicate<Integer> all2 = notP.and(q.or(r));  //100 <=i && (i<200||i%2==0)
    
  • 등가비교를 위한 Predicate의 작성에는 isEqual()를 사용 (static메서드)
      Predicate<String> p = Predicate.isEqual(str1); //isEqual()은 static메서드
    

1-7. 메서드 참조(method reference)

  • 하나의 메서드만 호출하는 람다식은 ‘메서드 참조’로 간단히 할 수 있다.
    • static메서드 참조 - 람다식: (x)->className.method(x) / 메서드 참조: ClassName::method
    • 인스턴스메서드 참조 - 람다식: (obj.x)->obj.method(x) / 메서드 참죄: ClassName::method
    • 특정 객체 인스턴스메서드 참조 - 람다식: (x) -> obj.method(x) / 메서드 참조: obj::method
  • static 메서드 참조
      Integer method(String s){
          return Integer.parseInt(s);
      }
                      
      Function<String,Integer> f = (String s) -> Integer.parseInt(s);
                      
      Function<String,Integer> f = Integer::parseInt; // 메서드 참조
    
  • 인스턴스 메서드 참조
      BiFunction<String,String,Boolean> f = (s1,s2) -> s1.equals(s2);
                      
      BiFunction<String,String,Boolean> f = String::equals;
    
  • 특정 객체의 인스턴스 메서드 참조
      MyClass obj = new MyClass();
      Function<String, Boolean> f = (x) -> obj.equals(x); //람다식
      Function<String BOolean> f2 = obj::equals;  //메서드 참조
    
  • new 연산자(생성자,배열)와 메서드 참조
      Supplier<MyClass> s = MyClass::new;  //() -> new MyClass()
      Function<Integer, MyClass> f2 = MyClass::new; // (i) -> new MyClass(i)
      Function<Integer int[]> f2 = int[]::new;  //x -> new int[x];
    

2. 스트림(stream)

2-1. 스트림(Stream) 이란?

  • 다양한 데이터 소스를 표준화된 방법으로 다루기 위한 것
      List<Integer> list = Arrays.asList(1,2,3,4,5);
      Stream<Integer> intStream = list.stream(); //컬렉션
      Stream<String> strStream = Stream.of(new String[] {"a","b","c"}); //배열
      Stream<Integer> evenStream = Stream.iterate(0, n->n+2); 0,2,4,6,...
      Stream<Dobule> randomStream = Stream.generate(Math::random); //람다식
      IntStream intStream = new Random().int(5); //난수 스트림(크기가 5)
    
  • 스트림이 제공하는 기능 - 중간 연산과 최종 연산
  • 중간 연산: 연산 결과가 스트림인 연산. 반복적으로 적용가능
  • 최종 연산: 연산결과가 스트림이 아닌 연산. 스트림의 요소를 소모하므로 한번만 적용가능
      stream.distinct().limit(5).sorted().forEach(System.out::println)
             중간연산   중간연산  중간연산        최종연산
    
      String[] strArr = {"dd","aaa","CC","cc","b"};
      Stream<String> stream = Stream.of(strArr); //문자열 배열이 소스인 스트림
      Stream<String> filteredStream = stream.filter(); //걸러내기(중간연산)
      Stream<String> distinctedSTream = stream.distinct(); //중복제거(중간연산)
      Stream<String> sortedStream = stream.sort();  //정렬(중간연산)
      Stream<String> limitedStream = stream.limit(5);  //스트림 자르기(중간연산)
      int total = stream.count();  //요소 개수 세기(최종연산)
    

2-2. 스트림(Stream)의 특징

  • 스트림은 데이터 소스로부터 데이터를 읽기만할 뿐 변경하지 않는다.
      List<Integer> list = Arrays.asList(3,1,5,4,2);
      List<Integer> sortedList = list.stream().sorted().collect(Collectors.toList());  //list를 정렬해서 새로운 List에 저장
      System.out.println(list); //[3,1,5,4,2]
      System.out.println(sortedList);  //[1,2,3,4,5]
    
  • 스트림은 Iterator처럼 일회용이다.(필요하면 다시 스트림을 생성해야 함)
      strStream.forEach(System.out::println); //모든 요소를 화면에 출력(최종연산)
      int numOfStr = strStream.count();  //에러. 스트림이 이미 닫혔음
    
  • 최종 연산 전까지 중간연산이 수행되지 않는다 -지연된 연산
      IntStream intStream = new Random().ints(1,46) //1~45범위의 무한 스트림
      IntStream.distinct().limit(6).sorted().forEach(i-> System.out.print(i+","));
    
  • 스트림은 작업을 내부 반복으로 처리한다.
      void forEach(Consumer<? super T> action){
          Objects.requireNonNull(action); //매개변수의 널 체크
          for(T t : src) //내부 반복(for문을 메서드 안으로 넣음)
              action.accept(T);
      }
    
  • 스트림의 작업을 병렬로 처리 - 병렬스트림
      Strema<String> strStream = Stream.of("dd","aaa","CC","cc","b");
      int sum = strStream.parallel() //병렬 스틞으로 전환(속성만 변경)
              .mapToInt(s -> s.length()).sum(); //모든 문자열의 길이의 합
    
  • 기본형 스트림 - IntStream, LongStream, DoubleStream
    • 오토박싱&언박싱의 비효율이 제거됨(Stream 대신 IntStream사용)
    • 숫자와 관련된 유용한 메서드를 Stream보다 더 많이 제공

2-3. 스트림의 생성

  • 컬렉션으로부터 스트림 생성하기
      List<Integer> list = Arrays.asList(1,2,3,4,5);
      Stream<Integer> intStream = list.stream(); //Stream<T> Collection.stream()
    
  • 배열로부터 스트림 생성하기
      Stream<String> strStream = Stream.of("a","b","c"); //가변 인자
      Strema<String> strStream = Stream.of(new String[]{"a","b","c"});
      Stream<String> strStream = Arrays.stream(new String[]{"a","b","c"});
      Stream<String> strStream = Arrays.stream(new String[]{"a","b","c"},0,3);
    
  • 특정 범위의 정수를 요소로 갖는 스트림 생성하기
      IntStream intStream = IntStream.range(1,5); //1,2,3,4
      IntStream intStream = IntStream.rangeClosed(1,5); //1,2,3,4,5
    
  • 난수를 요소로 갖는 스트림 생성하기
      IntStream intStream = new Random().ints(); //무한 스트림
      intStream.limit(5).forEach(System.out::println); //5개의 요소만 출력한다
      IntStream intStrema = new Random().ints(5); //크기가 5인 난수 스트림을 반환
    
  • 람다식을 소스로 하는 스트림 생성하기
      static<T> Stream<T> iterate(T seed, UnaryOperator<T> f) //이전 요소에 종속적
      static<T> Stream<T> generate(Supplier<T> s) //이전 요소에 독립적
    
  • 파일을 소스로 하는 스트림 생성하기
      Stream<Path> Files.list(Path dir) //Path는 파일 또는 디렉토리
    

2-4. 스트림의 중간연산

  • 스트림 자르기: skip(), limit()
      Stream<T> skip(long n)  //앞에서부터 n개 건너뛰기
      Strema<T> limit(long maxSize)  //maxSize 이후의 요소는 잘라냄
    
  • 스트림의 요소 걸러내기: filter(), distinct()
      Stream<T> filter(Predicate<? super T> predicate) //조건에 맞지 않는 요소 제거
      Stream<T> distinct() //중복제거
    
  • 스트림 정렬하기: sorted()
      Stream<T> sorted(); //스트림 요소의 기본 정렬(Comparable)로 정렬
      Strema<T> sorted(Comparator<? super T> comparator) //지정된 Comparator로 정렬
    
  • 스트림의 요소 반환하기: map()
      Stream<R> map(Function<? super T,? extends R> mapper) //Stream<T> -< Stream<R>
    
  • 스트림을 기본 스트림으로 변환: mapToInt(), mapToLong(), mapToDouble()
      IntStream mapToInt(ToIntFunction<? super T> mapper) //Stream<T> -> IntStream
      LongStream mapToLong(ToLongFunction<? super T> mapper) //stream<T> -> LongStream
      DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper) //Stream<T> -> DoubleStream
    
  • 기본 스트림을 스트림으로 변환: mapToObj(), boxed()
      Stream<T> mapToObj(IntFunction<? extends T> mapper) //IntStream -> Stream<T>
      Stream<Integer> boxed()  //IntStream -> Stream<Integer>
    
  • 스트림의 스트림을 스트림으로 변환: flatMap()
      Stream<String[]> strArrStrm = Stream.of(new String[]{"abc","def","ghi"}, new String[]{"ABC","GHI","JKLMN"});
    
      Stream<Stream<string>> strStrStrm = strArrStrm.map(Arrays::stream);
    
      Strea<String> strStrStrm= strArrStrm.flatMap(Arrays::stream); //Arrays.stream(T[])
    
  • 스트림의 요소를 소비하지 않고 엿보기: peek()
      Stream<T> peek(Consumer<? super T> actoin)  //중간연산(스트림을 소비x)
      void forEach(Consumer<? super T> action)  //최종연산(스트림을 소비O)
    

2-5. Optional과 OptionalInt

  • ‘T’타입 객체의 래퍼클래스 - Optional
      String str = "abc";
      Optional<String> optVal = Optional.of(str);
      Optional<String> optVal = Optinal.of("abc");
      Optional<String> optVal = Optinal.of(null);  //NullPointerException발생
      Optional<String> optval = Optinal.ofNullable(null);  //OK
    
  • Optional객체의 값 가져오기 - get(), orElse(), orElseGet(), orElseThrow()
      Optional<String> optVal = Optional.of("abc");
      String str1 = optVal.get();  //optVal에 저장된 값을 반환. null이면 예외발생
      String str2 = optVal.orElse(""); //optVal에 저장된 값이 null일 때는, ""를 반환
      String str3 = optVal.orElseGet(String::new);  //람다식 사용가능() -> new String()
      String str4 = optVal.orElseThrow(NullPointerException::new);  //널이면 예외발생
    
  • isPresent() - Optional객체의 값이 null이면 false, 아니면 true를 반환
      if(Optional.ofNullable(str).isPresent()){ //if(str!=null){
          System.out.println(str);
      }
    
  • 기본형 값을 감싸는 래퍼클래스 - OptionalInt, OptionalLong, OptionalDouble
      public final class OptionalInt{
          private final boolean isPresent; //값이 저장되어 있으면 true
          private final int value;  //int타입의 변수
      }
    
  • OptionalInt의 값 가져오기 - int getAsInt()

2-6. 스트림의 최종연산

  • 스트림의 모든 요소에 지정된 작업을 수행 - forEach(), forEachOrdered()
      void forEach(Consumer<? super T> action) //병렬스트림인 경우 순서가 보장되지 않음
      void forEachOrdered(Consumer<? super T> action)  //병렬스트림인 경우에도 순서가 보장됨
    
      IntStream.range(1,10).sequential().forEach(System.out::print);  //123456789
      IntStream.range(1,10).sequential().forEachOrdered(System.out::print);  //123456789
    
      IntStream.range(1,10).parallel().forEach(System.out::print);  //683295714
      IntStream.range(1,10).parallel().forEachOrdered(System.out::print);  //123456789
    
  • 스트림을 배열로 변환 - toArray()
      Object[] toArray()   //스트림의 모든 요소를 Object배열에 담아 반환
      A[] toArray(IntFunction<A[]> generator)  //스트림의 모든 요소를 A타입의 배열에 담아 반환
    
  • 조건 검사 - allMatch(), anyMatch(), noneMatch()
      boolean allMatch(Predicate<? super T> predicate) //모든 요소가 조건을 만족시키면 true
      boolean anyMatch(Predicate<? super T> predicate) //한 요소라도 조건을 만족시키면 true
      boolean noneMatch(Predicate<? super T> predicate)  //모든 요소가 조건을 만족시키지 않으면 true
    
  • 조건에 일치하는 요소 찾기 - findFirst(), findAny()
      Optional<T> findFirst()  //첫 번째 요소를 반환. 순차 스트림에 사용
      Optional<T> findAny()  //아무거나 하나를 반환. 병렬 스트림에 사용
    
  • 스트림에 대한 통계정보 제공 - count(), sum(), average(), max(), min()
      long count()
      Optional<T> max(Comparator<? super T> comparator)
      Optional<T> min(Comparator<? super T> comparator)
    
      long count()
      Int sum()
      OptionalDouble average()
      OptionalInt max()
      OptionalInt min()
      IntSummaryStatistice summaryStatistics()
    
  • 스트림의 요소를 하나씩 줄여가며 누적연산 수행 - reduce()
      Optional<T> reduce(BinaryOperator<T> accumulator)
      T reduce(T identity, BinaryOperator<T> accumulator)
      U reduce(U identity, Bifunction<U,T,U> accumulator, BinaryOperator<U> combiner)
    
      identity -초기값  accumulator - 이전 연산결과와 스트림의 요소에 수행할 연산
      combiner - 병렬처리된 결과를 합치는데 사용할 연산 (병렬 스트림)
    

2-7. collect(), Collector, Collectors

  • collect()는 Collector를 매개변수로 하는 스트림의 최종연산
      Object collect(Collect collect) //Collector를 구현한 클래스의 객체를 매개변수로
      Object collect(Supplier supplier, BiConsumer accumulator, BiConsumer combiner)
    
  • Collector는 수집(collect)에 필요한 메서드를 정의해 놓은 인터페이스
  • Collects클래스는 다양한 기능의 컬렉터(Collector를 구현한 클래스)를 제공
      변환 - mapping(), toList(), toSet(), toMap(), toCollection()
      통계 - counting(), summingInt(), averagingInt(), maxBy(), minBy(), summarizingInt()
      문자열 결합 - joining()
      리듀싱 - reducing()
      그룹화와 분활 - groupingBy(), partitioningBy(), collectingAndThen()
    

2-8. Collectors의 메서드

  • 스트림을 컬렉션으로 변환 - toList(), toSet(), toMap(), toCollection()
      List<String> names = stuStream.map(Student::getName) // Stream<Student> -> Stream<String>
                                  .collect(Collectors.toList()); //Stream<String> -> List<String>
    
  • 스트림의 통계정보 제공 - counting(), summingInt(), maxBy(), minBy()
      long count = stuStream.count();
      long count = stuStream.collect(counting()); //Collectors.counting()
    
  • 스트림을 리듀싱 - reducing()
      Collector reducing(BinaryOperator<T> op)
      Collector reducing(T identity, BinaryOperator<T> op)
      Collector reducing(U identity, Function<T,U> mapper, BinaryOperator<U> op) //map+reduce
    
  • 문자열 스트림의 요소를 모두 연결 - joining()
      String studentNames = stuStream.map(student::getName).collect(joining());
    
  • 스트림의 요소를 2분할 - partitioningBy()
      Collector partitioningBy(Predicate predicate)
    
  • 스트림의 요소를 그룹화 - groupingBy()
      Collector groupingBy<Function classifier>
      Collector groupingBy<Function classifier, Collector downstream>
      Collector groupingBy<Function classifier, Supplier mapFactory, Collector downstream>   
    

태그:

카테고리:

업데이트: