Final ile efektif final arasındaki fark


351

Java 8'de lambdaslarla oynuyorum ve uyarı ile karşılaştım local variables referenced from a lambda expression must be final or effectively final. Anonim sınıf içindeki değişkenleri kullandığımda bunların dış sınıfta final olması gerektiğini biliyorum, ama yine de - final ile etkili final arasındaki fark nedir?


2
Birçok cevap, ama hepsi aslında "fark yok" anlamına gelir. Ama bu gerçekten doğrumu? Ne yazık ki, Java 8 için bir Dil Spesifikasyonu bulamıyorum
Aleksandr Dubinsky

3
@AleksandrDubinsky docs.oracle.com/javase/specs
eis

@AleksandrDubinsky "gerçekten" doğru değil. Bu kurala bir istisna buldum. Sabit ile başlatılan bir yerel değişken derleyiciye sabit bir ifade değildir. Son anahtar kelimeyi açıkça ekleyene kadar, anahtar / vakadaki bir vaka için böyle bir değişkeni kullanamazsınız. Örneğin "int k = 1; switch (someInt) {case k: ...".
Henno Vermeulen

Yanıtlar:


234

... Java SE 8'den başlayarak, yerel bir sınıf, son veya etkin bir şekilde nihai olan kapalı bloğun yerel değişkenlerine ve parametrelerine erişebilir. Başlatıldıktan sonra değeri hiçbir zaman değiştirilmeyen bir değişken veya parametre etkili bir şekilde kesindir.

Örneğin, değişkenin numberLengthnihai olarak bildirilmediğini ve işaretli atama deyimini yapıcıya eklediğinizi varsayalım PhoneNumber:

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

  ...

  }

...

}

Bu atama deyimi nedeniyle, numberLength değişkeni artık etkin bir şekilde son değil. Sonuç olarak, Java derleyicisi , iç sınıf PhoneNumber numberLength değişkenine erişmeye çalıştığında "iç sınıftan başvuruda bulunulan yerel değişkenler son ya da etkin bir şekilde son olmalıdır" benzeri bir hata iletisi oluşturur :

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

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


68
+1 Not: bir başvuru değiştirilmezse, başvurulan nesne değiştirilse bile etkin olarak son halini alır.
Peter Lawrey

1
@stanleyerror Bu biraz yardımcı olabilir: stackoverflow.com/questions/4732544/…

1
Ben bir örnek daha yararlı düşünmek değil etkin bir nihai şey olduğunda bir örnektir olduğunu etkili bir şekilde nihai. Yine de açıklama bunu açıkça ortaya koymaktadır. Herhangi bir kodun değeri değişmezse, var'ın son olarak bildirilmesine gerek yoktur.
Skychan

1
Örnek yanlış. Bu kod mükemmel bir şekilde derlenir (elbette noktalar olmadan). Derleyici hatası almak için bu kod bazı yöntemin içinde olmalıdır, böylece numberLengthbu yöntemin yerel değişkeni olur.
mykola

1
Bu örneğin bu kadar karmaşık olmasının bir nedeni var mı? Kodun çoğunluğu neden tamamen alakasız bir normal ifade işlemiyle uğraşıyor? Ve @mykola'nın daha önce de söylediği gibi, sadece yerel değişkenlerle ilgili olduğu ve bu örnekte yerel bir değişken olmadığı için , etkili nihai mülkle ilgili işareti tamamen eksik .
Holger

131

Ben "etkili nihai" açıklamak için en basit yolu finaldeğiştirici değişken bir bildirime eklemek hayal olduğunu bulmak . Bu değişiklikle birlikte, program hem derleme zamanında hem de çalışma zamanında aynı şekilde davranmaya devam ederse, bu değişken etkili bir şekilde kesindir.


4
Java 8'in "nihai" anlayışı iyi anlaşıldığı sürece bu doğrudur. Aksi takdirde, daha sonra atama yaptığınızı kesin olarak beyan etmeyen bir değişkene bakarım ve bunun nihai olmadığını düşünürüm. "Elbette" diyebilirsiniz ... ama herkes en son dil sürümü değişikliklerine olması gerektiği kadar dikkat etmiyor.
fool4jesus

8
Bu kuralın bir istisnası, sabit ile başlatılan bir yerel değişkenin derleyiciye sabit bir ifade olmamasıdır. Son anahtar kelimeyi açıkça ekleyene kadar, bir anahtar / vakadaki bir vaka için böyle bir değişkeni kullanamazsınız. Örneğin "int k = 1; switch (someInt) {case k: ...".
Henno Vermeulen

2
@HennoVermeulen anahtar durumu, bu yanıttaki kural için bir istisna değildir. Dil , sabit bir değişken olabilen sabit bir ifadecase k gerektirdiğini belirtir ("Sabit değişken, ilkel türün veya sabit bir ifadeyle başlatılan String" in son bir değişkeni " JLS 4.12.4 ). değişken.
Colin D Bennett

3
Örneğimde derleyici k'nin sabit bir ifade olmadığından, anahtar için kullanılamayacağından şikayet ediyor. Final eklenirken derleme davranışı artık sabit bir değişken olduğu ve anahtarda kullanılabileceği için değişir. Yani haklısın: kural hala doğru. Sadece bu örnek için geçerli değildir ve k'nın etkili bir şekilde nihai olup olmadığını söylemez.
Henno Vermeulen

36

Dokümanlara göre :

Başlatıldıktan sonra değeri hiçbir zaman değiştirilmeyen bir değişken veya parametre etkili bir şekilde kesindir.

Temel olarak, derleyici bir değişkenin başlatılmasının dışındaki atamalarda görünmediğini fark ederse, değişken etkili bir şekilde son olarak kabul edilir .

Örneğin, bir sınıf düşünün:

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

Dokümanlar yerel değişkenler hakkında konuşur. barÖrneğin yerel bir değişken değil, bir alandır. Yukarıdaki gibi hata iletisindeki "etkin son" alanları hiç geçerli değildir.
Antti Haapala

6
@AnttiHaapala barburada bir parametredir, bir alan değil.
peter.petrov

30

'Etkili final', 'final' eklenecekse derleyici hatası vermeyecek bir değişkendir

'Brian Goetz' tarafından yazılan bir makaleden,

Gayri resmi olarak, bir yerel değişken, başlangıç ​​değeri asla değişmezse etkili bir şekilde kesindir - başka bir deyişle, nihai olarak bildirilmesi derleme hatasına neden olmaz.

lambda-state-final- Brian Goetz


2
bu cevap bir alıntı olarak gösterilir, ancak Brian'ın makalesinde kesin bir metin yoktur, kesinlikle eklenen kelime değildir . Bu bir alıntıdır: Gayri resmi olarak, yerel bir değişken, başlangıç ​​değeri asla değişmezse etkili bir şekilde kesindir - başka bir deyişle, son olarak bildirilmesi derleme hatasına neden olmaz.
lcfd

Makalenin kelimesi kelimesine kopyasından: Gayri resmi olarak, yerel bir değişken, başlangıç ​​değeri asla değişmezse etkili bir şekilde kesindir - başka bir deyişle, son olarak bildirilmesi derleme hatasına neden olmaz.
Ajeet Ganga

26

Aşağıdaki bu değişken kesindir , bu nedenle başlatıldıktan sonra değerini değiştiremeyiz. Eğer denersek bir derleme hatası alırız ...

final int variable = 123;

Ancak böyle bir değişken yaratırsak, değerini değiştirebiliriz ...

int variable = 123;
variable = 456;

Ama içinde Java 8 , bütün değişkenler nihai varsayılan olarak. Ancak koddaki 2. satırın varlığı onu sonlandırmaz . Yukarıdaki koddan 2. satırı kaldırırsak, değişkenimiz şimdi "etkin bir şekilde son" olur ...

int variable = 123;

Yani .. Bir kez ve sadece bir kez atanan herhangi bir değişken "etkin bir şekilde nihai" dir .


Cevap kadar basit olmalı.
superigno

@Eurig, "Tüm değişkenler varsayılan olarak kesindir" için gerekli alıntı.
Pacerier

10

Bir değişken, bir kez başlatıldığında ve sahip sınıfında hiçbir zaman mutasyona uğratılmadığında nihai veya etkili bir şekilde nihai olur . Ve bunu döngülerde veya iç sınıflarda başlatamayız .

Final :

final int number;
number = 23;

Etkili Final :

int number;
number = 34;

Not : Nihai ve Etkili Nihai benzerdir (atama sonrasında değerleri değişmez), ancak sadece etkili Nihai değişkenler Anahtar Kelime ile bildirilmez final.


7

Bir lambda ifadesi, kapalı alanından atanmış bir yerel değişken kullandığında önemli bir kısıtlama vardır. Lambda ifadesi yalnızca değeri değişmeyen yerel değişkeni kullanabilir. Bu kısıtlamaya " değişken yakalama " denir ; lambda ifadesi değişkenleri değil değerleri yakalar .
Bir lambda ifadesinin kullanabileceği yerel değişkenler " etkin bir şekilde nihai " olarak bilinir .
Etkili bir son değişken, değeri ilk atandıktan sonra değişmeyen değişkendir. Böyle bir değişkeni kesin olarak kesin olarak bildirmeye gerek yoktur, ancak bunu yapmak bir hata olmaz.
Bir örnekle görelim, 7 değeriyle başlatılmış yerel bir i değişkenimiz var, lambda ifadesinde i'ye yeni bir değer atayarak bu değeri değiştirmeye çalışıyoruz. Bu derleyici hatası ile sonuçlanır - " Kapalı bir kapsamda tanımlanan yerel değişken i son veya etkin bir şekilde son olmalıdır "

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

2

Etkili nihai konu JLS 4.12.4'te açıklanmıştır ve son paragraf açık bir açıklama içermektedir:

Bir değişken etkili bir şekilde nihai ise, son değiştiriciyi bildirimine eklemek derleme zamanı hataları oluşturmaz. Buna karşılık, geçerli bir programda nihai olarak bildirilen bir yerel değişken veya parametre, son değiştirici kaldırıldığında etkili bir şekilde son haline gelir.


2

final , anahtar kelimeye sahip değişken bir deklarasyondur final, örnek:

final double pi = 3.14 ;

finalprogramın dışında kalır .

etkin bir şekilde son : şu anda yalnızca bir kez değer atanan (veya yalnızca bir kez güncellenen) herhangi bir yerel değişken veya parametre. Program boyunca etkili bir şekilde nihai kalamayabilir . bu da etkili bir şekilde son değişkenin, en az bir ödev daha atandığında / güncellendikten hemen sonra etkin bir şekilde nihai özelliğini kaybedebileceği anlamına gelir . misal:

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

Bu Java Dil Şartnamede göre yanlıştır: " Her ne zaman bir atama ifadesinde sol tarafta olarak ortaya çıkar, kesinlikle atanmamış ve kesinlikle göreve kadar atanmış değil." Bir değişken / parametre ya her zaman ya da asla etkili bir şekilde nihai değildir. Daha açık bir ifadeyle, finalanahtar kelimeyi derleme hataları eklemeden bir bildirime ekleyemezseniz, etkili bir şekilde nihai değildir . Bu ifadenin çelişkilidir: "Eğer bir değişken etkili bir şekilde nihai ise, son değiştiriciyi bildirimine eklemek derleme zamanı hataları oluşturmaz."
AndrewF

Örnek koddaki yorumlar, yorumumda açıklanan tüm nedenlerden dolayı yanlış. "Etkili nihai", zaman içinde değişebilen bir durum değildir.
AndrewF

@AndrewF zaman içinde değişmezse, son satırın ne derlemediğini düşünüyorsunuz? rem hesaplama yönteminde 1. satırda etkili bir şekilde son halini aldı. Ancak, son satırda derleyici rem'in etkili bir şekilde nihai olmadığından şikayet ediyor
Bilimsel Yöntem

Derlemek için bazı kodların kod bloğunuzdan kaldırılması gerektiği doğrudur, ancak bu çalışma zamanı davranışını yansıtmaz. Derleme zamanında, bir değişkenin etkili bir şekilde nihai olup olmadığına karar verebilirsiniz - spesifikasyona göre, ya her zaman etkili bir şekilde nihai ya da asla etkili bir şekilde nihai değildir. Derleyici, değişkenin kapsamı boyunca nasıl kullanıldığına statik olarak bakarak söyleyebilir. Program çalışırken özellik kazanılamaz veya kaybedilemez. Terim spec tarafından iyi tanımlanmıştır - oldukça iyi açıklayan diğer cevaplara göz atın.
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; 

        }
    }    
}

Diğerlerinin söylediği gibi, değeri başlatıldıktan sonra hiçbir zaman değiştirilmeyen bir değişken veya parametre etkili bir şekilde kesindir. Yukarıdaki kodda, xiç sınıftaki değerini değiştirirseniz FirstLevelderleyici size hata mesajı verir:

Lambda ifadesinden referans alınan yerel değişkenler nihai veya etkili bir şekilde nihai olmalıdır.


1

finalDeğiştiriciyi yerel bir değişkene ekleyebildiyseniz , etkili bir şekilde son halini aldı.

Lambda ifadeleri erişebilir

  • statik değişkenler,

  • örnek değişkenler,

  • etkili bir şekilde nihai yöntem parametreleri ve

  • etkili nihai yerel değişkenler.

Kaynak: OCP: Oracle Sertifikalı Profesyonel Java SE 8 Programcı II Çalışma Kılavuzu, Jeanne Boyarsky, Scott Selikoff

Bunlara ek olarak,

Bir effectively finaldeğişken, değeri hiç değişmedi bir değişkendir, ancak bildirilmez finalanahtar kelime.

Kaynak: Java ile Başlamak: Kontrol Yapılarından Nesnelere (6. Baskı), Tony Gaddis

Ayrıca, finalilk kez kullanılmadan önce tam olarak bir kez başlatılmasının anlamını unutmayın .


0

Bir değişkeni bildirmek finalveya bildirmemek final, ancak etkin bir şekilde nihai tutmak farklı bir bayt koduyla sonuçlanabilir (derleyiciye bağlıdır).

Küçük bir örneğe bakalım:

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

mainYöntemin karşılık gelen bayt kodu ( Windows 64 Bit'te Java 8u161):

  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

İlgili satır numarası tablosu:

 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

Biz çizgilerine kaynak kodunu görmek üzere 12, 13, 14bayt kodu görünmüyor. Çünkü iolduğunu trueve 's durumunu değiştirmeyecektir. Böylece bu koda ulaşılamaz (bu cevapta daha fazla ). Aynı nedenden dolayı satırdaki kod da 9özlüyor. Kesin iolduğu trueiçin durumunun değerlendirilmesi gerekmez .

Değişken olsa Öte yandan jise etkili bir nihai aynı şekilde işlenir değil. Uygulanan böyle bir optimizasyon yoktur. Durumu jiki kez ölçüldü. Bayt kodu, etkin bir şekilde nihaij olmasına bakılmaksızın aynıdır .


Bunu bir derleyici verimsizliği olarak düşünürdüm ve yeni derleyicilerde hala geçerli olacak bir tane değil. Mükemmel bir derlemede, eğer bir değişken etkili bir şekilde nihai ise, beyan edilen bir final ile aynı optimizasyonları üretecektir. Bu yüzden, nihai finalin otomatik olarak nihai bir şey ilan etmekten daha yavaş olduğu fikrine güvenmeyin.
AndrewF

@AndrewF Genellikle haklısınız, davranış değişebilir. Bu yüzden " farklı bayt kodlarında sonuç verebilir (derleyiciye bağlıdır) " yazdım . Sadece eksik optimizasyon (farklı bayt kodu) nedeniyle yürütmenin daha yavaş olduğunu varsaymazdım. Ancak gösterilen durumda hala bir fark var.
LuCio

0

Etkili son değişken şu yerel bir değişkendir:

  1. Olarak tanımlanmadı final
  2. SADECE bir kez atandı.

Son değişken şu değişken:

  1. bir finalanahtar kelime ile bildirildi .

-6

Bununla birlikte, Java SE 8'den başlayarak, yerel bir sınıf, son veya etkin olarak nihai olan> kapalı bloğun yerel değişkenlerine ve parametrelerine erişebilir.

Bu Java 8'de başlamadı, bunu uzun zamandır kullanıyorum. Bu kod (java 8'den önce) yasal olarak kullanıldı:

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

2
Bu ifade, <Java 8'de yalnızca final değişkenlere erişilebildiğini, ancak Java 8'de de etkili bir şekilde nihai olanları ifade eder .
Antti Haapala

Java 7 veya Java 8 kullanmanıza bakılmaksızın, yalnızca çalışmayan kodu görüyorum.
Holger
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.