Lambda ifadesinde kullanılan değişken nihai veya etkili bir şekilde nihai olmalıdır


134

Lambda ifadesinde kullanılan değişken nihai veya etkili bir şekilde nihai olmalıdır

Kullanmaya çalıştığımda calTzbu hatayı gösteriyor.

private TimeZone extractCalendarTimeZoneComponent(Calendar cal, TimeZone calTz) {
    try {
        cal.getComponents().getComponents("VTIMEZONE").forEach(component -> {
            VTimeZone v = (VTimeZone) component;
            v.getTimeZoneId();
            if (calTz == null) {
                calTz = TimeZone.getTimeZone(v.getTimeZoneId().getValue());
            }
        });
    } catch (Exception e) {
        log.warn("Unable to determine ical timezone", e);
    }
    return null;
}

5
calTzLambda'dan değiştiremezsiniz .
Elliott Frisch

2
Bunun Java 8 için zamanında yapılmayan şeylerden biri olduğunu varsaydım. Ama Java 8 2014'tür. Scala ve Kotlin buna yıllardır izin veriyor, bu yüzden açıkça mümkün. Java bu tuhaf kısıtlamayı kaldırmayı planlıyor mu?
GlenPeterson

5
İşte @MSDousti'nin yorumunun güncellenmiş bağlantısı.
geisterfurz007

Bir çözüm olarak Tamamlanabilir Vadeli İşlemleri kullanabileceğinizi düşünüyorum.
Kraulain

Gözlemlediğim önemli bir şey - Normal değişkenler yerine statik değişkenler kullanabilirsiniz (Bu, tahmin ettiğim gibi etkili bir şekilde nihai hale getirir)
kaushalpranav

Yanıtlar:


68

Bir finaldeğişken vasıta bunun yalnızca bir kez örneği oluşturulmak. Java'da son olmayan değişkenleri hem lambda'da hem de anonim iç sınıflarda kullanamazsınız.

Kodunuzu her döngü için eski ile yeniden düzenleyebilirsiniz:

private TimeZone extractCalendarTimeZoneComponent(Calendar cal,TimeZone calTz) {
    try {
        for(Component component : cal.getComponents().getComponents("VTIMEZONE")) {
        VTimeZone v = (VTimeZone) component;
           v.getTimeZoneId();
           if(calTz==null) {
               calTz = TimeZone.getTimeZone(v.getTimeZoneId().getValue());
           }
        }
    } catch (Exception e) {
        log.warn("Unable to determine ical timezone", e);
    }
    return null;
}

Bu kodun bazı parçalarını anlamasam bile:

  • v.getTimeZoneId();dönüş değerini kullanmadan a çağırırsınız
  • ödev ile calTz = TimeZone.getTimeZone(v.getTimeZoneId().getValue());orijinal olarak geçileni değiştirmezsiniz calTzve bu yöntemde kullanmazsınız
  • Her zaman geri dönüyorsun null, neden voiddönüş türünü belirlemiyorsun?

Umarım bu ipuçları da gelişmenize yardımcı olur.


nihai olmayan statik değişkenleri kullanabiliriz
Narendra Jaggi

93

Diğer yanıtlar gereksinimi kanıtlasa da gereksinimin neden var olduğunu açıklamıyorlar .

JLS, bunun nedenini §15.27.2'de belirtiyor :

Etkili son değişkenlere yönelik kısıtlama, yakalanması muhtemelen eşzamanlılık sorunlarına yol açacak olan dinamik olarak değişen yerel değişkenlere erişimi engeller.

Hata riskini azaltmak için, yakalanan değişkenlerin asla mutasyona uğramamasını sağlamaya karar verdiler.


11
İyi cevap +1 ve etkili bir şekilde finalin nedeninin ne kadar az yer kapladığına şaşırıyorum . Not: Yerel bir değişken, ancak aynı zamanda kesinlikle lambda gövdesinden önce atanmışsa bir lambda tarafından yakalanabilir . Her iki gereksinim de yerel değişkene erişimin iş parçacığı açısından güvenli olmasını sağlayacak gibi görünmektedir.
Tim Biegeleisen

2
bunun neden sınıf üyeleriyle değil de yerel değişkenlerle sınırlı olduğu hakkında bir fikriniz var mı? Değişkenimi sınıf üyesi olarak ilan ederek kendimi sık sık problemin üstesinden gelirken buluyorum ...
David Refaeli

4
@DavidRefaeli Sınıf üyeleri, takip edilirse paylaşıldığında öngörülebilir sonuçlar üretecek olan bellek modeli tarafından kapsanır / etkilenir. Yerel değişkenler, §17.4.1
Dioxin

Bu, kaldırılması gereken aptalca bir hack'tir. Derleyici potansiyel çapraz iş parçacığı değişken erişimi konusunda uyarmalı, ancak buna izin vermelidir. Ya da lambda'nızın aynı iş parçacığı üzerinde mi yoksa paralel mi çalıştığını vb. Bilecek kadar akıllı olmalı. Bu beni üzen aptalca bir sınırlamadır. Ve diğerlerinin de belirttiği gibi, sorunlar örneğin C # 'da mevcut değildir.
Josh M.

@JoshM. C # ayrıca, insanların sorunları önlemek için kaçınılmasını önerdiği, değiştirilebilir değer türleri oluşturmanıza da olanak tanır . Java, bu tür ilkelere sahip olmak yerine bunu tamamen engellemeye karar verdi. Esneklik pahasına kullanıcı hatasını azaltır. Bu kısıtlamaya katılmıyorum ama haklı. Paralelliği hesaba katmak, derleyicinin ucunda fazladan çalışma gerektirecektir, muhtemelen bu yüzden " çapraz iş parçacıklı erişimi uyarma " yolu izlenmedi . Spesifikasyon üzerinde çalışan bir geliştirici muhtemelen bunun için tek onayımız olacaktır.
Dioksin

59

Bir lambdadan, nihai olmayan hiçbir şeye referans alamazsınız. Değişkeninizi tutmak için lamda dışından son bir sarmalayıcı bildirmeniz gerekir.

Bu sarmalayıcı olarak son 'referans' nesnesini ekledim.

private TimeZone extractCalendarTimeZoneComponent(Calendar cal,TimeZone calTz) {
    final AtomicReference<TimeZone> reference = new AtomicReference<>();

    try {
       cal.getComponents().getComponents("VTIMEZONE").forEach(component->{
        VTimeZone v = (VTimeZone) component;
           v.getTimeZoneId();
           if(reference.get()==null) {
               reference.set(TimeZone.getTimeZone(v.getTimeZoneId().getValue()));
           }
           });
    } catch (Exception e) {
        //log.warn("Unable to determine ical timezone", e);
    }
    return reference.get();
}   

Aynı veya benzer bir yaklaşım hakkında düşünüyordum - ancak bu cevapla ilgili bazı uzman tavsiyeleri / geri bildirimleri görmek isterim?
YoYo

4
Bu kod bir baş harfini kaçırır reference.set(calTz);veya referans kullanılarak yaratılmalıdır new AtomicReference<>(calTz), aksi takdirde parametre olarak sağlanan boş olmayan Zaman Dilimi kaybolur.
Julien Kronegg

8
Bu ilk cevap olmalı. Bir AtomicReference (veya benzer bir Atomic___ sınıfı), mümkün olan her durumda bu sınırlamanın etrafında güvenli bir şekilde çalışır.
GlenPeterson

1
Kabul edildi, bu kabul edilen cevap olmalıdır. Diğer cevaplar, işlevsel olmayan bir programlama modeline nasıl geri dönüleceği ve bunun neden yapıldığı hakkında yararlı bilgiler verir, ancak aslında size problemi nasıl çözeceğinizi söylemeyin!
Jonathan Benn

2
@GlenPeterson ve aynı zamanda korkunç bir karar, sadece bu şekilde çok daha yavaş değil, aynı zamanda dokümantasyonun gerektirdiği yan etkiler özelliğini de görmezden geliyorsunuz.
Eugene

41

Java 8 , "Etkili nihai" değişken adı verilen yeni bir konsepte sahiptir. Bu, değeri başlatmadan sonra hiçbir zaman değişmeyen son olmayan yerel değişkene "Etkili Son" denildiği anlamına gelir.

Bu kavram tanıtıldı çünkü Java 8'den önce , anonim bir sınıfta son olmayan bir yerel değişken kullanamıyorduk . Anonim sınıfta yerel bir değişkene erişmek istiyorsanız, onu son haline getirmelisiniz.

Lambda getirildiğinde, bu kısıtlama hafifletildi. Dolayısıyla, kendi başına lambda olarak başlatıldığında değiştirilmezse yerel değişkeni son haline getirme ihtiyacı, anonim bir sınıftan başka bir şey değildir.

Java 8 , bir geliştirici lambda'yı her kullandığında yerel değişkeni son olarak bildirmenin acısını fark etti, bu kavramı tanıttı ve yerel değişkenleri son haline getirmeyi gereksiz kıldı. Dolayısıyla, anonim sınıflar için kuralın değişmediğini görürseniz, finallambdaları her kullandığınızda anahtar kelimeyi yazmanız gerekmez .

Burada iyi bir açıklama buldum


Kod biçimlendirme , genel olarak teknik terimler için değil, yalnızca kod için kullanılmalıdır . effectively finalkod değil, terminolojidir. Kod olmayan metin için kod biçimlendirme ne zaman kullanılmalıdır? Konusuna bakın. üzerinde Meta yığın taşması .
Charles Duffy

(Yani " finalanahtar kelime" bir kod kelimesidir ve bu şekilde biçimlendirmek için doğrudur, ancak "son" kelimesini kod yerine açıklayıcı olarak kullandığınızda, bunun yerine terminolojidir).
Charles Duffy

9

Senin örnekte, yerine forEachbasit ile lamdba ile fordöngü ve serbestçe herhangi bir değişkeni değiştirin. Veya, muhtemelen, herhangi bir değişkeni değiştirmeniz gerekmeyecek şekilde kodunuzu yeniden düzenleyin. Bununla birlikte, hatanın ne anlama geldiğini ve bunun üzerinden nasıl geçileceğini tamlık için açıklayacağım.

Java 8 Dil Spesifikasyonu, §15.27.2 :

Lambda ifadesinde kullanılan ancak bildirilmeyen herhangi bir yerel değişken, biçimsel parametre veya istisna parametresi ya son olarak bildirilmeli ya da etkin bir şekilde son olmalıdır ( §4.12.4 ), aksi takdirde kullanım denendiğinde bir derleme zamanı hatası oluşur.

Temel olarak, yerel bir değişkeni ( calTzbu durumda) bir lambda (veya yerel / anonim bir sınıf) içinden değiştiremezsiniz. Java'da bunu başarmak için, değiştirilebilir bir nesne kullanmanız ve onu (son değişken aracılığıyla) lambda'dan değiştirmeniz gerekir. Buradaki değişebilir bir nesneye bir örnek, bir öğeden oluşan bir dizi olabilir:

private TimeZone extractCalendarTimeZoneComponent(Calendar cal, TimeZone calTz) {
    TimeZone[] result = { null };
    try {
        cal.getComponents().getComponents("VTIMEZONE").forEach(component -> {
            ...
            result[0] = ...;
            ...
        }
    } catch (Exception e) {
        log.warn("Unable to determine ical timezone", e);
    }
    return result[0];
}

Başka bir yol, bir nesnenin alanını kullanmaktır. Örneğin MyObj sonucu = yeni MyObj (); ...; result.timeZone = ...; ....; return result.timezone; Yine de, yukarıda açıklandığı gibi, bunun sizi iş parçacığı güvenliği sorunlarına maruz bıraktığını unutmayın. Bkz stackoverflow.com/a/50341404/7092558
Gibezynu Nu

0

Değişkeni değiştirmek gerekli değilse, bu tür bir sorun için genel bir geçici çözüm, lambda kullanan kod parçasını ayıklamak ve metod parametresinde final anahtar sözcüğünü kullanmak olacaktır.


0

Lambda ifadesinde kullanılan bir değişken nihai veya etkili bir son olmalıdır, ancak son bir öğe dizisine bir değer atayabilirsiniz.

private TimeZone extractCalendarTimeZoneComponent(Calendar cal, TimeZone calTz) {
    try {
        TimeZone calTzLocal[] = new TimeZone[1];
        calTzLocal[0] = calTz;
        cal.getComponents().get("VTIMEZONE").forEach(component -> {
            TimeZone v = component;
            v.getTimeZoneId();
            if (calTzLocal[0] == null) {
                calTzLocal[0] = TimeZone.getTimeZone(v.getTimeZoneId().getValue());
            }
        });
    } catch (Exception e) {
        log.warn("Unable to determine ical timezone", e);
    }
    return null;
}

Bu, Alexander Udalov'un önerisine çok benziyor. Bunun dışında bu yaklaşımın yan etkilere dayandığını düşünüyorum.
Scratte
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.