메소드 참조, 스트림 Optional

dejeong·2024년 9월 25일
0

JAVA

목록 보기
19/24
post-thumbnail

메소드 참조

메소드를 참조해서 매개 변수의 정보 및 리턴 타입을 알아내어 람다식에서 불필요한 매개 변수를 제거하는 것이 목적이다. 인터페이스의 익명 구현 객체로 생성되며 타겟 타입인 인터페이스의 추상 메소드가 어떤 매개 변수를 가지고, 리턴 타입이 무엇인가에 따라 달라진다.

정적 메소드또는 인스턴스 메소드를 참조할 수 있고, 생성자 참조도 가능하다.

종류람다메소드 참조
정적(static) 메소드 참조(x) → ClassName.method(x)ClassName::method
인스턴스 메소드 참조(obj, x) → obj.method(x)ClassName::method
// 두 개의 값을 받아 큰 수를 리턴하는 Math 클래스의 max() 정적 메소드를 호출하는 람다식
(left, right) -> Math.max(left, right);

// 메소드 참조
Math::max;

실습

@FunctionalInterface
public interface IntBinaryOperator {
    int applyAsInt(int left, int right);
}

// Math.max 메소드
public static int max(int a, int b) {
    return Math.max(a, b); 
}

// 1단계
IntBinaryOperator operator = (a, b) -> Math.max(a, b);

// 2단계
IntBinaryOperator operator = Math::max;   // 메소드 참조

정적 메소드 및 인스턴스 메소드 참조

// 정적 메소드
클래스::메소드 

// 인스턴스 메소드
참조변수::메소드 

실습

package chap12;

public class Calculator {
    // 정적 메소드
    static int staticMethod(int left, int right){
        return left + right;
    }

    // 인스턴스 메소드
    public int method(int left, int right){
        return left + right;
    }
}
package chap12;

import java.util.function.IntBinaryOperator;

public class MethodReferenceExample {
    public static void main(String[] args) {
        IntBinaryOperator operator;

        // 정적 메소드 호출
        operator = (left, right) -> Calculator.staticMethod(left, right);
        System.out.println(operator.applyAsInt(1, 2));

        operator = Calculator::staticMethod; // 메소드 참조

        // 인스턴스 메소드 호출
        Calculator calculator = new Calculator();
        operator = (left, right) -> calculator.method(left, right);
        operator = calculator::method;
        System.out.println(operator.applyAsInt(1,2));
    }
}

매개변수의 메소드 참조

// a 매개변수의 메소드를 호출해서 b 매개변수를 매개값으로 사용하는 경우
(a, b) -> { a.instanceMethod(b); }

// 메소드 참조
클래스::instanceMethod

실습

package chap12;

import java.util.function.ToIntBiFunction;

public class MethodParameterReferencesExample {
    public static void main(String[] args) {
        ToIntBiFunction<String, String> function;

        function = String::compareToIgnoreCase;
        print(function.applyAsInt("a","A"));
    }

    static void print(int order){
        if(order < 0){
            System.out.println("사전순으로 먼저 옵니다.");
        } else if(order == 0){
            System.out.println("동일한 문자열입니다.");
        } else{
            System.out.println("사전순으로 나중에 옵니다.");
        }
    }
}
동일한 문자열입니다.

작성 방법은 정적 메소드 참조와 동일하지만, a의 인스턴스 메소드가 참조되므로 전혀 다른 코드가 실행된다.

생성자 참조

생성자를 참조한다는 것은 객체를 생성하는 것을 의미, 단순히 객체를 생성하고 리턴하는 람다식은 생성자 참조로 대치할 수 있다.

(a, b) -> { return new 클래스(a, b); }

// 생성자 참조
클래스 :: new

실습

package chap12;

public class Member {
    private String name;
    private String id;

    public Member(String name) {
        System.out.println("매개변수 하나인 생성자 호출");
        this.name = name;
    }

    public Member(String name, String id) {
        System.out.println("매개변수 두개인 생성자 호출");
        this.name = name;
        this.id = id;
    }
}
package chap12;

import java.util.function.BiFunction;
import java.util.function.Function;

public class ConstructorReferencesExample {
    public static void main(String[] args) {
        Function<String, Member> function = Member::new;
        function.apply("string");

        BiFunction<String, String, Member> function2 = Member::new;
        function2.apply("string", "string2");
    }
}
매개변수 하나인 생성자 호출
매개변수 두개인 생성자 호출

스트림

스트림

데이터 흐름(생성 → 가공 → 결과), 자바 8버전 부터 사용할 수 있는 기능, 배열이나 컬렉션을 가공하여 원하는 결과를 얻을 수 있다. 람다식을 가공할 때 많이 사용한다. 자바에서 제공해주는 Collection 인터페이스의 메서드로 스트림을 제공해준다. → Stream<E> stream()

스트림은 배열과 컬렉션을 처리할 때 for, while 문을 사용하지 않고 함수형으로 처리하며 데이터 처리 과정을 filter, forEach 와 같은 단어로 사용하여 가독성을 높여주고, 병렬 처리(하나의 작업을 둘 이상으로 나누어 동시에 진행)가 가능하다는 장점이 있다.

  • 생성 : 스트림 인스턴스 생성(배열이나 컬렉션을 스트림 인스턴스로 변환)
  • 가공 : 원하는 결과를 만들어가는 중간 작업 distinct, filter, map, flatMap, sorted, peek
  • 결과 : 최종 결과를 만들어내는 작업 forEach, collect()

“java.util.stream” 패키지에는 스트림 인터페이스가 정의되어 있고, BaseStream을 부모로 해서 자식 인터페이스들이 상속 구조를 이루고 있다.

BaseStream

  • Stream
  • IntStream
  • LongStream
  • DoubleStream
이름설명
BaseStream모든 스트림에서 사용할 수 있는 공통 메소드들이 정의되어 있습니다. 코드에서 직접 사용하지는 않습니다.
Stream객체 요소를 처리하는 스트림입니다.
IntStreamint 형을 처리하는 스트림입니다.
LongStreamlong 형을 처리하는 스트림입니다.
DoubleStreamdouble 형을 처리하는 스트림입니다.

스트림을 만드는 방법

  • 컬렉션으로부터 스트림 생성 : 컬렉션 타입(Collection, List, Set)의 경우 디폴트 메소드 stream 을 이용하여 스트림을 만들 수 있다 .
  • 배열로부터 스트림 생성 : Arrays.stream 메소드를 사용하여 스트림을 생성
  • 숫자 범위로부터 스트림 생성 : range, rangeClosed 메소드를 이용하여 특정 범위의 숫자를 가지는 스트림을 생성, Random 클래스로 스트림을 생성
  • 파일로부터 스트림 생성 : Files 클래스의 정적 메소드인 lines와 BufferedReader의 lines 메소드로 해당 파일의 각 라인을 스트링 타입의 스트림으로 만들 수 있다.
  • 스트림 연결 : Stream.concat 메소드를 이용해 두 개의 스트림을 연결할 수 있다.

실습

package chap13;

import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
import java.util.stream.Stream;

public class StreamExample {
    public static void main(String[] args) throws IOException {

        // 1. 컬렉션으로 스트림 생성
        List<String> list = Arrays.asList("a", "b", "c");
        Stream<String> stream = list.stream(); // 흐름 생성

        // 2. 배열로 스트림 생성
        String[] array = {"a", "b", "c"};
        Stream<String> stream2 = Arrays.stream(array);

        // 3. 숫자 범위로 스트림 생성
        IntStream intStream = IntStream.rangeClosed(1, 5); // [1,2,3,4,5]
        LongStream longStream = LongStream.range(1, 5); // [1,2,3,4]
        DoubleStream doubleStream = DoubleStream.of(1, 2, 3); // of : 특정 값을 입력해서 만듦 [1.0,2.0.3.0]

        // 4. 파일을 통한 스트림 생성
        Stream<String> fileStream = Files.lines(Paths.get("example.txt"), Charset.forName("UTF-8"));

        fileStream.forEach(System.out::println); // stream 안에서 제공해주는 루프 메소드 사용, 요소 각각 출력

        // 5. 스트림 연결해서 하나의 스트림 생성
        Stream<Integer> intStream1 = Stream.of(1, 2, 3);
        Stream<Integer> intStream2 = Stream.of(4, 5, 6);
        Stream<Integer> concated = Stream.concat(intStream2, intStream1);
        concated.forEach(System.out::println); // 요소 각각 출력, 4 5 6 1 2 3
    }
}

Optional

NPE를 방지할 수 있도록 도입된 null을 포함한 모든 데이터를 저장할 수 있는 Wrapper 클래스이며, 실제 값이 있는지 여부를 체크하거나, 만약 값이 없을 경우 디폴트 값을 지정할 수 있는 기능 등을 제공한다. → 객체(Object)가 null인지 아닌지는 알 수 없으나 일단 Optional로 감싼다.

스트림과Optional

List<Integer> list = null;

list.stream().forEach(System.out::println);   // NPE

// Optional로 컬렉션의 null 여부 체크
List<Integer> list = null;

Optional.ofNullable(list) // Optional.ofNullable 사
	.orElseGet(Collections::emptyList)
	.forEach(System.out::println);

Optional 객체 생성 메소드

Optional 클래스는 ofofNullable메소드를 지원한다. of() 메소드나 ofNullable() 메소드를 사용하여 Optional 객체를 생성할 수 있다.of는 null값을 허용하지 않고 ofNullable은 null값을 허용한다.

메소드설명
static Optional empty()아무런 값도 가지지 않는 비어있는 Optional 객체를 반환함.
static Optional of(T value)null이 아닌 명시된 값을 가지는 Optional 객체를 반환함.
static Optional ofNullable(T value)명시된 값이 null이 아니면 명시된 값을 가지는 Optional 객체를 반환하며, 명시된 값이 null이면 비어있는 Optional 객체를 반환함.

Optional 객체 반환

Optional 클래스의 get()메서드를 사용하여 Optional 객체에 저장된 값을 반환하지만 안전하게 객체를 꺼내오기 위해 null일 경우 사용할 default 값을 정할 수 있는데, 이때 orElse(), orElseGet() 메소드를 사용한다. → orElseGet() 메소드는 인수로 람다 표현식을 사용할 수 있다.

메소드설명
T get()Optional 객체에 저장된 값을 반환함.
T orElse(T other)저장된 값이 존재하면 그 값을 반환하고, 값이 존재하지 않으면 인수로 전달된 값을 반환함.
T orElseGet(Supplier<? extends T> other)저장된 값이 존재하면 그 값을 반환하고, 값이 존재하지 않으면 인수로 전달된 람다 표현식의 결과값을 반환.
T orElseThrow(Supplier<? extends X>  exceptionSupplier)저장된 값이 존재하면 그 값을 반환하고, 값이 존재하지 않으면 인수로 전달된 예외를 발생시킴.
boolean isPresent()저장된 값이 존재하면 true를 반환하고, 값이 존재하지 않으면 false를 반환함.

실습

package chap13.optional;

import java.util.*;

public class OptionalExample {
    public static void main(String[] args) {
        List<String> list = null;
        // list.stream().forEach(System.out::println); // NullPointerException

        // Optional
        Optional<List<String>> optional = Optional.ofNullable(list);
        // optional.orElseGet(() -> Collections.emptyList()); // 값 꺼내오기
        optional.orElseGet(Collections::emptyList)
                .forEach(System.out::println);

        // Optional 객체 생성 empty(), of(), ofNullable()
        Optional<Integer> empty = Optional.empty();
        OptionalInt empty2 = OptionalInt.empty();
        OptionalDouble empty3 = OptionalDouble.empty();

        Optional<Integer> optional2 = Optional.of(10);

        Optional<Integer> optional3 = Optional.ofNullable(3456);

        // Optional 객체 꺼내기 get(), orElse(), orElseGet(), orElseThrow()
        Integer value = empty.orElse(123); // value 123
        // empty2.orElseGet(() -> 12);

        Integer i = optional2.orElseThrow(NoSuchElementException::new);
        System.out.println(i);

        // isPresent()
        if (optional.isPresent()) {
            Integer integer = optional3.get();
            System.out.println(integer);
        }
    }
}

Optional을 이용한 Null 처리 방법 예시

컬렉션이 null은 아니지만 아무런 값도 가지고 있지 않은 경우

  • 방법1 : Optional의 isPresent는 값이 존재하는지 여부를 반환
  • 방법2 : 값이 없을 경우 디폴트 값을 정하는 Optional의 orElse를 사용
  • 방법3 : 스트림에서 ifPresent 메소드를 사용 → Optional의 ifPresent에는 값이 실제로 존재할 경우에만 동작하는 코드를 작성할 수 있다.

실습

package chap13.optional;

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

public class OptionalExample2 {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();

        // NoSuchElementException 예외 발생
        OptionalDouble optionalDouble = list.stream()
                .mapToInt(Integer::intValue)
                .average()
                ;
        optionalDouble.ifPresent(System.out::println); // 방법 3

        /* 빙법 2
        double result = optionalDouble.orElse(0.0);
        System.out.println(result);
         */

        /* 방법 1
        if(optionalDouble.isPresent()) {
            double avg = optionalDouble.getAsDouble();
            System.out.println(avg);
        }
        */
    }
}
profile
룰루

0개의 댓글