Rambda / Stream

jungseo·2023년 5월 3일
0

Java

목록 보기
6/10

애너테이션(Annotation)

소스 코드가 컴파일되거나 실행될때 컴파일러 및 다른 프로그램에게 필요한 정보를 전달해주는 문법 요소

  • 표준 애너테이션 : JDK에 내장

    • @Override : 메서드 앞에만 붙일 수 있는 애너테이션. 선언한 메서드가 상위 클래스의 메서드를 오버라이딩 하거나 추상 메서드를 구현하는 메서드라는 것을 컴파일러에게 알려줌
      하위 클래스에서 @Override가 붙어있는 메서드를 오버라이딩 할때 상위 클래스에서 같은 이름의 메서드를 검색, 없으면 컴파일 에러 발생
public class AnnotationTest {
    public static void main(String[] args) {
        SuperClass superClass = new SuperClass();
        SubClass subClass = new SubClass();

        superClass.example();
        subClass.example();
    }
}
class SuperClass {
    public void example() {
        System.out.println("SuperClass example method");
    }
}
class SubClass extends SuperClass {

    @Override
    public void example() {
        System.out.println("SubClass example method");
    }
}
    • @Deprecated : 기존 사용하던 기술이 다른 기술로 대체되어 기존 기술을 적용한 코드를 더이상 사용하지 않도록 유도하는 경우 사용

    • @SuppressWarnings : 특정 컴파일 경고 메세지를 지정하여 나타나지 않도록 함

    • @FunctionalInterface : 함수형 인터페이스를 선언할 때 선언이 올바르게 되었는지 확인하도록 함


  • 메타 애너테이션 : 다른 애너테이션을 정의할때 사용

    • @interface 키워드를 사용하여 애너테이션을 정의
      public @interface Override{}
    • @Target : 애너테이션을 적용할 대상을 지정
      @Target({FIELD, TYPE 등})
    • @Documented : 애너테이션에 대한 정보가 javadoc으로 작성한 문서에 포함되도록 함
    • Inherited : 하위 클래스가 애너테이션을 상속받도록 함
    • Retention : 특정 애너테이션의 지속시간을 결정
      SOURCE, CLASS, RUNTIME
    • @Repeatable : 애너테이션을 여러 번 붙일 수 있도록 허용
  • 사용자 정의 애너테이션


람다(Rambda)

메서드를 하나의 '식(expression)'으로 표현한 것. 간결하고 명확하게 표현 가능

  • 반환 타입과 이름을 생략할 수 있어 익명 함수(anonymous function)라 부르기도 함

람다식은 익명 메서드이자 익명 클래스로부터 생성된 객체이다.
익명 클래스는 객체의 선언과 생성을 동시에 하며 오직 하나의 객체를 생성되고 단 한번만 사용되는 클래스이다.

new 인터페이스명() {
익명 클래스 내용
}

  • 람다식을 사용하기 위하여 1:1로 대응하는 함수형 인터페이스가 필요

  • 기본 제공되는 함수형 인터페이스를 사용하여 함수형 인터페이스 작성 생략 가능

  • 함수형 인터페이스


    함수형 인터페이스

public class LambdaTest {
    public static void main(String[] args) {  
//        람다식을 Object 타입의 참조변수에 할당해도 sum 메서드를 호출 할 수 없다
//        Object object = new Object() {
//            int sum(int num1, int num2)
//                return num1 + num2;
//            }
//        };
//        System.out.println(object.sum);

        ExampleFunction1 exampleFunction1 = (int num1, int num2) -> num1 + num2;
        System.out.println(exampleFunction1.example1(10, 15) + "\n");
//        출력 : 25

        ExampleFunction2 exampleFunction2 = () -> System.out.println("example2 호출 \n");
        exampleFunction2.example2();
//        출력 : example2 호출

        ExampleFunction3 exampleFunction3;
        exampleFunction3 = (x) -> {
            int result = x * 5;
            System.out.println(result);
        };
        exampleFunction3.example3(10);
//        출력 : 50

        exampleFunction3 = (x) -> System.out.println(x * 10 + "\n");
        exampleFunction3.example3(10);
//        출력 : 100

        ExampleFunction4 exampleFunction4 = (x, y) -> x * y;
        System.out.println(exampleFunction4.example4(3, 2) + "\n");
//        출력 : 6

        ExampleFunction4 exampleFunction4_1 = (x, y) -> exampleFunction4.example4(sum(x, y), y);
        System.out.println(exampleFunction4_1.example4(3, 3));
//        출력 : 18
//        
    }
    static int sum(int x, int y) {
        return x + y;
    }
}
@FunctionalInterface
interface ExampleFunction1 {
    int example1 (int num1, int num2);
}
interface ExampleFunction2 {
    void example2 ();
}
interface ExampleFunction3 {
    void example3 (int x);
}
interface ExampleFunction4 {
    int example4 (int x, int y);
}

메서드 레퍼런스

  • 람다식이 매개값을 전달하는 역할만 할경우 클래스이름::메서드이름 으로 작성 가능
  • 정적 메서드를 참조할때 클래스::메서드
  • 인스턴스 메서드를 참조할때 참조변수::메서드
  • 람다식의 반환값을 다른 변수에 할당 하기 위해선 함수형 인터페이스를 사용해야함
    • Function<T, R> : T타입을 인자로 받아 R타입을 반환하는 인터페이스 // apply()
    • IntBinaryOperator : 두 정수 값을 받아 연산을 하고 정수형 결과값을 생산하는 인터페이스 // applyAsInt()
    • BiFunction<T, U, R> : T, U타입을 인자로 받아 R타입을 반환하는 인터페이스 // apply()
  • 생성자 참조도 가능(객체를 생성하고 리턴하도록 구성된 람다식)
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.IntBinaryOperator;

public class MethodReferenceTest {
    public static void main(String[] args) {

//        Ex1 ex1 = ((x, y) -> Math.max(x, y));
        Ex1 ex1 = Math::max;
        System.out.println(ex1.exMethod1(123, 400) + "\n");

//        IntBinartOperator : 두 정수 값을 받아 연산을 하고 정수형 결과값을 생산하는 인터페이스
        IntBinaryOperator operator;

//        Ex2클래스의 정적 메서드 참조
        operator = Ex2::exMethod2;
        System.out.println(operator.applyAsInt(5, 8) + "\n");

//        Ex2클래스의 인스턴스 메서드 참조
        Ex2 ex2 = new Ex2();
        operator = ex2::exMethod3;
        System.out.println(operator.applyAsInt(5, 8) + "\n");

//        생성자 참조

//        Function<T, R> : T타입을 인자로 받아 R타입을 반환하는 인터페이스
        Function<String, ConstructorRefer> function1 = ConstructorRefer::new;
        ConstructorRefer person1 = function1.apply("4885");
//        출력 : ConstructorRefer(String id) !!

//        String 값을 입력받아 ConstructorRefer 객체를 생성하는 함수를 표현하는 function1 변수를 선언하고
//        이 함수를 구현하는데 ConstructorRefer 클래스의 생성자를 사용하겠다

//        BiFunction<T, U, R> T, U타입을 인자로 받아 R타입을 반환하는 인터페이스
        BiFunction<String, String, ConstructorRefer> function2 = ConstructorRefer::new;
        ConstructorRefer person2 = function2.apply("4885", "하정우");
//        출력 : ConstructorRefer(String name, String id) !!

//        String, String 값을 입력받아 ConstructorRefer 객체를 생성하는 함수를 표현하는 function2 변수를 선언하고
//        이 함수를 구현하는데 ConstructorRefer 클래스의 생성자를 사용하겠다
    }
}
interface Ex1 {
    int exMethod1(int x, int y);
}
class Ex2 {
    public static int exMethod2(int x, int y) {
        return x * y;
    }
    public int exMethod3(int x, int y) {
        return x + y;
    }
}
class ConstructorRefer {
    private String name;
    private String id;

    public ConstructorRefer() {
        System.out.println("ConstructorRefer() !!");
    }
    public ConstructorRefer(String id) {
        System.out.println("ConstructorRefer(String id) !!");
        this.id = id;
    }
    public ConstructorRefer(String name, String id) {
        System.out.println("ConstructorRefer(String name, String id) !!");
        this.name = name;
        this.id = id;
    }
}

Optional class

  • 일종의 래퍼 클래스로, null 값으로 인해서 NullPointerException (NPE) 에러가 발생하는 현상을 객체 차원에서 효율적으로 방지하기 위한 목적으로 도입
  • 연산 결과를 Optional 객체 안에 담아서 반환하면, 따로 if문을 사용한 조건문으로 반환된 결과가 null인지 여부를 체크하지 않아도 에러가 발생하지 않도록 코드 작성 가능
  • 메서드

스트림(Stream)

배열 및 컬렉션의 저장 요소를 하나씩 참조해 람다식으로 처리할 수 있도록 하는 반복자

  • 선언형 프로그래밍
  • 데이터 소스에 상관없이 같은 방식으로 데이터 처리 가능
  1. 생성, 중간 연산, 최종 연산 세 단계의 파이프라인으로 구성
  2. 원본 데이터 소스를 변경하지 않는다.
  3. 일회용
  4. 내부 반복자

스트림 생성

Stream< T >, IntStream, ...

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Random;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class StreamProducing {
    public static void main(String[] args) {
        ArrayList<String> numList = new ArrayList<>();
        numList.add("one ");
        numList.add("two ");
        numList.add("three\n");

        String[] numArr = {"one ", "two ", "three\n"};

//        스트림 생성
        Stream<String> listStream = numList.stream();

        Stream<String> arrStream = Arrays.stream(numArr);
        Stream<String> arrStream2 = Stream.of(numArr);

//        IntStream ints = new Random().ints(); // 무한 스트림
        IntStream ints2 = new Random().ints(5); // 스트림 생성 범위 제한
        IntStream ints3 = new Random().ints().limit(5);
        IntStream ints4 = IntStream.range(1, 10); // 특정 범위 지정 // rangeClosed는 두번째 인자까지 포함

//        출력
        listStream.forEach(System.out::print); // one two three
        System.out.println();
        arrStream.forEach(System.out::print); // one two three
        System.out.println();
//        ints.forEach(System.out::print);
        ints2.forEach(System.out::println);
        System.out.println();
        ints3.forEach(System.out::println);
        System.out.println();
        ints4.forEach(System.out::print); // 123456789
    }
}

스트림 중간 연산

필터링 filter(), distinct()

조건에 맞는 데이터 정제

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

public class FilterTest {
    public static void main(String[] args) {

        List<String> names = Arrays.asList("오태식 ", "남춘식 ", "곽두팔 ", "김창식 ", "남춘식 ", "남봉헌");

        names.stream()
                .distinct() // 중복 제거
//                .forEach(System.out::println);
                .forEach(element -> System.out.print(element));
//        오태식 남춘식 곽두팔 김창식 남봉헌
        System.out.println();

        names.stream()
                .filter(element -> element.startsWith("남"))
                .forEach(element -> System.out.print(element));
//        남춘식 남춘식 남봉헌
        System.out.println();

        names.stream()
                .distinct()
                .filter(element -> element.startsWith("남"))
                .forEach(element -> System.out.print(element));
//        남춘식 남봉헌
    }
}

매핑 map()

원하는 필드만 추출 혹은 특정 형태로 변환

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

public class MapTest {
    public static void main(String[] args) {

        List<String> fruits = Arrays.asList("apple ", "strawberry ", "peach ", "orange ");
        fruits.stream()
                .map(String::toUpperCase) // .map(element -> element.toUpperCase()) // 메서드 참조
                .forEach(System.out::print); // .forEach(element -> System.out.print(element));
//        APPLE STRAWBERRY PEACH ORANGE
        System.out.println();

        List<Integer> nums = Arrays.asList(1, 3, 4, 6, 8, 9);
        nums.stream()
                .map(number -> number * 2)
                .forEach(System.out::print);
//        268121618
    }
}

플래트닝 flatMap()

import java.util.Arrays;

public class FlatTest {
    public static void main(String[] args) {
        String[][] names = {{"오태식 ", "남춘식 "}, {"곽두팔 ", "김창식 "}};

        Arrays.stream(names)
                .map(Arrays::stream)
                .forEach(System.out::println);
//        주소값 출력됨

        Arrays.stream(names)
                .flatMap(Arrays::stream)
                .forEach(System.out::print);
//        오태식 남춘식 곽두팔 김창식
//        flatMap() : 중첩구조를 제거하고 단일 컬렉션으로 만들어줌
    }
}

정렬 sorted()

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

public class SortTest {
    public static void main(String[] args) {
        List<String> fruits = Arrays.asList("apple ", "strawberry ", "peach ", "orange ");

        fruits.stream()
                .sorted() // 인자값 없는 sort() -> 오름차순
                .forEach(System.out::print);
//        apple orange peach strawberry
        System.out.println();

        fruits.stream()
                .sorted(Comparator.reverseOrder()) // 내림차순
                .forEach(System.out::print);
//        strawberry peach orange apple
        System.out.println();

        fruits.stream()
                .limit(2) // 일부를 자름 // apple strawberry
                .skip(1) // 일부 요소를 건너뜀 // strawberry
                .peek(System.out::print)     // forEach()와 같이 요소들을 순회하며 작업 수행하지만
                .forEach(System.out::print); // 중간 연산자로 여러번 연결하여 사용 가능
    }
}

스트림 최종 연산

최종 연산 후 스트림 파이프라인이 닫히고 사라짐

반복 순회 forEach()


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

  • average(), max(), min() 등의 메서드는 반환 타입이 OptionalDouble
    NullPointerException 방지를 위해 도입
    래퍼 클래스 객체로 기본형으로 변환 필요
import java.util.Arrays;

public class Basic {
    public static void main(String[] args) {
        int[] numbers = {1, 2, 3, 4, 5};

//        카운트
        long count = Arrays.stream(numbers).count();
        System.out.println(count); // 5

//        합계
        int sum = Arrays.stream(numbers).sum();
        System.out.println(sum);  // 15

//        평균
        double average = Arrays.stream(numbers).average().getAsDouble();
        System.out.println(average); // 3.0

//        최대값
        int max = Arrays.stream(numbers).max().getAsInt();
        System.out.println(max); // 5

//        최소값
        int min = Arrays.stream(numbers).min().getAsInt();
        System.out.println(min); // 1

//        배열 첫번째 요소
        int first = Arrays.stream(numbers).findFirst().getAsInt();
        System.out.println(first); // 1
    }
}

매칭 allMatch(), anyMatch(), noneMatch()

특정 조건을 충족하는지 검사, boolean 타입으로 반환

import java.util.Arrays;

public class matchTest {
    public static void main(String[] args) {
        int[] arr = {2, 4, 6};

        Boolean result = Arrays.stream(arr).allMatch(element -> element % 2 == 0);
        System.out.println(result); // 모든 요소가 조건을 만족하는지 // true

        result = Arrays.stream(arr).anyMatch(element -> element % 3 == 0);
        System.out.println(result); // 하나의 요소라도 조건을 만족하는지 // true

        result = Arrays.stream(arr).noneMatch(element -> element % 2 == 0);
        System.out.println(result); // 모든 요소가 조건을 만족하지 않는지 // false
    }
}

요소 소모 reduce()

스트림 요소를 줄여나가며 연산 수행 후 최종결과 반환

  • 매개변수가 2개 -> 첫 번째 요소와 두 번재 요소를 연산 후 그 결과와 세 번째 요소 와 연산 ...
  • 매개변수가 3개 -> 첫 번째 매개변수는 초기값
  • 반환값이 BinaryOperatorM< T >로 연산 후 변환 필요
import java.util.Arrays;

public class ReduceTest {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5};

        long result = Arrays.stream(arr).sum();
        System.out.println(result);
//        15

        int sum1 = Arrays.stream(arr)
                .map(element -> element * 2)
                .reduce((a, b) -> a + b)
                .getAsInt();
        System.out.println(sum1);
//        30

        int sum2 = Arrays.stream(arr)
                .map(element -> element * 2)
                .reduce(3, (a, b) -> a + b); // a : 누적된 값, b : 더할 값
        System.out.println(sum2);
//        33
    }
}

요소 수집 collect()

Collectors 클래스에 여러 메서드 정의
스트림의 요소들을 List, Set, Map 등 다른 타입의 결과로 수집

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

public class CollectTest {
    public static void main(String[] args) {
        List<Flower> flowerList = Arrays.asList(
                new Flower("매화", 2, Flower.Season.Winter),
                new Flower("벚꽃", 4, Flower.Season.Spring),
                new Flower("데이지", 5, Flower.Season.Spring),
                new Flower("장미", 8, Flower.Season.Summer)
        );
        Map<String, Integer> springMap = flowerList.stream()
                .filter(s -> s.getSeason() == Flower.Season.Spring)
                .collect(Collectors.toMap(
                        flower -> flower.getName(), // Key
                        flower -> flower.getMonth() // Value
                ));
        System.out.println(springMap);
//        {데이지=5, 벚꽃=4}
    }
}
class Flower {
    public enum Season {Spring, Summer, Fall, Winter}
    private String name;
    private int month;
    private Season season;

    public Flower(String name, int month, Season season) {
        this.name = name;
        this.month = month;
        this.season = season;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getMonth() {
        return month;
    }
    public void setMonth(int month) {
        this.month = month;
    }
    public Season getSeason() {
        return season;
    }
    public void setSeason(Season season) {
        this.season = season;
    }
}

0개의 댓글