ไม่สามารถอ้างถึงตัวแปรที่ไม่ใช่ครั้งสุดท้ายภายในคลาสภายในที่กำหนดไว้ในวิธีการอื่น


247

แก้ไข: ฉันจำเป็นต้องเปลี่ยนค่าของตัวแปรหลายตัวในขณะที่พวกเขาทำงานหลายครั้งก่อนที่ตัวจับเวลา ฉันต้องการอัปเดตค่ากับการวนซ้ำทุกครั้งผ่านตัวจับเวลา ฉันไม่สามารถตั้งค่าเป็นค่าสุดท้ายได้ซึ่งจะทำให้ฉันไม่สามารถอัปเดตค่าได้ แต่ฉันได้รับข้อผิดพลาดที่ฉันอธิบายในคำถามเริ่มต้นด้านล่าง:

ฉันเคยเขียนสิ่งที่อยู่ด้านล่าง:

ฉันได้รับข้อผิดพลาด "ไม่สามารถอ้างถึงตัวแปรที่ไม่ใช่ครั้งสุดท้ายภายในคลาสภายในที่กำหนดในวิธีอื่น"

สิ่งนี้เกิดขึ้นสำหรับราคาที่มีการเรียกสองครั้งและราคาที่เรียกว่า 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);
}

สิ่งที่ฉันถามคือฉันจะได้รับตัวแปรในตัวจับเวลาที่ฉันสามารถปรับปรุงอย่างต่อเนื่อง
Ankur

1
@Ankur: คำตอบง่ายๆคือ "ไม่" แต่คุณสามารถบรรลุผลตามที่ต้องการโดยใช้คลาสภายใน ดูคำตอบของ @ petercardona
สตีเฟ่น C

คำตอบ:


197

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);      
}

34
ไม่เป็นความจริงเลย Java สร้างการจับภาพสำหรับตัวแปรที่เป็นปัญหาเพื่อจับภาพค่าเวลาทำงานของมัน แต่พวกเขาต้องการหลีกเลี่ยงผลข้างเคียงที่แปลกประหลาดที่เป็นไปได้ใน. Net โดยคุณจับค่าในตัวแทน value ในเมธอด outer และตอนนี้ผู้ร่วมประชุมจะเห็นค่าใหม่ see, stackoverflow.com/questions/271440/c-captured-variable-in-loopสำหรับตัวอย่าง C # ของพฤติกรรมนี้ที่ Java ตั้งเป้าที่จะหลีกเลี่ยง
Chris Chilvers

14
นั่นไม่ใช่ "ผลข้างเคียงที่แปลก" มันเป็นพฤติกรรมปกติที่ผู้คนคาดหวัง - และ Java ตัวใดที่ไม่สามารถส่งมอบได้เพราะมันไม่ได้สร้างการดักจับ ตัวแปรในท้องถิ่นที่ใช้ในคลาสที่ไม่ระบุชื่อจะต้องเป็นที่สุด
Michael Borgwardt

12
ใช่คุณควรแก้ไขส่วนที่ไม่ถูกต้องของคำตอบของคุณแทนที่จะมีเพียงข้อความที่บอกว่าข้างต้นไม่ถูกต้อง
James McMahon

19
Java ไม่สนับสนุนการปิดจริง ภาษาที่รองรับการปิดทำได้โดยการเก็บสภาพแวดล้อมท้องถิ่นทั้งหมด (นั่นคือชุดของตัวแปรท้องถิ่นที่กำหนดในสแต็กเฟรมปัจจุบัน) เป็นวัตถุฮีป Java ไม่ได้รับการสนับสนุนนี้ (นักออกแบบภาษาต้องการนำไปใช้ แต่หมดเวลา) ดังนั้นจึงเป็นวิธีแก้ปัญหาเมื่อใดก็ตามที่คลาสโลคัลถูกสร้างอินสแตนซ์ค่าของตัวแปรโลคัลใด ๆ ที่มันอ้างถึงจะถูกคัดลอกลงในฮีป . อย่างไรก็ตาม JVM จะไม่สามารถเก็บค่าให้สอดคล้องกับตัวแปรท้องถิ่นได้ซึ่งเป็นเหตุผลว่าทำไมจึงต้องเป็นค่าสุดท้าย
Taymon

64
คำตอบนี้ทำให้สับสนอย่างสมบูรณ์ตอนนี้ไม่มีใครชื่อ "KeeperOfTheSoul" ที่ได้แสดงความคิดเห็น คำตอบควรได้รับการแก้ไข
Adam Parkin

32

เพื่อหลีกเลี่ยงผลข้างเคียงที่แปลกประหลาดที่มีการปิดในตัวแปร 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);
}

1
หรือคุณสามารถทำให้ Foo ใช้ Runnable ..
vidstige

18

คุณสามารถเข้าถึงตัวแปรสุดท้ายจากคลาสที่มีเมื่อใช้คลาสที่ไม่ระบุชื่อ ดังนั้นคุณจำเป็นต้องประกาศตัวแปรที่ใช้เป็นครั้งสุดท้าย (ซึ่งไม่ได้เป็นตัวเลือกสำหรับคุณเพราะคุณมีการเปลี่ยนแปลงlastPriceและราคา ) หรือไม่ใช้ชั้นที่ไม่ระบุชื่อ

ดังนั้นทางเลือกของคุณคือการสร้างคลาสภายในที่แท้จริงซึ่งคุณสามารถส่งผ่านตัวแปรและใช้พวกเขาในแบบปกติ

หรือ:

มีแฮ็ค (และในความคิดของฉันน่าเกลียด) อย่างรวดเร็วสำหรับราคาสุดท้ายและตัวแปรราคาของคุณซึ่งจะประกาศเช่นนั้น

final double lastPrice[1];
final double price[1];

และในชั้นเรียนที่ไม่ระบุชื่อของคุณคุณสามารถกำหนดค่าเช่นนี้

price[0] = priceObject.getNextPrice(lastPrice[0]);
System.out.println();
lastPrice[0] = price[0];

14

คำอธิบายที่ดีเกี่ยวกับสาเหตุที่คุณไม่สามารถทำสิ่งที่คุณพยายามทำแล้วได้ เป็นวิธีแก้ปัญหาอาจพิจารณา:

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 );
    }
}

ดูเหมือนว่าคุณอาจออกแบบได้ดีกว่า แต่ความคิดก็คือคุณสามารถจัดกลุ่มตัวแปรที่ได้รับการปรับปรุงภายในการอ้างอิงคลาสที่ไม่เปลี่ยนแปลง


11

ด้วยคลาสที่ไม่ระบุชื่อคุณจะประกาศคลาสแบบ "ไม่มีชื่อ" สำหรับคลาสที่ซ้อนกันคอมไพเลอร์จะสร้างคลาสพับลิกแบบสแตนด์อโลนใหม่ที่มีตัวสร้างที่จะใช้ตัวแปรทั้งหมดที่ใช้เป็นอาร์กิวเมนต์ สิ่งนี้เกิดขึ้นเนื่องจากสภาพแวดล้อมรันไทม์ไม่มีความคิดเกี่ยวกับคลาสที่ซ้อนกันดังนั้นจึงจำเป็นต้องมีการแปลง (อัตโนมัติ) จากคลาสที่ซ้อนกันเป็นคลาสแบบสแตนด์อโลน

นำรหัสนี้มาเป็นตัวอย่าง:

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 จะได้รับการเปลี่ยนแปลงอินสแตนซ์นั้นชี้ไปที่จะไม่แก้ไข และตัวแปรอ้างอิงอื่น ๆ ทั้งหมดที่ชี้ไป (เช่นในคลาสที่ไม่ระบุชื่อ: การปิดล้อม $ 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);
    }
}

คอมไพล์นี้ดีเพราะคอมไพเลอร์จะแก้ไขโค้ดเพื่อให้คลาสที่สร้างขึ้นใหม่การปิดล้อม $ 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 ได้รับการกำหนดใหม่และสิ่งนี้เกิดขึ้นก่อนที่จะเรียกการเรียกใช้เธรด # () คุณจะเห็น "อื่น ๆ สวัสดี" พิมพ์สองครั้งเพราะตอนนี้ตัวแปร EnclosingClass $ 1 # จะเก็บการอ้างอิง ไปยังวัตถุของคลาสที่มีการประกาศดังนั้นการเปลี่ยนแปลงคุณลักษณะใด ๆ บนวัตถุนั้นจะสามารถมองเห็นได้โดยอินสแตนซ์ของ EnclosingClass $ 1

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับหัวข้อนี้คุณสามารถดูโพสต์บล็อกยอดเยี่ยม (ไม่ได้เขียนโดยฉัน): http://kevinboone.net/java_inner.html


เกิดอะไรขึ้นถ้าตัวแปรท้องถิ่น 'แชร์' เป็นวัตถุที่ไม่แน่นอน ตามคำอธิบายของคุณที่ประกาศ 'ขั้นสุดท้าย' จะไม่ช่วยเช่นกันใช่ไหม
sactiw

การประกาศ "ใช้ร่วมกัน" เป็นขั้นสุดท้ายจะช่วยให้คุณสามารถแก้ไขสถานะของวัตถุเป็นการอ้างอิงตัวแปรสุดท้าย แต่สำหรับตัวอย่างนี้โดยเฉพาะที่ไม่ทำงานเนื่องจากคุณจะไม่สามารถเปลี่ยนค่าของตัวแปร "แชร์" (ซึ่ง เป็นสิ่งที่ OP ต้องการ) คุณจะสามารถใช้ภายในคลาสที่ไม่ระบุชื่อ แต่ค่าจะไม่เปลี่ยนแปลง (เนื่องจากมีการประกาศครั้งสุดท้าย) สิ่งสำคัญคือต้องสังเกตความแตกต่างระหว่างตัวแปรและค่าจริงที่พวกเขาเก็บไว้ (ซึ่งอาจเป็นแบบดั้งเดิมหรือการอ้างอิงถึงวัตถุในกอง)
emerino

>>> แต่ค่ามันจะไม่เปลี่ยนแปลง ฉันเดาว่าคุณพลาดจุดเช่นถ้าตัวแปรอ้างอิงสุดท้ายชี้ไปที่วัตถุที่ไม่แน่นอนมันยังคงสามารถปรับปรุงได้อย่างไรก็ตามคลาสนิรนามจะสร้างสำเนาตื้นดังนั้นการเปลี่ยนแปลงจะปรากฏในนิรนาม ชั้น กล่าวอีกนัยหนึ่งรัฐกำลังซิงค์ซึ่งเป็นสิ่งที่ต้องการที่นี่ ที่นี่ OP ต้องการความสามารถในการปรับเปลี่ยนตัวแปรที่ใช้ร่วมกัน (ชนิดดั้งเดิม) และเพื่อให้บรรลุว่า OP จะต้องห่อค่าภายใต้วัตถุที่ไม่แน่นอนและใช้ร่วมกันวัตถุที่ไม่แน่นอน
sactiw

1
แน่นอนว่า OP สามารถห่อค่าที่ต้องการภายใต้วัตถุที่ไม่แน่นอนได้ประกาศตัวแปรเป็นขั้นสุดท้ายและใช้แทน อย่างไรก็ตามเขาสามารถหลีกเลี่ยงการใช้วัตถุพิเศษโดยการประกาศตัวแปรเป็นคุณลักษณะของคลาสปัจจุบัน (ตามที่แหลมและอธิบายในคำตอบ) การบังคับให้วัตถุที่ไม่แน่นอน (เช่นใช้อาร์เรย์เพียงเพื่อให้สามารถปรับเปลี่ยนค่าของตัวแปรที่แชร์) ไม่ใช่ความคิดที่ดี
emerino

7

เมื่อฉันสะดุดปัญหานี้ฉันเพิ่งส่งวัตถุไปยังชั้นในผ่านตัวสร้าง ถ้าฉันต้องการส่งวัตถุพื้นฐานหรือวัตถุที่ไม่เปลี่ยนรูป (เช่นในกรณีนี้) จำเป็นต้องใช้คลาส wrapper

แก้ไข: จริงๆแล้วฉันไม่ได้ใช้คลาสที่ไม่ระบุชื่อเลย แต่เป็นคลาสย่อยที่เหมาะสม:

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);
    }

2

คุณไม่สามารถอ้างถึงตัวแปรที่ไม่ได้อยู่ในขั้นสุดท้ายเนื่องจาก Java Language Specification บอกไว้ จาก 8.1.3:
"ตัวแปรโลคอลใด ๆ พารามิเตอร์เมธอดทางการหรือพารามิเตอร์ตัวจัดการข้อยกเว้นที่ใช้ แต่ไม่ประกาศในคลาสภายในต้องถูกประกาศให้เป็นที่สุด" ทั้งย่อหน้า
ฉันสามารถเห็นเพียงบางส่วนของรหัสของคุณ - ตามที่ฉันกำหนดเวลาการปรับเปลี่ยนตัวแปรท้องถิ่นเป็นความคิดที่แปลก ตัวแปรโลคัลหยุดอยู่เมื่อคุณออกจากฟังก์ชัน บางทีฟิลด์คงที่ของคลาสจะดีกว่าไหม?


2

ฉันเขียนบางสิ่งบางอย่างที่จะจับบางสิ่งบางอย่างไปตามความตั้งใจของผู้เขียน ฉันพบสิ่งที่ดีที่สุดที่ต้องทำคือให้ตัวสร้างใช้วัตถุทั้งหมดและจากนั้นในวิธีการที่คุณนำไปใช้ใช้วัตถุตัวสร้างนั้น

อย่างไรก็ตามถ้าคุณกำลังเขียนคลาสอินเตอร์เฟสทั่วไปคุณจะต้องผ่าน Object หรือดีกว่ารายการของ Objects สิ่งนี้สามารถทำได้โดย Object [] หรือดีกว่าObject ...เพราะโทรได้ง่ายกว่า

ดูชิ้นตัวอย่างของฉันด้านล่าง

List<String> lst = new ArrayList<String>();
lst.add("1");
lst.add("2");        

SomeAbstractClass p = new SomeAbstractClass (lst, "another parameter", 20, true) {            

    public void perform( ) {                           
        ArrayList<String> lst = (ArrayList<String>)getArgs()[0];                        
    }

};

public abstract class SomeAbstractClass{    
    private Object[] args;

    public SomeAbstractClass(Object ... args) {
        this.args = args;           
    }      

    public abstract void perform();        

    public Object[] getArgs() {
        return args;
    }

}

โปรดดูโพสต์นี้เกี่ยวกับการปิดของ Java ที่รองรับสิ่งนี้นอกกรอบ: http://mseifed.blogspot.se/2012/09/closure-implementation-for-java-5-6-and.html

เวอร์ชัน 1 รองรับการส่งผ่านการปิดที่ไม่ใช่ครั้งสุดท้ายด้วยการปิดอัตโนมัติ:
https://github.com/MSeifeddo/Closure-implementation-for-Java-5-6-and-7/blob/master/org/mo/closure/v1/ Closure.java

    SortedSet<String> sortedNames = new TreeSet<String>();
    // NOTE! Instead of enforcing final, we pass it through the constructor
    eachLine(randomFile0, new V1<String>(sortedNames) {
        public void call(String line) {
            SortedSet<String> sortedNames = castFirst();  // Read contructor arg zero, and auto cast it
            sortedNames.add(extractName(line));
        }
    });

2

หากคุณต้องการที่จะเปลี่ยนค่าในวิธีการเรียกภายในชั้นที่ไม่ระบุชื่อว่า "คุ้มค่า" Futureเป็นจริง ดังนั้นถ้าคุณใช้ Guava คุณสามารถเขียนได้

...
final SettableFuture<Integer> myvalue = SettableFuture<Integer>.create();
...
someclass.run(new Runnable(){

    public void run(){
        ...
        myvalue.set(value);
        ...
    }
 }

 return myvalue.get();

2

ทางออกหนึ่งที่ฉันสังเกตเห็นไม่ได้กล่าวถึง (ยกเว้นว่าฉันพลาดถ้าฉันแก้ไขให้ถูกต้อง) ก็คือการใช้ตัวแปรคลาส new Thread(){ Do Something }วิ่งเข้าไปในปัญหานี้พยายามที่จะเรียกใช้หัวข้อใหม่ภายในวิธีการ:

การโทรdoSomething()จากอุปกรณ์ต่อไปนี้จะใช้งานได้ คุณไม่จำเป็นต้องประกาศมันfinalเพียงแค่ต้องเปลี่ยนขอบเขตของตัวแปรดังนั้นมันจึงไม่ถูกรวบรวมก่อนชั้นใน นี่คือกระบวนการของคุณมีขนาดใหญ่มากและการเปลี่ยนแปลงขอบเขตอาจสร้างความขัดแย้งบางอย่าง ฉันไม่ต้องการทำให้ตัวแปรของฉันเป็นขั้นสุดท้าย

public class Test
{

    protected String var1;
    protected String var2;

    public void doSomething()
    {
        new Thread()
        {
            public void run()
            {
                System.out.println("In Thread variable 1: " + var1);
                System.out.println("In Thread variable 2: " + var2);
            }
        }.start();
    }

}

1

หากตัวแปรที่ต้องการเป็นค่าสุดท้ายไม่สามารถเป็นได้คุณสามารถกำหนดค่าของตัวแปรให้กับตัวแปรอื่นและทำให้เป็นค่าสุดท้ายเพื่อให้คุณสามารถใช้แทนได้


1

ใช้ ClassName.this.variableName เพื่ออ้างอิงตัวแปรที่ไม่ได้อยู่ในขั้นสุดท้าย


1

คุณสามารถประกาศตัวแปรภายนอกคลาสภายนอกได้ หลังจากนี้คุณจะสามารถแก้ไขตัวแปรจากภายในชั้นใน บางครั้งฉันต้องเผชิญกับปัญหาที่คล้ายกันในขณะที่เขียนโค้ดใน Android ดังนั้นฉันจึงประกาศตัวแปรเป็นแบบโกลบอลและใช้งานได้สำหรับฉัน


สิ่งนี้ไม่ได้ตอบคำถามจริงๆ ... มันเป็นสาเหตุที่คุณถูกลดระดับลง
Stuart Siegler

0

คุณสามารถสร้างlastPrice, priceObjectและpriceสาขาของระดับชั้นที่ไม่ระบุชื่อ?


0

ข้อกังวลหลักคือว่าตัวแปรภายในอินสแตนซ์ของคลาสที่ไม่ระบุชื่อสามารถแก้ไขได้ในเวลาทำงาน ไม่จำเป็นต้องทำให้ตัวแปรสุดท้ายตราบใดที่มีการประกันว่าตัวแปรนั้นอยู่ภายในขอบเขตของเวลาทำงาน ตัวอย่างเช่นโปรดดูสองตัวแปร _statusMessage และ _statusTextView ภายในเมธอด updateStatus ()

public class WorkerService extends Service {

Worker _worker;
ExecutorService _executorService;
ScheduledExecutorService _scheduledStopService;

TextView _statusTextView;


@Override
public void onCreate() {
    _worker = new Worker(this);
    _worker.monitorGpsInBackground();

    // To get a thread pool service containing merely one thread
    _executorService = Executors.newSingleThreadExecutor();

    // schedule something to run in the future
    _scheduledStopService = Executors.newSingleThreadScheduledExecutor();
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {

    ServiceRunnable runnable = new ServiceRunnable(this, startId);
    _executorService.execute(runnable);

    // the return value tells what the OS should
    // do if this service is killed for resource reasons
    // 1. START_STICKY: the OS restarts the service when resources become
    // available by passing a null intent to onStartCommand
    // 2. START_REDELIVER_INTENT: the OS restarts the service when resources
    // become available by passing the last intent that was passed to the
    // service before it was killed to onStartCommand
    // 3. START_NOT_STICKY: just wait for next call to startService, no
    // auto-restart
    return Service.START_NOT_STICKY;
}

@Override
public void onDestroy() {
    _worker.stopGpsMonitoring();
}

@Override
public IBinder onBind(Intent intent) {
    return null;
}

class ServiceRunnable implements Runnable {

    WorkerService _theService;
    int _startId;
    String _statusMessage;

    public ServiceRunnable(WorkerService theService, int startId) {
        _theService = theService;
        _startId = startId;
    }

    @Override
    public void run() {

        _statusTextView = MyActivity.getActivityStatusView();

        // get most recently available location as a latitude /
        // longtitude
        Location location = _worker.getLocation();
        updateStatus("Starting");

        // convert lat/lng to a human-readable address
        String address = _worker.reverseGeocode(location);
        updateStatus("Reverse geocoding");

        // Write the location and address out to a file
        _worker.save(location, address, "ResponsiveUx.out");
        updateStatus("Done");

        DelayedStopRequest stopRequest = new DelayedStopRequest(_theService, _startId);

        // schedule a stopRequest after 10 seconds
        _theService._scheduledStopService.schedule(stopRequest, 10, TimeUnit.SECONDS);
    }

    void updateStatus(String message) {
        _statusMessage = message;

        if (_statusTextView != null) {
            _statusTextView.post(new Runnable() {

                @Override
                public void run() {
                    _statusTextView.setText(_statusMessage);

                }

            });
        }
    }

}

0

สิ่งที่ใช้ได้ผลกับฉันแค่กำหนดตัวแปรนอกฟังก์ชั่นของคุณ

ก่อนหน้าที่ประกาศหลักคือ

Double price;
public static void main(String []args(){
--------
--------
}

คุณจะต้องประกาศตัวแปรอินสแตนซ์เพื่อใช้งานคุณจะต้องสร้างอินสแตนซ์ภายในเมธอดหลักของคุณ คุณควรเจาะจงมากกว่านี้หรือเพิ่มตัวแก้ไขแบบคงที่ให้กับตัวแปร 'ราคา'
emerino

0

ประกาศตัวแปรเป็นแบบคงที่และอ้างอิงในวิธีการที่จำเป็นโดยใช้ className.variable


Non-static parameter cannot be referenced from a static context
stephen

@Shweta ตัวแปรโลคัลและพารามิเตอร์เมธอดไม่สามารถประกาศ 'คงที่' ยิ่งไปกว่านั้นมันเป็นเรื่องเกี่ยวกับวิธีการใช้งานเพื่ออนุญาตให้คลาสภายในเมธอด (คลาสที่ไม่ระบุชื่อโลคัล) สามารถเข้าถึงตัวแปรโลคอลและเมธอดพารามิเตอร์ต่อ ได้กลับมาคือมันทำสำเนา 'สุดท้าย' ของพวกเขาและใช้พวกเขาเป็นตัวแปรเช่น
sactiw

0

เป็นอีกคำอธิบาย ลองพิจารณาตัวอย่างด้านล่างนี้

public class Outer{
     public static void main(String[] args){
         Outer o = new Outer();
         o.m1();        
         o=null;
     }
     public void m1(){
         //int x = 10;
         class Inner{
             Thread t = new Thread(new Runnable(){
                 public void run(){
                     for(int i=0;i<10;i++){
                         try{
                             Thread.sleep(2000);                            
                         }catch(InterruptedException e){
                             //handle InterruptedException e
                         }
                         System.out.println("Thread t running");                             
                     }
                 }
             });
         }
         new Inner().t.start();
         System.out.println("m1 Completes");
    }
}

เอาท์พุทที่นี่จะเป็น

m1 เสร็จสิ้น

ด้ายวิ่ง

ด้ายวิ่ง

ด้ายวิ่ง

................

ตอนนี้เมธอด m1 () เสร็จสมบูรณ์และเรากำหนดตัวแปรอ้างอิงให้เป็นโมฆะตอนนี้อ็อบเจกต์คลาสภายนอกมีสิทธิ์ใช้งาน GC แต่วัตถุชั้นในยังคงมีอยู่ที่มีความสัมพันธ์ (Has-A) กับวัตถุเธรดซึ่งกำลังทำงานอยู่ หากไม่มีวัตถุคลาสภายนอกที่มีอยู่จะไม่มีโอกาสของวิธีการ m1 () ที่มีอยู่และไม่มีวิธี m1 () ที่มีอยู่ไม่มีโอกาสของตัวแปรท้องถิ่นที่มีอยู่ แต่ถ้า Inner Class Object ใช้ตัวแปรท้องถิ่นของวิธี m1 () ทุกอย่างจะอธิบายตนเอง .

เพื่อแก้ปัญหานี้เราต้องสร้างสำเนาของตัวแปรท้องถิ่นแล้วต้องคัดลอกจากนั้นลงใน heap ด้วย Inner class object สิ่งที่จาวาทำสำหรับตัวแปรขั้นสุดท้ายเท่านั้นเพราะพวกเขาไม่ได้เป็นตัวแปรจริง ๆ พวกเขาเป็นเหมือนค่าคงที่ (ทุกอย่างเกิดขึ้นในเวลารวบรวมเท่านั้น) ไม่ได้อยู่ที่รันไทม์)


-1

เพื่อแก้ปัญหาข้างต้นภาษาที่แตกต่างกันทำการตัดสินใจแตกต่างกัน

สำหรับ Java การแก้ปัญหาเป็นสิ่งที่เราเห็นในบทความนี้

สำหรับ C # วิธีแก้ปัญหาคืออนุญาตให้มีผลข้างเคียงและการดักจับโดยการอ้างอิงเป็นตัวเลือกเดียว

สำหรับ C ++ 11 การแก้ปัญหาคืออนุญาตให้โปรแกรมเมอร์ทำการตัดสินใจ พวกเขาสามารถเลือกที่จะจับตามมูลค่าหรือโดยการอ้างอิง หากจับตามค่าจะไม่มีผลข้างเคียงเกิดขึ้นเพราะตัวแปรที่อ้างอิงนั้นแตกต่างกันจริง หากจับภาพโดยอ้างอิงผลข้างเคียงอาจเกิดขึ้น แต่โปรแกรมเมอร์ควรตระหนักถึงมัน


-2

เพราะมันทำให้เกิดความสับสนหากตัวแปรไม่เป็นที่สิ้นสุดเนื่องจากการเปลี่ยนแปลงจะไม่ได้รับในคลาสที่ไม่ระบุชื่อ

เพียงทำให้ตัวแปร 'ราคา' และ 'lastPrice' สุดท้าย

- แก้ไข

อ๊ะและคุณจะต้องไม่กำหนดให้กับพวกเขาอย่างเห็นได้ชัดในการทำงานของคุณ คุณจะต้องมีตัวแปรท้องถิ่นใหม่ อย่างไรก็ตามฉันสงสัยว่ามีคนให้คำตอบที่ดีกว่าในตอนนี้


2
มันไม่เพียง แต่ทำให้สับสน - มันไม่ถูกต้องทีเดียวดังนั้นคอมไพเลอร์จึงไม่อนุญาต
Chii

แต่แล้วฉันจะเปลี่ยนค่าได้อย่างไรเมื่อต้องการ?
Ankur

ไม่ใช่เพียงเพราะมันทำให้เกิดความสับสน นี่เป็นเพราะ Java ไม่รองรับการปิด ดูคำตอบของฉันด้านล่าง @Ankur: คุณสามารถสร้างตัวแปรสมาชิกตัวแปรของวัตถุคลาสนิรนามแทนตัวแปรท้องถิ่นใน main ()
Jesper

เขากำลังปรับเปลี่ยนพวกเขาดังนั้นพวกเขาจึงไม่สามารถสุดท้ายได้
Robin

หากราคาและราคาสุดท้ายเป็นที่สิ้นสุดการมอบหมายให้พวกเขาจะไม่รวบรวม
เกร็ก Mattes
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.