Lambda : 로컬 변수에는 최종, 인스턴스 변수는 필요하지 않습니다.
람다에서 지역 변수는 최종적이어야하지만 인스턴스 변수는 그렇지 않습니다. 왜 그렇습니까?
필드와 로컬 변수의 근본적인 차이점은 JVM이 람다 인스턴스를 만들 때 로컬 변수가 복사 된다는 것 입니다. 반면에 필드는 변경 사항이 외부 클래스 인스턴스로 전파되기 때문에 자유롭게 변경할 수 있습니다 (그 범위 는 Boris가 아래에서 지적한대로 전체 외부 클래스입니다).
익명 클래스, 클로저 및 labmdas에 대해 생각하는 가장 쉬운 방법은 가변 범위 관점에서 보는 것입니다. 클로저에 전달하는 모든 지역 변수에 대해 추가 된 복사 생성자를 상상해보십시오.
프로젝트 람다 문서에서 : State of the Lambda v4
섹션 7. 변수 캡처 아래에 언급되어 있습니다 ....
변경 가능한 지역 변수의 캡처를 금지하는 것이 우리의 의도입니다. 그 이유는 다음과 같은 관용구입니다.
int sum = 0; list.forEach(e -> { sum += e.size(); });
기본적으로 직렬입니다. 경쟁 조건이없는 이와 같이 람다 본문을 작성하는 것은 매우 어렵습니다. 이러한 함수가 캡처 스레드를 벗어날 수 없도록 컴파일 타임에 선호하지 않는 한이 기능은 해결하는 것보다 더 많은 문제를 일으킬 수 있습니다.
편집하다 :
여기서 주목해야 할 또 다른 점은 내부 클래스 내에서 액세스 할 때 내부 클래스의 생성자에 지역 변수가 전달된다는 것입니다. 최종 변수가 아닌 변수의 값은 생성 후 변경 될 수 있기 때문에 최종 변수가 아닌 변수에서는 작동하지 않습니다.
인스턴스 변수의 경우 컴파일러는 클래스의 참조를 전달하고 클래스의 참조는 인스턴스 변수에 액세스하는 데 사용됩니다. 따라서 인스턴스 변수의 경우에는 필요하지 않습니다.
추신 : 익명 클래스는 (JAVA SE 7에서) 최종 지역 변수에만 액세스 할 수 있지만 Java SE 8에서는 내부 클래스뿐만 아니라 람다 내부에서도 효과적으로 최종 변수에 액세스 할 수 있습니다.
인스턴스 변수는 항상 일부 객체에 대한 참조에 대한 필드 액세스 작업을 통해 액세스되기 때문 some_expression.instance_variable
입니다. 명시 적으로 점 표기법을 통해 액세스하지 않는 경우에도, 같은 instance_variable
, 그것은 암시 적으로 처리됩니다 this.instance_variable
(또는 외부 클래스의 인스턴스 변수에 액세스 할 경우 내부 클래스에 있다면, OuterClass.this.instance_variable
후드 아래에있는 this.<hidden reference to outer this>.instance_variable
).
따라서 인스턴스 변수는 직접 액세스되지 않으며 직접 액세스하는 실제 "변수" this
는 할당 할 수 없기 때문에 "효과적으로 최종"이거나 다른 표현식의 시작 부분에있는 변수입니다.
에서 액션에서 자바 8 책,이 상황은 다음과 같이 설명한다 :
지역 변수에 이러한 제한이있는 이유를 스스로 물어볼 수 있습니다. 첫째, 인스턴스 및 지역 변수가이면에서 구현되는 방식에 중요한 차이가 있습니다. 인스턴스 변수는 힙에 저장되는 반면 로컬 변수는 스택에 있습니다. 람다가 지역 변수에 직접 액세스 할 수 있고 람다가 스레드에서 사용 된 경우 람다를 사용하는 스레드는 변수를 할당 한 스레드가 변수를 할당 해제 한 후 변수에 액세스하려고 할 수 있습니다. 따라서 Java는 원래 변수에 대한 액세스가 아닌 사본에 대한 액세스로 자유 지역 변수에 대한 액세스를 구현합니다. 로컬 변수가 한 번만 할당 된 경우에는 차이가 없습니다. 따라서 제한이 있습니다. 둘째,이 제한은 또한 전형적인 명령형 프로그래밍 패턴을 방해합니다 (이후 장에서 설명 하겠지만
미래 방문자를위한 몇 가지 개념 제시 :
기본적으로 모든 것은 컴파일러가 람다 식 본문이 오래된 변수 복사본에서 작동하지 않는다는 것을 결정 론적으로 말할 수 있어야한다는 점으로 요약됩니다 .
지역 변수의 경우 컴파일러는 람다 식 본문이 변수의 부실 복사본에서 작동하지 않는지 확인할 방법이 없으므로 해당 변수가 최종적이거나 사실상 최종적이지 않으면 지역 변수가 최종적이거나 사실상 최종적이어야합니다.
이제 인스턴스 필드의 경우 람다 식 내부의 인스턴스 필드에 액세스하면 컴파일러가 this
해당 변수 액세스에를 추가합니다 (명시 적으로 수행하지 않은 경우). 왜냐하면 this
사실상 최종이므로 컴파일러는 람다 식 본문이 항상 변수의 최신 복사본을 가져야합니다 (이 토론에서는 멀티 스레딩이 현재 범위를 벗어납니다). 따라서 인스턴스 필드의 경우 컴파일러는 람다 본문에 인스턴스 변수의 최신 복사본이 있음을 알릴 수 있으므로 인스턴스 변수가 최종적이거나 효과적으로 최종적 일 필요가 없습니다. 아래의 Oracle 슬라이드 스크린 샷을 참조하십시오.
또한 람다 식의 인스턴스 필드에 액세스하고 다중 스레드 환경에서 실행되는 경우 잠재적으로 문제가 발생할 수 있습니다.
람다 본문에서 참조 할 수있는 변수에 대해 묻는 것 같습니다.
로부터 JLS §15.27.2
사용되었지만 람다 식에서 선언되지 않은 모든 지역 변수, 형식 매개 변수 또는 예외 매개 변수는 final로 선언되거나 사실상 최종 (§4.12.4)이어야합니다. 그렇지 않으면 사용을 시도 할 때 컴파일 타임 오류가 발생합니다.
따라서 변수 final
가 "효과적으로 최종"인지 확인하기 위해 변수를 선언 할 필요가 없습니다 . 이것은 익명 클래스에 적용되는 것과 동일한 규칙입니다.
Lambda 표현식 내에서 주변 범위의 최종 변수를 효과적으로 사용할 수 있습니다. 사실상 final 변수를 선언하는 것이 필수는 아니지만 람다 표현 내에서 상태를 변경하지 않도록하십시오.
You can also use this within closures and using "this" means the enclosing object but not the lambda itself as closures are anonymous functions and they do not have class associated with them.
So when you use any field (let say private Integer i;)from the enclosing class which is not declared final and not effectively final it will still work as the compiler makes the trick on your behalf and insert "this" (this.i).
private Integer i = 0;
public void process(){
Consumer<Integer> c = (i)-> System.out.println(++this.i);
c.accept(i);
}
Here is a code example, as I didn't expect this either, I expected to be unable to modify anything outside my lambda
public class LambdaNonFinalExample {
static boolean odd = false;
public static void main(String[] args) throws Exception {
//boolean odd = false; - If declared inside the method then I get the expected "Effectively Final" compile error
runLambda(() -> odd = true);
System.out.println("Odd=" + odd);
}
public static void runLambda(Callable c) throws Exception {
c.call();
}
}
Output: Odd=true
YES, you can change the member variables of the instance but you CANNOT change the instance itself just like when you handle variables.
Something like this as mentioned:
class Car {
public String name;
}
public void testLocal() {
int theLocal = 6;
Car bmw = new Car();
bmw.name = "BMW";
Stream.iterate(0, i -> i + 2).limit(2)
.forEach(i -> {
// bmw = new Car(); // LINE - 1;
bmw.name = "BMW NEW"; // LINE - 2;
System.out.println("Testing local variables: " + (theLocal + i));
});
// have to comment this to ensure it's `effectively final`;
// theLocal = 2;
}
The basic principle to restrict the local variables is about data and computation validity
If the lambda, evaluated by the second thread, were given the ability to mutate local variables. Even the ability to read the value of mutable local variables from a different thread would introduce the necessity for synchronization or the use of volatile in order to avoid reading stale data.
But as we know the principal purpose of the lambdas
Amongst the different reasons for this, the most pressing one for the Java platform is that they make it easier to distribute processing of collections over multiple threads.
Quite unlike local variables, local instance can be mutated, because it's shared globally. We can understand this better via the heap and stack difference:
Whenever an object is created, it’s always stored in the Heap space and stack memory contains the reference to it. Stack memory only contains local primitive variables and reference variables to objects in heap space.
So to sum up, there are two points I think really matter:
It's really hard to make the instance effectively final, which might cause lots of senseless burden (just imagine the deep-nested class);
the instance itself is already globally shared and lambda is also shareable among threads, so they can work together properly since we know we're handling the mutation and want to pass this mutation around;
Balance point here is clear: if you know what you are doing, you can do it easily but if not then the default restriction will help to avoid insidious bugs.
는 IF PS는 동기화가 필요 인스턴스 돌연변이 , 당신이 직접 사용할 수있는 스트림 감소 방법을 또는 의존성 문제가있는 경우 인스턴스 돌연변이 , 당신은 여전히 사용할 수 있습니다 thenApply
또는 thenCompose
에서 기능 하는 동안 mapping
또는 방법과 유사.
'Development Tip' 카테고리의 다른 글
입력 할 때 html 텍스트 입력 필드가 커지나요? (0) | 2020.11.10 |
---|---|
C에서 char 배열을 복사하는 방법은 무엇입니까? (0) | 2020.11.10 |
약속-약속을 강제로 취소 할 수 있습니까? (0) | 2020.11.10 |
전자와 함께 sqlite3 모듈을 사용하는 방법은 무엇입니까? (0) | 2020.11.10 |
SSL이 최대 허용 길이를 초과하는 레코드를 받았습니다. (0) | 2020.11.10 |