คำหลัก "สุดท้าย" ใน Java ทำงานอย่างไร (ฉันยังคงสามารถปรับเปลี่ยนวัตถุ)


480

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

นี่คือรหัส:

import java.util.ArrayList;
import java.util.List;

class Test {
  private final List foo;

  public Test()
  {
      foo = new ArrayList();
      foo.add("foo"); // Modification-1
  }
  public static void main(String[] args) 
  {
      Test t = new Test();
      t.foo.add("bar"); // Modification-2
      System.out.println("print - " + t.foo);
  }
}

โค้ดด้านบนทำงานได้ดีและไม่มีข้อผิดพลาด

ตอนนี้เปลี่ยนตัวแปรเป็นstatic:

private static final List foo;

ตอนนี้มันเป็นข้อผิดพลาดในการรวบรวม มันใช้งานได้finalจริงอย่างไร


ตั้งแต่มองไม่เห็น foo - มันจะรวบรวมได้อย่างไร?
BjörnHallström

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

3
@mbdavis โอ้ใช่! ขอบคุณ. แต่ถึงกระนั้นฉันจะไม่ลบความคิดเห็นเพื่อช่วยคนที่คิดเหมือนฉันแล้วความคิดเห็นของคุณจะทำให้พวกเขาคิดในทิศทางที่ถูกต้อง
ผ่านมา

@therealprashant โอเคไม่ต้องกังวล!
mbdavis

คำตอบ:


518

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

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

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


12
ลองทำ t.foo = new ArrayList (); ในวิธีการหลักและคุณจะได้รับข้อผิดพลาดในการคอมไพล์ ... foo อ้างอิงถูกผูกเข้ากับวัตถุสุดท้ายเพียงหนึ่งเดียวของ ArrayList ... มันไม่สามารถชี้ไปที่ ArrayList อื่น ๆ ได้
Code2Interface

50
อืมม มันคือทั้งหมดที่เกี่ยวกับการอ้างอิงไม่คุ้มค่า ขอบคุณ!
GS

2
ฉันมีคำถาม. บางคนที่ฉันรู้จักอ้างว่า "ขั้นสุดท้าย" ยังทำให้ตัวแปรถูกเก็บไว้ในสแต็ก ถูกต้องหรือไม่ ฉันค้นหาทุกที่และไม่พบการอ้างอิงใด ๆ ที่สามารถอนุมัติหรือไม่อนุมัติการอ้างสิทธิ์นี้ ฉันค้นหาทั้งเอกสาร Java และ Android ค้นหาด้วย "Java memory model" อาจใช้วิธีนี้กับ C / C ++ แต่ฉันคิดว่ามันใช้ไม่ได้กับ Java ฉันถูกไหม?
นักพัฒนา Android

4
@androiddeveloper ไม่มีอะไรใน Java ที่สามารถควบคุมการวางซ้อน / กองได้อย่างชัดเจน โดยเฉพาะอย่างยิ่งตำแหน่งสแต็คเป็นตัดสินใจโดยคอมไพเลอร์ HotSpot JIT อาจมีการหลบหนีการวิเคราะห์finalซึ่งอยู่ไกลมีส่วนร่วมมากขึ้นกว่าการตรวจสอบว่าตัวแปรคือ วัตถุที่ไม่แน่นอนสามารถจัดสรรได้เช่นกัน finalเขตข้อมูลอาจช่วยในการวิเคราะห์การหลบหนี แต่เป็นเส้นทางที่ค่อนข้างอ้อม นอกจากนี้โปรดทราบว่าตัวแปรสุดท้ายอย่างมีประสิทธิภาพมีการปฏิบัติเหมือนกันกับที่ทำเครื่องหมายไว้finalในซอร์สโค้ด
Marko Topolnik

5
finalมีอยู่ในไฟล์คลาสและมีผลต่อความหมายที่สำคัญสำหรับการปรับให้เหมาะสมรันไทม์ นอกจากนี้ยังอาจมีค่าใช้จ่ายเนื่องจาก JLS มีการรับประกันที่แข็งแกร่งเกี่ยวกับความสอดคล้องของfinalเขตข้อมูลของวัตถุ ตัวอย่างเช่นหน่วยประมวลผล ARM จะต้องใช้คำสั่งอุปสรรคหน่วยความจำที่ชัดเจนในตอนท้ายของแต่ละตัวสร้างของชั้นเรียนที่มีfinalเขตข้อมูล สำหรับโปรเซสเซอร์อื่น ๆ นี้ไม่จำเป็นต้องใช้
Marko Topolnik

574

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

import java.util.ArrayList;
import java.util.List;

class Test {
    private final List foo;

    public Test() {
        foo = new ArrayList();
        foo.add("foo"); // Modification-1
    }

    public void setFoo(List foo) {
       //this.foo = foo; Results in compile time error.
    }
}

ในกรณีข้างต้นเราได้กำหนดนวกรรมิกสำหรับ 'ทดสอบ' และให้มันเป็นวิธีการ 'setFoo'

เกี่ยวกับตัวสร้าง: สร้างสามารถเรียกเพียงหนึ่งครั้งต่อการสร้างวัตถุโดยใช้newคำหลัก คุณไม่สามารถเรียกใช้ตัวสร้างได้หลายครั้งเนื่องจากตัวสร้างไม่ได้ออกแบบมาให้ทำ

About method:สามารถเรียกใช้เมธอดได้หลายครั้งตามที่คุณต้องการ (ไม่เคยมีมาก่อน) และคอมไพเลอร์ก็รู้

สถานการณ์ 1

private final List foo;  // 1

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

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

สถานการณ์ที่ 2

private static final List foo = new ArrayList();

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

สถานการณ์ 3

t.foo.add("bar"); // Modification-2

ด้านบนModification-2มาจากคำถามของคุณ ในกรณีข้างต้นคุณจะไม่เปลี่ยนวัตถุอ้างอิงแรก แต่คุณกำลังเพิ่มเนื้อหาภายในfooที่ได้รับอนุญาต คอมไพเลอร์บ่นถ้าคุณพยายามที่จะกำหนดnew ArrayList()ให้กับfooตัวแปรอ้างอิง
กฎหากคุณเริ่มต้นfinalตัวแปรแล้วคุณจะไม่สามารถเปลี่ยนแปลงมันเพื่ออ้างถึงวัตถุอื่น (ในกรณีนี้ArrayList)

คลาสสุดท้ายไม่สามารถซับคลาสได้วิธี
สุดท้ายไม่สามารถเขียนทับได้ (วิธีนี้อยู่ใน superclass) วิธี
สุดท้ายสามารถแทนที่ (อ่านในรูปแบบไวยากรณ์วิธีนี้อยู่ในคลาสย่อย)


1
เพียงเพื่อให้ชัดเจน ในสถานการณ์ที่ 2 คุณกำลังบอกว่าfooจะถูกตั้งค่าหลายครั้งแม้จะมีการกำหนดขั้นสุดท้ายหากfooมีการตั้งค่าในคลาสทดสอบและมีการสร้างอินสแตนซ์ทดสอบหลายรายการ
Rawr

ไม่เข้าใจบรรทัดสุดท้ายสำหรับสถานการณ์ที่ 2: แต่fooอาจเป็น .. วัตถุหลายรายการ)นั่นหมายความว่าถ้าฉันสร้างวัตถุหลายรายการในเวลาเดียววัตถุใดที่เริ่มต้นตัวแปรสุดท้ายขึ้นอยู่กับการดำเนินการ
Saumya Suhagiya

1
ฉันคิดว่าวิธีที่มีประโยชน์ในการคิดเกี่ยวกับสถานการณ์ที่ 3 คือคุณกำลังกำหนดfinalที่อยู่หน่วยความจำที่อ้างถึงfooซึ่งเป็น ArrayList คุณไม่ได้กำหนดfinalที่อยู่หน่วยความจำที่อ้างอิงโดยองค์ประกอบแรกของfoo(หรือองค์ประกอบใด ๆ สำหรับเรื่องนั้น) ดังนั้นคุณไม่สามารถเปลี่ยนแปลงได้fooแต่สามารถเปลี่ยนแปลงfoo[0]ได้
Pinkerton

@Rrr ดังที่กล่าวไว้สถานการณ์ที่ 2 จะทำให้เกิดข้อผิดพลาดในการคอมไพล์เนื่องจากfoo = new ArrayList();- fooหมายถึงตัวแปรสแตติกเนื่องจากเราอยู่ในคลาสเดียวกัน
flow2k

ฉันเป็นนักพัฒนา C ++ เรียนรู้ Java คิดว่าปลอดภัยหรือเปล่าfinalตัวแปรที่เหมือนกับconstคำหลักใน C ++
Doug Barbieri

213

คำหลักสุดท้ายมีวิธีใช้มากมาย:

  • คลาสสุดท้ายไม่สามารถเป็นคลาสย่อยได้
  • วิธีสุดท้ายไม่สามารถถูกแทนที่ด้วยคลาสย่อย
  • ตัวแปรสุดท้ายสามารถเริ่มต้นได้เพียงครั้งเดียว

การใช้งานอื่น ๆ :

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

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


24
นี่คือคำตอบที่ฉันโปรดปราน ง่ายและตรงไปตรงมานี่คือสิ่งที่ฉันคาดว่าจะอ่านในเอกสารออนไลน์เกี่ยวกับ java
RAnders00

ดังนั้นในตัวแปรแบบคงที่เราสามารถเริ่มต้นได้บ่อยเท่าที่เราต้องการ?
jorge saraiva

1
@ jorgesaraiva ใช่ตัวแปรสแตติกไม่ใช่ค่าคงที่
czupe

1
@jorgesaraiva คุณสามารถกำหนดเขตข้อมูล(ไม่เริ่มต้น ) static(ตราบเท่าที่ยังไม่ได้final) ได้หลายครั้งตามที่คุณต้องการ ดูวิกินี้สำหรับความแตกต่างระหว่างการกำหนดและการเริ่มต้น

56

finalคำหลักที่สามารถตีความในสองวิธีที่แตกต่างกันขึ้นอยู่กับสิ่งที่มันใช้บน:

ชนิดของค่า:สำหรับints,double s เป็นต้นจะทำให้แน่ใจว่าค่าไม่สามารถเปลี่ยนแปลงได้

ประเภทการอ้างอิง:สำหรับการอ้างอิงไปยังวัตถุfinalให้แน่ใจว่าการอ้างอิงจะไม่เปลี่ยนแปลงซึ่งหมายความว่ามันจะอ้างถึงวัตถุเดียวกันเสมอ ไม่มีการรับประกันใด ๆ เกี่ยวกับค่าภายในวัตถุที่ถูกอ้างถึงว่าเหมือนกัน

ดังนั้นfinal List<Whatever> foo;มั่นใจได้ว่าfooหมายถึงรายการเดียวกันเสมอ แต่เนื้อหาของรายการดังกล่าวอาจเปลี่ยนแปลงได้ตลอดเวลา


23

หากคุณทำให้fooคงที่คุณต้องเริ่มต้นได้ในตัวสร้างคลาส (หรืออินไลน์ที่คุณกำหนดไว้) เช่นตัวอย่างต่อไปนี้

ตัวสร้างคลาส (ไม่ใช่อินสแตนซ์):

private static final List foo;

static
{
   foo = new ArrayList();
}

แบบอินไลน์:

private static final List foo = new ArrayList();

ปัญหาที่นี่ไม่ใช่วิธีการfinalทำงานของตัวดัดแปลง แต่เป็นการstaticทำงานของตัวดัดแปลง

โมดิfinalฟายเออร์บังคับให้เริ่มต้นการอ้างอิงของคุณตามเวลาที่การเรียกไปยังคอนสตรัคเตอร์ของคุณเสร็จสมบูรณ์ (เช่นคุณต้องเริ่มต้นในคอนสตรัคเตอร์)

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

  • ถ้าfooเป็นstatic, foo = new ArrayList()จะถูกดำเนินการก่อนstatic{}คอนสตรัคคุณได้กำหนดไว้สำหรับการเรียนของคุณจะถูกดำเนินการ
  • ถ้าfooไม่ได้static, foo = new ArrayList()จะได้รับการดำเนินการก่อนคอนสตรัคของคุณเป็นระยะ

เมื่อคุณไม่ได้เริ่มต้นแอ็ตทริบิวต์ในบรรทัดfinalตัวดัดแปลงจะบังคับให้คุณเริ่มต้นมันและคุณต้องทำในตัวสร้าง หากคุณมีstaticตัวปรับแต่งตัวสร้างคุณจะต้องเริ่มต้นแอตทริบิวต์ในนั้นคือบล็อกการเริ่มต้นของคลาส:static{}สร้างคุณจะต้องเริ่มต้นแอตทริบิวต์ในเป็นชั้นเริ่มต้นบล็อก:

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

คิดว่าบล็อกเป็นตัวสร้างสำหรับวัตถุของการพิมพ์static{} Classนี่คือที่ที่คุณต้องทำการเริ่มต้นของstatic finalคุณลักษณะคลาสของคุณ(ถ้าไม่ได้ทำแบบอินไลน์)

หมายเหตุด้านข้าง:

finalมั่นใจปรับปรุง const-Ness เฉพาะสำหรับรูปแบบดั้งเดิมและการอ้างอิง

เมื่อคุณประกาศfinalวัตถุสิ่งที่คุณได้รับคือการfinal อ้างอิงไปยังวัตถุนั้น แต่วัตถุนั้นไม่คงที่

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


8

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

1) เมื่อมีคนพูดถึงวัตถุสุดท้ายหมายความว่าไม่สามารถเปลี่ยนแปลงการอ้างอิงได้ แต่สถานะ (ตัวแปรอินสแตนซ์) สามารถเปลี่ยนแปลงได้

2) วัตถุที่ไม่เปลี่ยนรูปเป็นวัตถุที่ไม่สามารถเปลี่ยนแปลงสถานะได้ แต่การอ้างอิงสามารถเปลี่ยนแปลงได้ Ex:

    String x = new String("abc"); 
    x = "BCG";

ref ตัวแปร x สามารถเปลี่ยนให้ชี้ไปยังสตริงอื่นได้ แต่ค่าของ "abc" ไม่สามารถเปลี่ยนแปลงได้

3) ตัวแปรอินสแตนซ์ (ไม่ใช่ฟิลด์แบบสแตติก) ถูกเตรียมใช้งานเมื่อมีการเรียกตัวสร้าง ดังนั้นคุณสามารถเริ่มต้นค่าให้กับตัวแปรภายในตัวสร้าง

4) "แต่ฉันเห็นว่าคุณสามารถเปลี่ยนค่าในตัวสร้าง / วิธีการของชั้นเรียน" - คุณไม่สามารถเปลี่ยนได้ด้วยวิธีการ

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


7

finalคำหลักใน java ถูกนำมาใช้เพื่อ จำกัด การใช้ finalคีย์เวิร์ดjava สามารถใช้ได้หลายบริบท รอบชิงชนะเลิศสามารถ:

  1. ตัวแปร
  2. วิธี
  3. ชั้น

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

ตัวแปรสุดท้ายของ Java:

หากคุณสร้างตัวแปรใด ๆ เช่นเดียวกับfinalคุณไม่สามารถเปลี่ยนค่าของfinalตัวแปร (มันจะคงที่)

ตัวอย่างของfinalตัวแปร

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

class Bike9{  
    final int speedlimit=90;//final variable  
    void run(){  
        speedlimit=400;  // this will make error
    }  

    public static void main(String args[]){  
    Bike9 obj=new  Bike9();  
    obj.run();  
    }  
}//end of class  

Java ชั้นสุดท้าย:

ถ้าคุณทำการเรียนใด ๆ เป็นfinalคุณไม่สามารถขยายได้

ตัวอย่างของชั้นเรียนสุดท้าย

final class Bike{}  

class Honda1 extends Bike{    //cannot inherit from final Bike,this will make error
  void run(){
      System.out.println("running safely with 100kmph");
   }  

  public static void main(String args[]){  
      Honda1 honda= new Honda();  
      honda.run();  
      }  
  }  

วิธีสุดท้ายของ Java:

หากคุณทำวิธีใดเป็นขั้นสุดท้ายคุณจะไม่สามารถแทนที่ได้ได้

ตัวอย่างของfinalเมธอด (run () ใน Honda ไม่สามารถแทนที่ run () ใน Bike)

class Bike{  
  final void run(){System.out.println("running");}  
}  

class Honda extends Bike{  
   void run(){System.out.println("running safely with 100kmph");}  

   public static void main(String args[]){  
   Honda honda= new Honda();  
   honda.run();  
   }  
}  

แบ่งปันจาก: http://www.javatpoint.com/final-keyword


7

คุ้มค่าที่จะพูดถึงคำจำกัดความตรงไปตรงมา:

ชั้นเรียน / วิธีการ

คุณสามารถประกาศเมธอดคลาสบางส่วนหรือทั้งหมดfinalเพื่อระบุว่าเมธอดไม่สามารถถูกแทนที่โดยคลาสย่อย

ตัวแปร

เมื่อfinalตัวแปรได้รับการเริ่มต้นมันมักจะมีค่าเดียวกัน

final โดยทั่วไปหลีกเลี่ยงการเขียนทับ / แทนที่โดยอะไร (คลาสย่อยตัวแปร "กำหนดใหม่") ขึ้นอยู่กับกรณีและปัญหา


1
ฉันคิดว่านิยามขั้นสุดท้ายเกี่ยวกับตัวแปรนั้นค่อนข้างสั้น "ใน Java เมื่อคำหลักสุดท้ายถูกใช้กับตัวแปรของชนิดข้อมูลดั้งเดิม (int, float, .. ฯลฯ ) ค่าของตัวแปรจะไม่สามารถเปลี่ยนแปลงได้ แต่เมื่อสุดท้ายใช้กับตัวแปรที่ไม่ใช่ดั้งเดิม (โปรดทราบว่าตัวแปรที่ไม่ใช่ดั้งเดิม) มักอ้างอิงถึงวัตถุใน Java) สมาชิกของออบเจกต์ที่อ้างอิงสามารถเปลี่ยนแปลงได้สุดท้ายสำหรับตัวแปรที่ไม่ใช่แบบดั้งเดิมหมายถึงว่าพวกมันไม่สามารถเปลี่ยนแปลงได้เพื่ออ้างถึงออบเจกต์อื่น " geeksforgeeks.org/g-fact-48
ceyun

มีผลบังคับใช้เป็นพิเศษสำหรับการกล่าวถึงเป็นกรณีดั้งเดิมและไม่ใช่ดั้งเดิม Tks
ivanleoncz

4

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

public static final String hello = "Hello";

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

ตัวอย่างเช่น:

public class ClassDemo {
  private final int var1 = 3;
  public ClassDemo() {
    ...
  }
}

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

ประโยชน์ของการใช้คำหลักขั้นสุดท้ายได้รับการกล่าวถึงใน ชุดข้อความนี้


2
the value stored inside that variable cannot be changed latterเป็นจริงบางส่วน เป็นจริงสำหรับชนิดข้อมูลดั้งเดิมเท่านั้น ในกรณีที่วัตถุใด ๆ ที่ทำเช่นfinalเดียวกับ arraylist ค่าของมันสามารถเปลี่ยนแปลงได้ แต่ไม่ใช่การอ้างอิง ขอบคุณ!
GS

3

สมมติว่าคุณมีสองตู้รับเงินสีแดงและขาว คุณกำหนดกล่องเงินเหล่านี้ให้กับเด็กสองคนเท่านั้นและพวกเขาไม่ได้รับอนุญาตให้แลกเปลี่ยนกล่องของพวกเขา ดังนั้นคุณมีกล่องเงินสีแดงหรือสีขาว (สุดท้าย) คุณไม่สามารถปรับเปลี่ยนกล่อง แต่คุณสามารถใส่เงินในกล่องของคุณไม่มีใครใส่ใจ (Modification-2)


2

อ่านคำตอบทั้งหมด

มีอีกกรณีผู้ใช้ที่finalสามารถใช้คำสำคัญเช่นในอาร์กิวเมนต์วิธี:

public void showCaseFinalArgumentVariable(final int someFinalInt){

   someFinalInt = 9; // won't compile as the argument is final

}

สามารถใช้สำหรับตัวแปรที่ไม่ควรเปลี่ยนแปลง


1

เมื่อคุณทำให้มันคงสุดท้ายมันควรจะเริ่มต้นในบล็อกการเริ่มต้นคงที่

    private static final List foo;

    static {
        foo = new ArrayList();
    }

    public Test()
    {
//      foo = new ArrayList();
        foo.add("foo"); // Modification-1
    }

1

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

foo = new ArrayList();

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

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


1

ก่อนอื่นสถานที่ในรหัสของคุณที่คุณกำลังเริ่มต้น (เช่นการกำหนดเป็นครั้งแรก) foo อยู่ที่นี่:

foo = new ArrayList();

foo เป็นวัตถุ (ที่มีรายการชนิด) ดังนั้นจึงเป็นประเภทอ้างอิงไม่ใช่ประเภทค่า (เช่น int) ดังนั้นมันจึงมีการอ้างอิงไปยังตำแหน่งหน่วยความจำ (เช่น 0xA7D2A834) ซึ่งเป็นที่เก็บองค์ประกอบรายการของคุณ เส้นแบบนี้

foo.add("foo"); // Modification-1

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

foo = new ArrayList();

นั่นจะทำให้คุณมีข้อผิดพลาดในการรวบรวม


ทีนี้เมื่อคิดถึงเรื่องนี้ให้คิดว่าเกิดอะไรขึ้นเมื่อคุณเพิ่มสแตติกคำหลัก

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

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


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

1

ต่อไปนี้เป็นบริบทที่แตกต่างกันเมื่อมีการใช้งานครั้งสุดท้าย

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

class Main {
   public static void main(String args[]){
      final int i = 20;
      i = 30; //Compiler Error:cannot assign a value to final variable i twice
   }
}

ตัวแปรสุดท้ายสามารถกำหนดค่าได้ในภายหลัง (ไม่ใช่ค่าบังคับสำหรับการกำหนดค่าเมื่อมีการประกาศ) แต่เพียงครั้งเดียว

คลาสสุดท้ายไม่สามารถขยายคลาสสุดท้ายได้ (สืบทอดมา)

final class Base { }
class Derived extends Base { } //Compiler Error:cannot inherit from final Base

public class Main {
   public static void main(String args[]) {
   }
}

เมธอดสุดท้ายไม่สามารถแทนที่เมธอดสุดท้ายได้ด้วยคลาสย่อย

//Error in following program as we are trying to override a final method.
class Base {
  public final void show() {
       System.out.println("Base::show() called");
    }
}     
class Derived extends Base {
    public void show() {  //Compiler Error: show() in Derived cannot override
       System.out.println("Derived::show() called");
    }
}     
public class Main {
    public static void main(String[] args) {
        Base b = new Derived();;
        b.show();
    }
}

1

ฉันคิดถึงการเขียนคำตอบที่อัปเดตและเชิงลึกที่นี่

final คำหลักสามารถใช้ได้หลายแห่ง

  1. ชั้นเรียน

A final classหมายถึงไม่มีคลาสอื่นที่สามารถขยายคลาสสุดท้ายได้ เมื่อ Java Run Time ( JRE ) รู้ว่าการอ้างอิงวัตถุอยู่ในประเภทของคลาสสุดท้าย (พูด F) ก็รู้ว่าค่าของการอ้างอิงนั้นสามารถอยู่ในประเภทของ F

Ex:

F myF;
myF = new F();    //ok
myF = someOther;  //someOther cannot be in type of a child class of F.
                  //because F cannot be extended.

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

  1. วิธีการ

A final methodของคลาสใด ๆ หมายความว่าคลาสย่อยใด ๆ ที่ขยายคลาสนั้นไม่สามารถแทนที่เมธอดสุดท้ายได้ ดังนั้นพฤติกรรมเวลาทำงานในสถานการณ์นี้ก็ค่อนข้างเหมือนกันกับพฤติกรรมก่อนหน้านี้ที่ฉันกล่าวถึงในชั้นเรียน

  1. ฟิลด์ตัวแปรโลคัลพารามิเตอร์เมธอด

หากมีการระบุชนิดใด ๆ ข้างต้นเป็นfinalหมายความว่าค่านั้นได้รับการสรุปแล้วดังนั้นจึงไม่สามารถเปลี่ยนค่าได้

Ex:

สำหรับฟิลด์พารามิเตอร์ท้องถิ่น

final FinalClass fc = someFC; //need to assign straight away. otherwise compile error.
final FinalClass fc; //compile error, need assignment (initialization inside a constructor Ok, constructor can be called only once)
final FinalClass fc = new FinalClass(); //ok
fc = someOtherFC; //compile error
fc.someMethod(); //no problem
someOtherFC.someMethod(); //no problem

สำหรับพารามิเตอร์วิธีการ

void someMethod(final String s){
    s = someOtherString; //compile error
}

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

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


0

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

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