การทำให้อาร์กิวเมนต์ของเมธอด java เป็นขั้นสุดท้าย


92

อะไรคือความแตกต่างfinalระหว่างโค้ดด้านล่าง มีข้อได้เปรียบในการประกาศอาร์กิวเมนต์เป็นfinal.

public String changeTimezone( Timestamp stamp, Timezone fTz, Timezone toTz){  
    return ....
}

public String changeTimezone(final Timestamp stamp, final Timezone fTz, 
        final Timezone toTz){
    return ....
}

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


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

คำตอบ:


131

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

สิ่งนี้ช่วยให้คุณไม่ต้องประกาศตัวแปรสุดท้ายในเครื่องอื่นในเนื้อความของวิธีการ:

 void m(final int param) {
        new Thread(new Runnable() {
            public void run() {
                System.err.println(param);
            }
        }).start();
    }

27
1: นี่คือกรณีการใช้งานที่สำคัญและเพียงครั้งเดียวเมื่อคุณต้องการมัน (เวลาที่เหลือเป็นเพียงเรื่องของความสะดวกในการช่วยเหลือโปรแกรมเมอร์)
Donal Fellows

3
ฉันขอเหตุผลเบื้องหลังเรื่องนี้ได้ไหม
KodeWarrior

21
ไม่จำเป็นอีกต่อไปด้วย Java 8
Amit Parashar

3
@simgineer อ่านที่นี่: stackoverflow.com/questions/28408109/…
PeterMmm

3
@AmitParashar True แต่ Java 8 ช่วยให้คุณไม่ต้องใช้คีย์เวิร์ด "final" ทุกครั้งที่คุณต้องใช้ตัวแปรในคลาสภายใน ... ความจริงก็คือคอมไพเลอร์เป็นเพียงการสร้างความสมบูรณ์โดยนัยคุณยัง ต้องการให้ตัวแปรเป็นขั้นสุดท้ายอย่างมีประสิทธิภาพ ... ดังนั้นคุณยังคงได้รับข้อผิดพลาดเวลาคอมไพล์ในกรณีที่คุณพยายามกำหนดให้ในภายหลัง! Java 8 : SNEAK 100 :)
varun

38

แยกจากคำสุดท้ายของคำหลักสุดท้าย

พารามิเตอร์สุดท้าย

ตัวอย่างต่อไปนี้ประกาศพารามิเตอร์สุดท้าย:

public void doSomething(final int i, final int j)
{
  // cannot change the value of i or j here...
  // any change would be visible only inside the method...
}

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

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


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

27

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

ต้องยอมรับว่าฉันจำไม่ค่อยได้ว่าใช้ final สำหรับพารามิเตอร์บางทีฉันควร

public int example(final int basicRate){
    int discountRate;

    discountRate = basicRate - 10;
    // ... lots of code here 
    if ( isGoldCustomer ) {
        basicRate--;  // typo, we intended to say discountRate--, final catches this
    }
    // ... more code here

    return discountRate;
}

1
ตัวอย่างที่ดีว่าการประกาศอาร์กิวเมนต์เป็นขั้นสุดท้ายมีประโยชน์อย่างไร ฉันเป็นส่วนหนึ่งของสิ่งนี้ แต่ก็เป็นคำหนึ่งสำหรับพารามิเตอร์ 3+
JoseHdez_2

14

มันไม่ได้สร้างความแตกต่างมากมาย หมายความว่าคุณไม่สามารถเขียนได้:

stamp = null;
fTz = new ...;

แต่คุณยังสามารถเขียน:

stamp.setXXX(...);
fTz.setXXX(...);

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


3

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


1
ฉันต้องเพิ่มบางอย่าง: ถ้าพารามิเตอร์เป็นแบบดั้งเดิมฉันไม่เห็นความแตกต่างใด ๆ นอกจากนี้หากพารามิเตอร์เป็น Collections (รายการวัตถุ ... ) การเพิ่มขั้นสุดท้ายไม่สามารถป้องกันไม่ให้แก้ไขได้
Sam003

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

1
ฉันเห็นด้วย. แต่ถ้าเราต้องการให้วัตถุไม่เปลี่ยนรูปจริง ๆ เราสามารถลองสร้างโคลนลึก
Sam003

2

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


2

ข้อดีสองประการที่ฉันเห็นอยู่ในรายการ:

1 การทำเครื่องหมายอาร์กิวเมนต์ของวิธีการเป็นขั้นสุดท้ายจะป้องกันการกำหนดอาร์กิวเมนต์ใหม่ภายในวิธีการ

จากคุณตัวอย่าง

    public String changeTimezone(final Timestamp stamp, final Timezone fTz, 
            final Timezone toTz){
    
    // THIS WILL CAUSE COMPILATION ERROR as fTz is marked as final argument

      fTz = Calendar.getInstance().getTimeZone();     
      return ..
    
    }

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

2 ส่งผ่านอาร์กิวเมนต์ไปยังคลาสภายในที่ไม่ระบุชื่อ

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


1

- ในอดีต (ก่อน Java 8 :-))

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

- ในภาษาที่ทันสมัย ​​(Java 8+) ไม่จำเป็นต้องใช้งานดังกล่าว:

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


0

มันเป็นเพียงโครงสร้างใน Java เพื่อช่วยให้คุณกำหนดสัญญาและยึดติดกับมัน การสนทนาที่คล้ายกันที่นี่: http://c2.com/cgi/wiki?

BTW - (ตามที่ twiki กล่าว) การทำเครื่องหมาย args เป็นขั้นสุดท้ายโดยทั่วไปจะซ้ำซ้อนหากคุณปฏิบัติตามหลักการเขียนโปรแกรมที่ดีและได้ทำการกำหนด / กำหนดค่าการอ้างอิงอาร์กิวเมนต์ขาเข้าใหม่แล้ว

ในกรณีที่เลวร้ายที่สุดหากคุณกำหนดการอ้างอิง args ใหม่การอ้างอิงนั้นจะไม่ส่งผลต่อค่าจริงที่ส่งไปยังฟังก์ชันเนื่องจากมีการส่งผ่านข้อมูลอ้างอิงเท่านั้น


0

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

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


-3

คีย์เวิร์ดสุดท้ายป้องกันไม่ให้คุณกำหนดค่าใหม่ให้กับพารามิเตอร์ ฉันอยากจะอธิบายเรื่องนี้ด้วยตัวอย่างง่ายๆ

สมมติว่าเรามีวิธีการ

วิธีที่ 1 () {

วันที่ dateOfBirth = วันที่ใหม่ ("1/1/2009");

วิธีที่ 2 (dateOfBirth);

วิธีที่ 3 (dateOfBirth); }

mehod2 สาธารณะ (วันที่ dateOfBirth) {
....
....
....
}

mehod2 สาธารณะ (วันที่ dateOfBirth) {
....
....
....
}

ในกรณีข้างต้นถ้า "dateOfBirth" ถูกกำหนดค่าใหม่ใน method2 มากกว่านี้จะทำให้ผลลัพธ์ผิดจาก method3 เนื่องจากค่าที่ส่งผ่านไปยัง method3 ไม่ใช่ค่าก่อนที่จะถูกส่งไปยัง method2 ดังนั้นเพื่อหลีกเลี่ยงคำหลักสุดท้ายนี้ใช้สำหรับพารามิเตอร์

และนี่เป็นหนึ่งในแนวทางปฏิบัติที่ดีที่สุดในการเข้ารหัส Java


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