ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 9/13 TIL | 람다식에서는 왜 final, effectively final 변수만 사용해야 하는가?🧐
    📝 기록/매일의 기록 2022. 9. 13. 23:33

    코딩 도장에서는 항상 아침에 코딩 테스트를 보는데, 오늘은 프로그래머스에서 제일 작은 수 제거하기를 풀어보았다.

    문제 설명

    - 정수를 저장한 배열, arr에서 가장 작은 수를 제거한 배열을 리턴하는 함수, solution 완성해주세요.
    - 단, 리턴하려는 배열이 배열인 경우엔 배열에 -1 채워 리턴하세요.
    - 예를 들어 arr이 [4,3,2,1] 경우는 [4,3,2]리턴하고, [10]인 경우는 [-1]을 리턴합니다.

    제한 조건

    arr 길이 1 이상인 배열입니다.
    - 인덱스 i, j 대해 i ≠ j이면 arr[i] ≠ arr[j] 입니다.

    입출력 예

    arr return
    [4,3,2,1] [4,3,2]
    [10] [-1]

    나의 문제 풀이

    import java.util.Arrays;
    
    class Solution {
        public int[] solution(int[] arr) {
    	// 정답을 담을 answer 배열 선언
    	int[] answer;
    
    	// arr가 빈배열인 경우 -1을 담아 리턴
            if (arr.length <= 1) {
                answer = new int[]{-1};
    
                return answer;
            }
    
    	// answer 배열에 제일 작은 수를 제거하여 리턴할 것이므로
        	// 배열 크기를 arr보다 하나 작게 초기화
            answer = new int[arr.length - 1];
    	
        	// 제일 작은 수를 저장할 변수를 선언하고, arr[0]을 기준으로 한다.
            int min = arr[0];
    
    	// 제일 작은 수를 찾기 위해 배열 돌기
            for (int i = 0; i < arr.length; i += 1) {
                if (arr[i] < min) {
                	// 배열의 값이 기준인 min보다 작다면 min에 그 값을 재할당한다.
                    min = arr[i];
                }
            }
    
            int finalMin = min;
            // stream의 filter를 사용하여 가장 작은 수(finalMin == min)를
            // 제외한 새로운 배열을 만들어 answer에 할당
            answer = Arrays.stream(arr).filter(i -> i != finalMin).toArray();
    
            return answer;
        }
    }

    오늘 처음으로 stream을 활용하여 문제를 풀었다. 먼저, stream은 자바 8 버전에서 추가된 기능으로, stream이 없을 적에 배열 또는 컬렉션 인스턴스를 다루기 위해서는 for 또는 foreach 문을 돌면서 요소 하나씩을 꺼내서 다뤄야 했다. 물론 로직이 간단한 경우에는 상관없지만 로직이 복잡해질수록 코드의 양이 많아지고, 루프를 여러 번 도는 경우가 발생하는 단점이 있었다.

    스트림을 활용해 배열 또는 컬렉션 인스턴스에 함수 여러 개를 조합해서 원하는 결과를 필터링하고 가공된 결과를 얻을 수 있는데, 오늘 나는 가장 작은 수를 제외하고 배열을 생성하기 위해 특정 기준으로 요소를 걸러낼 수 있는 filter()를 사용하였다.

    // Arrays.stream(배열).filter(인자 -> 인자 [조건]).toArray();
    Arrays.stream(arr).filter(i -> i != finalMin).toArray();

    문제 풀이 과정에서 조건에 처음엔 i -> i != min을 사용하였으나, "Variable used in lambda expression should be final or effectively final"이라는 오류를 뱉는 것이다. 찾아보니 람다식에서는 final 변수 또는 effectively final 변수만 접근할 수 있어 생긴 문제였다. min을 finalMin이라는 새로운 변수에 할당하니 생각했던 대로 동작하였다.

    그렇다면 final, effectively final 변수는 무엇이며, 람다식에서는 왜 이러한 변수만을 사용해야 하는 것일까?

    final은 재정의할 수 없는 변수를 말하고, effectively final은 'final틱'한 변수로 역시나 값이 재정의되지 않는 변수를 말한다. 람다를 실행할 때, 실행되던 메서드의 스택 영역에 저장되는 외부 변수들에 대해서는 참조만 가능하고 값 변경은 불가능하다고 한다. 이는 '람다 캡쳐링'이 일어나면서 발생되는 현상이라고 하는데....

    람다 캡쳐링... 뭔 소리지...

    람다 캡쳐링의 원리가 런타임 메모리 영역을 다루고 있어 한번에 이해하기에는 다소 복잡했다. 하지만 단순하게 filtering을 해야 하는 기능에 값의 변경이 필요한가?를 생각해보면 답은 '아니다'라는 결론이 나온다. stream의 filter() 메서드는 특정 기준으로 어떤 요소를 걸러내는 단순히 filter 작업만을 하면 되는 기능이다. 그렇기에 값의 변경이 필요하지 않고, 재정의할 수 없는 final 변수를 사용해야 하는 것이라고 오늘은 이렇게 이해하고 넘어가 본다. (람다 캡쳐링은 좀 더 알아보고 새로운 TIL로 작성을 작성해보자!)

    - 참고한 블로그: 링크1, 링크2