람다 식에 사용되는 변수는 최종적이거나 효과적으로 최종적이어야합니다


134

람다 식에 사용되는 변수는 최종적이거나 효과적으로 최종적이어야합니다

사용하려고하면 calTz이 오류가 표시됩니다.

private TimeZone extractCalendarTimeZoneComponent(Calendar cal, TimeZone calTz) {
    try {
        cal.getComponents().getComponents("VTIMEZONE").forEach(component -> {
            VTimeZone v = (VTimeZone) component;
            v.getTimeZoneId();
            if (calTz == null) {
                calTz = TimeZone.getTimeZone(v.getTimeZoneId().getValue());
            }
        });
    } catch (Exception e) {
        log.warn("Unable to determine ical timezone", e);
    }
    return null;
}

5
calTz람다 에서는 수정할 수 없습니다 .
Elliott Frisch

2
나는 이것이 Java 8을 위해 제 시간에 이루어지지 않은 것들 중 하나라고 가정했습니다. 그러나 Java 8은 2014였습니다. Scala와 Kotlin은 이것을 수년간 허용했기 때문에 가능합니다. Java가이 이상한 제한을 제거 할 계획입니까?
GlenPeterson

5
다음 은 @MSDousti의 의견에 대한 업데이트 된 링크입니다.
geisterfurz007

Completable Futures를 해결 방법으로 사용할 수 있다고 생각합니다.
Kraulain

내가 관찰 한 한 가지 중요한 점-일반 변수 대신 정적 변수를 사용할 수 있습니다 (이것이 효과적으로 최종적으로 추측합니다)
kaushalpranav

답변:


68

final변수 수단은 한 번만 인스턴스화 될 수있다. Java에서는 람다 및 익명 내부 클래스에서 비 최종 변수를 사용할 수 없습니다.

이전 for-each 루프를 사용하여 코드를 리팩터링 할 수 있습니다.

private TimeZone extractCalendarTimeZoneComponent(Calendar cal,TimeZone calTz) {
    try {
        for(Component component : cal.getComponents().getComponents("VTIMEZONE")) {
        VTimeZone v = (VTimeZone) component;
           v.getTimeZoneId();
           if(calTz==null) {
               calTz = TimeZone.getTimeZone(v.getTimeZoneId().getValue());
           }
        }
    } catch (Exception e) {
        log.warn("Unable to determine ical timezone", e);
    }
    return null;
}

이 코드의 일부를 이해하지 못하더라도 :

  • 당신은 v.getTimeZoneId();그것의 반환 값을 사용하지 않고 호출
  • 과제 calTz = TimeZone.getTimeZone(v.getTimeZoneId().getValue());를 통해 원래 전달 된 내용을 수정하지 않습니다calTz 사용하면 방법에서는 사용하지 않습니다
  • 항상을 반환합니다 null. 왜 void반환 유형으로 설정하지 않습니까?

이 팁이 개선에 도움이되기를 바랍니다.


비 정적 정적 변수를 사용할 수 있습니다
Narendra Jaggi

92

다른 답변이 요구 사항을 입증하지만 이유를 설명하지 않습니다. 요구 사항이 존재 는 .

JLS는 §15.27.2의 이유를 언급합니다 .

효과적으로 최종 변수에 대한 제한은 동적으로 변경되는 지역 변수에 대한 액세스를 금지합니다.

버그의 위험을 줄이기 위해 캡처 된 변수가 절대 변이되지 않도록 결정했습니다.


10
좋은 대답 +1, 그리고 효과적으로 최종 이유가 얼마만큼의 범위를 차지하는 지에 놀랐습니다 . 참고로 :이 경우 로컬 변수는 람다에 의해 캡처 할 수 확실히 람다의 몸 이전에 할당. 두 가지 요구 사항 모두 로컬 변수에 액세스하는 것이 스레드 안전을 보장하는 것으로 보입니다.
Tim Biegeleisen

2
왜 이것이 클래스 멤버가 아닌 로컬 변수로만 제한되는지 아십니까? 변수를 클래스 멤버로 선언하여 문제를 우회하는 경우가 자주 있습니다.
David Refaeli

4
@DavidRefaeli 클래스 멤버는 메모리 모델에 의해 보호 / 영향을받으며, 공유 할 경우 예측 가능한 결과를 생성합니다. §17.4.1
Dioxin

이것은 바보 같은 해킹이며 제거해야합니다. 컴파일러는 잠재적 인 스레드 간 변수 액세스에 대해 경고해야하지만 허용해야합니다. 또는 람다가 동일한 스레드에서 실행 중이거나 병렬로 실행 중인지 알 수있을 정도로 똑똑해야합니다. 이것은 바보 같은 한계이므로 나를 슬프게합니다. 그리고 다른 사람들이 언급했듯이 C #과 같은 문제는 존재하지 않습니다.
Josh M.

@JoshM. C #을 사용하면 변경 가능한 값 형식 을 만들 수 있으므로 사람들은 문제를 방지하지 않는 것이 좋습니다. Java는 그러한 원칙을 갖지 않고 그것을 완전히 방지하기로 결정했습니다. 유연성의 대가로 사용자 오류를 줄입니다. 이 제한에 동의하지 않지만 정당합니다. 병렬 처리에 대한 설명은 컴파일러의 마지막 부분에 대한 추가 작업이 필요하기 때문에 아마도 " 크로스 스레드 액세스 경고 "경로 가 사용되지 않은 것입니다. 사양을 다루는 개발자는 아마도 이것에 대한 유일한 확인 일 것입니다.
Dioxin

57

람다에서 최종적이지 않은 것에 대한 참조를 얻을 수 없습니다. 변수를 유지하려면 라마 외부에서 최종 래퍼를 선언해야합니다.

이 래퍼로 최종 '참조'객체를 추가했습니다.

private TimeZone extractCalendarTimeZoneComponent(Calendar cal,TimeZone calTz) {
    final AtomicReference<TimeZone> reference = new AtomicReference<>();

    try {
       cal.getComponents().getComponents("VTIMEZONE").forEach(component->{
        VTimeZone v = (VTimeZone) component;
           v.getTimeZoneId();
           if(reference.get()==null) {
               reference.set(TimeZone.getTimeZone(v.getTimeZoneId().getValue()));
           }
           });
    } catch (Exception e) {
        //log.warn("Unable to determine ical timezone", e);
    }
    return reference.get();
}   

나는 동일하거나 유사한 접근법에 대해 생각하고 있었지만이 답변에 대한 전문가의 조언 / 피드백을 원하십니까?
YoYo December

4
이 코드는 초기 값이 누락 reference.set(calTz);되었거나를 사용하여 참조를 작성해야합니다 new AtomicReference<>(calTz). 그렇지 않으면 매개 변수로 제공된 널이 아닌 TimeZone이 유실됩니다.
Julien Kronegg

7
이것이 첫 번째 답이어야합니다. AtomicReference (또는 유사한 Atomic___ 클래스)는 가능한 모든 상황에서이 제한을 안전하게 해결합니다.
GlenPeterson

1
동의합니다. 이것이 정답입니다. 다른 답변은 작동하지 않는 프로그래밍 모델로 대체하는 방법과 이것이 수행 된 이유에 대한 유용한 정보를 제공하지만 실제로 문제를 해결하는 방법을 알려주지는 않습니다!
조나단 벤

2
@GlenPeterson과 끔찍한 결정이기도합니다.이 방법은 훨씬 느릴뿐만 아니라 문서가 요구하는 부작용을 무시하고 있습니다.
Eugene

41

자바 8 에는 "Effectively final"변수라는 새로운 개념이 있습니다. 초기화 후에도 값이 변하지 않는 비 최종 로컬 변수를 "Effectively Final"이라고합니다.

Java 8 이전 에는 익명 클래스 에서 비 최종 로컬 변수를 사용할 수 없었기 때문에이 개념이 도입되었습니다 . 익명 클래스 의 로컬 변수에 액세스하려는 경우 최종 로 만들어야합니다.

람다가 도입되었을 때이 제한이 완화되었습니다. 따라서 로컬 변수가 람다 자체로 초기화되면 변경되지 않으면 로컬 변수를 final로 만들어야 할 필요가 있습니다.

Java 8 은 개발자가 람다를 사용할 때마다 지역 변수 final을 선언하는 어려움을 인식 하고이 개념을 도입했으며 지역 변수를 최종적으로 만들 필요가 없었습니다. 따라서 익명 클래스에 대한 규칙이 변경되지 않은 경우에는 작성하지 않아도됩니다.final 람다를 사용할 때마다 키워드 .

여기서 좋은 설명을 찾았 습니다


코드 형식은 일반적인 기술 용어가 아닌 코드 에만 사용해야합니다 . effectively final코드가 아니라 용어입니다. 비 코드 텍스트에 코드 형식을 사용해야하는 경우를 참조하십시오 . 에 대한 메타 스택 오버플로 .
Charles Duffy

(따라서 " final키워드"는 코드의 한마디로 그 형식을 지정하는 것이 맞지만, 코드가 아닌 "최종"을 설명 적으로 사용하는 경우 대신 용어입니다.)
Charles Duffy

9

귀하의 예에서 forEach간단한 for루프 로 lamdba를 사용하여 변수를 자유롭게 수정할 수 있습니다. 또는 변수를 수정할 필요가 없도록 코드를 리팩터링하십시오. 그러나 오류의 의미와 오류 해결 방법에 대해 설명하겠습니다.

Java 8 언어 사양, §15.27.2 :

람다 식에서 선언되었지만 아직 사용되지 않은 로컬 변수, 형식 매개 변수 또는 예외 매개 변수는 final로 선언되거나 효과적으로 최종적이거나 ( §4.12.4 ), 사용을 시도 할 때 컴파일 타임 오류가 발생합니다.

기본적으로 calTz람다 (또는 로컬 / 익명 클래스) 내에서 로컬 변수 ( 이 경우)를 수정할 수 없습니다 . Java에서이를 달성하려면 가변 객체를 사용하고 람다에서 최종 변수를 통해 수정해야합니다. 여기에서 변경 가능한 객체의 한 예는 한 요소의 배열입니다.

private TimeZone extractCalendarTimeZoneComponent(Calendar cal, TimeZone calTz) {
    TimeZone[] result = { null };
    try {
        cal.getComponents().getComponents("VTIMEZONE").forEach(component -> {
            ...
            result[0] = ...;
            ...
        }
    } catch (Exception e) {
        log.warn("Unable to determine ical timezone", e);
    }
    return result[0];
}

또 다른 방법은 객체의 필드를 사용하는 것입니다. 예 : MyObj 결과 = 새로운 MyObj (); ...; result.timeZone = ...; ....; 결과 반환. 시간대; 그러나 위에서 설명한 것처럼 스레드 안전 문제에 노출 될 수 있습니다.
Gibezynu Nu

0

이런 종류의 문제에 대한 일반적인 해결 방법보다 변수를 수정할 필요가 없다면 람다를 사용하고 method-parameter에서 final 키워드를 사용하는 코드 부분 추출하는 입니다.


0

람다 식에 사용 된 변수는 최종 변수이거나 효과적으로 최종 변수 여야하지만 마지막 요소 배열 하나에 값을 할당 할 수 있습니다.

private TimeZone extractCalendarTimeZoneComponent(Calendar cal, TimeZone calTz) {
    try {
        TimeZone calTzLocal[] = new TimeZone[1];
        calTzLocal[0] = calTz;
        cal.getComponents().get("VTIMEZONE").forEach(component -> {
            TimeZone v = component;
            v.getTimeZoneId();
            if (calTzLocal[0] == null) {
                calTzLocal[0] = TimeZone.getTimeZone(v.getTimeZoneId().getValue());
            }
        });
    } catch (Exception e) {
        log.warn("Unable to determine ical timezone", e);
    }
    return null;
}

이것은 Alexander Udalov의 제안과 매우 유사합니다. 그 외에도이 접근법은 부작용에 의존하고 있다고 생각합니다.
Scratte
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.