Anonim sınıfta neden yalnızca son değişkenlere erişilebilir?


355
  1. asadece burada nihai olabilir. Neden? Nasıl yeniden atayabilirsiniz aiçinde onClick()özel üye olarak tutmadan yöntemle?

    private void f(Button b, final int a){
        b.addClickHandler(new ClickHandler() {
    
            @Override
            public void onClick(ClickEvent event) {
                int b = a*5;
    
            }
        });
    }
  2. 5 * aTıklandığında nasıl geri dönebilirim ? Demek istediğim,

    private void f(Button b, final int a){
        b.addClickHandler(new ClickHandler() {
    
            @Override
            public void onClick(ClickEvent event) {
                 int b = a*5;
                 return b; // but return type is void 
            }
        });
    }

1
Java anonim sınıflarının beklediğiniz lambda kapatma türünü sağladığını sanmıyorum, ama biri yanılıyorsam lütfen beni düzeltin ...
user541686

4
Ne elde etmeye çalışıyorsunuz? "F" bittiğinde tıklama işleyici yürütülebilir.
Ivan Dubrov

Eğer onClick yönteminde bir kullanmak istiyorsanız nihai @Ivan olmak zorunda @Lambert nasıl teneke f () onClick gibi yöntem davranacağını () tıklandığında yöntem dönüş int

2
Demek istediğim bu - tam kapanmayı desteklemiyor çünkü nihai olmayan değişkenlere erişime izin vermiyor.
user541686

4
Not: Java 8'den itibaren değişkeninizin etkili bir şekilde nihai
Peter Lawrey

Yanıtlar:


489

Yorumlarda belirtildiği gibi, bunların bazıları finalörtük olabilen Java 8'de önemsiz hale gelir . Anonim bir iç sınıf veya lambda ifadesinde sadece etkili bir son değişken kullanılabilir.


Temel olarak Java'nın kapanışları yönetme biçimi nedeniyle .

Anonim bir iç sınıf örneği oluşturduğunuzda, o sınıfta kullanılan tüm değişkenlerin değerleri olur otomatik olarak oluşturulmuş kurucu aracılığıyla kopyalanır. Bu, derleyicinin, örneğin C # derleyicisinin yaptığı gibi, "yerel değişkenlerin" mantıksal durumunu tutmak için çeşitli ekstra türleri otomatik olarak oluşturmak zorunda kalmasını önler ... (C #, anonim bir işlevde bir değişkeni yakaladığında, gerçekten değişkeni yakalar - kapatma, değişkeni yöntemin ana gövdesi tarafından görülen bir şekilde güncelleyebilir (veya tam tersi.)

Değer, anonim iç sınıf örneğine kopyalandığından, değişkenin yöntemin geri kalanıyla değiştirilebilmesi garip görünecektir - eski bir değişkenle çalışıyor gibi görünen bir kodunuz olabilir ( bu etkili bir şekilde ne çünkü olurdu gerçekleşiyor ... Eğer farklı bir zamanda alınan bir kopyasını) çalışacaksın. Benzer şekilde, anonim iç sınıf içinde değişiklikler yapabiliyorsanız, geliştiriciler bu değişikliklerin ekteki yöntemde görünür olmasını bekleyebilirler.

Değişkenin sonlandırılması tüm bu olasılıkları ortadan kaldırır - değer hiç değiştirilemediğinden, bu değişikliklerin görünür olup olmayacağı konusunda endişelenmenize gerek yoktur. Yöntemin ve anonim iç sınıfın birbirlerinin değişikliklerini görmesine izin vermenin tek yolu, değiştirilebilir bir tür açıklama kullanmaktır. Bu, içine alan sınıfın kendisi, bir dizi, değiştirilebilir bir sarmalayıcı türü ... böyle bir şey olabilir. Temel olarak bir yöntemle diğeri arasında iletişim kurmak gibi: bir yöntemin parametrelerinde yapılan değişiklikler arayan tarafından görülmez, ancak parametrelerle atıfta bulunulan nesnelerde yapılan değişiklikler görülür.

Eğer Java ve C # kapanışları arasında daha ayrıntılı bir karşılaştırma ilgilenen ediyorsanız, bir var makale ayrıntılı bir şekilde yardımcı gider. Bu cevapta Java tarafına odaklanmak istedim :)


4
@Ivan: C # gibi, temelde. Yine de, farklı kapsamlardaki değişkenlerin farklı sayıda "somutlaştırılabileceği" C # ile aynı işlevsellik istiyorsanız, oldukça karmaşık bir derece ile birlikte gelir.
Jon Skeet


11
Tüm bunlar Java 7 için geçerliydi, Java 8 ile kapakların tanıtıldığını ve şimdi sınıfın nihai olmayan bir alanına iç sınıfından erişmenin mümkün olduğunu unutmayın.
Mathias Bader

22
@MathiasBader: Gerçekten mi? Hala aynı mekanizma olduğunu düşündüm, derleyici şimdi çıkarım yapmak için yeterince akıllı final(ama yine de etkili bir şekilde nihai olması gerekiyor).
Thilo

3
@Mathias Bader: Yerel değişkenlerle karıştırılmaması gereken, nihai olması gereken ve yine de etkili bir şekilde nihai olması gereken nihai olmayan alanlara her zaman erişebilirsiniz , bu nedenle Java 8 anlambilimi değiştirmez.
Holger

41

Anonim sınıfın dış kapsamdaki verileri güncellemesine izin veren bir hile var.

private void f(Button b, final int a) {
    final int[] res = new int[1];
    b.addClickHandler(new ClickHandler() {
        @Override
        public void onClick(ClickEvent event) {
            res[0] = a * 5;
        }
    });

    // But at this point handler is most likely not executed yet!
    // How should we now res[0] is ready?
}

Ancak, bu hile senkronizasyon sorunları nedeniyle çok iyi değil. İşleyici daha sonra çağrılırsa, 1) işleyici farklı iş parçacığından çağrılmışsa res erişimini senkronize etmeniz gerekir 2) res'in güncellenmiş bir tür bayrağı veya göstergesi olması gerekir

Anonim sınıf hemen aynı iş parçacığında çağrılırsa, bu hile Tamam çalışır. Sevmek:

// ...

final int[] res = new int[1];
Runnable r = new Runnable() { public void run() { res[0] = 123; } };
r.run();
System.out.println(res[0]);

// ...

2
Cevabınız için teşekkürler. Tüm bunları biliyorum ve benim çözümüm bundan daha iyi. sorum "neden sadece nihai"?

6
O zaman cevap şu şekilde uygulanmaktadır :)
Ivan Dubrov 19:11

1
Teşekkürler. Yukarıdaki numarayı kendi başıma kullanmıştım. İyi bir fikir olup olmadığından emin değildim. Java buna izin vermiyorsa, bunun iyi bir nedeni olabilir. Cevabınız kodumun List.forEachgüvenli olduğunu açıklığa kavuşturuyor .
RuntimeException

"Neden yalnızca nihai" ifadesinin ardındaki mantıkla ilgili iyi bir tartışma için stackoverflow.com/q/12830611/2073130 adresini okuyun .
LCN

birkaç geçici çözüm vardır. Benimki: son int resf = res; Başlangıçta dizi yaklaşımını kullandım, ama çok hantal bir sözdizimine sahip olduğunu düşünüyorum. AtomicReference belki biraz daha yavaştır (bir nesne ayırır).
zakmck

17

Anonim sınıf bir iç sınıftır ve katı kural iç sınıflar için geçerlidir (JLS 8.1.3) :

İç sınıfta bildirilen ancak kullanılan herhangi bir yerel değişken, biçimsel yöntem parametresi veya özel durum işleyici parametresi son olarak bildirilmelidir . Bir iç sınıfta kullanılan ancak bir sınıfta beyan edilmeyen herhangi bir yerel değişken, kesinlikle iç sınıfın gövdesinden önce atanmalıdır .

Henüz jls veya jvms üzerinde bir neden veya açıklama bulamadık, ama derler ki, derleyici her iç sınıf için ayrı bir sınıf dosyası oluşturur ve bu sınıf dosyasında ( bayt kodu düzeyinde) en azından yerel değişkenlerin değerlerine erişebilir.

( Jon'un tam cevabı var - Bunu silmeye devam ediyorum çünkü biri JLS kuralıyla ilgilenebilir)


11

Döndürülen değeri almak için sınıf düzeyinde bir değişken oluşturabilirsiniz. Demek istediğim

class A {
    int k = 0;
    private void f(Button b, int a){
        b.addClickHandler(new ClickHandler() {
        @Override
        public void onClick(ClickEvent event) {
            k = a * 5;
        }
    });
}

Artık K değerini alabilir ve istediğiniz yerde kullanabilirsiniz.

Nedeninizin cevabı:

Bir yerel iç sınıf örneği Ana sınıfa bağlıdır ve içerme yönteminin son yerel değişkenlerine erişebilir. Örnek, içerdiği yöntemin bir son yerelini kullandığında, değişken, kapsam dışına çıkmış olsa bile, değişken, örneğin oluşturulması sırasında tuttuğu değeri korur (bu, Java'nın kaba, sınırlı kapanma sürümüdür).

Yerel bir iç sınıf, bir sınıfın veya paketin üyesi olmadığından, bir erişim düzeyi olarak bildirilmez. (Ancak, kendi üyelerinin normal bir sınıftaki gibi erişim seviyelerine sahip oldukları açık olmalıdır.)


Ben "özel üye olarak tutmadan" bahsettim

6

Java'da, bir değişken sadece bir parametre olarak değil, aynı zamanda sınıf düzeyinde bir alan olarak da son olabilir

public class Test
{
 public final int a = 3;

veya yerel bir değişken olarak (ör.

public static void main(String[] args)
{
 final int a = 3;

Anonim bir sınıftan bir değişkene erişmek ve değiştirmek istiyorsanız, değişkeni içine alan sınıfta sınıf düzeyinde bir değişken yapmak isteyebilirsiniz .

public class Test
{
 public int a;
 public void doSomething()
 {
  Runnable runnable =
   new Runnable()
   {
    public void run()
    {
     System.out.println(a);
     a = a+1;
    }
   };
 }
}

Son olarak bir değişkeniniz olamaz ve ona yeni bir değer veremezsiniz . finalsadece şu anlama gelir: değer değişmez ve kesindir.

Son olduğu için Java, yerel anonim sınıflara güvenle kopyalayabilir . İnt'e bazı atıfta bulunmuyorsunuz (özellikle Java'daki int gibi ilkellere referansınız olmadığından, yalnızca Nesnelere yapılan referanslar ).

Sadece a'nın değeri üzerinden anonim sınıfınızda a adı verilen örtük bir int'e kopyalar.


3
"Sınıf düzeyi değişkeni" ile ilişkilendiriyorum static. Bunun yerine "örnek değişkeni" kullanmanız daha açık olabilir.
eljenso

1
teknik, hem örnek hem de statik değişkenlerle çalışacağı için sınıf düzeyinde kullandım.
Zach L

finalin erişilebilir olduğunu zaten biliyoruz ama nedenini bilmek istiyoruz? neden tarafında biraz daha açıklama ekleyebilir misiniz?
Saurabh Oza

6

Erişimin yalnızca yerel nihai değişkenlerle sınırlandırılmasının nedeni, tüm yerel değişkenlerin erişilebilir hale getirilmesi durumunda, önce iç sınıfların bunlara erişebileceği ve değişken yerel değişkenler tutarsız verilere yol açabilir. Oysa nihai değişkenler değiştirilemez ve bu nedenle bunlara herhangi bir sayıda kopya verinin tutarlılığı üzerinde herhangi bir etkisi olmayacaktır.


Bu özelliği destekleyen C # gibi dillerde bu şekilde uygulanmaz. Aslında, derleyici değişkeni yerel bir değişkenden bir örnek değişkeni olarak değiştirir veya bu değişkenler için dış sınıfın kapsamını aşabilecek ek bir veri yapısı oluşturur. Ancak, "yerel değişkenlerin birden fazla kopyası" yoktur
Mike76

Mike76 C # uygulamasına bir göz atmadım, ama Scala bahsettiğiniz ikinci şeyi düşünüyorum: Ben Intbir kapatma içinde yeniden atandıysa, bu değişkeni bir örneğe değiştirin IntRef(aslında değiştirilebilir bir Integersargı). Daha sonra her değişken erişim buna göre yeniden yazılır.
Adowrath

3

Bu kısıtlamanın gerekçesini anlamak için aşağıdaki programı göz önünde bulundurun:

public class Program {

    interface Interface {
        public void printInteger();
    }
    static Interface interfaceInstance = null;

    static void initialize(int val) {
        class Impl implements Interface {
            @Override
            public void printInteger() {
                System.out.println(val);
            }
        }
        interfaceInstance = new Impl();
    }

    public static void main(String[] args) {
        initialize(12345);
        interfaceInstance.printInteger();
    }
}

İnterfaceInstance sonra bellekte kalır initialize yöntemi dönüşleri, ancak parametre val değildir. JVM, kapsamı dışında bir yerel değişkene erişemez, bu nedenle Java , val değerini, interfaceInstance içinde aynı adın örtülü bir alanına kopyalayarak printInteger'e sonraki çağrıyı yapar . İnterfaceInstance söylenen yakalanan yerel parametrenin değerini. Parametre nihai değilse (veya etkili bir şekilde nihai değilse) değeri değişebilir ve yakalanan değerle senkronize olmayabilir ve potansiyel olarak sezgisel olmayan davranışlara neden olabilir.


2

Anonomiyöz bir iç sınıf içindeki yöntemler, ortaya çıkan ipliğin sona ermesinden sonra iyi bir şekilde çağrılabilir. Örneğinizde, iç sınıf olay gönderen iş parçacığında çağrılır ve onu oluşturan iş parçacığında çağrılmaz. Bu nedenle, değişkenlerin kapsamı farklı olacaktır. Bu nedenle, bu tür değişken atama kapsamı sorunlarını korumak için bunları nihai olarak bildirmeniz gerekir.


2

Bir yöntem gövdesi içinde anonim bir iç sınıf tanımlandığında, bu yöntem kapsamında nihai olarak bildirilen tüm değişkenlere iç sınıf içinden erişilebilir. Skaler değerler için, atandıktan sonra, son değişkenin değeri değiştirilemez. Nesne değerleri için başvuru değişemez. Bu, Java derleyicisinin çalışma zamanında değişkenin değerini "yakalamasına" ve bir kopyasını iç sınıfta alan olarak depolamasına olanak tanır. Dış yöntem sonlandırıldığında ve yığın çerçevesi kaldırıldıktan sonra, orijinal değişken gider, ancak iç sınıfın özel kopyası sınıfın kendi belleğinde kalır.

( http://en.wikipedia.org/wiki/Final_%28Java%29 )


1
private void f(Button b, final int a[]) {

    b.addClickHandler(new ClickHandler() {

        @Override
        public void onClick(ClickEvent event) {
            a[0] = a[0] * 5;

        }
    });
}

0

As Jon sahip uygulama detayları bir diğer muhtemel cevap JVM onun aktivasyonunu sona ermiş kayıtlarındaki yazma işlemek istemiyor olurdu cevap.

Lambdalarınızın uygulamak yerine bir yerde saklandığı ve daha sonra çalıştırıldığı kullanım durumunu düşünün.

Smalltalk'ta böyle bir değişiklik yaptığınızda yasadışı bir mağaza açacağınızı hatırlıyorum.


0

Bu kodu deneyin,

Dizi Listesi oluşturun ve değeri buna yerleştirin ve döndürün:

private ArrayList f(Button b, final int a)
{
    final ArrayList al = new ArrayList();
    b.addClickHandler(new ClickHandler() {

         @Override
        public void onClick(ClickEvent event) {
             int b = a*5;
             al.add(b);
        }
    });
    return al;
}

OP bir şeyin neden gerekli olduğuna dair nedenler istiyor. Bu nedenle, kodunuzun nasıl ele
aldığını

0

Java anonim sınıfı Javascript kapatılmasına çok benzer, ancak Java bunu farklı şekilde uygular. (Andersen'in cevabını kontrol edin)

Java Geliştiricisini Javascript arka planından gelenler için ortaya çıkabilecek tuhaf davranışlarla karıştırmamak için. Sanırım bizi bu yüzden kullanmaya zorluyorlar final, bu JVM sınırlaması değil.

Aşağıdaki Javascript örneğine bakalım:

var add = (function () {
  var counter = 0;

  var func = function () {
    console.log("counter now = " + counter);
    counter += 1; 
  };

  counter = 100; // line 1, this one need to be final in Java

  return func;

})();


add(); // this will print out 100 in Javascript but 0 in Java

Javascript'te, counterdeğer 100 olacaktır, çünkü sadece bir tane varcounter baştan sona değişken .

Ancak Java'da, yoksa final, yazdırılacaktır 0, çünkü iç nesne oluşturulurken, 0değer iç sınıf nesnesinin gizli özelliklerine kopyalanır. (burada biri yerel yöntemde, diğeri iç sınıf gizli özelliklerinde olmak üzere iki tamsayı değişkeni vardır)

Bu nedenle, iç nesne oluşturulduktan sonra herhangi bir değişiklik (satır 1 gibi), iç nesneyi etkilemez. Böylece iki farklı sonuç ve davranış arasında (Java ve Javascript arasında) karışıklık yaratacaktır.

Bu yüzden Java'nın nihai olmaya zorlamaya karar verdiğine inanıyorum, bu yüzden veriler baştan sona 'tutarlı'.


0

İç sınıf içindeki Java son değişkeni

iç sınıf sadece kullanabilir

  1. dış sınıftan referans
  2. referans türü olmayan kapsam dışı son yerel değişkenler (örneğin Object...)
  3. değer (ilkel) (örneğin int...) türü, son bir referans türü ile sarılabilir . IntelliJ IDEAbunu bir öğe dizisine dönüştürmenize yardımcı olabilir

Derleyici tarafından bir non static nested( inner class) [Hakkında] oluşturulduğunda - yeni bir sınıf - <OuterClass>$<InnerClass>.classoluşturulur ve bağlı parametreler yapıcıya [Yığında yerel değişken] geçirilir . Kapanışa benzer

son değişken, yeniden atanamayan bir değişkendir. son referans değişkeni hala bir durum değiştirilerek değiştirilebilir

Garip olurdu, çünkü bir programcı olarak böyle yapabilirsin

//Not possible 
private void foo() {

    MyClass myClass = new MyClass(); //address 1
    int a = 5;

    Button button = new Button();

    //just as an example
    button.addClickHandler(new ClickHandler() {


        @Override
        public void onClick(ClickEvent event) {

            myClass.something(); //<- what is the address ?
            int b = a; //<- 5 or 10 ?

            //illusion that next changes are visible for Outer class
            myClass = new MyClass();
            a = 15;
        }
    });

    myClass = new MyClass(); //address 2
    int a = 10;
}

-2

Belki bu numara sana bir fikir verir

Boolean var= new anonymousClass(){
    private String myVar; //String for example
    @Overriden public Boolean method(int i){
          //use myVar and i
    }
    public String setVar(String var){myVar=var; return this;} //Returns self instane
}.setVar("Hello").method(3);
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.