[Java] 람다와 스트림

thingzoo·2023년 6월 19일
0

Java

목록 보기
15/20
post-thumbnail

모던자바: 자바 8 변경사항들

프로그래밍 언어의 진화

  • 변화하는 시대에 따라 프로그래밍 언어에 요구되는 기능들도 변화함
  • 시대에 맞는 기능들을 제공하지 않는 언어들은 새로운 언어의 탄생을 야기
  • 새로운 언어가 탄생하고 해당 언어들이 프로덕션 레벨에 도달하는 동안 적응하지 못하면 해당 언어는 도태되기 시작
  • 예를들어 C/C++ 같은 언어들은 프로그램 실행에 대한 비용이 크지 않고, 하드웨어적인 보편성, 호환성에 큰 장점을 가지고 있었기 때문에 시장에서 가장 지배적인 입지를 차지했었음
  • 하지만 특유의 난이도와 다른 요인들의 영향으로 예기치 않게 종료되거나, 보안 이슈가 많아지는 등 안정성이 떨어지게 됨
  • 그러던 와중에 하드웨어는 꾸준한 진화를 거듭했고, 그 진화의 결과로 프로그램을 실행시키는 자원을 더 많이 사용해도 괜찮은 환경에서는, 더 안정성이 높은 c#, java와 같은 언어들이 각광받게 됨
  • 이처럼 시장의 상황에 따라서 프로그래밍 언어는 새로운 대안으로 등장하고, 적응해서 살아남거나, 적응하지 못해서 도태되기도 함

자바의 대격변, 자바 8

자바의 시작

  • 자바는 객체지향 프로그래밍, 그 중에서도 캡슐화와 같은 장점을 가지고 있어서 프로그래머들의 생산성과 프로그램의 안정성을 가지고 있었음
  • 초기의 c언어가 이식성이 높아서 사랑받았던 것처럼(다양한 하드웨어에서 최소한의 수정으로 c로 작성된 프로그램을 실행 시킬 수 있음), 자바 역시 jvm이 설치되어있는 환경이라면 프로그램을 실행 시킬 수 있었음
  • 그 외에도 다양한 요인 덕에 웹 어플리케이션 시장에서 가장 사랑받는 언어중 하나로 거듭나게 됨

자바가 직면했던 새로운 요구사항들

1. 병렬 처리

  • 빅데이터를 처리할 필요성이 늘어났고, 멀티코어 컴퓨터와 같은 병렬 프로세싱이 가능한 장비들이 보급되면서 새로운 요구사항들이 생겨나게 됨

2. 함수형 프로그래밍

객체 지향 프로그래밍처럼 프로그래밍의 패러다임의 한 종류

객체 지향 프로그래밍의 핵심 아이디어

“프로그램을 객체들의 협력과 상호작용으로 바라보고 구현한다”

  • 효용
    • 코드의 재사용성이 높아진다.
    • 코드를 유지보수, 확장 하기 쉬워진다.
    • 코드를 신뢰성 있게 사용하기 쉬워진다.
함수형 프로그래밍의 핵심 아이디어

"프로그램을 순수한 함수의 모음으로 바라보고 구현한다"

  • 순수한 함수와 순수하지 못한 함수들

    • 순수함수: 수학의 함수처럼, 특정한 데이터에 의존하지 않고, 관련없는 데이터를 변경하지도 않으며, 결과값이 오직 입력값에만 영향을 받는 함수
    // 수학의 함수
    f(x, y) = x + 2xy
    
    // 순수한 메서드
    // input에 따라 output은 항상 일정하죠!
    public int someMethod(int x, int y) {
        return x + 2y;
    }
    
    class notFunc {
        private int y = 0;
        private int result;
    
        // 순수 메서드가 아닌 메서드
        // 메서드 안에서 제어할 수 없는 y라는 값에 의해 output이 바뀔 수 있죠
        public int anotherMethod(int x) {
            return x + this.y * 2;
        }
    
        // 순수 메서드가 아닌 메서드 2
        // 메서드 내에서 this.result 값을 변경하고 반환하기 때문에
        // 순수 메서드라고 보기는 어렵습니다!
        public int otherMethod(int x, int y) {
            int result = x + 2 * y;
            this.result = result;
            return result;
        }
    }
  • 효용

    • 검증이 쉽다 (검증이 필요한 부분만 검증 할 수 있음)
    • 성능 최적화가 쉽다 (특정 input에 대한 output을 재사용 할 수 있음 - 캐싱)
    • 동시성 문제를 해결하기 쉽다 (함수는 다른 값의 변경을 야기하지 않음)

Java 8에서 새롭게 추가된 개념들

자바 함수의 변화

  1. 함수를 1급값으로( = 함수를 객체처럼 쓰겠다)
    • 프로그래밍에서 지원하는 모든 연산 모두 지원하는 “값”들을 1급 시민, 또는 1급 객체(예: 기본값, 객체)
    • 메소드는 특정한 연산을 지원하지 않는 값이기 때문에 2급 시민
    • 그렇지만 함수를 값으로 취급 할 수 있다면 매우 많은 것들을 할 수 있다!
  2. 람다: 익명함수
    • 익명함수란 말 그대로 이름이 없는 함수, 일급 객체로 취급
    • 함수를 값으로 사용 할 수도 있으며 파라미터에 전달, 변수에 대입와 같은 연산들 가능

스트림: 흐름

  • 스트림은 데이터 처리연산을 지원하도록 소스에서 추출된 연속된 요소
  • 컬렉션이 데이터를 저장하거나 접근하는데 초점을 맞춘 인터페이스라면
  • 스트림은 데이터를 처리하는데 초점을 맞춘 인터페이스

람다와 스트림

람다(lambda)

함수형 인터페이스

함수를 값으로 사용할떼 타입으로 사용되는 인터페이스

  • 단 하나의 추상메소드만 선언됨
  • 추상메소드와 함수의 매개변수의 개수와 반환타입이 일치해야함
interface Predicate<T> {
    boolean test(T t);
}

public static boolean hasTicket(Car car) {
	return car.hasParkingTicket;
}

람다식

  • 함수(메소드)를 간단한 식으로 표현하는 방법
  • 익명 함수: 이름이 없는 함수

람다식 작성법

public int toLambdaMethod(int x, int y) { // 기존 메소드
	return x + y;
}
  1. 메소드의 이름과 반환 타입 제거후 {}앞에 -> 추가
(int x, int y) -> {
	return x + y;
}
  1. 코드 1줄인 경우, 식이나 값만 적고 return문, ; 생략 가능
(int x, int y) -> x + y
  1. 매개변수의 타입이 추론 가능하면 생략가능(대부분 가능)
(x, y) -> x + y

메소드 참조

하나의 메소드만 호출하는 람다식은 '메소드 참조'로 간단히 할 수 있음

종류람다식메소드 참조
static 메소드(x) -> ClassName.method(x)ClassName::method
인스턴스 메소드(obj, x) -> obj.method(x)ClassName::method
특정 객체 인스턴스 메소드(x) -> obj.method(x)obj::method

스트림(stream) ✨

자료구조의 “흐름”을 객체로 제공해주고, 그 흐름동안 사용할 수 있는 메서드들을 api로 제공

  • 중간 연산과 최종 연산 기능 제공
    • 중간 연산: 연산결과가 스트림인 연산, 반복적으로 적용 가능
    • 최종 연산: 연산결과가 스트림이 아닌 연산, 스트림의 요소를 소모하므로 한번만 적용 가능

스트림의 특징

  1. 원본의 데이터 변경 안함
    • 자바 컬렉션으로부터 스트림(해당 컬렉션의 흐름)을 받아서 한 번 사용
  2. 일회용
    • 한 번 사용한 스트림은 어디에도 남지 않음
  3. 작업을 내부 반복으로 처리

    collection에 정의되어있기 때문에,
    모든 컬렉션을 상속하는 구현체들은 스트림 반환 가능

스트림의 생성

컬랙션으로부터 스트림 생성

Stream<Integer> intStream = list.stream(); // Collection.stream()

배열로부터 스트림 생성

Stream<String> strStream = Stream.of("a", "b", "c"); // 가변인자
Stream<String> strStream = Stream.of(new String[] {"a", "b", "c"});
Stream<String> strStream = Arrays.stream(new String[] {"a", "b", "c"});

String으로부터 스트림 생성

IntSteam intStream = str.chars();

특정범위의 정수를 요소로 갖는 스트림 생성

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 intStream = new Random.ints(5); // 크기가 5인 난수스트림 반환

람다식을 소스로 하는 스트림 생성

Stream<Integer> evenStream = Stream.iterate(0, n->n+2); // 0,2,4,6,...
Stream<Double> randomsStream = Stream.generate(Math::random); //

스트림의 중간연산

스트림 자르기

  • skip(n) : 앞에서부터 n개 건너뛰기
  • limit(n) : n개까지 잘라내기

스트림 요소 걸러내기

  • filter() : 조건에 맞지않는 요소 제거 ✨
  • distinct() : 중복 제거

스트림 정렬하기

  • sorted() : 기본 정렬(오름차순)
  • sorted(Comparator.reverseOrder()) : 지정된 비교함수로 정렬(내림차순)

스트림 요소 변환하기

  • map(람다식) : 스트림의 모든 요소를 변환가능 ✨

스트림의 최종연산

  • forEach(i -> i를 이용한 연산) : 스트림의 모든요소에 지정 작업 수행 ✨
  • toArray(Class[]::new) : 스트림의 모든요소를 Class[]에 담아 반환(생략시 Object[])
  • sum(), count(), max(), min(), average(): 스트림에 대한 통계정보 제공

for문 대신 사용하기

// for문을 이용한 코드
for(int i = 0; i < m; i++) {
	for(int j = 0; j < n; j++) {
    	System.out.print("*");
    }
    System.out.println();
}	

// 스트림을 이용한 코드
IntStream.range(0, n).forEach(i -> sb.append("*"));
IntStream.range(0, m).forEach(i -> System.out.println(sb));

🚨 max(), min(), average()의 반환값은 Optional!
따라서 값을 사용하려면 이렇게 getAsType()을 호출해줘야함

.max().getAsInt();
.min().getAsInt();
.average().getAsDouble();

옵셔널(Optional)

Optional

  • Java8에서는 Optional<T> 클래스를 사용해 Null Pointer Exception을 방지할 수 있도록 도와줌
  • Optional<T>는 null이 올 수 있는 값을 감싸는 Wrapper 클래스
  • Optional이 비어있더라도, 참조해도 Null Pointer Exception가 발생하지 않음

Optional 간단 사용법

  • 값이 null 인 Optional 생성하기
Optional<Car> emptyOptional = Optional.empty();
  • 값이 있는 Optional 생성하기
Optional<Car> hasDataOptional = Optional.of(new Car());
  • 값이 있을수도 없을수도 있는 Optional 생성하기
Optional<Car> hasDataOptional = Optional.ofNullable(getCarFromDB());
  • Optional 객체 사용하기 (값 받아오기)
Optional<String> carName = getCarNameFromDB(); 
// orElse() 를 통해 값을 받아옵니다, 파라미터로는 null인 경우 반환할 값을 적습니다.
String realCarName = carName.orElse("NoCar");
        
// 위는 예시코드고 실제는 보통 아래와 같이 사용하겠죠?
String carName = getCarNameFromDB().orElse("NoCar");
        
// orElseGet()이라는 메서드를 사용해서 값을 받아올 수 있습니다.
// 파라미터로는 없는 경우 실행될 함수를 전달합니다.
Car car = getCarNameFromDB().orElseGet(Car::new);
        
// 값이 없으면, 그 아래 로직을 수행하는데 큰 장애가 되는경우 에러를 발생시킬수도 있습니다.
Car car = getCarNameFromDB()
			.orElseThrow(() -> new CarNotFoundException("NO CAR!)

Reference

🔗 스파르타코딩클럽 Java 문법 종합반
🔗 자바의 정석

profile
공부한 내용은 바로바로 기록하자!

0개의 댓글