ใน Java ฉันควรใช้ "final" สำหรับพารามิเตอร์และ locals แม้ว่าฉันไม่จำเป็นต้อง?


105

Java อนุญาตให้ทำเครื่องหมายตัวแปร (ฟิลด์ / โลแคล / พารามิเตอร์) เป็นfinalเพื่อป้องกันการกำหนดใหม่ ฉันพบว่ามันมีประโยชน์มากกับฟิลด์เนื่องจากมันช่วยให้ฉันดูได้อย่างรวดเร็วว่าคุณลักษณะบางอย่างหรือทั้งคลาสนั้นมีจุดมุ่งหมายที่ไม่เปลี่ยนรูป

ในทางกลับกันฉันพบว่ามันมีประโยชน์น้อยกว่ามากกับคนในท้องถิ่นและพารามิเตอร์และฉันมักจะหลีกเลี่ยงการทำเครื่องหมายว่าfinalแม้ว่าพวกเขาจะไม่ได้รับการมอบหมายซ้ำอีกครั้ง (ยกเว้นอย่างชัดเจนเมื่อพวกเขาต้องการใช้ในชั้นเรียน) . อย่างไรก็ตามเมื่อเร็ว ๆ นี้ฉันใช้รหัสที่ใช้ขั้นสุดท้ายเมื่อใดก็ตามที่ทำได้ซึ่งฉันเดาว่าจะให้ข้อมูลเพิ่มเติมในทางเทคนิค

ไม่มั่นใจเกี่ยวกับรูปแบบการเขียนโปรแกรมของฉันอีกต่อไปฉันสงสัยว่าอะไรคือข้อดีและข้อเสียอื่น ๆ ของการใช้finalที่ใดก็ได้ลักษณะอุตสาหกรรมที่พบบ่อยที่สุดคืออะไรและเพราะเหตุใด


@ คำถามสไตล์การเข้ารหัสของ Amir ดูเหมือนจะเป็นของที่นี่ดีกว่าใน SO และฉันไม่พบนโยบายใด ๆ ในคำถามที่พบบ่อยหรือเมตาของเว็บไซต์นี้เกี่ยวกับเรื่องนี้ คุณช่วยบอกฉันทีได้ไหม
โอ๊ก

1
@Oak มันพูดว่า: "ปัญหาการเขียนโปรแกรมเฉพาะ, อัลกอริทึมซอฟต์แวร์, การเขียนโปรแกรม , ถามเกี่ยวกับ Stack Overflow"
Amir Rezaei

7
@ ฉันไม่เห็นด้วยฉันไม่เห็นว่านี่เป็นปัญหาการเข้ารหัสอย่างไร ไม่ว่าในกรณีใดการอภิปรายนี้ไม่ได้อยู่ที่นี่ดังนั้นฉันได้เปิดเมตา - หัวข้อเกี่ยวกับปัญหานี้
โอ๊ก

3
@amir ทั้งหมดนี้อยู่ในหัวข้อสำหรับเว็บไซต์นี้เนื่องจากเป็นคำถามที่เป็นอัตนัยเกี่ยวกับการเขียนโปรแกรม - โปรดดู / faq
Jeff Atwood

1
เรื่องตัวแปรในเครื่อง: คอมไพเลอร์ Java รุ่นเก่าให้ความสนใจว่าคุณประกาศในเครื่องfinalหรือไม่และปรับให้เหมาะสม คอมไพเลอร์สมัยใหม่นั้นฉลาดพอที่จะคิดออกเอง อย่างน้อยเกี่ยวกับตัวแปรท้องถิ่นfinalอย่างเคร่งครัดเพื่อประโยชน์ของผู้อ่านของมนุษย์ หากกิจวัตรของคุณไม่ซับซ้อนเกินไปผู้อ่านที่เป็นมนุษย์ส่วนใหญ่ก็ควรที่จะเข้าใจตัวเองเช่นกัน
โซโลมอนช้า

คำตอบ:


67

ฉันใช้finalวิธีเดียวกับคุณ สำหรับฉันแล้วมันดูไม่จำเป็นสำหรับตัวแปรโลคัลและพารามิเตอร์เมธอดและมันไม่ได้นำเสนอข้อมูลเพิ่มเติมที่เป็นประโยชน์

สิ่งหนึ่งที่สำคัญคือการที่มุ่งมั่นที่จะให้วิธีการของฉันสั้นและทำความสะอาดแต่ละทำงานเดียว ดังนั้นตัวแปรและพารามิเตอร์ในเครื่องของฉันจึงมีขอบเขต จำกัด และใช้เพื่อจุดประสงค์เดียวเท่านั้น สิ่งนี้จะลดโอกาสในการมอบหมายใหม่โดยไม่ได้ตั้งใจ

ยิ่งกว่านั้นอย่างที่คุณรู้finalไม่รับประกันว่าคุณจะไม่สามารถเปลี่ยนค่า / สถานะของตัวแปร (ไม่แปร) ได้ มีเพียงคุณเท่านั้นที่ไม่สามารถมอบหมายการอ้างอิงไปยังวัตถุนั้นอีกครั้งเมื่อเริ่มต้นแล้ว กล่าวอีกนัยหนึ่งมันทำงานได้อย่างราบรื่นเฉพาะกับตัวแปรประเภทดั้งเดิมหรือไม่เปลี่ยนรูป พิจารณา

final String s = "forever";
final int i = 1;
final Map<String, Integer> m = new HashMap<String, Integer>();

s = "never"; // compilation error!
i++; // compilation error!
m.put(s, i); // fine

ซึ่งหมายความว่าในหลายกรณียังไม่สามารถเข้าใจได้ง่ายขึ้นว่าเกิดอะไรขึ้นภายในรหัสและการเข้าใจผิดในความเป็นจริงอาจทำให้เกิดข้อบกพร่องที่ละเอียดซึ่งยากต่อการตรวจสอบ


1
เกี่ยวกับการแก้ไข - ฉันทราบถึงความหมายของfinalคำขอบคุณ :) แต่ข้อดีของวิธีการแบบสั้นและสะอาด - ฉันเดาว่าถ้าวิธีนั้นสั้นพอที่จะเห็นได้ชัดว่าตัวแปรไม่ได้ถูกกำหนดใหม่ แรงจูงใจแม้แต่น้อยสำหรับการพิจารณาfinalคำหลัก
โอ๊ก

จะดีหรือไม่ถ้าเรามีพารามิเตอร์สุดท้ายและตัวแปรท้องถิ่นและยังเป็นไวยากรณ์ที่สั้นและสะอาด programmers.stackexchange.com/questions/199783/…
oberlies

7
"ขั้นสุดท้ายไม่รับประกันว่าคุณจะไม่สามารถเปลี่ยนค่า / สถานะของตัวแปร (ไม่คาดการณ์) ได้เฉพาะที่คุณไม่สามารถมอบหมายการอ้างอิงไปยังวัตถุนั้นอีกครั้งเมื่อเริ่มต้นแล้ว" Nonprimitive = การอ้างอิง (ชนิดเท่านั้นใน Java เป็นชนิดดั้งเดิมและประเภทการอ้างอิง) ค่าของตัวแปรประเภทอ้างอิงเป็นข้อมูลอ้างอิง ดังนั้นคุณไม่สามารถมอบหมายการอ้างอิง = คุณไม่สามารถเปลี่ยนค่าได้
user102008

2
+1 สำหรับข้อผิดพลาดอ้างอิง / สถานะ โปรดทราบว่าการทำเครื่องหมายตัวแปรสุดท้ายอาจจำเป็นต่อการสร้างการปิดในด้านฟังก์ชันใหม่ของ Java8
Newtopian

การเปรียบเทียบของคุณมีความเอนเอียงเป็น @ user102008 ชี้ให้เห็น การกำหนดตัวแปรไม่เหมือนกับการอัปเดตค่า
ericn

64

สไตล์และความคิดในการเขียนโปรแกรม Java ของคุณนั้นดี - ไม่ต้องสงสัยเลยว่ามี

ในทางกลับกันฉันพบว่ามีประโยชน์น้อยกว่ามากกับคนในท้องถิ่นและพารามิเตอร์และฉันมักจะหลีกเลี่ยงการทำเครื่องหมายเป็นขั้นสุดท้ายแม้ว่าพวกเขาจะไม่ได้รับมอบหมายอีกครั้งใน (ด้วยข้อยกเว้นที่ชัดเจนเมื่อพวกเขาต้องการใช้ในชั้นเรียน )

นี่คือเหตุผลที่คุณควรใช้finalคำหลัก คุณระบุว่าคุณรู้ว่ามันจะไม่ถูกกำหนดใหม่ แต่ไม่มีใครรู้ว่า ใช้finaldisambiguates รหัสของคุณทันทีที่น้อยมาก


8
หากวิธีการของคุณชัดเจนและทำสิ่งเดียวเท่านั้นผู้อ่านก็จะรู้เช่นกัน คำสุดท้ายทำให้โค้ดไม่สามารถอ่านได้เท่านั้น และถ้ามันไม่สามารถอ่านได้มันจะคลุมเครือกว่ามาก
eddieferetro

4
@eddieferetro ไม่เห็นด้วย คำหลักfinalระบุเจตนาซึ่งทำให้โค้ดอ่านง่ายขึ้น นอกจากนี้เมื่อคุณต้องจัดการกับรหัสโลกแห่งความจริงคุณจะสังเกตเห็นว่ามันไม่ค่อยบริสุทธิ์และชัดเจนและการเพิ่มอย่างอิสระfinalทุกที่ที่คุณสามารถช่วยให้คุณพบจุดบกพร่องและเข้าใจรหัสเดิมได้ดีขึ้น
Andres F.

4
"เจตนา" ที่ตัวแปรจะไม่มีวันเปลี่ยนแปลงและรหัสของคุณอาศัยความจริงนั้นหรือไม่? หรือเป็นเจตนา "มันเกิดขึ้นเพียงว่าตัวแปรนี้ไม่มีการเปลี่ยนแปลงดังนั้นฉันจึงทำเครื่องหมายสุดท้าย" ภายหลังเป็นเสียงที่ไร้ประโยชน์และอาจเป็นอันตราย และเนื่องจากคุณดูเหมือนจะสนับสนุนการทำเครื่องหมายคนในพื้นที่ทั้งหมดคุณกำลังทำในภายหลัง ใหญ่ -1
949300

2
finalระบุเจตนาซึ่งมักจะเป็นสิ่งที่ดีในส่วนของรหัส แต่มันมาในราคาของการเพิ่มความยุ่งเหยิงที่มองเห็น เช่นเดียวกับสิ่งส่วนใหญ่ในการเขียนโปรแกรมมีการแลกเปลี่ยนที่นี่: เป็นการแสดงให้เห็นถึงความจริงที่ว่าตัวแปรของคุณจะถูกนำมาใช้เพียงครั้งเดียวที่คุ้มค่ากับการใช้คำฟุ่มเฟื่อยเพิ่มเติมหรือไม่
christopheml

30

ข้อดีอย่างหนึ่งของการใช้final/ constทุกที่ที่เป็นไปได้คือมันช่วยลดภาระทางจิตสำหรับผู้อ่านรหัสของคุณ

เขาสามารถมั่นใจได้ว่าค่า / การอ้างอิงจะไม่เปลี่ยนแปลงในภายหลัง ดังนั้นเขาไม่จำเป็นต้องใส่ใจกับการดัดแปลงเพื่อทำความเข้าใจกับการคำนวณ

ฉันเปลี่ยนใจหลังจากเรียนรู้ภาษาการเขียนโปรแกรมที่ใช้งานได้จริง เด็กชายสิ่งที่ผ่อนคลายถ้าคุณสามารถเชื่อถือ "ตัวแปร" เพื่อเก็บค่าเริ่มต้นเสมอ


16
ใน Java finalไม่รับประกันว่าคุณจะไม่สามารถเปลี่ยนค่า / สถานะของตัวแปร (ไม่แปร) ได้ มีเพียงคุณเท่านั้นที่ไม่สามารถมอบหมายการอ้างอิงไปยังวัตถุนั้นอีกครั้งเมื่อเริ่มต้นแล้ว
PéterTörök

9
ฉันรู้ว่านั่นเป็นเหตุผลที่ฉันแยกความแตกต่างระหว่างค่าและการอ้างอิง แนวคิดนี้มีประโยชน์มากที่สุดในบริบทของโครงสร้างข้อมูลที่เปลี่ยนแปลงไม่ได้และ / หรือฟังก์ชั่นบริสุทธิ์
LennyProgrammers

7
@ PéterTörökเป็นประโยชน์ทันทีกับประเภทดั้งเดิมและค่อนข้างมีประโยชน์กับการอ้างอิงถึงวัตถุที่ไม่แน่นอน (อย่างน้อยคุณก็รู้ว่าคุณกำลังติดต่อกับวัตถุเดียวกันอยู่เสมอ!) เป็นประโยชน์อย่างมากเมื่อจัดการกับรหัสที่ออกแบบมาให้ไม่เปลี่ยนรูปตั้งแต่ต้น
Andres F.

18

ฉันพิจารณาfinalในพารามิเตอร์เมธอดและตัวแปรโลคัลเพื่อเป็นจุดรบกวนของโค้ด การประกาศเมธอด Java สามารถค่อนข้างยาว (โดยเฉพาะกับ generics) - ไม่จำเป็นต้องทำให้อีกต่อไป

หากการทดสอบหน่วยจะถูกเขียนอย่างถูกต้อง, การกำหนดค่าพารามิเตอร์ที่เป็น "อันตราย" จะถูกหยิบขึ้นมาดังนั้นจึงไม่ควรที่จริงจะมีปัญหา ความคมชัดของภาพมีความสำคัญมากกว่าการหลีกเลี่ยงไปได้ข้อผิดพลาดที่ไม่ได้หยิบขึ้นมาเพราะการทดสอบหน่วยของคุณมีความคุ้มครองไม่เพียงพอ

เครื่องมือเช่น FindBugs และ CheckStyle ที่สามารถกำหนดค่าให้ทำลายโครงสร้างได้หากการมอบหมายให้กับพารามิเตอร์หรือตัวแปรในตัวเครื่องหากคุณใส่ใจในสิ่งต่าง ๆ อย่างลึกซึ้ง

แน่นอนถ้าคุณต้องการทำให้มันเป็นที่สิ้นสุดตัวอย่างเช่นเพราะคุณกำลังใช้ค่าในคลาสที่ไม่ระบุชื่อดังนั้นก็ไม่มีปัญหานั่นเป็นวิธีการแก้ปัญหาที่ง่ายที่สุด

นอกเหนือจากผลกระทบที่ชัดเจนของการเพิ่มคำหลักพิเศษลงในพารามิเตอร์ของคุณและด้วยเหตุนี้ IMHO จึงพรางคำเหล่านั้นการเพิ่มขั้นสุดท้ายให้กับพารามิเตอร์วิธีสามารถทำให้รหัสในวิธีการเนื้อความอ่านง่ายขึ้นซึ่งทำให้รหัสแย่ลง - เป็น "ดี" รหัส จะต้องสามารถอ่านได้และเรียบง่ายที่สุดเท่าที่จะทำได้ สำหรับตัวอย่างที่วางแผนไว้บอกว่าฉันมีวิธีการที่จำเป็นต้องใช้กรณีศึกษาอย่างไม่รู้สึกตัว

โดยไม่ต้องfinal:

public void doSomething(String input) {
    input = input.toLowerCase();
    // do a few things with input
}

ง่าย สะอาด ทุกคนรู้ว่าเกิดอะไรขึ้น

ตอนนี้ด้วย 'final' ตัวเลือก 1:

public void doSomething(final String input) {
    final String lowercaseInput = input.toLowerCase();
    // do a few things with lowercaseInput
}

ในขณะที่การทำให้พารามิเตอร์finalหยุด coder การเพิ่มรหัสเพิ่มเติมจากที่คิดว่าเขากำลังทำงานกับค่าเดิมมีความเสี่ยงที่เท่าเทียมกันที่รหัสต่อไปอาจใช้inputแทนlowercaseInputซึ่งไม่ควรและที่ไม่สามารถป้องกันเพราะคุณสามารถ ' ไม่นำออกจากขอบเขต (หรือแม้กระทั่งมอบหมายnullให้inputหากสิ่งนั้นจะช่วยได้อยู่ดี)

ด้วย 'final' ตัวเลือก 2:

public void doSomething(final String input) {
    // do a few things with input.toLowerCase()
}

ตอนนี้เราเพิ่งสร้างรหัสเสียงให้มากขึ้นและนำเสนอประสิทธิภาพที่ยอดเยี่ยมในการเรียกใช้toLowerCase()n ครั้ง

ด้วย 'final' ตัวเลือก 3:

public void doSomething(final String input) {
    doSomethingPrivate(input.toLowerCase());
}

/** @throws IllegalArgumentException if input not all lower case */
private void doSomethingPrivate(final String input) {
    if (!input.equals(input.toLowerCase())) {
        throw new IllegalArgumentException("input not lowercase");
    }
    // do a few things with input
}

พูดคุยเกี่ยวกับเสียงรหัส นี่คือซากรถไฟ เรามีวิธีการใหม่ซึ่งเป็นบล็อกข้อยกเว้นที่จำเป็นเนื่องจากรหัสอื่นอาจเรียกใช้อย่างไม่ถูกต้อง ทดสอบหน่วยเพิ่มเติมเพื่อให้ครอบคลุมข้อยกเว้น ทั้งหมดเพื่อหลีกเลี่ยงหนึ่งบรรทัดที่ง่ายและ IMHO ที่เป็นที่นิยมและไม่เป็นอันตราย

นอกจากนี้ยังมีปัญหาที่วิธีการไม่ควรยาวจนคุณไม่สามารถมองเห็นได้ง่ายและรู้ได้ทันทีว่ามีการกำหนดพารามิเตอร์ให้

ฉันคิดว่ามันเป็นแนวปฏิบัติที่ดี / ถ้าคุณกำหนดให้กับพารามิเตอร์ที่คุณทำมันทุกต้นในวิธีการโดยเฉพาะอย่างยิ่งบรรทัดแรกหรือตรงหลังจากการตรวจสอบอินพุตขั้นพื้นฐานอย่างมีประสิทธิภาพแทนที่มันสำหรับวิธีการทั้งหมดซึ่งมีผลสอดคล้องภายใน วิธี. ผู้อ่านรู้ว่าคาดหวังว่าการมอบหมายใด ๆ จะชัดเจน (ใกล้กับการประกาศลายเซ็น) และในสถานที่ที่สอดคล้องกันซึ่งช่วยลดปัญหาที่เพิ่มสุดท้ายพยายามหลีกเลี่ยงอย่างมาก ที่จริงฉันไม่ค่อยได้กำหนดพารามิเตอร์ แต่ถ้าฉันทำฉันมักจะทำที่ด้านบนของวิธีการ


โปรดทราบว่าfinalในตอนแรกไม่ได้ปกป้องคุณอย่างที่คุณเห็น

public void foo(final Date date) {
    date.setTime(0); 
    // code that uses date
}

final ไม่ได้ป้องกันคุณอย่างสมบูรณ์เว้นแต่ประเภทพารามิเตอร์จะเป็นแบบดั้งเดิมหรือไม่เปลี่ยนรูป


ในกรณีสุดท้ายfinalให้การรับประกันบางส่วนที่คุณกำลังเผชิญกับDateอินสแตนซ์เดียวกัน การค้ำประกันบางอย่างดีกว่าไม่มีอะไร (ถ้ามีอะไรคุณกำลังเถียงกันว่าคลาสที่ไม่เปลี่ยนรูปแบบตั้งแต่แรกเริ่ม) ในกรณีใด ๆ ในทางปฏิบัติสิ่งที่คุณพูดมากควรส่งผลกระทบต่อภาษาที่ไม่เปลี่ยนรูปแบบโดยเริ่มต้นที่มีอยู่ แต่มันไม่ได้หมายความว่ามันไม่ใช่ปัญหา
Andres F.

+1 สำหรับการชี้ไปที่ความเสี่ยง "รหัสต่อไปอาจใช้อินพุต" ยังฉันต้องการพารามิเตอร์ของฉันfinalเป็นไปได้ที่จะทำให้พวกเขาไม่สุดท้ายสำหรับกรณีดังกล่าวข้างต้น มันไม่คุ้มค่าที่จะสแปมลายเซ็นด้วยคำหลัก
maaartinus

7

ฉันปล่อยให้ eclipse วางไว้ข้างfinalหน้าตัวแปรแต่ละตัวเนื่องจากฉันคิดว่ามันจะทำให้โปรแกรมอ่านง่ายขึ้น ฉันไม่ได้ใช้พารามิเตอร์เนื่องจากฉันต้องการให้รายการพารามิเตอร์สั้นที่สุดเท่าที่จะเป็นไปได้โดยควรมีขนาดพอดีในหนึ่งบรรทัด


2
นอกจากนี้สำหรับพารามิเตอร์คุณสามารถให้คอมไพเลอร์ออกคำเตือนหรือข้อผิดพลาดหากกำหนดพารามิเตอร์ไว้
oberlies

3
ค่อนข้างตรงข้ามฉันพบว่าอ่านยากขึ้นเพราะเพียงแค่ตัดโค้ด
Steve Kuo

3
@Steve Kuo: มันทำให้ฉันสามารถค้นหาตัวแปรทั้งหมดได้อย่างรวดเร็ว ไม่ได้กำไรมาก แต่เมื่อรวมกับการป้องกันการมอบหมายโดยไม่ได้ตั้งใจมันมีค่า 6 ตัวอักษร ฉันจะมีความสุขมากขึ้นถ้ามีบางอย่างที่เหมือนกับvarการทำเครื่องหมายตัวแปรที่ไม่สิ้นสุด YMMV
maaartinus

1
@maaartinus เห็นด้วยกับvar! น่าเสียดายที่มันไม่ใช่ค่าเริ่มต้นใน Java และตอนนี้มันก็สายเกินไปที่จะเปลี่ยนภาษา ดังนั้นฉันยินดีที่จะทนกับความไม่สะดวกเล็ก ๆ น้อย ๆ ของการเขียนfinal:)
Andres F.

1
@maaartinus เห็นด้วย ฉันพบว่าประมาณ 80-90% ของตัวแปร (ในเครื่อง) ของฉันไม่จำเป็นต้องแก้ไขหลังจากการเริ่มต้น ดังนั้น 80-90% ของพวกเขามีfinalคำหลักใช้ได้ขณะที่มีเพียง 10-20% จะต้องมีvar...
JimmyB
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.