개발/java

[java] lambda stream and final

방푸린 2023. 2. 6. 18:13
반응형

환경: java 8 ++

 

stream을 사용하여 리스트의 홀수번째(index 기준 0, 2, 4)에 있는 원소를 콘솔에 찍어본다고 가정하자.

아래와 같이 짜야지라고 쉽게 생각할 수 있다.

variable used in lambda expression should be final or effectively final

근데 위와 같은 에러가 난다.

그 이유는 아래와 같다. 자세한 내용을 알려면 람다 캡쳐링과 그 원리에 대해 이해해야한다.

The restriction to effectively final variables prohibits access to dynamically-changing local variables, whose capture would likely introduce concurrency problems.

람다식 내부에서는 람다식 안에서 정의된 변수가 아닌 외부 변수에 접근할 수 있는데 이를 람다 캡쳐링이라고 한다.

스트림은 여러 thread의 병렬처리를 염두하고 만들어졌다. 즉 별도의 스레드에서 실행할 수 있다. 그렇다면 어떻게 기존 스레드의 값을 참조하여 쓸 수 있을까? 기존 스레드의 작업이 종료되었을 수도 있는데 말이다.

람다 캡쳐링이 일어날 때 데이터의 참조값(call by reference)이 아닌 데이터 값 그 자체(call by value)를 복사하여 자신의 스택에 두고 작업을 한다. 그렇기 때문에 값이 변경될 여지가 있는 변수는 사용할 수 없고 final에 준하는(effectively final) 변수만 람다 안에서 사용할 수 있다.

반면 heap에 저장된 값은 thread끼리 공유하고 있기 때문에 언제든지 구할 수 있으므로 변경하더라도 에러가 나지 않는다.

Java의 스트림 API에서 외부 변수를 사용할 때 해당 변수가 effectively final해야 하는 이유는 다음과 같습니다:

1. 스레드 안전성

  • 스트림의 연산은 종종 병렬로 실행되며, 외부 변수가 여러 스레드에서 동시에 접근될 수 있습니다. 이를 방지하기 위해 외부 변수가 변경되지 않도록 보장해야 합니다.

2. 불변성

  • 외부 변수가 effectively final이면, 그 값이 변경되지 않는 것을 보장합니다. 이로 인해, 람다 표현식이나 메서드 참조가 이 변수를 안전하게 사용할 수 있습니다. 불변성을 유지함으로써 예측 가능한 동작을 보장합니다.

3. 람다 캡처

  • Java의 람다 표현식은 외부 변수를 캡처할 수 있지만, 캡처된 변수는 내부적으로 복사되어 사용됩니다. 만약 이 변수가 변경 가능하다면, 예상치 못한 결과를 초래할 수 있습니다. 이를 방지하기 위해 effectively final 조건이 필요합니다.`

 

위 함수의 에러는 여러 방법으로 수정할 수 있다.

1. AtomicInteger를 사용하여 수정

2. list/array를 이용해 수정

 

2번 방식으로 수정하다 보니 신기했던 것은 단순 값이 변경된다는 것에 초점을 둘게 아니라 final이면 된다는 점에 초점을 뒀어야 한다는 것이다. 일반적으로 final이라고 하면 불변, 즉 string이나 int인 경우 값이 바뀌면 안 된다고 인식하기 때문에 '값의 변경'에 나도 모르게 초점이 갔는데, collection 같은 경우에는 final이어도 값이 변동(추가 혹은 수정)될 수 있다!

전체 변경; 재할당
일부 변경

즉 참조값이 바뀌면 안 되고 같은 참조값 안에서의 변경은 된다(final but mutable)

final --> You cannot change the reference to the collection (Object). You can modify the collection / Object the reference points to. You can still add elements to the collection
immutable --> You cannot modify the contents of the Collection / Object the reference points to. You cannot add elements to the collection.

실제로 list를 final로 선언하고 값을 수정할 때 별문제 없이 작동한다.


위에서 말한 것과 같이 heap에 있는 변수는 수정이 가능하다.

stack
heap

 


 

Arrays.asList는 값 추가 안됨; size가 정해진 list라서: https://www.baeldung.com/java-list-unsupported-operation-exception

collection의 final이란? https://stackoverflow.com/questions/26500423/what-does-it-mean-for-a-collection-to-be-final-in-java

https://www.geeksforgeeks.org/final-arrays-in-java/

람다 캡쳐링: https://perfectacle.github.io/2019/06/30/java-8-lambda-capturing/

728x90
반응형