Yöntem parametreleri ve yerel değişkenler için final ne zaman kullanılmalıdır?


171

Mümkün olduğunca kullanmayı öneren birkaç referans ( örneğin ) buldum finalve bunun ne kadar önemli olduğunu merak ediyorum. Bu esas olarak, nihai yöntemler veya sınıflar değil, yöntem parametreleri ve yerel değişkenler bağlamındadır. Sabitler için bu çok mantıklı.

Bir yandan, derleyici bazı optimizasyonlar yapabilir ve programcının amacını daha net hale getirir. Öte yandan, ayrıntı katıyor ve optimizasyonlar önemsiz olabilir.

Hatırlamak için çabalamam gereken bir şey mi?


İşte bakmak için ilgili bir yazı: stackoverflow.com/questions/137868/…
mattlant


1
Sadece bunu okumadan önce parametrelerde bir değiştirici olarak final kullanmak mümkün olduğunu bilmiyordum çünkü ben vekalıyorum. Teşekkürler!
Kip

Yanıtlar:


178

Takıntılı:

  • Son alanlar - Alanları son olarak işaretlemek, inşaat bitimine göre ayarlanmaya zorlar ve bu alan referansını değişmez kılar. Bu, alanların güvenli bir şekilde yayınlanmasını sağlar ve daha sonraki okumalarda senkronizasyon ihtiyacını önleyebilir. (Bir nesne başvurusu için yalnızca alan başvurusunun değiştirilemez olduğunu unutmayın - nesne başvurusunun başvurduğu şeyler değişebilir ve değişmezliği etkiler.)
  • Son statik alanlar - Artık statik son alanları kullandığım çoğu durumda enum kullanmama rağmen.

Şunları düşünün, ancak dikkatli kullanın:

  • Son sınıflar - Çerçeve / API tasarımı bunu düşündüğüm tek durum.
  • Son yöntemler - Temel olarak son sınıflarla aynı. Çılgın ve işaretleme gibi final şablonu yöntem kalıpları kullanıyorsanız, muhtemelen mirasa çok fazla güveniyorsunuz ve yetki devrine yetmiyorsunuz.

Anal hissetmedikçe yoksay:

  • Yöntem parametreleri ve yerel değişkenler - Tembel olduğum ve kodu kümeler bulduğu için nadiren bunu büyük ölçüde yaparım. Ben değiştirmeyeceğim işaretleme parametreleri ve yerel değişkenlerin "daha righter" olduğunu tamamen kabul edeceğim. Keşke varsayılan olsaydı. Ama değil ve her yerde finaller ile kodu anlamak daha zor buluyorum. Eğer başka birinin kodundaysam, onları dışarı çekmeyeceğim ama yeni bir kod yazıyorsam onları koyamayacağım. anonim bir iç sınıfın içinden.

  • Düzenleme: @ adam-gent tarafından belirtildiği gibi son yerel değişkenlerin aslında çok yararlı olduğu bir kullanım durumunun if/ elsedallarındaki varlığa değer atandığı zaman olduğunu unutmayın .


8
Joshua Bloch, miras için tasarlanmadığı sürece tüm sınıfların final olarak tanımlanması gerektiğini savunuyor. Ona katılıyorum; Bir arabirimi uygulayan her sınıfa final ekliyorum (birim testleri oluşturabilmek için). Ayrıca, geçersiz kılınmayacak tüm korumalı / sınıf yöntemleri nihai olarak işaretleyin.
rmaruszewski

61
Josh Bloch'a tüm saygımla (ve bu önemli bir miktar), genel davaya katılmıyorum. Bir API oluşturma durumunda, işleri kilitleyin. Bui kendi kodunuzun içinde, daha sonra yıkmak zorunda olduğunuz duvarları dikmek zaman kaybıdır.
Alex Miller

9
Kesinlikle bir "zaman kaybı" değil, özellikle hiçbir zaman maliyeti yok çünkü ... Bir uygulamada, normalde finalvarsayılan olarak hemen hemen tüm sınıfları yapmak . Yine de, gerçekten modern bir Java IDE (yani IDEA) kullanmadığınız sürece faydalarını fark edemeyebilirsiniz.
Rogério

9
IDEA'nın (kutudan çıktığı gibi) yüzlerce kod denetimi vardır ve bunlardan bazıları finalsınıflarda / yöntemlerde kullanılmayan / gereksiz kodları algılayabilir . Örneğin, son bir yöntem işaretli bir istisnayı attığını bildirir ancak hiçbir zaman gerçekten atmazsa, IDEA size bunu söyleyecektir ve istisnayı throwsyan tümceden kaldırabilirsiniz . Bazen, kullanılmayan tüm yöntemleri de bulabilirsiniz, bu da geçersiz kılınamayacakları zaman algılanabilir.
Rogério

8
@rmaruszewski Sınıfları final olarak işaretlemek, çoğu alaycı çerçevenin onları alay etmesini önler ve böylece test etmenizi zorlaştırır. Bir sınıfı, ancak uzatılmaması kritik öneme sahip olsaydı, final yapardım.
Mike Rylander

44

Hatırlamak için çaba göstermem gereken bir şey mi?

Hayır, Eclipse kullanıyorsanız, bu son değiştiricileri sizin için otomatik olarak eklemek üzere bir Kaydetme İşlemi yapılandırabilirsiniz . Sonra daha az çaba için fayda elde edersiniz.


3
Kaydet Eylem ile büyük ipucu, bu konuda bilmiyordum.
aklı

1
Ben esas olarak bu faydayı düşünüyorum nihai yanlışlıkla doğrusu ya gerçekleşmeyebilir herhangi optimizasyonlar daha yanlış değişkene atayarak böcek gelen kod güvenli hale getirir.
Peter Hilton

4
Bu gerçekten senin için bir problem mi? Ne sıklıkta bundan kaynaklanan bir hata yaşadınız?
Alex Miller

1
Eclipse için yararlı ipucu için +1. Sanırım hataları önlemek için finali mümkün olduğunca kullanmalıyız.
emeraldhieu

Bunu IntelliJ'de de yapabilir misiniz?
Koray Tugay

15

"Nihai" nin geliştirme zamanı yararları, en azından çalışma zamanı yararları kadar önemlidir. Kodun gelecekteki editörlerine niyetleriniz hakkında bir şeyler söyler.

Bir sınıfı "final" olarak işaretlemek, sınıfın tasarımı veya uygulanması sırasında uzantıyı zarif bir şekilde işlemek için çaba göstermediğiniz anlamına gelir. Okuyucular sınıfta değişiklik yapabilir ve "son" değiştiriciyi kaldırmak isterse, bunu kendi sorumluluğunda yapabilirler. Sınıfın uzantıyı iyi ele alacağından emin olmak onlara kalmıştır.

Değişken bir "son" işaretleme (ve yapıcıya atama) bağımlılık enjeksiyonu için kullanışlıdır. Değişkenin "ortak çalışan" niteliğini belirtir.

Bir yöntemi "final" olarak işaretlemek soyut sınıflarda faydalıdır. Uzatma noktalarının nerede olduğunu açıkça belirler.


11

finalJava'yı daha fazla ifade tabanlı yapmak için her zaman kullanıyorum . Java'nın koşullarına bakın (if,else,switch ) özellikle işlevsel programlamaya alışkınsanız (yani ML, Scala veya Lisp) her zaman nefret ettiğim ifade tabanlı değildir.

Bu nedenle koşulları kullanırken daima (IMHO) son değişkenleri kullanmaya çalışmalısınız.

Sana bir örnek vereyim:

    final String name;
    switch(pluginType) {
        case CANDIDATE_EXPORT:
            name = "Candidate Stuff";
            break;
        case JOB_POSTING_IMPORT:
            name = "Blah";
            break;
        default:
            throw new IllegalStateException();
    }

Şimdi başka bir casedeyim eklerseniz ve ayarlamazsanız namederleyici başarısız olur. Derleyici her durumda (değişkeni ayarladığınız) bozmazsanız da başarısız olur. Bu, Java'yı Lisp'lere çok benzetmenizi sağlarlet ifadelerine ve kodunuzun büyük bir girintili olmaması için (sözlük kapsamı değişkenleri nedeniyle).

Ve @Recurse belirtildiği gibi (ama görünüşe göre -1 beni) String name finalderleyici hatası almak için dışarı ile yapabilirsiniz (ki ben asla söyleyemedim) ama geçişten sonra derleyici hatası ayar adını kolayca yapabilirsiniz ifadesini anlambilimi veya daha kötü unutmayı break(@Recurse'in söylediklerine rağmen) kullanamayacağınız bir ifadeyi atar final:

    String name;
    switch(pluginType) {
        case CANDIDATE_EXPORT:
            name = "Candidate Stuff";
            //break; whoops forgot break.. 
            //this will cause a compile error for final ;P @Recurse
        case JOB_POSTING_IMPORT:
            name = "Blah";
            break;
    }
    // code, code, code
    // Below is not possible with final
    name = "Whoops bug";

Hata ayar adı nedeniyle ( breakbaşka bir hata da unutmak yanı sıra ) şimdi yanlışlıkla bunu yapabilirsiniz:

    String name;
    switch(pluginType) {
        case CANDIDATE_EXPORT:
            name = "Candidate Stuff";
            break;
        //should have handled all the cases for pluginType
    }
    // code, code, code
    // Below is not possible with final
    name = "Whoops bug";

Son değişken, ismin ne olması gerektiğine dair tek bir değerlendirmeyi zorlar. Dönüş değerine sahip bir işlevin her zaman bir değer döndürmesi gerektiği gibi (istisnalar yoksayılır), ad anahtar bloğunun adı çözümlemesi gerekir ve bu nedenle kod parçalarını yeniden düzenlemeyi kolaylaştıran anahtar anahtarına bağlanır (örn. Eclipe refactor: extract yöntemi) .

OCaml'de yukarıdaki bilgiler:

type plugin = CandidateExport | JobPostingImport

let p = CandidateExport

let name = match p with
    | CandidateExport -> "Candidate Stuff"
    | JobPostingImport -> "Blah" ;;

match ... with ...Bir fonksiyon, yani ifade gibi değerlendirir. Anahtar ifademizin nasıl göründüğüne dikkat edin.

Şemada (Raket veya Tavuk) bir örnek:

(define name 
    (match b
      ['CandidateExport "Candidate Stuff"]
      ['JobPostingImport "Blah"]))

1
Java derleyici zaten bir "potansiyel olarak başlatılmamış ad" hatası verecek ve final ile veya olmadan derlemeyi reddetmek hariç.
Recurse

4
Evet ama final dışında istediğiniz zaman sıfırlayabilirsiniz. Aslında bir geliştirici bir döndü nerede bunun olmasını gördük else if (...)bir içine if(...)ve böylece değişken sıfırlama. Ona hiçbir zaman nihai bir değişkenle gerçekleşmeyeceğini gösterdim. Temel olarak finaldeğişkeni bir kez ve sadece bir kez atamaya zorlar ... yani: P
Adam Gent

9

finalSöz konusu yöntem birkaç sayfa uzunluğunda anlaşılmaz bir karışıklık olduğunda bir yeniden düzenleme yardım olarak yararlı olarak işaretleme yöntemi parametreleri ve yerliler buldum . tutamfinal özgürce, yorumlar o kutuyu yemin kusar hatalar derleyici "nihai değişken atayamayacağı" (ya da IDE) ne olduğunu görmek ve sadece boş yukarı hatta birkaç olsa (güncel) neden değişken olarak adlandırılan "veri" uçlarını fark edebilirsiniz olmadı.

Ardından, yeniden kullanılan değişkenleri kullanım noktasına daha yakın bildirilen yeni değişkenlerle değiştirerek bazı hataları düzeltebilirsiniz. Ardından, yöntemin tüm parçalarını kapsam belirleme parantezinde satabileceğinizi fark edersiniz ve aniden "Extract Method" dan bir IDE tuşa basarsınız ve canavarınız daha anlaşılır hale gelir.

Senin yöntem ise değil zaten unmaintainable batık dedim Enkazın dönüştürerek insanların cesaretini nihai malzeme yapımında değer olabilir sanırım; ancak kısa bir yöntemse (bkz: sürdürülemez), çok fazla ayrıntı ekleme riskiyle karşı karşıya kalırsınız. Özellikle, Java işlevi imzaları, her argüman için altı tane daha eklemeden 80 karaktere sığacak kadar zordur!


1
Çok geçerli son nokta, ekran çözünürlüğü son 10 yılda biraz değiştiğinden uzun zaman önce 80 karakter sınırından vazgeçmiş olmama rağmen. Ekranıma 300 karakterlik bir çizgiyi kaydırma yapmadan kolayca sığdırabilirim. Bununla birlikte, okunabilirlik elbette finalher parametreden önce olmadan daha iyidir .
brimborium

8

Bu tamamen tarzınıza bağlıdır ... Eğer değişkeni değiştirmeyeceğiniz zaman finali görmek isterseniz, onu kullanın. Görmekten hoşlanmıyorsanız ... o zaman dışarıda bırakın.

Ben şahsen mümkün olduğunca az ayrıntı düzeyini seviyorum, bu yüzden gerçekten gerekli olmayan ekstra anahtar kelimeler kullanmaktan kaçınma eğilimindedir.

Yine de dinamik dilleri tercih ederim, bu yüzden ayrıntılardan kaçınmak muhtemelen şaşırtıcı değil.

Bu yüzden, sadece eğildiğiniz yönü seçin ve sadece onunla devam edin (durum ne olursa olsun, tutarlı olmaya çalışın).


Bir yan not olarak, hem böyle bir desen kullanan hem de kullanmayan projeler üzerinde çalıştım ve hataların veya hataların miktarında hiçbir fark görmedim ... Bunun çok fazla olacak bir desen olduğunu düşünmüyorum hata sayınızı veya herhangi bir şeyi iyileştirin, ancak yine de stil ve eğer değiştirmeyeceğiniz niyetini ifade etmek isterseniz, devam edin ve kullanın.


6

Parametre değerini yanlışlıkla değiştirmekten kaçınmak ve ince bir hata getirmek için parametrelerde yararlıdır. Bu öneri yoksaymak için kullanın ama bazı 4 saat geçirdikten sonra. korkunç bir yöntemde (kod satırı ve çoklu fors, iç içe ifs ve her türlü kötü uygulamalar yüzlerce) bunu yapmanızı tavsiye ederim.

 public int processSomethingCritical( final int x, final int y ){
 // hundreds of lines here 
     // for loop here...
         int x2 = 0;
        x++; // bug aarrgg...
 // hundreds of lines there
 // if( x == 0 ) { ...

 }

Tabii ki mükemmel bir dünyada bu olmazdı, ama .. şey .. bazen başkalarının kodunu desteklemelisin. :(


2
Bu yöntemin eksik finalden daha ciddi sorunları vardır. İmkansız olmasa da, bir yöntemin bu tür hataların ortaya çıkacağı kadar karmaşık olması için iyi bir neden olması oldukça nadirdir. Değişken isimlerine konan küçük bir düşünce, bu gibi kazalara çok yol kat edecektir.
ykaganovich

2
Tek bir yöntemde "yüzlerce kod satırınız" varsa, bunu daha küçük yöntemlere bölmek isteyebilirsiniz.
Steve Kuo

5

Birinin kodu 1 yıl sonra okuması gerekecek bir uygulama yazıyorsanız, evet, her zaman değiştirilmemesi gereken değişken üzerinde final kullanın. Bunu yaparak, kodunuz daha "kendi kendini belgeleyecektir" ve diğer geliştiricilerin yerel bir değişkeni yerel bir geçici değişken olarak kullanmak gibi aptalca şeyler yapma şansını da azaltırsınız.

Eğer bir ıskarta kodu yazıyorsanız, o zaman, nah, tüm sabiti tanımlamak ve sonlandırmak için zahmet etmeyin.


4

Finali elimden geldiğince kullanacağım. Bunu yapmak istemeden alanı değiştirirseniz işaretlenecektir. Yöntem parametrelerini de son olarak ayarladım. Bunu yaparken, Java'nın değere göre geçerken unutmasını sağlayan bir parametre ayarlamaya çalıştığımda aldığım koddan birkaç hata yakaladım.


2

Bunun açık olup olmadığı sorudan net değil, ancak bir yöntem parametresinin sonlandırılması sadece yöntemin gövdesini etkiler. Bu mu DEĞİL invoker için yöntemin niyetleri hakkında herhangi ilginç bilgi iletmek. Aktarılan nesne yine de yöntem içinde mutasyona uğrayabilir (finaller sabit değildir) ve değişkenin kapsamı yöntem dahilindedir.

Kesin sorunuza cevap vermek için, kod gerektirmedikçe (örn. Değişken bir iç sınıftan atıfta bulunulan) bir örnek veya yerel değişken (yöntem parametreleri dahil) final yapmak veya bazı gerçekten karmaşık mantığı netleştirmek için rahatsız etmem.

Örneğin, değişkenler olarak, mantıklı bir şekilde sabit olmaları durumunda onları son haline getiririm.


2

Değişken için birçok kullanım alanı vardır final . İşte sadece birkaçı

Nihai Sabitler

 public static class CircleToolsBetter {
     public final static double PI = 3.141;
        public double getCircleArea(final double radius) {
          return (Math.pow(radius, 2) * PI);
        }
    }

Bu, daha sonra kodlarınızın diğer bölümleri için kullanılabilir veya diğer sınıflar tarafından erişilebilir, böylece değeri tek tek değiştirmek zorunda kalmazsınız.

Nihai Değişkenler

public static String someMethod(final String environmentKey) {
    final String key = "env." + environmentKey;
    System.out.println("Key is: " + key);
    return (System.getProperty(key));

  }

}

Bu sınıfta, environmentKey parametresine önek ekleyen kapsamlı bir son değişken oluşturursunuz. Bu durumda, son değişken yalnızca yöntemin her yürütmesinde farklı olan yürütme kapsamı içinde kesindir. Yönteme her girildiğinde final yeniden yapılandırılır. Oluşturulur inşa edilmez, yöntem yürütme kapsamında değiştirilemez. Bu, yöntemdeki bir değişkeni yöntemde sabitlemenizi sağlar. aşağıya bakınız:

public class FinalVariables {


  public final static void main(final String[] args) {
    System.out.println("Note how the key variable is changed.");
    someMethod("JAVA_HOME");
    someMethod("ANT_HOME");
  }
}

Nihai Sabitler

public double equation2Better(final double inputValue) {
    final double K = 1.414;
    final double X = 45.0;

double result = (((Math.pow(inputValue, 3.0d) * K) + X) * M);
double powInputValue = 0;         
if (result > 360) {
  powInputValue = X * Math.sin(result); 
} else {
  inputValue = K * Math.sin(result);   // <= Compiler error   
}

Bunlar özellikle uzun kod satırlarınız olduğunda kullanışlıdır ve derleyici hatası oluşturur, böylece birisi yanlışlıkla değiştirilmemesi gereken değişkenleri değiştirdiğinde mantık / iş hatasına girmezsiniz.

Final Koleksiyonları

Koleksiyonlar hakkında konuşurken farklı bir durum, bunları değiştirilemez olarak ayarlamanız gerekir.

 public final static Set VALID_COLORS; 
    static {
      Set temp = new HashSet( );
      temp.add(Color.red);
      temp.add(Color.orange);
      temp.add(Color.yellow);
      temp.add(Color.green);
      temp.add(Color.blue);
      temp.add(Color.decode("#4B0082")); // indigo
      temp.add(Color.decode("#8A2BE2")); // violet
      VALID_COLORS = Collections.unmodifiableSet(temp);
    }

aksi takdirde değiştirilemez olarak ayarlamazsanız:

Set colors = Rainbow.VALID_COLORS;
colors.add(Color.black); // <= logic error but allowed by compiler

Final Sınıfları ve Final Yöntemleri sırasıyla genişletilemez veya üzerine yazılamaz.

DÜZENLEME: KAPSAM İLE İLGİLİ NİHAİ SINIF SORUNUNUN ADRESİNDE:

Bir sınıfı final yapmak için iki yol vardır. Birincisi, sınıf bildiriminde final anahtar sözcüğünü kullanmaktır:

public final class SomeClass {
  //  . . . Class contents
}

Bir sınıf finali yapmanın ikinci yolu, tüm kurucularını özel olarak ilan etmektir:

public class SomeClass {
  public final static SOME_INSTANCE = new SomeClass(5);
  private SomeClass(final int value) {
  }

Son olarak işaretlemek, bu Test sınıfına bir bakış göstermenin gerçek bir final olduğunu öğrenirseniz size sorun çıkarır. ilk bakışta herkese açık görünüyor.

public class Test{
  private Test(Class beanClass, Class stopClass, int flags)
    throws Exception{
    //  . . . snip . . . 
  }
}

Ne yazık ki, sınıfın tek kurucusu özel olduğundan, bu sınıfı genişletmek imkansızdır. Test sınıfı söz konusu olduğunda, sınıfın final olması için bir neden yoktur. Test sınıfı, örtük son sınıfların nasıl sorunlara neden olabileceğine iyi bir örnektir.

Bu yüzden, bir sınıfı yapıcıyı özel yaparak örtülü olarak final yaptığınızda bunu son olarak işaretlemelisiniz.


1

Bahsettiğiniz gibi bir değiş tokuş, ama örtülü kullanım yerine bir şeyin açık kullanımını tercih ederim. Bu, yalnızca siz bile olsanız, gelecekteki kod sahipleri için bir belirsizliğin kaldırılmasına yardımcı olacaktır.


1

İç (anonim) sınıflarınız varsa ve yöntemin içeren yöntemin değişkenine erişmesi gerekiyorsa, bu değişkenin son olarak olması gerekir.

Bunun dışında söylediğin doğru.


Artık java 8, etkili son değişkenlerle esneklik sunuyor.
Ravindra babu

0

finalBir değişkeni şu şekilde oluşturuyorsanız, bir değişken için anahtar kelime kullanınimmutable

Değişkeni nihai olarak bildirerek, geliştiricilere çok iş parçacıklı bir ortamda değişkenlerin olası değişiklik konularını dışlamalarına yardımcı olur.

Java 8 sürümünde " effectively final variable" adında bir konseptimiz daha var . Nihai olmayan bir değişken son değişken olarak yükselebilir.

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

Bir değişken, yerel blokta başlatmadan sonra değiştirilmezse etkili son olarak kabul edilir . Bu, etkili bir şekilde nihai olmaları şartıyla artık anonim bir sınıf veya lambda ifadesi içinde son anahtar kelime olmadan yerel değişkeni kullanabileceğiniz anlamına gelir.

Java 7'ye kadar, anonim bir sınıf içinde nihai olmayan bir yerel değişkeni kullanamazsınız, ancak Java 8'den

Bu makaleye bir göz atın


-1

Her şeyden önce, son anahtar kelime bir değişkeni sabit yapmak için kullanılır. Sabit değişmediği anlamına gelir. Örneğin:

final int CM_PER_INCH = 2.54;

Değişken finali bildirirsiniz, çünkü inç başına bir santimetre değişmez.

Bir son değeri geçersiz kılmaya çalışırsanız, değişken ilk olarak bildirilen değerdir. Örneğin:

final String helloworld = "Hello World";
helloworld = "A String"; //helloworld still equals "Hello World"

Aşağıdaki gibi bir derleme hatası var:

local variable is accessed from inner class, must be declared final

Değişkeniniz son olarak bildirilemiyorsa veya son olarak bildirmek istemiyorsanız şunu deneyin:

final String[] helloworld = new String[1];
helloworld[0] = "Hello World!";
System.out.println(helloworld[0]);
helloworld[0] = "A String";
System.out.println(helloworld[0]);

Bu yazdırılacak:

Hello World!
A String
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.