편집 : 타이머를 통해 여러 번 실행되는 여러 변수의 값을 변경해야합니다. 타이머를 통해 반복 할 때마다 값을 계속 업데이트해야합니다. 값을 업데이트 할 수 없으므로 값을 final로 설정할 수 없지만 아래의 초기 질문에서 설명하는 오류가 발생합니다.
이전에 아래 내용을 작성했습니다.
“다른 방법으로 정의 된 내부 클래스 내의 비 최종 변수를 참조 할 수 없습니다”라는 오류가 발생합니다.
이것은 price라고하는 가격과 priceObject라는 가격에 발생합니다. 내가 왜이 문제가 발생하는지 아십니까? 최종 선언이 필요한 이유를 이해하지 못합니다. 또한 내가하려는 일을 볼 수 있다면이 문제를 해결하기 위해 무엇을해야합니까?
public static void main(String args[]) {
int period = 2000;
int delay = 2000;
double lastPrice = 0;
Price priceObject = new Price();
double price = 0;
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
price = priceObject.getNextPrice(lastPrice);
System.out.println();
lastPrice = price;
}
}, delay, period);
}
답변
Java는 여기에서 사용하는 것과 같은 익명 클래스를 사용하는 것이 일종의 클로저처럼 보이지만 진정한 클로저를 지원하지 않습니다 new TimerTask() { ... }
.
편집 – 아래 주석을 참조하십시오-KeeperOfTheSoul이 지적한대로 다음은 올바른 설명이 아닙니다.
이것이 작동하지 않는 이유입니다.
변수 lastPrice
와 가격은 main () 메소드의 로컬 변수입니다. 익명 클래스로 만든 객체는 main()
메서드가 반환 될 때까지 지속될 수 있습니다 .
때 main()
메소드가 리턴 (같은 지역 변수 lastPrice
와는 price
) 스택에서 정리됩니다, 그래서 그들은 이후 더 이상 존재하지 않을 것이다 main()
돌아갑니다.
그러나 익명 클래스 객체는 이러한 변수를 참조합니다. 익명 클래스 객체가 변수를 정리 한 후 변수에 액세스하려고하면 상황이 끔찍하게 잘못 될 수 있습니다.
함으로써 lastPrice
그리고 price
final
, 그들은 더 이상 변수 만 상수는 정말 아니다. 그러면 컴파일러는 익명 클래스 의 사용 lastPrice
과 price
익명 클래스 의 사용을 상수 값 (물론 컴파일 타임에)으로 대체 할 수 있으며 더 이상 존재하지 않는 변수에 액세스하는 데 문제가 없습니다.
클로저를 지원하는 다른 프로그래밍 언어는 이러한 변수를 특수하게 처리하여 메소드가 종료 될 때 변수가 손상되지 않도록하여 클로저가 여전히 변수에 액세스 할 수 있도록합니다.
@Ankur : 당신은 이것을 할 수 있습니다 :
public static void main(String args[]) {
int period = 2000;
int delay = 2000;
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
// Variables as member variables instead of local variables in main()
private double lastPrice = 0;
private Price priceObject = new Price();
private double price = 0;
public void run() {
price = priceObject.getNextPrice(lastPrice);
System.out.println();
lastPrice = price;
}
}, delay, period);
}
답변
익명 대리자가 참조한 Java 변수의 클로저로 이상한 부작용을 피하려면 lastPrice
타이머 작업 내에서 참조 하고 가격 을 정하기 위해 최종으로 표시해야합니다.
변경하기를 원하기 때문에 분명히 효과가 없을 것입니다.이 경우 클래스 내에서 캡슐화하는 것을 봐야합니다.
public class Foo {
private PriceObject priceObject;
private double lastPrice;
private double price;
public Foo(PriceObject priceObject) {
this.priceObject = priceObject;
}
public void tick() {
price = priceObject.getNextPrice(lastPrice);
lastPrice = price;
}
}
이제 마지막으로 새 Foo를 만들고 타이머에서 .tick를 호출하십시오.
public static void main(String args[]){
int period = 2000;
int delay = 2000;
Price priceObject = new Price();
final Foo foo = new Foo(priceObject);
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
foo.tick();
}
}, delay, period);
}
답변
익명 클래스를 사용할 때는 포함하는 클래스에서만 최종 변수에 액세스 할 수 있습니다. 따라서 final로 사용되는 변수를 선언하거나 ( lastPrice 및 price를 변경 하고 있으므로 옵션이 아님 ) 익명 클래스를 사용하지 않아야합니다.
따라서 옵션은 변수를 전달하고 정상적인 방식으로 사용할 수있는 실제 내부 클래스를 만드는 것입니다.
또는:
lastPrice 및 가격 변수에 대한 빠른 (그리고 내 견해로는 추악한) 해킹이 있습니다.
final double lastPrice[1];
final double price[1];
익명 클래스에서 다음과 같이 값을 설정할 수 있습니다
price[0] = priceObject.getNextPrice(lastPrice[0]);
System.out.println();
lastPrice[0] = price[0];
답변
이미 제공하려는 것을 할 수없는 이유에 대한 좋은 설명. 해결책으로 다음을 고려하십시오.
public class foo
{
static class priceInfo
{
public double lastPrice = 0;
public double price = 0;
public Price priceObject = new Price ();
}
public static void main ( String args[] )
{
int period = 2000;
int delay = 2000;
final priceInfo pi = new priceInfo ();
Timer timer = new Timer ();
timer.scheduleAtFixedRate ( new TimerTask ()
{
public void run ()
{
pi.price = pi.priceObject.getNextPrice ( pi.lastPrice );
System.out.println ();
pi.lastPrice = pi.price;
}
}, delay, period );
}
}
아마도 그보다 더 나은 디자인을 할 수있는 것처럼 보이지만 아이디어는 변경되지 않은 클래스 참조 내에서 업데이트 된 변수를 그룹화 할 수 있다는 것입니다.
답변
익명 클래스를 사용하면 실제로 “이름이없는”중첩 클래스를 선언합니다. 중첩 클래스의 경우 컴파일러는 인수로 사용하는 모든 변수를 인수로 사용하는 생성자를 사용하여 새 독립 실행 형 공용 클래스를 생성합니다 ( “명명 된”중첩 클래스의 경우 항상 원본 / 클래 싱 클래스의 인스턴스 임). 런타임 환경에는 중첩 클래스에 대한 개념이 없기 때문에 중첩 클래스에서 독립형 클래스로 (자동) 변환해야합니다.
이 코드를 예로 들어 보겠습니다.
public class EnclosingClass {
public void someMethod() {
String shared = "hello";
new Thread() {
public void run() {
// this is not valid, won't compile
System.out.println(shared); // this instance expects shared to point to the reference where the String object "hello" lives in heap
}
}.start();
// change the reference 'shared' points to, with a new value
shared = "other hello";
System.out.println(shared);
}
}
이것은 컴파일러가 후드에서하는 일이기 때문에 작동하지 않습니다.
public void someMethod() {
String shared = "hello";
new EnclosingClass$1(shared).start();
// change the reference 'shared' points to, with a new value
shared = "other hello";
System.out.println(shared);
}
원래의 익명 클래스는 컴파일러가 생성하는 독립형 클래스로 대체됩니다 (코드는 정확하지 않지만 좋은 아이디어를 제공해야합니다).
public class EnclosingClass$1 extends Thread {
String shared;
public EnclosingClass$1(String shared) {
this.shared = shared;
}
public void run() {
System.out.println(shared);
}
}
보시다시피 독립형 클래스는 공유 객체에 대한 참조를 보유하고 있으며 Java의 모든 것은 값을 전달한다는 점을 기억하십시오. 따라서 EnclosingClass의 ‘shared’참조 변수가 변경 되더라도 가리키는 인스턴스는 수정되지 않습니다. 익명 클래스의 Enclosing $ 1과 같은 다른 모든 참조 변수는이를 인식하지 못합니다. 이것이 컴파일러가이 ‘공유’변수를 최종 변수로 선언하도록하는 주된 이유입니다. 따라서 이러한 유형의 동작은 이미 실행중인 코드에 영향을 미치지 않습니다.
이제 이것은 익명 클래스 내에서 인스턴스 변수를 사용할 때 발생하는 현상입니다 (문제를 해결하고 로직을 “인스턴스”메서드 또는 클래스 생성자로 이동하려면 수행해야합니다).
public class EnclosingClass {
String shared = "hello";
public void someMethod() {
new Thread() {
public void run() {
System.out.println(shared); // this is perfectly valid
}
}.start();
// change the reference 'shared' points to, with a new value
shared = "other hello";
System.out.println(shared);
}
}
컴파일러가 코드를 수정하여 새로 생성 된 클래스 Enclosing $ 1이 인스턴스화 된 EnclosingClass 인스턴스에 대한 참조를 보유하기 때문에 이것은 잘 컴파일됩니다 (이것은 단지 표현 일뿐입니다).
public void someMethod() {
new EnclosingClass$1(this).start();
// change the reference 'shared' points to, with a new value
shared = "other hello";
System.out.println(shared);
}
public class EnclosingClass$1 extends Thread {
EnclosingClass enclosing;
public EnclosingClass$1(EnclosingClass enclosing) {
this.enclosing = enclosing;
}
public void run() {
System.out.println(enclosing.shared);
}
}
이와 같이 EnclosingClass에서 ‘shared’참조 변수가 재 할당되고 Thread # run ()을 호출하기 전에이 문제가 발생하면 EnclosingClass $ 1 # enclosing 변수가 참조를 유지하므로 “other hello”가 두 번 인쇄됩니다. 선언 된 클래스의 개체에 대한 속성이므로 해당 개체의 모든 특성에 대한 변경 내용은 EnclosingClass $ 1 인스턴스에 표시됩니다.
주제에 대한 자세한 내용은 다음과 같은 우수한 블로그 게시물을 참조하십시오 (저는 작성하지 않음). http://kevinboone.net/java_inner.html
답변
이 문제를 우연히 발견하면 생성자를 통해 객체를 내부 클래스로 전달합니다. 프리미티브 또는 변경 불가능한 객체 (이 경우와 같이)를 전달해야하는 경우 래퍼 클래스가 필요합니다.
편집 : 실제로 익명 클래스를 사용하지 않고 적절한 하위 클래스를 사용합니다.
public class PriceData {
private double lastPrice = 0;
private double price = 0;
public void setlastPrice(double lastPrice) {
this.lastPrice = lastPrice;
}
public double getLastPrice() {
return lastPrice;
}
public void setPrice(double price) {
this.price = price;
}
public double getPrice() {
return price;
}
}
public class PriceTimerTask extends TimerTask {
private PriceData priceData;
private Price priceObject;
public PriceTimerTask(PriceData priceData, Price priceObject) {
this.priceData = priceData;
this.priceObject = priceObject;
}
public void run() {
priceData.setPrice(priceObject.getNextPrice(lastPrice));
System.out.println();
priceData.setLastPrice(priceData.getPrice());
}
}
public static void main(String args[]) {
int period = 2000;
int delay = 2000;
PriceData priceData = new PriceData();
Price priceObject = new Price();
Timer timer = new Timer();
timer.scheduleAtFixedRate(new PriceTimerTask(priceData, priceObject), delay, period);
}
답변
Java 언어 사양에 나와 있기 때문에 비 최종 변수를 참조 할 수 없습니다. 8.1.3부터 :
“내부 클래스에서 선언되었지만 선언되지 않은 모든 로컬 변수, 공식 메소드 매개 변수 또는 예외 핸들러 매개 변수는 final로 선언되어야합니다.” 전체 단락.
로컬 변수의 수정을 예약하는 것은 이상한 생각입니다. 함수를 떠날 때 지역 변수가 존재하지 않습니다. 어쩌면 클래스의 정적 필드가 더 좋을까요?