เหตุใดฉันจึงควรใช้คำว่า“ final” บนพารามิเตอร์ method ใน Java?


381

ฉันไม่เข้าใจว่าfinalคำหลักนั้นมีประโยชน์จริง ๆเมื่อใช้กับพารามิเตอร์ของวิธี

ถ้าเราไม่รวมการใช้คลาสที่ไม่ระบุชื่อการอ่านและการประกาศเจตนานั้นดูเหมือนว่าจะไร้ค่าสำหรับฉัน

การบังคับใช้ว่าข้อมูลบางอย่างยังคงไม่คงที่เท่าที่ควร

  • หากพารามิเตอร์เป็นแบบดั้งเดิมมันจะไม่มีผลกระทบเนื่องจากพารามิเตอร์ถูกส่งผ่านไปยังวิธีการเป็นค่าและการเปลี่ยนแปลงมันจะไม่มีผลนอกขอบเขต

  • หากเราส่งพารามิเตอร์โดยการอ้างอิงตัวอ้างอิงเองนั้นเป็นตัวแปรในตัวเครื่องและหากการอ้างอิงนั้นเปลี่ยนจากภายในเมธอดนั่นจะไม่มีผลกระทบใด ๆ จากภายนอกขอบเขตของเมธอด

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

public void testNullify() {
    Collection<Integer> c  = new ArrayList<Integer>();      
    nullify(c);
    assertNotNull(c);       
    final Collection<Integer> c1 = c;
    assertTrue(c1.equals(c));
    change(c);
    assertTrue(c1.equals(c));
}

private void change(Collection<Integer> c) {
    c = new ArrayList<Integer>();
}

public void nullify(Collection<?> t) {
    t = null;
}

101
หนึ่งจุดด่วนเกี่ยวกับคำศัพท์ - Java ไม่มีการอ้างอิงผ่านเลย มันมีรหัสผ่านอ้างอิงตามมูลค่าซึ่งไม่เหมือนกัน ด้วยความหมายของการส่งผ่านจริงโดยอ้างอิงผลของรหัสของคุณจะแตกต่างกัน
Jon Skeet

6
ความแตกต่างระหว่างการส่งโดยอ้างอิงและการอ้างอิงผ่านตามมูลค่าคืออะไร
NobleUplift

เป็นการง่ายกว่าที่จะอธิบายความแตกต่างในบริบท C (อย่างน้อยสำหรับฉัน) ถ้าฉันส่งตัวชี้ไปยังวิธีเช่น: <code> int foo (int bar) </code> แสดงว่าตัวชี้นั้นถูกส่งผ่านโดยค่า ความหมายมันถูกคัดลอกดังนั้นถ้าฉันทำบางสิ่งภายในวิธีการเช่น <code> ฟรี (แถบ); bar = malloc (... ); </code> จากนั้นฉันเพิ่งทำสิ่งที่ไม่ดีจริงๆ การโทรฟรีจะทำให้หน่วยความจำที่ถูกชี้ไปนั้นว่างเปล่า (ดังนั้นสิ่งที่ตัวชี้ที่ฉันผ่านเข้าไปตอนนี้ก็ห้อยอยู่) อย่างไรก็ตาม <code> int foo (int & bar) </bar> หมายความว่ารหัสนั้นถูกต้องและค่าของตัวชี้ที่ส่งผ่านจะถูกเปลี่ยน
jerslan

1
คนแรกที่ควรจะเป็นและคนสุดท้ายint foo(int* bar) int foo(int* &bar)หลังกำลังส่งตัวชี้โดยการอ้างอิงตัวเก่ากำลังผ่านตัวอ้างอิงโดยค่า
jerslan

2
@ มาร์ตินในความคิดของฉันมันเป็นคำถามที่ดี; ดูชื่อเรื่องของคำถามและเนื้อหาโพสต์เป็นคำอธิบายว่าทำไมคำถามจึงถูกถาม บางทีฉันกำลังเข้าใจผิดกฎที่นี่ แต่ตรงนี้เป็นคำถามที่ผมอยากเมื่อค้นหา"การใช้พารามิเตอร์สุดท้ายในวิธีการ"
Victor Zamanian

คำตอบ:


239

หยุดการมอบหมายตัวแปรใหม่

แม้ว่าคำตอบเหล่านี้น่าสนใจ แต่ฉันไม่ได้อ่านคำตอบสั้น ๆ ง่ายๆ:

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

ไม่ว่าจะเป็นตัวแปรที่เป็นตัวแปรสแตติกตัวแปรสมาชิกตัวแปรโลคัลหรือตัวแปรอาร์กิวเมนต์ / พารามิเตอร์ผลจะเหมือนกันทั้งหมด

ตัวอย่าง

เรามาดูผลของการกระทำกัน

พิจารณาวิธีการง่ายๆนี้ซึ่งทั้งสองตัวแปร ( หาเรื่องและx ) สามารถกำหนดวัตถุที่แตกต่างกันได้อีกครั้ง

// Example use of this method: 
//   this.doSomething( "tiger" );
void doSomething( String arg ) {
  String x = arg;   // Both variables now point to the same String object.
  x = "elephant";   // This variable now points to a different String object.
  arg = "giraffe";  // Ditto. Now neither variable points to the original passed String.
}

ทำเครื่องหมายตัวแปรท้องถิ่นเป็นครั้งสุดท้าย ซึ่งส่งผลให้เกิดข้อผิดพลาดของคอมไพเลอร์

void doSomething( String arg ) {
  final String x = arg;  // Mark variable as 'final'.
  x = "elephant";  // Compiler error: The final local variable x cannot be assigned. 
  arg = "giraffe";  
}

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

void doSomething( final String arg ) {  // Mark argument as 'final'.
  String x = arg;   
  x = "elephant"; 
  arg = "giraffe";  // Compiler error: The passed argument variable arg cannot be re-assigned to another object.
}

นิทานสอนใจ:

ถ้าคุณต้องการเพื่อให้แน่ใจว่าตัวแปรเสมอจุดไปที่วัตถุเดียวกันทำเครื่องหมายตัวแปรสุดท้าย

ไม่ต้องกำหนดอาร์กิวเมนต์ใหม่

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

ในการหวนกลับ

ดังที่ได้กล่าวไว้ในคำตอบอื่น ๆ ... เนื่องจากเป้าหมายการออกแบบดั้งเดิมของ Java ในการช่วยเหลือโปรแกรมเมอร์เพื่อหลีกเลี่ยงข้อผิดพลาดที่เป็นใบ้เช่นการอ่านที่ผ่านมาในตอนท้ายของอาร์เรย์ Java ควรได้รับการออกแบบมาเพื่อบังคับใช้พารามิเตอร์ / อาร์กิวเมนต์ตัวแปรทั้งหมดโดยอัตโนมัติ ในคำอื่น ๆข้อโต้แย้งที่ไม่ควรจะเป็นตัวแปร แต่ปัญหาหลังวิสัยทัศน์คือ 20/20 วิสัยทัศน์และนักออกแบบ Java มีมือเต็มในเวลา

ดังนั้นเพิ่มfinalอาร์กิวเมนต์ทั้งหมดเสมอหรือไม่

เราควรจะเพิ่มfinalพารามิเตอร์ method แต่ละอันและทุกๆอันที่ถูกประกาศ?

  • ในทางทฤษฎีใช่
  • ในทางปฏิบัติไม่
    ➥เพิ่มfinalเฉพาะเมื่อโค้ดของเมธอดยาวหรือซับซ้อนซึ่งการโต้แย้งอาจทำให้เข้าใจผิดว่าเป็นตัวแปรโลคอลหรือสมาชิกและอาจได้รับมอบหมายอีกครั้ง

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

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

เพิ่มอีกกรณีหนึ่งเพื่อความสมบูรณ์

public class MyClass {
    private int x;
    //getters and setters
}

void doSomething( final MyClass arg ) {  // Mark argument as 'final'.

   arg =  new MyClass();  // Compiler error: The passed argument variable arg  cannot be re-assigned to another object.

   arg.setX(20); // allowed
  // We can re-assign properties of argument which is marked as final
 }

23
"ตามแนวทางปฏิบัติในการเขียนโปรแกรมที่ดี (ในภาษาใด ๆ ) คุณไม่ควรกำหนดตัวแปร / อาร์กิวเมนต์ของพารามิเตอร์ [.. ]" อีกครั้งขออภัยฉันต้องโทรหาคุณในที่นี้ การกำหนดอาร์กิวเมนต์ใหม่เป็นแนวปฏิบัติมาตรฐานในภาษาเช่น Javascript ซึ่งจำนวนอาร์กิวเมนต์ที่ส่งผ่าน (หรือแม้ว่าจะมีการส่งผ่านใด ๆ ) จะไม่ถูกกำหนดโดยลายเซ็นเมธอด เช่นได้รับลายเซ็นดังนี้: "function say (msg)" ผู้ใช้จะต้องแน่ใจว่าได้กำหนดอาร์กิวเมนต์ 'msg' ไว้เช่น: "msg = msg || 'Hello World!'; โปรแกรมเมอร์ Javascript ที่ดีที่สุดในโลกกำลังฝ่าฝืนแนวปฏิบัติที่ดีของคุณ เพียงอ่านแหล่ง jQuery
Stijn de Witt

37
@StijndeWitt ตัวอย่างของคุณแสดงปัญหาอย่างมากในการกำหนดตัวแปรอาร์กิวเมนต์ใหม่ คุณสูญเสียข้อมูลโดยไม่ได้รับผลตอบแทนใด: (ก) คุณสูญเสียค่าเดิมที่ส่งผ่านแล้ว (ข) คุณสูญเสียความตั้งใจในวิธีการโทร (ผู้โทรส่ง 'Hello World!' หรือเราเป็นผู้เริ่มต้น) ทั้ง & & b มีประโยชน์สำหรับการทดสอบโค้ดแบบยาวและเมื่อมีการเปลี่ยนแปลงค่าเพิ่มเติมในภายหลัง ฉันยืนโดยคำพูดของฉัน: vars arg ไม่ควรกำหนดใหม่ รหัสของคุณควรเป็น: message = ( msg || 'Hello World"' ). ไม่มีเหตุผลที่จะไม่ใช้ var แยกกัน ค่าใช้จ่ายเพียงอย่างเดียวคือหน่วยความจำไม่กี่ไบต์
Basil Bourque

9
@Basil: มันเป็นรหัสเพิ่มเติม (เป็นไบต์) และใน Javascript ที่นับ หนัก เช่นเดียวกับหลาย ๆ สิ่งมันมีพื้นฐานมาจากความเห็น เป็นไปได้ทั้งหมดที่จะเพิกเฉยต่อการเขียนโปรแกรมนี้และยังคงเขียนโค้ดที่ยอดเยี่ยม แบบฝึกหัดการเขียนโปรแกรมของคนคนหนึ่งไม่ได้ทำให้การปฏิบัติของทุกคน เตรียมพร้อมทุกอย่างที่คุณต้องการฉันเลือกที่จะเขียนมันต่างออกไป นั่นทำให้ฉันเป็นโปรแกรมเมอร์ที่ไม่ดีหรือรหัสของฉันไม่ดี?
Stijn de Witt

14
โดยใช้ความเสี่ยงฉันในภายหลังโดยไม่ได้ตั้งใจใช้message = ( msg || 'Hello World"' ) msgเมื่อสัญญาที่ฉันตั้งใจคือ "พฤติกรรมที่ไม่มี / โมฆะ / ไม่ได้ระบุ ARG จะแยกไม่ออกจากการผ่าน"Hello World"" มันเป็นวิธีการเขียนโปรแกรมที่ดีที่จะกระทำในช่วงต้นของฟังก์ชั่น [สิ่งนี้สามารถทำได้โดยไม่ต้องกำหนดใหม่โดยเริ่มต้นด้วยif (!msg) return myfunc("Hello World");แต่ที่ไม่ได้โต้แย้งกับหลายอาร์กิวเมนต์] ในกรณีที่หายากที่ตรรกะในฟังก์ชั่นควรสนใจว่าค่าเริ่มต้นถูกใช้หรือไม่ฉันควรกำหนดค่า Sentinel พิเศษ .
Beni Cherniavsky-Paskin

6
@ BeniCherniavsky-ร์กพาสกินความเสี่ยงที่คุณอธิบายเป็นเพียงเพราะความคล้ายคลึงกันระหว่างและmessage msgแต่ถ้าเขาจะเรียกมันว่าอะไรprocessedMsgอย่างอื่นที่ให้บริบทเพิ่มเติมโอกาสของความผิดพลาดก็จะต่ำกว่ามาก มุ่งเน้นสิ่งที่เขาพูดไม่ได้อยู่ที่ "อย่างไร" เขาพูด ;)
alfasin

230

บางครั้งมันก็ดีที่จะชัดเจน (เพื่อให้สามารถอ่านได้) ซึ่งตัวแปรไม่เปลี่ยนแปลง นี่คือตัวอย่างง่ายๆที่การใช้finalสามารถบันทึกอาการปวดหัวที่เป็นไปได้:

public void setTest(String test) {
    test = test;
}

หากคุณลืมคำหลัก 'this' ใน setter ดังนั้นตัวแปรที่คุณต้องการตั้งค่าจะไม่ได้รับการตั้งค่า อย่างไรก็ตามหากคุณใช้finalคำหลักในพารามิเตอร์แล้วข้อผิดพลาดจะถูกดักจับในเวลารวบรวม


65
btw คุณจะเห็นคำเตือน "การกำหนดให้การทดสอบตัวแปรไม่มีผล" ต่อไป
AvrDragon

12
@AvrDragon แต่เราอาจเพิกเฉยต่อคำเตือนเช่นกัน ดังนั้นจะดีกว่าเสมอถ้ามีบางอย่างที่จะหยุดเราไม่ให้ไปไกลกว่านี้เช่นข้อผิดพลาดในการรวบรวมซึ่งเราจะได้รับโดยใช้คำหลักสุดท้าย
Sumit Desai

10
@AvrDragon ขึ้นอยู่กับสภาพแวดล้อมการพัฒนา คุณไม่ควรพึ่ง IDE ในการจับสิ่งนี้ให้กับคุณเว้นแต่คุณจะต้องการพัฒนานิสัยที่ไม่ดี
b1nary.atr0phy

25
@ b1naryatr0phy จริงๆแล้วมันเป็นคำเตือนของคอมไพเลอร์ไม่ใช่แค่ IDE-tip
AvrDragon

8
@SumitDesai "แต่เราอาจเพิกเฉยต่อคำเตือนเช่นกันดังนั้นมันจะดีกว่าเสมอถ้ามีบางอย่างที่จะหยุดเราไม่ให้ทำอะไรต่อไปเช่นข้อผิดพลาดในการรวบรวมซึ่งเราจะได้รับโดยใช้คำหลักสุดท้าย" ฉันใช้จุดของคุณ แต่นี่เป็นคำสั่งที่แข็งแกร่งมากที่ฉันคิดว่านักพัฒนา Java จำนวนมากจะไม่เห็นด้วย คำเตือนของคอมไพเลอร์มีเหตุผลและผู้พัฒนาที่มีความสามารถไม่ควรมีข้อผิดพลาดในการ 'บังคับ' พวกเขาให้พิจารณาผลกระทบของมัน
Stuart Rossiter

127

ใช่ไม่รวมคลาสที่ไม่ระบุชื่อการอ่านและการประกาศเจตนามันเกือบจะไร้ค่า สามสิ่งนั้นไร้ค่าไหม?

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

จุดของคุณจะมีความสำคัญมากขึ้นถ้าทุกคนเป็นจริงอ้างว่ามันไม่เก็บข้อมูลอย่างต่อเนื่องในลักษณะที่ว่ามันไม่ได้ - แต่ฉันไม่สามารถจำได้ว่าเห็นการเรียกร้องใด ๆ ดังกล่าว คุณกำลังแนะนำว่ามีกลุ่มนักพัฒนาที่สำคัญแนะนำว่าfinalมีผลกระทบมากกว่าที่เป็นจริงหรือไม่?

แก้ไข: ฉันควรสรุปทั้งหมดนี้ด้วยการอ้างอิง Monty Python; คำถามดูเหมือนจะค่อนข้างคล้ายกับการถามว่า "ชาวโรมันทำอะไรให้เราบ้าง?"


17
แต่การถอดความครัสตี้กับชาวเดนมาร์กของเขาพวกเขาทำอะไรให้เราอย่างLATELY ? =)
James Schek

Yuval มันสนุก! ฉันเดาว่าความสงบสามารถเกิดขึ้นได้แม้ว่ามันจะถูกบังคับด้วยดาบ!
gonzobrains

1
คำถามดูเหมือนคล้ายกันมากขึ้นกับการถามว่า " ชาวโรมันยังไม่ได้ทำอะไรให้เรา" เพราะมันเป็นคำวิจารณ์มากกว่าสิ่งที่คำหลักสุดท้ายไม่ได้ทำ
NobleUplift

"คุณกำลังแนะนำว่ามีกลุ่มนักพัฒนาที่สำคัญแนะนำว่าขั้นตอนสุดท้ายมีผลกระทบมากกว่าที่เป็นจริงหรือไม่" สำหรับฉันที่เป็นปัญหาหลัก: ฉันสงสัยอย่างยิ่งว่าสัดส่วนที่สำคัญของนักพัฒนาที่ใช้มันคิดว่ามันบังคับใช้ไม่ได้กับรายการที่ส่งผ่านของผู้โทรเมื่อไม่ได้ใช้ แน่นอนหนึ่งในนั้นถูกดึงเข้าสู่การอภิปรายว่ามาตรฐานการเข้ารหัสควร 'ป้องกัน' ความเข้าใจผิดทางแนวคิด (ซึ่งนักพัฒนา 'ที่มีความสามารถ' ควรระวัง) หรือไม่ (และสิ่งนี้จะมุ่งสู่ความคิดเห็นนอกขอบเขต คำถามประเภท)!
Stuart Rossiter

1
@SarthakMittal: ค่าจะไม่ถูกคัดลอกจนกว่าคุณจะใช้มันจริง ๆ ถ้านั่นคือสิ่งที่คุณสงสัย
Jon Skeet

76

ผมขออธิบายเกี่ยวกับกรณีที่คุณต้องใช้ขั้นตอนสุดท้ายซึ่งจอนพูดถึงแล้ว:

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

public Iterator<Integer> createIntegerIterator(final int from, final int to)
{
    return new Iterator<Integer>(){
        int index = from;
        public Integer next()
        {
            return index++;
        }
        public boolean hasNext()
        {
            return index <= to;
        }
        // remove method omitted
    };
}

ที่นี่fromและtoพารามิเตอร์จะต้องเป็นที่สิ้นสุดเพื่อให้สามารถใช้ภายในคลาสที่ไม่ระบุชื่อ

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

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


1
คุณสูญเสียฉันไปจาก "แต่ถ้าพวกเขายังไม่จบ .... " คุณช่วยลองใช้ถ้อยคำใหม่ได้มั้ยบางทีฉันอาจจะมีคนทำกาแฟไม่พอ
hhafez

1
คุณมีตัวแปรในตัวจาก - คำถามคือสิ่งที่เกิดขึ้นถ้าคุณใช้อินสแตนซ์ของคลาส anon ภายในเมธอดและเปลี่ยนค่าจาก - คนคาดว่าจะเห็นการเปลี่ยนแปลงในเมธอดเนื่องจากพวกเขาเห็นตัวแปรเพียงตัวเดียว เพื่อหลีกเลี่ยงความสับสนนี้จะต้องเป็นที่สิ้นสุด
Michael Borgwardt

มันไม่ได้ทำสำเนามันเป็นเพียงการอ้างอิงถึงสิ่งที่ถูกอ้างอิง
vickirk

1
@ vickirk: ให้แน่ใจว่าได้ทำสำเนา - ของการอ้างอิงในกรณีของประเภทการอ้างอิง
Michael Borgwardt

Btw สมมติว่าเราไม่มีคลาสนิรนามอ้างอิงตัวแปรเหล่านั้นคุณทราบหรือไม่ว่ามีความแตกต่างระหว่างfinalพารามิเตอร์ฟังก์ชันและพารามิเตอร์ที่ไม่ใช่ฟังก์ชันสุดท้ายในสายตาของ HotSpot หรือไม่
Pacerier

25

ฉันใช้ขั้นสุดท้ายตลอดเวลากับพารามิเตอร์

มันเพิ่มมากขึ้นหรือไม่ ไม่ได้จริงๆ

ฉันจะปิดหรือไม่ เลขที่

เหตุผล: ฉันพบข้อบกพร่อง 3 ข้อที่ผู้คนเขียนโค้ดเลอะเทอะและไม่สามารถตั้งค่าตัวแปรสมาชิกใน accessors ข้อบกพร่องทั้งหมดพิสูจน์แล้วว่าหายาก

ฉันต้องการดูสิ่งนี้ทำให้ค่าเริ่มต้นใน Java รุ่นในอนาคต การผ่านสิ่ง / ค่าอ้างอิงทำให้ผู้เขียนโปรแกรมรุ่นเยาว์จำนวนมากแย่มาก

อีกอย่าง .. วิธีการของฉันมักจะมีพารามิเตอร์จำนวนน้อยดังนั้นข้อความพิเศษในการประกาศเมธอดไม่ใช่ปัญหา


4
ฉันกำลังจะแนะนำสิ่งนี้เช่นกันว่าสุดท้ายจะเป็นค่าเริ่มต้นในเวอร์ชันในอนาคตและคุณต้องระบุคำหลัก "ไม่แน่นอน" หรือดีกว่าที่คิดไว้ นี่เป็นบทความที่ดีเกี่ยวกับเรื่องนี้: lpar.ath0.com/2008/08/26/java-annoyance-final-parameters
Jeff Axelrod

เป็นเวลานานแล้ว แต่คุณสามารถให้ตัวอย่างของข้อผิดพลาดที่คุณจับได้หรือไม่?
user949300

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

18

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


2
แน่นอนว่ามันไม่ได้เป็นส่วนหนึ่งของฟังก์ชั่นการใช้งานเพียงอย่างเดียว มันสับสนที่ java อนุญาตให้ (แต่ไม่ใส่ใจ) finalเกี่ยวกับพารามิเตอร์ในการประกาศ interface / abstract
Beni Cherniavsky-Paskin

8

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

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

แน่นอนฉันต้องการสิ่งที่แข็งแกร่งเช่น C / C ++ const


ไม่แน่ใจว่าการอ้างอิง IDE และเครื่องมือนั้นใช้ได้กับการโพสต์ OP หรือหัวข้อ คือ "ขั้นสุดท้าย" คือการตรวจสอบเวลาแบบคอมไพล์ซึ่งการอ้างอิงนั้นไม่ได้เปลี่ยนแปลง / มีการเปลี่ยนแปลง นอกจากนี้เพื่อบังคับใช้สิ่งต่าง ๆ เหล่านี้อย่างแท้จริงดูคำตอบเกี่ยวกับการป้องกันไม่ให้สมาชิกเด็กของการอ้างอิงขั้นสุดท้าย เมื่อสร้าง API เช่นการใช้ IDE หรือเครื่องมือจะไม่ช่วยให้บุคคลภายนอกใช้ / ขยายรหัสดังกล่าว
Darrell Teague

4

เนื่องจาก Java ส่งสำเนาของข้อโต้แย้งฉันรู้สึกว่าความเกี่ยวข้องของfinalค่อนข้าง จำกัด ฉันเดานิสัยมาจากซี ++ const char const *ยุคที่คุณสามารถห้ามเนื้อหาอ้างอิงจากการเปลี่ยนแปลงด้วยการทำ ฉันรู้สึกว่าของประเภทนี้ทำให้คุณเชื่อว่านักพัฒนานั้นโง่โดยเนื้อแท้ในฐานะ f *** และจำเป็นต้องได้รับการปกป้องจากตัวละครทุกตัวที่เขาพิมพ์ ในความถ่อมใจทั้งหมดฉันอาจพูดว่าฉันเขียนข้อบกพร่องน้อยมากแม้ว่าฉันจะละเว้นfinal(เว้นแต่ฉันไม่ต้องการให้ใครบางคนแทนที่วิธีการและชั้นเรียนของฉัน) บางทีฉันอาจเป็นแค่นักพัฒนาคนเก่า


1

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

private String getString(String A, int i, String B, String C)
{
    if (i > 0)
        A += B;

    if (i > 100)
        A += C;

    return A;
}

เล่นเป็นผู้สนับสนุนของปีศาจเกิดอะไรขึ้นกับการทำเช่นนี้?


จงระวังให้แยกความแตกต่าง IDE จากรันไทม์ JVM สิ่งที่ IDE ทำนั้นไม่เกี่ยวข้องเมื่อโค้ดไบต์ที่คอมไพล์รันบนเซิร์ฟเวอร์เว้นแต่ว่า IDE จะเพิ่มโค้ดเพื่อป้องกันการเปลี่ยนแปลงตัวแปรสมาชิกเช่นข้อบกพร่องในรหัสเมื่อมันตั้งใจว่าตัวแปรไม่ควรถูกกำหนดใหม่ แต่ผิดพลาด - ดังนั้นวัตถุประสงค์ของ คำหลักสุดท้าย
ดาร์เรล Teague

1

คำตอบสั้น ๆ : finalช่วยนิด ๆ หน่อย ๆ แต่ ... ใช้การเขียนโปรแกรมเชิงป้องกันในฝั่งไคลเอ็นต์แทน

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


2
"ปัญหาสุดท้ายคือมันบังคับให้มีการอ้างอิงเท่านั้นที่ไม่มีการเปลี่ยนแปลง" - Untrue, Java ป้องกันตัวเอง ตัวแปรที่ส่งผ่านไปยังเมธอดไม่สามารถเปลี่ยนการอ้างอิงได้โดยเมธอดนั้น
Madbreaks

โปรดทำการวิจัยก่อนโพสต์ ... stackoverflow.com/questions/40480/…
Darrell Teague

พูดง่ายๆก็คือถ้ามันเป็นความจริงที่การอ้างอิงไปยังการอ้างอิงไม่สามารถเปลี่ยนแปลงได้จะไม่มีการอภิปรายเกี่ยวกับการป้องกันการคัดลอกการเปลี่ยนไม่ได้ไม่จำเป็นสำหรับคำหลักสุดท้าย ฯลฯ
Darrell Teague

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

Downvoting เนื่องจาก op ถามว่าเพราะเหตุใดจึงใช้ขั้นสุดท้ายและคุณให้เหตุผลที่ไม่ถูกต้องเพียงข้อเดียว
Madbreaks

0

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

อย่างไรก็ตามโดยทั่วไปแล้วฉันลบพวกเขาเป็นฟุ่มเฟือยในตอนท้ายของการปรับโครงสร้าง


-1

ติดตามสิ่งที่โพสต์ของ Michel ฉันเองทำให้ตัวเองอีกตัวอย่างเพื่ออธิบาย ฉันหวังว่ามันจะช่วยได้

public static void main(String[] args){
    MyParam myParam = thisIsWhy(new MyObj());
    myParam.setArgNewName();

    System.out.println(myParam.showObjName());
}

public static MyParam thisIsWhy(final MyObj obj){
    MyParam myParam = new MyParam() {
        @Override
        public void setArgNewName() {
            obj.name = "afterSet";
        }

        @Override
        public String showObjName(){
            return obj.name;
        }
    };

    return myParam;
}

public static class MyObj{
    String name = "beforeSet";
    public MyObj() {
    }
}

public abstract static class MyParam{
    public abstract void setArgNewName();
    public abstract String showObjName();
}

จากรหัสข้างต้นในวิธีการthisIsWhy ()เราจริงไม่ได้กำหนด[MyObj อาร์กิวเมนต์ obj]ไปอ้างอิงจริงใน MyParam ในทางกลับกันเราเพียงแค่ใช้[อาร์กิวเมนต์ MyObj obj]ในวิธีการภายใน MyParam

แต่หลังจากที่เราเสร็จสิ้นวิธีการนี้is (ทำไม) , อาร์กิวเมนต์ (วัตถุ) ควร MyObj ยังคงอยู่?

ดูเหมือนว่ามันควรจะเป็นเพราะเราสามารถมองเห็นในหลักที่เรายังคงเรียกวิธีการ showObjName ()และจะต้องไปถึงobj MyParam จะยังคงใช้ / ถึงอาร์กิวเมนต์ของวิธีการแม้ว่าวิธีการจะกลับมาแล้ว!

วิธีการที่ Java บรรลุผลอย่างแท้จริงคือการสร้างสำเนาและการอ้างอิงอาร์กิวเมนต์ที่ซ่อนอยู่ของMyObj objภายในวัตถุ MyParam (แต่ไม่ใช่เขตข้อมูลอย่างเป็นทางการใน MyParam เพื่อที่เราจะไม่เห็น)

ในขณะที่เราเรียก "showObjName" มันจะใช้การอ้างอิงนั้นเพื่อรับค่าที่สอดคล้องกัน

แต่ถ้าเราไม่ได้ใส่สุดท้ายอาร์กิวเมนต์ซึ่งนำไปสู่สถานการณ์ที่เราสามารถกำหนดหน่วยความจำใหม่ (วัตถุ) เพื่อโต้แย้ง MyObj obj

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

  1. ตอนนี้เรามี [MyObj obj] ที่ซ่อนอยู่ชี้ไปที่ [Memory A in heap] ตอนนี้อยู่ในวัตถุ MyParam
  2. นอกจากนี้เรายังมี [MyObj obj] ซึ่งเป็นข้อโต้แย้งไปยัง [Memory B ในกอง] ตอนนี้อาศัยอยู่ในวิธีการนี้เป็นเพราะเหตุใด

ไม่มีการปะทะ แต่"มั่นใจ !!" เพราะพวกเขาจะทั้งหมดที่ใช้เหมือนกัน "ชื่ออ้างอิง" ซึ่งก็คือ "obj"

เพื่อหลีกเลี่ยงปัญหานี้ให้ตั้งเป็น "ขั้นสุดท้าย" เพื่อหลีกเลี่ยงโปรแกรมเมอร์ทำรหัส "ผิดพลาดได้ง่าย"

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.