Kapan harus menggunakan final untuk parameter metode dan variabel lokal?

171

Saya telah menemukan beberapa referensi ( misalnya ) yang menyarankan menggunakan finalsebanyak mungkin dan saya bertanya-tanya seberapa penting itu. Ini terutama dalam konteks parameter metode dan variabel lokal, bukan metode akhir atau kelas. Untuk konstanta, itu sangat masuk akal.

Di satu sisi, kompiler dapat membuat beberapa optimasi dan itu membuat maksud programmer lebih jelas. Di sisi lain, itu menambah kata kerja dan optimisasi mungkin sepele.

Apakah ini sesuatu yang harus saya ingat?

eaolson
sumber
Berikut ini adalah pos terkait untuk memeriksa: stackoverflow.com/questions/137868/…
mattlant
3
Duplikat dari: stackoverflow.com/questions/137868/…
Alex Miller
1
Saya upvot hanya karena saya tidak tahu bahwa itu mungkin untuk menggunakan final sebagai pengubah parameter sebelum membaca ini. Terima kasih!
Kip

Jawaban:

178

Terobsesi:

  • Bidang terakhir - Menandai bidang sebagai kekuatan akhir harus ditetapkan pada akhir konstruksi, menjadikan referensi bidang tidak dapat diubah. Ini memungkinkan publikasi bidang yang aman dan dapat menghindari kebutuhan sinkronisasi pada pembacaan selanjutnya. (Perhatikan bahwa untuk referensi objek, hanya referensi bidang yang tidak dapat diubah - hal-hal yang mengacu pada referensi objek masih dapat berubah dan yang memengaruhi immutabilitas.)
  • Final static field - Meskipun saya menggunakan enums sekarang untuk banyak kasus di mana saya dulu menggunakan final static field.

Pertimbangkan tetapi gunakan dengan bijaksana:

  • Kelas akhir - Kerangka / desain API adalah satu-satunya kasus di mana saya mempertimbangkannya.
  • Metode akhir - Pada dasarnya sama dengan kelas akhir. Jika Anda menggunakan pola metode templat seperti hal-hal gila dan menandai akhir, Anda mungkin terlalu mengandalkan warisan dan tidak cukup pada delegasi.

Abaikan kecuali merasa anal:

  • Parameter metode dan variabel lokal - Saya Jarang melakukan ini sebagian besar karena saya malas dan saya menemukannya mengacaukan kode. Saya akan sepenuhnya mengakui bahwa menandai parameter dan variabel lokal yang tidak akan saya modifikasi adalah "righter". Saya berharap itu adalah default. Tetapi tidak dan saya menemukan kode lebih sulit untuk dipahami dengan final seluruh. Jika saya menggunakan kode orang lain, saya tidak akan menariknya tetapi jika saya menulis kode baru saya tidak akan memasukkannya. Satu pengecualian adalah kasus di mana Anda harus menandai sesuatu yang final sehingga Anda dapat mengakses itu dari dalam kelas batin anonim.

  • Sunting: perhatikan bahwa satu kasus penggunaan di mana variabel lokal akhir sebenarnya sangat berguna seperti yang disebutkan oleh @ adam-gent adalah ketika nilai ditugaskan ke var di if/ elsecabang.

Alex Miller
sumber
8
Joshua Bloch berpendapat bahwa semua kelas harus didefinisikan sebagai final, kecuali mereka dirancang untuk warisan. Aku setuju dengannya; Saya menambahkan final ke setiap kelas yang mengimplementasikan antarmuka (untuk dapat membuat tes unit). Tandai juga sebagai final semua metode protected / class, yang tidak akan ditimpa.
rmaruszewski
61
Dengan segala hormat kepada Josh Bloch (dan itu jumlah yang besar), saya tidak setuju untuk kasus umum. Dalam hal membangun API, pastikan mengunci semuanya. Bui di dalam kode Anda sendiri, mendirikan tembok yang nantinya harus Anda robohkan adalah buang-buang waktu.
Alex Miller
9
Ini jelas bukan "buang-buang waktu", khususnya karena tidak ada biaya sama sekali ... Dalam aplikasi, saya biasanya membuat hampir semua kelas finalsecara default. Anda mungkin tidak melihat manfaatnya kecuali Anda menggunakan IDE Java yang benar-benar modern (yaitu, IDEA).
Rogério
9
IDEA memiliki (di luar kotak) ratusan inspeksi kode, dan beberapa di antaranya dapat mendeteksi kode yang tidak terpakai / tidak perlu di finalkelas / metode. Misalnya, jika metode final menyatakan untuk melemparkan pengecualian yang diperiksa tetapi tidak pernah benar-benar melemparkannya, IDEA akan memberi tahu Anda hal itu, dan Anda dapat menghapus pengecualian dari throwsklausa. Terkadang, Anda juga dapat menemukan seluruh metode yang tidak digunakan, yang dapat dideteksi ketika mereka tidak dapat diganti.
Rogério
8
@rmaruszewski Menandai kelas sebagai tugas akhir mencegah sebagian besar kerangka kerja mengejek agar tidak dapat mengejeknya sehingga membuat Anda lebih sulit untuk menguji kode. Saya hanya akan membuat final kelas jika sangat penting bahwa itu tidak diperpanjang.
Mike Rylander
44

Apakah ini sesuatu yang harus saya ingat untuk dilakukan?

Tidak, jika Anda menggunakan Eclipse, karena Anda dapat mengonfigurasi Save Action untuk secara otomatis menambahkan pengubah akhir ini untuk Anda. Maka Anda mendapatkan manfaat dengan sedikit usaha.

Peter Hilton
sumber
3
Tip hebat dengan Save Action, tidak tahu tentang itu.
kewarasan
1
Saya terutama mempertimbangkan manfaat bahwa akhir membuat kode lebih aman dari bug dengan secara tidak sengaja menempatkan ke variabel yang salah, daripada optimasi yang mungkin atau mungkin tidak terjadi.
Peter Hilton
4
Apakah ini benar-benar masalah bagi Anda? Seberapa sering Anda benar-benar memiliki bug yang dihasilkan dari ini?
Alex Miller
1
+1 untuk kiat bermanfaat di Eclipse. Saya pikir kita harus menggunakan final sebanyak mungkin untuk menghindari bug.
emeraldhieu
Bisakah Anda melakukan ini di IntelliJ juga?
Koray Tugay
15

Manfaat waktu pengembangan dari "final" setidaknya sama pentingnya dengan manfaat waktu berjalan. Ini memberi tahu editor masa depan tentang sesuatu tentang niat Anda.

Menandai kelas "final" menunjukkan bahwa Anda tidak melakukan upaya selama desain atau implementasi kelas untuk menangani ekstensi dengan anggun. Jika pembaca dapat membuat perubahan pada kelas, dan ingin menghapus pengubah "final", mereka dapat melakukannya dengan risiko sendiri. Terserah mereka untuk memastikan kelas akan menangani ekstensi dengan baik.

Menandai variabel "final" (dan menugaskannya dalam konstruktor) berguna dengan injeksi dependensi. Ini menunjukkan sifat "kolaborator" dari variabel.

Menandai metode "final" berguna dalam kelas abstrak. Ini jelas menggambarkan di mana titik-titik ekstensi berada.

Eric R. Rath
sumber
11

Saya menggunakan finalsemua waktu untuk membuat Java lebih berdasarkan ekspresi. Lihat kondisi Java ( if,else,switch) bukan berbasis ekspresi yang selalu saya benci terutama jika Anda terbiasa pemrograman fungsional (yaitu ML, Scala atau Lisp).

Jadi Anda harus selalu mencoba (IMHO) menggunakan variabel akhir saat menggunakan kondisi.

Biarkan saya memberi Anda sebuah contoh:

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

Sekarang Jika tambahkan casepernyataan lain dan jangan atur namekompiler akan gagal. Kompiler juga akan gagal jika Anda tidak memecahkan setiap kasus (bahwa Anda mengatur variabel). Ini memungkinkan Anda untuk membuat Java sangat mirip dengan letekspresi Lisp dan membuatnya jadi kode Anda tidak diindentasi secara besar-besaran (karena variabel pelingkupan leksikal).

Dan seperti yang dicatat oleh @Recurse (tapi ternyata -1 saya) Anda dapat melakukan sebelumnya tanpa membuat String name finaluntuk mendapatkan kesalahan kompiler (yang saya tidak pernah bilang Anda tidak bisa) tetapi Anda dapat dengan mudah membuat kesalahan kompiler hilang pengaturan nama setelah saklar pernyataan yang membuang semantik ekspresi atau lupa yang lebih buruk breakyang Anda tidak dapat menyebabkan kesalahan (terlepas dari apa yang dikatakan @Recurse) tanpa menggunakan 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";

Karena nama pengaturan bug (selain lupa breakyang juga bug lain) sekarang saya dapat secara tidak sengaja melakukan ini:

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

Variabel terakhir memaksa evaluasi tunggal nama apa yang seharusnya. Mirip dengan bagaimana suatu fungsi yang memiliki nilai kembali harus selalu mengembalikan nilai (mengabaikan pengecualian) blok saklar nama harus menyelesaikan nama dan dengan demikian terikat pada blok switch yang membuat potongan kode refactoring lebih mudah (yaitu refactor Eclipe: ekstrak metode) .

Di atas dalam OCaml:

type plugin = CandidateExport | JobPostingImport

let p = CandidateExport

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

The match ... with ...mengevaluasi seperti ekspresi fungsi yaitu. Perhatikan bagaimana tampilannya seperti pernyataan pergantian kami.

Berikut adalah contoh dalam Skema (Raket atau Ayam):

(define name 
    (match b
      ['CandidateExport "Candidate Stuff"]
      ['JobPostingImport "Blah"]))
Adam Gent
sumber
1
Kecuali bahwa kompiler java sudah akan memberi Anda kesalahan "nama berpotensi tidak diinisialisasi" dan menolak untuk dikompilasi dengan atau tanpa final.
Perulangan
4
Ya tetapi tanpa final Anda dapat mengatur ulang kapan saja. Saya benar-benar melihat ini terjadi di mana pengembang mengubah else if (...)menjadi if(...)dan dengan demikian me-reset variabel. Saya menunjukkan kepadanya bahwa tidak akan pernah terjadi dengan variabel final. Pada dasarnya finalmemaksa Anda untuk menetapkan variabel sekali dan hanya sekali ... jadi: P
Adam Gent
9

Saya telah menemukan metode menandai parameter dan penduduk setempat sebagai finalberguna sebagai bantuan refactoring ketika metode tersebut adalah berantakan beberapa halaman panjangnya. Taburkan finalsecara bebas, lihat apa yang "tidak dapat ditetapkan ke variabel akhir" kesalahan kompilasi (atau IDE Anda) muntah, dan Anda mungkin menemukan mengapa variabel yang disebut "data" berakhir nol meskipun beberapa (dari tanggal) komentar bersumpah akan terjadi.

Kemudian Anda dapat memperbaiki beberapa kesalahan dengan mengganti variabel yang digunakan kembali dengan variabel baru yang dinyatakan lebih dekat ke titik penggunaan. Kemudian Anda menemukan bahwa Anda dapat membungkus seluruh bagian dari metode ini dalam scoping braces, dan tiba-tiba Anda hanya satu penekanan tombol IDE dari "Extract Method" dan monster Anda menjadi lebih mudah dipahami.

Jika metode Anda belum menjadi bangkai kapal yang tidak dapat dipelihara, saya kira mungkin ada nilai dalam membuat barang jadi final untuk mencegah orang mengubahnya menjadi bangkai kata; tetapi jika ini adalah metode singkat (lihat: tidak dapat dipelihara) maka Anda berisiko menambahkan banyak kata. Secara khusus, tanda tangan fungsi Java cukup sulit untuk masuk ke dalam 80 karakter karena tanpa menambahkan enam lagi per argumen!

Sam Stokes
sumber
1
Poin terakhir yang sangat valid, meskipun saya menyerah pada batas 80 karakter sejak lama karena resolusi layar telah berubah sedikit dalam 10 tahun terakhir. Saya dapat dengan mudah memasukkan 300 baris char di layar saya tanpa menggulir. Namun demikian, keterbacaan tentu saja lebih baik tanpa finalsebelumnya setiap parameter.
brimborium
8

Nah, ini semua tergantung pada gaya Anda ... jika Anda SUKA melihat final ketika Anda tidak akan memodifikasi variabel, maka gunakan itu. Jika Anda TIDAK SUKA melihatnya ... maka tinggalkan saja.

Saya pribadi suka sesedikit mungkin verbositas, jadi saya cenderung menghindari penggunaan kata kunci tambahan yang sebenarnya tidak perlu.

Saya lebih suka bahasa dinamis, jadi mungkin tidak mengherankan saya suka menghindari kata-kata kasar.

Jadi, saya akan mengatakan hanya memilih arah yang Anda condongkan dan ikuti saja (apa pun masalahnya, cobalah untuk konsisten).


Sebagai catatan, saya telah mengerjakan proyek yang menggunakan dan tidak menggunakan pola seperti itu, dan saya tidak melihat perbedaan dalam jumlah bug atau kesalahan ... Saya tidak berpikir itu adalah pola yang akan sangat besar tingkatkan jumlah bug Anda atau apa pun, tetapi sekali lagi itu adalah gaya, dan jika Anda suka menyatakan maksud bahwa Anda tidak akan memodifikasinya, maka silakan dan gunakan.

Mike Stone
sumber
6

Berguna dalam parameter untuk menghindari mengubah nilai parameter secara tidak sengaja dan memperkenalkan bug halus. Saya menggunakan untuk mengabaikan rekomendasi ini tetapi setelah menghabiskan 4 jam. dalam metode yang mengerikan (dengan ratusan baris kode dan fors ganda, jika bersarang dan semua jenis praktik buruk) saya akan merekomendasikan Anda untuk melakukannya.

 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 ) { ...

 }

Tentu saja di dunia yang sempurna ini tidak akan terjadi, tapi .. yah .. kadang-kadang Anda harus mendukung kode orang lain. :(

OscarRyz
sumber
2
Metode ini memiliki masalah yang lebih serius daripada tidak mencapai final. Ini sangat jarang, meskipun bukan tidak mungkin, bahwa ada alasan yang baik untuk metode menjadi sangat berantakan sehingga kesalahan semacam ini akan terjadi. Sedikit pemikiran dimasukkan ke dalam nama variabel akan pergi jauh ke arah kecelakaan seperti ini.
ykaganovich
2
Jika Anda memiliki "ratusan baris kode" dalam satu metode, Anda mungkin ingin memecahnya menjadi beberapa metode yang lebih kecil.
Steve Kuo
5

Jika Anda menulis aplikasi yang seseorang harus membaca kode setelah, katakanlah, 1 tahun, maka ya, gunakan final pada variabel yang tidak boleh dimodifikasi sepanjang waktu. Dengan melakukan ini, kode Anda akan lebih "mendokumentasikan diri sendiri" dan Anda juga mengurangi kesempatan bagi pengembang lain untuk melakukan hal-hal konyol seperti menggunakan konstanta lokal sebagai variabel temporer lokal.

Jika Anda menulis beberapa kode sekali pakai, maka, nah, jangan repot-repot mengidentifikasi semua konstanta dan membuatnya final.

Alvin
sumber
4

Saya akan menggunakan final sebanyak yang saya bisa. Melakukannya akan menandai jika Anda tidak sengaja mengubah bidang. Saya juga mengatur parameter Metode ke final. Melakukannya saya telah menangkap beberapa bug dari kode yang saya ambil ketika mereka mencoba untuk 'mengatur' parameter yang melupakan Java lewat nilai.

Javamann
sumber
2

Tidak jelas dari pertanyaan apakah ini jelas, tetapi membuat parameter metode akhir hanya mempengaruhi tubuh metode. Ini TIDAK menyampaikan informasi menarik tentang niat metode kepada penyerang. Objek yang diteruskan masih dapat dimutasi dalam metode (final bukan konstanta), dan ruang lingkup variabel berada dalam metode.

Untuk menjawab pertanyaan Anda yang tepat, saya tidak akan repot-repot membuat instance atau variabel lokal (termasuk parameter metode) final kecuali jika diperlukan kode itu (misalnya variabel direferensikan dari kelas batin), atau untuk memperjelas beberapa logika yang sangat rumit.

Sebagai contoh variabel, saya akan membuat mereka final jika mereka adalah konstanta secara logis.

ykaganovich
sumber
2

Ada banyak kegunaan untuk variabel final. Berikut ini beberapa di antaranya

Konstanta Terakhir

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

Ini dapat digunakan kemudian untuk bagian lain dari kode Anda, atau diakses oleh kelas lain, dengan cara itu jika Anda akan mengubah nilai Anda tidak perlu mengubahnya satu per satu.

Variabel akhir

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

  }

}

Di kelas ini, Anda membangun variabel akhir yang mencakup yang menambahkan awalan ke parameter environmentKey. Dalam hal ini, variabel final hanya final dalam lingkup eksekusi, yang berbeda pada setiap eksekusi metode. Setiap kali metode dimasukkan, final direkonstruksi. Segera setelah dibangun, itu tidak dapat diubah selama lingkup eksekusi metode. Ini memungkinkan Anda untuk memperbaiki variabel dalam metode selama durasi metode. Lihat di bawah:

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

Konstanta Terakhir

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   
}

Ini sangat berguna ketika Anda memiliki baris kode yang sangat panjang, dan itu akan menghasilkan kesalahan kompiler sehingga Anda tidak menjalankan kesalahan logika / bisnis ketika seseorang secara tidak sengaja mengubah variabel yang tidak boleh diubah.

Koleksi Terakhir

Berbeda halnya ketika kita berbicara tentang Koleksi, Anda perlu menetapkannya sebagai yang tidak dapat dimodifikasi.

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

jika tidak, jika Anda tidak menetapkannya sebagai tidak dapat dimodifikasi:

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

Kelas Final dan Metode Final tidak dapat diperpanjang atau ditimpa masing-masing.

SUNTING: UNTUK MENCOBA MASALAH KELAS FINAL TENTANG PENERAPAN:

Ada dua cara untuk membuat kelas menjadi final. Yang pertama adalah menggunakan kata kunci akhir dalam deklarasi kelas:

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

Cara kedua untuk membuat final kelas adalah mendeklarasikan semua konstruktornya sebagai pribadi:

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

Menandai itu final akan menyelamatkan Anda dari masalah jika mengetahui bahwa itu adalah final, untuk menunjukkan tampilan pada kelas Tes ini. terlihat publik pada pandangan pertama.

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

Sayangnya, karena satu-satunya konstruktor kelas adalah privat, tidak mungkin untuk memperpanjang kelas ini. Dalam hal kelas Tes, tidak ada alasan bahwa kelas tersebut harus final. Kelas Tes adalah contoh yang baik tentang bagaimana kelas akhir implisit dapat menyebabkan masalah.

Jadi Anda harus menandainya sebagai final ketika Anda secara implisit membuat final kelas dengan menjadikannya konstruktor pribadi.

mel3kings
sumber
1

Agak dari trade-off seperti yang Anda sebutkan, tapi saya lebih suka menggunakan sesuatu secara eksplisit daripada penggunaan implisit. Ini akan membantu menghilangkan beberapa ambiguitas bagi pengelola kode di masa mendatang - bahkan jika itu hanya Anda.

Sean
sumber
1

Jika Anda memiliki kelas dalam (anonim), dan metode ini perlu mengakses variabel dari metode yang mengandung, Anda harus memiliki variabel itu sebagai final.

Selain itu, apa yang Anda katakan itu benar.

anjanb
sumber
Sekarang java 8 menawarkan fleksibilitas dengan variabel akhir yang efektif.
Ravindra babu
0

Gunakan finalkata kunci untuk variabel jika Anda menjadikan variabel itu sebagaiimmutable

Dengan mendeklarasikan variabel sebagai final, ini membantu pengembang untuk menyingkirkan kemungkinan masalah modifikasi variabel dalam lingkungan yang sangat banyak-threaded.

Dengan rilis java 8, kami memiliki satu konsep lagi yang disebut " effectively final variable". Variabel non-final dapat naik sebagai variabel akhir.

variabel lokal yang dirujuk dari ekspresi lambda harus final atau efektif final

Suatu variabel dianggap efektif akhir jika tidak dimodifikasi setelah inisialisasi di blok lokal. Ini berarti Anda sekarang dapat menggunakan variabel lokal tanpa kata kunci akhir di dalam kelas anonim atau ekspresi lambda, asalkan mereka harus secara efektif final.

Hingga Java 7, Anda tidak dapat menggunakan variabel lokal non-final di dalam kelas anonim, tetapi dari Java 8 Anda bisa

Lihat artikel ini

Ravindra babu
sumber
-1

Pertama-tama, kata kunci akhir digunakan untuk membuat variabel konstan. Konstan berarti tidak berubah. Sebagai contoh:

final int CM_PER_INCH = 2.54;

Anda akan mendeklarasikan variabel final karena satu sentimeter per inci tidak berubah.

Jika Anda mencoba menimpa nilai akhir, variabelnya adalah apa yang dideklarasikan terlebih dahulu. Sebagai contoh:

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

Ada kesalahan kompilasi yang kira-kira seperti:

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

Jika variabel Anda tidak dapat dinyatakan final atau jika Anda tidak ingin menyatakannya final coba ini:

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

Ini akan mencetak:

Hello World!
A String
Jared Burrows
sumber