Perbedaan antara final dan final efektif

351

Saya bermain dengan lambdas di Java 8 dan saya mendapat peringatan local variables referenced from a lambda expression must be final or effectively final. Saya tahu bahwa ketika saya menggunakan variabel di dalam kelas anonim mereka harus final di kelas luar, tapi tetap saja - apa perbedaan antara final dan final efektif ?

alex
sumber
2
Banyak jawaban, namun semua pada dasarnya berjumlah "tidak ada perbedaan." Tetapi apakah itu benar? Sayangnya, saya sepertinya tidak dapat menemukan Spesifikasi Bahasa untuk Java 8.
Aleksandr Dubinsky
3
@AleksandrDubinsky docs.oracle.com/javase/specs
eis
@AlexandrDubinsky tidak "benar-benar" benar. Saya menemukan satu pengecualian untuk aturan ini. Variabel lokal diinisialisasi dengan konstanta bukan ekspresi konstan ke kompiler. Anda tidak dapat menggunakan variabel seperti itu untuk kasing / kasing hingga Anda menambahkan kata kunci akhir secara eksplisit. Misalnya "int k = 1; aktifkan (someInt) {case k: ...".
Henno Vermeulen

Jawaban:

234

... dimulai pada Java SE 8, kelas lokal dapat mengakses variabel dan parameter lokal dari blok penutup yang final atau efektif final. Variabel atau parameter yang nilainya tidak pernah berubah setelah diinisialisasi efektif secara final.

Misalnya, anggap bahwa variabel numberLengthtidak dinyatakan final, dan Anda menambahkan pernyataan penugasan yang ditandai di PhoneNumberkonstruktor:

public class OutterClass {  

  int numberLength; // <== not *final*

  class PhoneNumber {

    PhoneNumber(String phoneNumber) {
        numberLength = 7;   // <== assignment to numberLength
        String currentNumber = phoneNumber.replaceAll(
            regularExpression, "");
        if (currentNumber.length() == numberLength)
            formattedPhoneNumber = currentNumber;
        else
            formattedPhoneNumber = null;
     }

  ...

  }

...

}

Karena pernyataan penugasan ini, variabel numberLength tidak lagi final secara efektif. Akibatnya, kompiler Java menghasilkan pesan kesalahan yang mirip dengan "variabel lokal yang dirujuk dari kelas dalam harus final atau efektif final" di mana kelas dalam PhoneNumber mencoba mengakses variabel numberLength:

http://codeinventions.blogspot.in/2014/07/difference-between-final-and.html

http://docs.oracle.com/javase/tutorial/java/javaOO/localclasses.html

Suresh Atta
sumber
68
+1 Catatan: jika referensi tidak diubah secara efektif final bahkan jika objek yang direferensikan diubah.
Peter Lawrey
1
@stanleyerror Ini mungkin bisa membantu: stackoverflow.com/questions/4732544/…
1
Saya pikir lebih berguna daripada contoh tidak efektif akhir, adalah contoh ketika sesuatu adalah efektif final. Padahal deskripsinya membuatnya jelas. Var tidak perlu dinyatakan final jika tidak ada kode yang mengubah nilainya.
Skychan
1
Contohnya salah. Kode ini mengkompilasi dengan sempurna (tanpa titik, tentu saja). Untuk mendapatkan kesalahan kompiler, kode ini harus ada di dalam beberapa metode sehingga numberLengthmenjadi variabel lokal dari metode ini.
mykola
1
Apakah ada alasan mengapa contoh ini sangat rumit? Mengapa sebagian besar kode berurusan dengan operasi regex yang sama sekali tidak relevan? Dan, seperti yang sudah @mykola katakan, itu benar-benar kehilangan tanda mengenai properti akhir yang efektif , karena itu hanya relevan dengan variabel lokal dan tidak ada variabel lokal dalam contoh ini.
Holger
131

Saya menemukan cara paling sederhana untuk menjelaskan "efektif akhir" adalah membayangkan menambahkan finalpengubah ke deklarasi variabel. Jika, dengan perubahan ini, program terus berperilaku dengan cara yang sama, baik pada waktu kompilasi maupun pada saat run time, maka variabel tersebut secara efektif final.

Maurice Naftalin
sumber
4
Ini benar, selama pemahaman "final" java 8 dipahami dengan baik. Kalau tidak, saya akan melihat variabel tidak dinyatakan final bahwa Anda membuat tugas untuk nanti, dan keliru berpikir itu bukan final. Anda mungkin mengatakan "tentu saja" ... tetapi tidak semua orang memperhatikan perubahan versi bahasa terbaru sebanyak yang seharusnya.
fool4jesus
8
Satu pengecualian untuk aturan ini adalah bahwa variabel lokal diinisialisasi dengan konstanta bukan ekspresi konstan ke kompiler. Anda tidak dapat menggunakan variabel seperti itu untuk kasing / kasing hingga Anda menambahkan kata kunci akhir secara eksplisit. Misalnya "int k = 1; aktifkan (someInt) {case k: ...".
Henno Vermeulen
2
@HennoVermeulen switch-case bukan pengecualian dari aturan dalam jawaban ini. Bahasa menentukan yang case kmemerlukan ekspresi konstan yang bisa menjadi variabel konstan ("Variabel konstan adalah variabel akhir dari tipe primitif atau tipe String yang diinisialisasi dengan ekspresi konstan" JLS 4.12.4 ) yang merupakan kasus khusus dari final variabel.
Colin D Bennett
3
Dalam contoh saya kompiler mengeluh bahwa k bukan ekspresi konstan sehingga tidak dapat digunakan untuk switch. Saat menambahkan final, perilaku kompilasi berubah karena sekarang merupakan variabel konstan dan dapat digunakan di sakelar. Jadi Anda benar: aturannya masih benar. Ini sama sekali tidak berlaku untuk contoh ini dan tidak mengatakan apakah k efektif atau tidak.
Henno Vermeulen
36

Menurut dokumen :

Variabel atau parameter yang nilainya tidak pernah berubah setelah diinisialisasi efektif secara final.

Pada dasarnya, jika kompiler menemukan suatu variabel tidak muncul dalam tugas di luar inisialisasi, maka variabel tersebut dianggap efektif akhir .

Sebagai contoh, pertimbangkan beberapa kelas:

public class Foo {

    public void baz(int bar) {
        // While the next line is commented, bar is effectively final
        // and while it is uncommented, the assignment means it is not
        // effectively final.

        // bar = 2;
    }
}
Mark Elliot
sumber
Dokumen berbicara tentang variabel lokal. bardalam contoh Anda bukan variabel lokal, tetapi bidang. "Secara efektif final" dalam pesan kesalahan seperti di atas tidak berlaku untuk semua bidang.
Antti Haapala
6
@AnttiHaapala baradalah parameter di sini, bukan bidang.
peter.petrov
30

'Final efektif' adalah variabel yang tidak akan memberikan kesalahan kompilator jika itu akan ditambahkan oleh 'final'

Dari sebuah artikel oleh 'Brian Goetz',

Secara informal, variabel lokal efektif final jika nilai awalnya tidak pernah berubah - dengan kata lain, menyatakannya final tidak akan menyebabkan kegagalan kompilasi.

lambda-state-final- Brian Goetz

Ajeet Ganga
sumber
2
jawaban ini ditampilkan sebagai kutipan, namun tidak ada teks yang persis seperti itu dalam artikel Brian, pastinya bukan kata yang ditambahkan . Ini adalah kutipan sebagai gantinya: Secara informal, variabel lokal efektif final jika nilai awalnya tidak pernah berubah - dengan kata lain, menyatakannya final tidak akan menyebabkan kegagalan kompilasi.
lcfd
Dari artikel salin kata demi kata: Secara informal, variabel lokal secara final bersifat efektif jika nilai awalnya tidak pernah berubah - dengan kata lain, menyatakannya final tidak akan menyebabkan kegagalan kompilasi.
Ajeet Ganga
26

Variabel di bawah ini final , jadi kami tidak dapat mengubah nilainya setelah diinisialisasi. Jika kami mencoba, kami akan mendapatkan kesalahan kompilasi ...

final int variable = 123;

Tetapi jika kita membuat variabel seperti ini, kita dapat mengubah nilainya ...

int variable = 123;
variable = 456;

Namun di Java 8 , semua variabel bersifat final secara default. Tetapi keberadaan baris ke-2 dalam kode membuatnya tidak final . Jadi jika kita menghapus baris ke-2 dari kode di atas, variabel kita sekarang "efektif akhir" ...

int variable = 123;

Jadi .. Setiap variabel yang ditugaskan satu kali dan hanya sekali, adalah "efektif akhir" .

Eurig Jones
sumber
Sesederhana seharusnya jawabannya.
superigno
@ Eurig, Kutipan diperlukan untuk "semua variabel adalah final secara default".
Pacerier
10

Variabel adalah final atau efektif final ketika diinisialisasi sekali dan tidak pernah bermutasi di kelas pemiliknya. Dan kita tidak dapat menginisialisasi dalam loop atau kelas dalam .

Final :

final int number;
number = 23;

Final Efektif :

int number;
number = 34;

Catatan : Final dan Efektif Final adalah serupa (Nilainya tidak berubah setelah penugasan) tetapi hanya saja, variabel Final yang efektif tidak dideklarasikan dengan Kata Kunci final.

samadadi
sumber
7

Ketika ekspresi lambda menggunakan variabel lokal yang ditugaskan dari ruang terlampirnya ada batasan penting. Ekspresi lambda hanya dapat menggunakan variabel lokal yang nilainya tidak berubah. Pembatasan itu disebut sebagai " penangkapan variabel " yang digambarkan sebagai; nilai penangkapan ekspresi lambda, bukan variabel .
Variabel lokal yang menggunakan ekspresi lambda dikenal sebagai " efektif akhir ".
Variabel akhir yang efektif adalah variabel yang nilainya tidak berubah setelah ditetapkan pertama kali. Tidak perlu secara eksplisit mendeklarasikan variabel seperti itu sebagai final, meskipun hal itu tidak akan menjadi kesalahan.
Mari kita lihat dengan sebuah contoh, kita memiliki variabel lokal i yang diinisialisasi dengan nilai 7, dengan ekspresi lambda kita mencoba mengubah nilai itu dengan menetapkan nilai baru ke i. Ini akan menghasilkan kesalahan kompiler - " Variabel lokal yang saya definisikan dalam cakupan terlampir harus final atau efektif final "

@FunctionalInterface
interface IFuncInt {
    int func(int num1, int num2);
    public String toString();
}

public class LambdaVarDemo {

    public static void main(String[] args){             
        int i = 7;
        IFuncInt funcInt = (num1, num2) -> {
            i = num1 + num2;
            return i;
        };
    }   
}
infoj
sumber
2

Topik terakhir yang efektif dijelaskan dalam JLS 4.12.4 dan paragraf terakhir berisi penjelasan yang jelas:

Jika suatu variabel final secara efektif, menambahkan pengubah akhir ke deklarasi tidak akan memperkenalkan kesalahan waktu kompilasi. Sebaliknya, variabel lokal atau parameter yang dinyatakan final dalam program yang valid menjadi final efektif jika pengubah akhir dihapus.

Novdar
sumber
2

final adalah variabel yang mendeklarasikan dengan kata kunci final, contoh:

final double pi = 3.14 ;

tetap finalmelalui program.

final efektif : setiap variabel atau parameter lokal yang diberi nilai hanya sekali sekarang (atau diperbarui hanya sekali). Ini mungkin tidak tetap final secara efektif melalui program. jadi ini berarti bahwa variabel final efektif mungkin kehilangan properti final efektif setelah segera saat ia ditugaskan / diperbarui setidaknya satu tugas lagi. contoh:

class EffectivelyFinal {

    public static void main(String[] args) {
        calculate(124,53);
    }

    public static void calculate( int operand1, int operand2){   
     int rem = 0;  //   operand1, operand2 and rem are effectively final here
     rem = operand1%2  // rem lost its effectively final property here because it gets its second assignment 
                       // operand1, operand2 are still effectively final here 
        class operators{

            void setNum(){
                operand1 =   operand2%2;  // operand1 lost its effectively final property here because it gets its second assignment
            }

            int add(){
                return rem + operand2;  // does not compile because rem is not effectively final
            }
            int multiply(){
                return rem * operand1;  // does not compile because both rem and operand1 are not effectively final
            }
        }   
   }    
}
Metode Ilmiah
sumber
Ini tidak benar menurut Spesifikasi Bahasa Jawa: " Setiap kali itu terjadi sebagai sisi kiri dalam ekspresi penugasan, itu pasti tidak ditugaskan dan tidak jelas ditugaskan sebelum penugasan." Variabel / parameter selalu final atau tidak pernah efektif. Secara lebih eksplisit, jika Anda tidak dapat menambahkan finalkata kunci ke deklarasi tanpa memasukkan kesalahan kompilasi, maka itu tidak final secara efektif . Ini adalah kontrapositif dari pernyataan ini: "Jika suatu variabel secara efektif final, menambahkan pengubah akhir ke deklarasi tidak akan menimbulkan kesalahan waktu kompilasi."
AndrewF
Komentar dalam kode contoh salah karena semua alasan yang dijelaskan dalam komentar saya. "Secara efektif final" bukanlah keadaan yang dapat berubah seiring waktu.
AndrewF
@AndrewF jika tidak berubah dari waktu ke waktu, menurut Anda apa baris terakhir tidak dikompilasi? rem secara efektif final pada baris 1 dalam metode penghitungan. Namun, pada baris terakhir, kompiler mengeluh bahwa rem tidak efektif final
The Scientific Method
Anda benar bahwa beberapa kode perlu dihapus dari blok kode Anda untuk dikompilasi, tetapi itu tidak mencerminkan perilaku runtime. Pada waktu kompilasi, Anda dapat memutuskan apakah suatu variabel final efektif atau tidak - berdasarkan spesifikasi, itu selalu final efektif, atau tidak pernah final efektif. Kompilator dapat mengetahui dengan melihat secara statis bagaimana variabel digunakan di seluruh cakupannya. Properti tidak dapat diperoleh atau hilang saat program berjalan. Istilah ini didefinisikan dengan baik oleh spesifikasi - periksa jawaban lain, yang menjelaskannya dengan cukup baik.
AndrewF
1
public class LambdaScopeTest {
    public int x = 0;        
    class FirstLevel {
        public int x = 1;    
        void methodInFirstLevel(int x) {

            // The following statement causes the compiler to generate
            // the error "local variables referenced from a lambda expression
            // must be final or effectively final" in statement A:
            //
            // x = 99; 

        }
    }    
}

Seperti yang dikatakan orang lain, variabel atau parameter yang nilainya tidak pernah berubah setelah diinisialisasi secara efektif bersifat final. Dalam kode di atas, jika Anda mengubah nilai xdi kelas dalam FirstLevelmaka kompiler akan memberi Anda pesan kesalahan:

Variabel lokal yang dirujuk dari ekspresi lambda harus final atau efektif final.

Girish
sumber
1

Jika Anda bisa menambahkan finalpengubah ke variabel lokal, itu efektif final.

Ekspresi Lambda dapat mengakses

  • variabel statis,

  • variabel instan,

  • parameter metode akhir yang efektif, dan

  • variabel lokal akhir yang efektif.

Sumber: OCP: Panduan Studi II Programmer II Java Certified Professional Java SE, Jeanne Boyarsky, Scott Selikoff

Selain itu,

Sebuah effectively finalvariabel adalah variabel yang nilainya tidak pernah berubah, tetapi tidak dideklarasikan dengan finalkata kunci.

Sumber: Memulai dengan Java: Dari Struktur Kontrol melalui Objek (Edisi 6), Tony Gaddis

Selain itu, jangan lupa arti finalbahwa itu diinisialisasi tepat sekali sebelum digunakan untuk pertama kalinya.

snr
sumber
0

Mendeklarasikan suatu variabel finalatau tidak mendeklarasikannya final, tetapi menjaganya agar tetap final dapat menghasilkan (tergantung pada kompiler) dalam bytecode yang berbeda.

Mari kita lihat contoh kecil:

    public static void main(String[] args) {
        final boolean i = true;   // 6  // final by declaration
        boolean j = true;         // 7  // effectively final

        if (i) {                  // 9
            System.out.println(i);// 10
        }
        if (!i) {                 // 12
            System.out.println(i);// 13
        }
        if (j) {                  // 15
            System.out.println(j);// 16
        }
        if (!j) {                 // 18
            System.out.println(j);// 19
        }
    }

Yang sesuai bytecode dari mainmetode (Java 8u161 pada Windows 64 Bit):

  public static void main(java.lang.String[]);
    Code:
       0: iconst_1
       1: istore_1
       2: iconst_1
       3: istore_2
       4: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
       7: iconst_1
       8: invokevirtual #22                 // Method java/io/PrintStream.println:(Z)V
      11: iload_2
      12: ifeq          22
      15: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
      18: iload_2
      19: invokevirtual #22                 // Method java/io/PrintStream.println:(Z)V
      22: iload_2
      23: ifne          33
      26: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
      29: iload_2
      30: invokevirtual #22                 // Method java/io/PrintStream.println:(Z)V
      33: return

Tabel nomor baris yang sesuai:

 LineNumberTable:
   line 6: 0
   line 7: 2
   line 10: 4
   line 15: 11
   line 16: 15
   line 18: 22
   line 19: 26
   line 21: 33

Seperti yang kita lihat source code di garis 12, 13, 14tidak muncul dalam kode byte. Itu karena iini truedan tidak akan mengubah keadaan itu. Dengan demikian kode ini tidak dapat dijangkau (lebih banyak dalam jawaban ini ). Untuk alasan yang sama, kode pada baris 9juga tidak ada. Keadaan itidak harus dievaluasi karena truepasti.

Di sisi lain meskipun variabel jadalah efektif akhir itu tidak diproses dengan cara yang sama. Tidak ada optimasi yang diterapkan. Keadaan jdievaluasi dua kali. Bytecode adalah sama terlepas dari jmenjadi efektif akhir .

LuCio
sumber
Saya akan menganggap ini sebagai inefisiensi kompiler, dan belum tentu salah satu yang masih benar dalam kompiler baru. Dalam kompilasi yang sempurna, jika suatu variabel secara final efektif maka akan menghasilkan semua optimasi yang sama persis dengan yang dinyatakan final. Jadi jangan mengandalkan gagasan bahwa final yang efektif secara otomatis lebih lambat daripada menyatakan sesuatu yang final.
AndrewF
@AndrewF Secara umum Anda benar, perilaku dapat berubah. Itu sebabnya saya menulis " mungkin menghasilkan (tergantung pada kompiler) dalam bytecode yang berbeda ". Hanya karena optimasi yang hilang (bytecode berbeda) saya tidak akan menganggap eksekusi lebih lambat. Tetapi masih ada perbedaan dalam kasus yang ditunjukkan.
LuCio
0

Variabel terakhir yang efektif adalah variabel lokal yaitu:

  1. Tidak didefinisikan sebagai final
  2. Ditugaskan HANYA satu kali.

Sedangkan variabel terakhir adalah variabel yaitu:

  1. dideklarasikan dengan finalkata kunci.
Jimmy_Rw
sumber
-6

Namun, mulai di Java SE 8, kelas lokal dapat mengakses variabel dan parameter lokal dari blok penutup yang final atau efektif final.

Ini tidak dimulai pada Java 8, saya menggunakan ini sejak lama. Kode ini digunakan (sebelum java 8) legal:

String str = ""; //<-- not accesible from anonymous classes implementation
final String strFin = ""; //<-- accesible 
button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
         String ann = str; // <---- error, must be final (IDE's gives the hint);
         String ann = strFin; // <---- legal;
         String str = "legal statement on java 7,"
                +"Java 8 doesn't allow this, it thinks that I'm trying to use the str declared before the anonymous impl."; 
         //we are forced to use another name than str
    }
);
FiruzzZ
sumber
2
Pernyataan tersebut merujuk pada fakta bahwa di <Java 8, hanya final variabel yang dapat diakses, tetapi di Java 8 juga variabel yang final secara efektif .
Antti Haapala
Saya hanya melihat kode yang tidak berfungsi, terlepas dari apakah Anda menggunakan Java 7 atau Java 8.
Holger