topgaren / DevStudy

0 stars 0 forks source link

Java #2

Open topgaren opened 4 years ago

topgaren commented 4 years ago

Java Study

topgaren commented 4 years ago

람다 표현식(Lambda Expression)

1. 람다식 개요

람다 표현식은 기능 하나를 정의해서 전달해야 하는 상황에 사용될 수 있다. Comparator 인터페이스의 구현이 필요한 상황이 대표적인 예이다.

class SLenComp implements Comparator<String> {
    @Override
    public int compare(String s1, String s2) {
        return s1.length() - s2.length();
    }
}

class SLenComparator {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("Robot");
        list.add("Box");
        list.add("Lambda");

        Collections.sort(list, new SLenComp());

        for(String s : list) 
            System.out.println(s);
    }
}

위의 예제는 "리스트의 정렬"이라는 기능을 전달하기 위해 Comparator 인터페이스를 구현한 SLenComp 클래스를 정의한 것이다. 람다 표현식을 적용하면 리스트를 정렬 코드를 다음과 같이 수정할 수 있다.

Collection.sort(list, (s1, s2) -> s1.length() - s2.length());

또 하나의 대표적인 예는 Thread를 생성하는 것이다.

/* Thread - traditional */
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello World.");
    }
}).start();

/* Thread - Lambda Expression */
new Thread(()->{
    System.out.println("Hello World.");
}).start();

람다 표현식의 장점 중 하나는 이렇듯 불필요한 코드를 줄이고 가독성을 높인다는 것이다.

2. 람다식 표현 방법

( parameters ) -> expression body
( parameters ) -> { expression body }
( ) -> expression body
( ) -> { expression body }

3. 함수형 인터페이스 & @FunctionalInterface

추상 메소드가 딱 하나만 존재하는 인터페이스를 "함수형 인터페이스(Functional Interface)"라고 한다. 그리고 람다식은 함수형 인터페이스를 기반으로만 작성이 될 수 있다.

@FunctionalInterface
interface Calculate {
    int cal(int a, int b);
}

@FunctionalInterface는 함수형 인터페이스에 부합하는지 확인하기 위한 어노테이션이다. 해당 어노테이션을 갖는 인터페이스가 둘 이상의 추상 메소드를 갖는 경우 컴파일 오류로 이어진다. 그러나 static, default 선언이 붙은 메소드는 함수형 인터페이스의 정의에 아무런 영향을 미치지 않는다. 따라서 다음의 인터페이스도 함수형 인터페이스에 부합한다.

@FunctionalInterface
interface Calculate {
    int cal(int a, int b);
    default int add(int a, int b) { return a + b; }
    static int sub(int a, int b) { return a - b; }
}

Reference https://jdm.kr/blog/181 https://multifrontgarden.tistory.com/124 https://multifrontgarden.tistory.com/125?category=471239 윤성우의 열혈 Java 프로그래밍, 윤성우, 오렌지미디어, 2017

topgaren commented 4 years ago

스트림(Stream) Part 1

1. 스트림 개요

Java 8에서 추가된 스트림은 람다를 활용할 수 있는 기술이다. Java 8 이전의 배열 또는 컬렉션 인스턴스를 다루는 방법은 for 또는 foreach 문을 사용하여 요소 하나씩 꺼내서 다루는 것이었다. 로직이 복잡해질수록 여러 로직이 섞이게 되고 분기 발생 시 여러 번의 루프를 도는 경우가 발생하기도 한다.

내부가 비어있는 파이프에 물을 흘려보내면 다른 쪽 입구를 통해 물이 흘러나온다. 이와 유사하게 Java에서도 데이터의 흐름을 생성할 수 있으며, 이러한 데이터의 흐름을 가리켜 "스트림(Stream)"이라고 한다. 물이 흐르는 파이프는 물을 흘려보내는 공간을 제공할 뿐만 아니라, 정화, 수압 조절 등의 기능을 제공하기도 한다. 마찬가지로 Java의 스트림은 배열 또는 컬렉션 인스턴스를 대상으로 여러 연산을 수행하는 파이프를 생성할 수 있으며, 파이프의 출력을 또 다른 파이프의 입력으로 사용할 수 있다.

스트림에 대한 내용은 크게 세 가지로 나눌 수 있다.

  1. 스트림 생성 : 스트림 인스턴스 생성
  2. 중간 연산 : 마지막이 아닌 위치에서 진행되는 연산. 필터링(filtering), 맵핑(mapping) 등
  3. 최종 연산 : 마지막에 진행되어야 하는 연산. 최종적인 결과물을 만들어내는 작업.

2. 스트림 생성

스트림 생성은 배열 및 컬렉션 인스턴스를 대상으로 스트림을 생성하는 방법을 말한다.

2-1. 스트림 생성 : 배열

배열에 저장된 데이터를 대상으로 스트림을 생성할 때 호출하는 대표 메소드.

public static <T> Stream<T> stream(T[] array); // Arrays 클래스에 정의
String[] names = {"Kim", "Jin", "Sub"};
Stream<String> stm = Arrays.stream(names);  // 스트림 생성
stm.forEach(s -> System.out.println(s));    // 최종 연산 진행
String[] names = {"Kim", "Jin", "Sub"};
Arrays.stream(names)
      .forEach(s -> System.out.println(s));

기본(Primitive) 자료형의 값을 저장하는 배열을 대상으로 스트림을 생성하는 방법들

public static IntStream stream(int[] array)
public static IntStream stream(int[] array, int startInclusive, int endExclusive)
public static DoubleStream stream(double[] array)
public static DoubleStream stream(double[] array, int startInclusive, int endExclusive)
public static LongStream stream(long[] array)
public static LongStream stream(long[] array, int startInclusive, int endExclusive)

2-2. 스트림 생성 : 컬렉션 인스턴스

컬렉션 인스턴스를 대상으로 스트림을 생성하는 stream메소드는 java.util.Collection<E>에 디폴트 메소드로 정의되어 있다.

default Stream<E> stream()
List<String> list = Arrays.asList("Toy", "Robot", "Box");
list.stream()
    .forEach(s -> System.out.print(s + "\t"));

2-3. 다양한 스트림 생성

비어 있는 스트림

Stream<String> stream = Stream.empty();

Stream.builder()

Stream<String> buliderStream = 
    Stream.<String>builder()
          .add("Toy").add("Robot").add("Box")
          .build();  // [Toy, Robot, Box]

Stream.generate()

Stream<String> buliderStream = 
    Stream.generate(() -> "Hello").limit(5);  // [Hello, Hello, Hello, Hello, Hello]

Stream.iterate()

Stream<Integer> iteratedStream = 
    Stream.iterate(30, n -> n + 2).limit(5);  // [30, 32, 34, 36, 38]

문자열 스트림

IntStream charsStream = "Stream".chars();  // [83, 116, 114, 101, 97, 109]

스트림 생성에 필요한 데이터를 직접 전달 Stream<T> 인터페이스에는 데이터를 인자로 직접 전달함으로써 스트림을 생성하는 메소드가 존재한다.

static <T> Stream<T> of(T t)
static <T> Stream<T> of(T...values)

또한 인터페이스 Stream<T>의 타입 매개변수 Tint와 같은 기본 자료형의 이름이 올 수 없으므로 Java에서는 다음의 메소드들도 제공하고 있다.

/* DoubleStream 인터페이스의 메소드 */
static DoubleStream of(double...values)
static DoubleStream of(double t)

/* IntStream 인터페이스의 메소드 */
static IntStream of(int...values)
static IntStream of(int t)

/* LongStream 인터페이스의 메소드 */
static LongStream of(long...values)
static LongStream of(long t)

해당 메소드를 통해 기본 자료형 데이터로 이루어진 스트림을 생성하면, 불필요한 오토 박싱/언박싱을 피할 수 있다. 그리고 다음 메소드들을 통해 범위 내의 값들로 스트림을 생성할 수 있다.

/* IntStream 인터페이스의 메소드 */
static IntStream range(int startInclusive, int endExclusive)  // start <= n < end
static IntStream rangeClosed(int startInclusive, int endExclusive) // start <= n <= end

/* LongStream 인터페이스의 메소드 */
static LongStream range(long startInclusive, long endExclusive)  // start <= n < end
static LongStream rangeClosed(long startInclusive, long endExclusive) // start <= n <= end

스트림의 연결 두 개의 스트림을 연결하여 하나의 스트림을 생성할 수도 있다.

static <T> Stream<T> concat(Stream<?  extends T> a, Stream<? extends T> b)
static DoubleStream concat(DoubleStrema a, DoubleStream b)
static IntStream concat(IntStream a, IntStream b)
static LongStream concat(LongStream a, LongStream b)
Stream<String> ss1 = Stream.of("Cake", "Milk");
Stream<String> ss2 = Stream.of("Lemon", "Jelly");

Stream.concat(ss1, ss2).forEach(s -> System.out.println(s)); // Cake Milk Lemon Jelly

Reference https://futurecreator.github.io/2018/08/26/java-8-streams/ https://futurecreator.github.io/2018/08/26/java-8-streams-advanced/ 윤성우의 열혈 Java 프로그래밍, 윤성우, 오렌지미디어, 2017

topgaren commented 4 years ago

스트림(Stream) Part 2

3. 중간 연산

스트림이 배열을 대상으로 생성되었건 컬렉션 인스턴스를 대상으로 생성되었건, 이에 상관없이 동일한 방법으로 중간 연산 및 최종 연산을 진행할 수 있다. (적용 가능한 연산 종류에 약간의 차이가 존재)

3-1. 필터링(Filtering)

필터링은 스트림 내 요소들을 하나씩 평가해서 걸러내는 작업을 수행한다. 필터링에 사용되는 메소드는 다음과 같다.

Stream<T> filter(Predicate<? super T> predicate)

이 때 매개변수로 Predicate의 다음 추상 메소드의 구현에 해당하는 람다식을 인자로 전달해야한다.

Predicate<T>     boolean test(T t)

다음 Java 코드는 정수 배열과 문자열 배열에서 특정 조건에 해당하는 요소를 출력하는 코드이다.

int[] ar = {1, 2, 3, 4, 5};
Arrays.stream(ar)  // 배열 기반 스트림 생성
      .filter(n -> n % 2 == 1)  // 홀수만 통과시킨다.
      .forEach(n -> System.out.print(n + "\t"));  // 1    3    5

List<String> sl = Arrays.asList("Toy", "Robor", "Box");
sl.stream()  // 컬렉션 인스턴스 기반 스트림 생성
  .filter(s -> s.length() == 3)  // 길이가 3이면 통과시킨다.
  .forEach(s -> System.out.print(s + "\t"));  // Toy   Box

3-2. 맵핑(Mapping)

맵(map)은 스트림 내 요소들을 특정한 맵핑 기준에 따라 변환해주는 중간 연산이다. 다음은 문자열 배열을 대상으로 스트림을 생성하고 "문자열의 길이"라는 기준으로 맵핑을 적용하는 코드이다.

List<String> ls = Arrays.asList("Box", "Robot", "Simple");
ls.stream()
  .map(s -> s.length())
  .forEach(n -> System.out.print(n + "\t"));  // 3    5    6

맵핑에 사용되는 대표적인 메소드는 다음과 같다.

<R> Stream<R> map(Function<? super T, ? extends R> mapper)

매개변수가 Function이므로 다음 메소드의 구현에 해당하는 람다식을 인자로 전달해야 한다.

Function<T, R>     R apply(T t)

위의 메소드는 제네릭 타입 형태이기 때문에 정수의 반환 과정에서 오토 박싱이 진행된다. 그래서 Java에서는 기본 자료형의 값을 반환하는 경우를 고려하여 다음의 맵핑 관련 메소드들도 제공하고 있다.

IntStream mapToInt(ToIntFunction<? super T> mapper)
LongStream mapToLong(ToLongFunction<? super T> mapper)
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper)

3-3. 정렬

정렬 기능을 제공하는 중간 연산 메소드들은 다음과 같다.

Stream<T> sorted(Comparator<? super T> comparator)
Stream<T> sorted()
IntStream sorted()
LongStream sorted()
DoubleStream sorted()

이 중 첫 번째 메소드를 사용하기 위해선 Comparator<T> 인터페이스의 compare메소드 구현에 해당하는 람다식을 전달해야 한다.

Stream.of("Rabbit", "Box", "Apple")
      .sorted((s1, s2) -> s1.length() - s2.length())
      .forEach(s -> System.out.print(s + "\t"));  // Box    Apple    Rabbit

4. 최종 연산

가공한 스트림을 바탕으로 결과값을 만들어내는 단계이다. 즉 스트림을 끝내는 최종 작업에 해당한다.

4-1. Calculating

최소, 최대, 합, 평균 등의 결과를 계산할 수 있다. (스트림이 비어있는 경우 countsum은 0을 출력하지만 평균, 최소, 최대의 경우에는 표현할 수 없기 때문에 Optional을 이용해 리턴한다.)

long count = IntStream.of(1, 3, 5, 7, 9).count();  // 5
long sum = LongStream.of(1, 3, 5, 7, 9).sum();  // 25
OptionalInt min = IntStream.of(1, 3, 5, 7, 9).min(); // 1
OptionalInt max = IntStream.of(1, 3, 5, 7, 9).max(); // 9

4-2 리덕션(Reduction) & 병렬 스트림(Parallel Streams)

4-2-1. 리덕션에 사용되는 reduce 메소드

리덕션(Reduction)은 "데이터를 축소하는 연산"을 뜻한다. sum도 리덕션 연산에 해당한다. 또한 리덕션을 수행하기 위한 기준을 사용자가 정의할 수도 있으며 이 때 사용하는 메소드는 다음과 같다.

T reduce(T identity, BinaryOperator<T> accumulator)

reduce 메소드의 두 번째 인자로 다음의 추상 메소드를 람다식으로 구현하여 전달한다.

BinaryOperator<T>    T apply(T t1, T t2)

reduce를 호출하면 내부적으로 apply를 호출하면서 스트림에 저장된 데이터를 다음과 같이 줄여나간다.

image

Image 참고 : 윤성우의 열혈 Java 프로그래밍, 윤성우, 오렌지미디어, 2017, p.747

문자열 길이를 기준으로 리덕션을 수행하는 예

List<String> ls = Arrays.asList("Box", "Simple", "Complex", "Robot");

BinaryOperator<String> lc = (s1, s2) -> {
    if(s1.length() > s2.length())
        return s1;
    else
        return s2;
};

String str = ls.stream().reduce("", lc);
System.out.println(str);  // Complex

4-2-2. 병렬 스트림(Parallel Streams)

하나의 작업을 둘 이상의 작업으로 나누어 동시에 진행하는 것을 가리켜 "병렬 처리"라고 한다. Java는 언어 차원에서 병렬 처리를 지원하고 있다. 위의 예제를 "병렬 처리" 기반으로 수정한 코드는 다음과 같다.

// String str = ls.stream().reduce("", lc);
String str = ls.parallelStream().reduce("", lc);

병렬 스트림을 생성하면 이어지는 연산들은 CPU의 코어 개수를 고려하여 적절하게 병렬 처리가 된다.

image

Image 참고 : 윤성우의 열혈 Java 프로그래밍, 윤성우, 오렌지미디어, 2017, p.751

4-3. Matching

Stream<T> 인터페이스에는 다음 메소드들이 존재한다.

// 스트림의 데이터가 조건을 모두 만족하는가?
boolean allMatch(Predicate<? super T> predicate)

// 스트림의 데이터가 조건을 하나라도 만족하는가?
boolean anyMatch(Predicate<? super T> predicate)

// 스트림의 데이터가 조건을 하나도 만족하지 않는가?
boolean noneMatch(Predicate<? super T> predicate)

또한 IntStream, DoubleStream, LongStream 인터페이스에도 위와 동일한 메소드들이 존재한다.

Reference https://futurecreator.github.io/2018/08/26/java-8-streams/ https://futurecreator.github.io/2018/08/26/java-8-streams-advanced/ 윤성우의 열혈 Java 프로그래밍, 윤성우, 오렌지미디어, 2017