ทำไม Java ไม่อนุญาตให้แทนที่เมธอดแบบสแตติก


534

เหตุใดจึงไม่สามารถแทนที่วิธีการแบบคงที่ได้

ถ้าเป็นไปได้โปรดใช้ตัวอย่าง


3
ภาษา OOP ส่วนใหญ่ไม่อนุญาตสิ่งนี้
jmucchiello

7
@jmucchiello: ดูคำตอบของฉัน ฉันคิดเช่นเดียวกับคุณ แต่แล้วเรียนรู้เกี่ยวกับวิธีการ 'คลาส' ของ Ruby / Smalltalk และมีภาษา OOP จริงอื่น ๆ ที่ทำสิ่งนี้
Kevin Brock

5
@jmucchiello ภาษา OOP ส่วนใหญ่ไม่ใช่ภาษา OOP จริง (ฉันคิดถึง Smalltalk)
mathk


1
อาจเป็นเพราะ Java แก้ไขการโทรไปยังวิธีการคงที่ในเวลารวบรวม ดังนั้นแม้ว่าคุณจะเขียนParent p = new Child()แล้วp.childOverriddenStaticMethod()คอมไพเลอร์จะแก้ไขมันParent.childOverriddenStaticMethod()โดยดูที่ประเภทการอ้างอิง
Manoj

คำตอบ:


494

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

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


18
... แต่เฉพาะ "แก้ไข" ใน Java ตัวอย่างเช่น Scala ที่เทียบเท่ากับ "คลาสคงที่" (ซึ่งเรียกว่าobjects) อนุญาตให้ใช้วิธีการมากไป

32
วัตถุประสงค์ -C ยังอนุญาตให้มีวิธีการเรียนที่เอาชนะ
Richard

11
มีลำดับชั้นชนิดเวลาคอมไพล์และลำดับชั้นชนิดรันไทม์ มันเหมาะสมอย่างยิ่งที่จะถามว่าทำไมการเรียกเมธอดแบบสแตติกไม่ได้ประโยชน์กับลำดับชั้นชนิดรันไทม์ในสถานการณ์เหล่านั้นที่มีอยู่ ใน Java สิ่งนี้เกิดขึ้นเมื่อเรียกใช้วิธีการคงที่จากวัตถุ ( obj.staticMethod()) - ซึ่งได้รับอนุญาตและใช้ประเภทเวลารวบรวม เมื่อการเรียกแบบสแตติกอยู่ในวิธีที่ไม่คงที่ของคลาสวัตถุ "ปัจจุบัน" อาจเป็นชนิดที่ได้รับมาของคลาส - แต่วิธีการแบบคงที่ที่กำหนดไว้ในประเภทที่ได้รับจะไม่พิจารณา (พวกเขาอยู่ในประเภทเวลาทำงาน ลำดับชั้น)
Steve Powell

18
ฉันควรจะได้ทำให้มันชัดเจน: มันเป็นไม่ได้ความจริงว่าเป็นแนวคิดที่ไม่ได้บังคับ
Steve Powell

13
คำตอบนี้ในขณะที่ถูกต้องคล้ายกับ "มันเป็นอย่างไร" มากกว่าที่ควรจะเป็นหรือแม่นยำกว่าวิธีการที่จะสามารถตอบสนองความคาดหวังของ OP และดังนั้นที่นี่ฉันและคนอื่น ๆ ไม่มีเหตุผลที่เป็นรูปธรรมที่จะไม่อนุญาตให้ยกเลิกวิธีการคงที่นอกเหนือจาก "นั่นคือวิธี" ฉันคิดว่ามันเป็นข้อบกพร่องส่วนตัว
RichieHH

186

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

public class RegularEmployee {
    private BigDecimal salary;

    public void setSalary(BigDecimal salary) {
        this.salary = salary;
    }

    public static BigDecimal getBonusMultiplier() {
        return new BigDecimal(".02");
    }

    public BigDecimal calculateBonus() {
        return salary.multiply(getBonusMultiplier());
    }

    /* ... presumably lots of other code ... */
}

public class SpecialEmployee extends RegularEmployee {
    public static BigDecimal getBonusMultiplier() {
        return new BigDecimal(".03");
    }
}

รหัสนี้จะไม่ทำงานอย่างที่คุณคาดหวัง กล่าวคือ SpecialEmployee ได้รับโบนัส 2% เช่นเดียวกับพนักงานทั่วไป แต่ถ้าคุณลบ "static" s ดังนั้น SpecialEmployee จะได้รับโบนัส 3%

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

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

แต่มันไม่ทำงาน

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

บางทีถ้าฉันพยายามเขียนคอมไพเลอร์สำหรับภาษา OOP ฉันจะเห็นได้อย่างรวดเร็วว่าทำไมการใช้งานมันเพื่อที่ว่าฟังก์ชั่นแบบคงที่จะ overriden อาจเป็นเรื่องยากหรือเป็นไปไม่ได้

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

ปรับปรุง

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

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


11
@Bemrose: แต่นั่นคือจุดของฉัน: ทำไมฉันไม่ควรได้รับอนุญาตให้ทำเช่นนั้น? บางทีแนวคิดที่ใช้งานง่ายของฉันในสิ่งที่ "คงที่" ควรจะแตกต่างจากของคุณ แต่โดยทั่วไปฉันคิดว่าคงเป็นวิธีการที่สามารถคงที่เพราะมันไม่ได้ใช้ข้อมูลตัวอย่างใด ๆ และที่ควรจะคงที่เพราะคุณอาจต้องการ เพื่อเรียกมันว่าเป็นอิสระจากตัวอย่าง สแตติกจะเชื่อมโยงกับคลาสอย่างชัดเจน: ฉันคาดว่า Integer.valueOf จะผูกกับ Integers และ Double.valueOf เพื่อผูกกับ Doubles
Jay

9
@ewernli & Bemrose: ใช่แล้วมันเป็นอย่างนั้น ฉันไม่ได้โต้วาที เนื่องจากรหัสในตัวอย่างของฉันใช้งานไม่ได้แน่นอนฉันจะไม่พยายามเขียนมัน คำถามของฉันคือทำไมมันเป็นอย่างนั้น (ฉันกลัวว่าสิ่งนี้จะกลายเป็นหนึ่งในบทสนทนาที่เราไม่ได้สื่อสารกัน "ขอโทษพนักงานขายของฉันฉันจะได้หนึ่งในสีแดงได้ไหม" "ไม่เลยมันราคา 5 ดอลลาร์" "ใช่ฉันรู้ว่ามันมีค่าใช้จ่าย $ 5 แต่ฉันจะได้สีแดงได้ไหม "" ท่านฉันเพิ่งบอกคุณว่าราคา 5 เหรียญ "" โอเคฉันรู้ราคาแล้ว แต่ฉันถามเรื่องสี "" ฉันบอกราคาแล้ว! " ฯลฯ )
Jay

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

6
@Yishai: RE "อินสแตนซ์รันไทม์กำหนดวิธีการแบบคงที่ที่เรียกว่า": ตรง ฉันไม่เห็นสาเหตุที่คุณไม่สามารถทำสิ่งใดกับสถิตที่คุณสามารถทำกับเสมือน "แยกลำดับชั้น": ฉันจะบอกว่าทำให้เป็นส่วนหนึ่งของลำดับชั้นเดียวกัน เหตุใดสถิติจึงไม่รวมอยู่ในลำดับเดียวกัน "subsclass กำหนดลายเซ็นเดียวกันไม่คงที่": ฉันคิดว่ามันจะผิดกฎหมายเหมือนว่ามันผิดกฎหมายพูดมี subclass แทนที่ฟังก์ชั่นที่มีลายเซ็นเหมือนกัน แต่กลับประเภทที่แตกต่างกันหรือไม่โยนข้อยกเว้นทั้งหมดที่ ผู้ปกครองขว้างหรือมีขอบเขตที่แคบลง
Jay

28
ฉันคิดว่า Jay มีประเด็น - มันทำให้ฉันประหลาดใจเหมือนกันเมื่อฉันค้นพบว่าสถิตศาสตร์ไม่สามารถถูกแทนที่ได้ ส่วนหนึ่งเป็นเพราะถ้าฉันมี A พร้อมเมธอดsomeStatic()และ B ขยาย A แล้วB.someMethod() ผูกกับเมธอดใน A หากฉันเพิ่มsomeStatic()ใน B ในภายหลังรหัสการโทรยังคงเรียกใช้A.someStatic()จนกว่าฉันจะคอมไพล์รหัสการโทรซ้ำ นอกจากนี้ยังทำให้ฉันประหลาดใจที่bInstance.someStatic()ใช้ชนิดของ bInstance ที่ประกาศไม่ใช่ชนิดรันไทม์เนื่องจากมันผูกที่คอมไพล์ไม่ใช่ลิงก์ดังนั้นจึงA bInstance; ... bInstance.someStatic()เรียกใช้ A.someStatic () ถ้ามีถ้า B.someStatic () อยู่
Lawrence Dol

42

คำตอบสั้น ๆ คือ: เป็นไปได้ทั้งหมด แต่ Java ไม่ได้ทำ

นี่คือรหัสบางส่วนที่แสดงสถานะปัจจุบันของกิจการใน Java:

ไฟล์Base.java:

package sp.trial;
public class Base {
  static void printValue() {
    System.out.println("  Called static Base method.");
  }
  void nonStatPrintValue() {
    System.out.println("  Called non-static Base method.");
  }
  void nonLocalIndirectStatMethod() {
    System.out.println("  Non-static calls overridden(?) static:");
    System.out.print("  ");
    this.printValue();
  }
}

ไฟล์Child.java:

package sp.trial;
public class Child extends Base {
  static void printValue() {
    System.out.println("  Called static Child method.");
  }
  void nonStatPrintValue() {
    System.out.println("  Called non-static Child method.");
  }
  void localIndirectStatMethod() {
    System.out.println("  Non-static calls own static:");
    System.out.print("  ");
    printValue();
  }
  public static void main(String[] args) {
    System.out.println("Object: static type Base; runtime type Child:");
    Base base = new Child();
    base.printValue();
    base.nonStatPrintValue();
    System.out.println("Object: static type Child; runtime type Child:");
    Child child = new Child();
    child.printValue();
    child.nonStatPrintValue();
    System.out.println("Class: Child static call:");
    Child.printValue();
    System.out.println("Class: Base static call:");
    Base.printValue();
    System.out.println("Object: static/runtime type Child -- call static from non-static method of Child:");
    child.localIndirectStatMethod();
    System.out.println("Object: static/runtime type Child -- call static from non-static method of Base:");
    child.nonLocalIndirectStatMethod();
  }
}

หากคุณใช้งานสิ่งนี้ (ฉันทำบน Mac จาก Eclipse โดยใช้ Java 1.6) คุณจะได้รับ:

Object: static type Base; runtime type Child.
  Called static Base method.
  Called non-static Child method.
Object: static type Child; runtime type Child.
  Called static Child method.
  Called non-static Child method.
Class: Child static call.
  Called static Child method.
Class: Base static call.
  Called static Base method.
Object: static/runtime type Child -- call static from non-static method of Child.
  Non-static calls own static.
    Called static Child method.
Object: static/runtime type Child -- call static from non-static method of Base.
  Non-static calls overridden(?) static.
    Called static Base method.

ในกรณีนี้มีเพียงกรณีเดียวที่อาจเป็นเรื่องแปลกใจ (และเป็นคำถามที่เกี่ยวกับ) เป็นกรณีแรก

"ไม่ได้ใช้ชนิดเวลาทำงานเพื่อกำหนดวิธีการคงที่ที่เรียกว่าแม้ว่าจะถูกเรียกด้วยอินสแตนซ์ของวัตถุ ( obj.staticMethod())"

และกรณีสุดท้าย :

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

การโทรด้วยอินสแตนซ์ของวัตถุ

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

โทรจากภายในวัตถุวิธี

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

การเปลี่ยนกฎ

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

สิ่งนี้จะเป็นไปได้อย่างง่ายดาย (ถ้าเราเปลี่ยน Java: -O) และไม่ได้ไร้เหตุผลอย่างไรก็ตามมันมีข้อควรพิจารณาที่น่าสนใจ

พิจารณาหลักคือการที่เราต้องตัดสินใจซึ่งเรียกวิธีแบบคงที่ควรทำเช่นนี้

ในขณะนี้ Java มี "การเล่นโวหาร" ในภาษาที่การobj.staticMethod()โทรจะถูกแทนที่ด้วยการObjectClass.staticMethod()โทร (ปกติจะมีคำเตือน) [ หมายเหตุ: ObjectClassเป็นชนิดที่รวบรวมเวลาของobj.] objเหล่านี้จะเป็นผู้สมัครที่ดีสำหรับการเอาชนะในลักษณะนี้เอาชนิดที่เวลาทำงานของ

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

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

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

ข้อควรพิจารณาอื่น ๆ

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


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

25

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

import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;

class RegularEmployee {

    private BigDecimal salary = BigDecimal.ONE;

    public void setSalary(BigDecimal salary) {
        this.salary = salary;
    }
    public static BigDecimal getBonusMultiplier() {
        return new BigDecimal(".02");
    }
    public BigDecimal calculateBonus() {
        return salary.multiply(this.getBonusMultiplier());
    }
    public BigDecimal calculateOverridenBonus() {
        try {
            // System.out.println(this.getClass().getDeclaredMethod(
            // "getBonusMultiplier").toString());
            try {
                return salary.multiply((BigDecimal) this.getClass()
                    .getDeclaredMethod("getBonusMultiplier").invoke(this));
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }
        return null;
    }
    // ... presumably lots of other code ...
}

final class SpecialEmployee extends RegularEmployee {

    public static BigDecimal getBonusMultiplier() {
        return new BigDecimal(".03");
    }
}

public class StaticTestCoolMain {

    static public void main(String[] args) {
        RegularEmployee Alan = new RegularEmployee();
        System.out.println(Alan.calculateBonus());
        System.out.println(Alan.calculateOverridenBonus());
        SpecialEmployee Bob = new SpecialEmployee();
        System.out.println(Bob.calculateBonus());
        System.out.println(Bob.calculateOverridenBonus());
    }
}

ผลลัพธ์ที่ได้:

0.02
0.02
0.02
0.03

สิ่งที่เราพยายามทำสำเร็จ :)

แม้ว่าเราจะประกาศตัวแปรที่สามของ Carl เป็น RegularEmployee และมอบหมายให้เป็นอินสแตนซ์ของ SpecialEmployee เราจะยังคงมีการเรียกใช้ของวิธีการ CommonEmployee ในกรณีแรกและการเรียกของวิธี SpecialEmployee ในกรณีที่สอง

RegularEmployee Carl = new SpecialEmployee();

System.out.println(Carl.calculateBonus());
System.out.println(Carl.calculateOverridenBonus());

เพียงดูที่คอนโซลเอาต์พุต:

0.02
0.03

;)


9
ใช่การสะท้อนค่อนข้างมากสิ่งเดียวที่ทำได้ - แต่คำถามไม่ได้ตรงนี้ - มีประโยชน์ที่จะมีที่นี่แม้ว่า
Mr_and_Mrs_D

1
คำตอบนี้เป็นแฮ็คที่ใหญ่ที่สุดที่ฉันเคยเห็นในทุกหัวข้อ Java ก็สนุกที่จะอ่านมันยังคง :)
Andrejs

19

วิธีการแบบคงที่จะถือว่าเป็นโลกโดย JVM มีไม่ผูกพันกับวัตถุเช่น

มันอาจเป็นไปได้ในแนวความคิดถ้าคุณสามารถเรียกวิธีการคงที่จากวัตถุคลาส (เช่นในภาษาเช่น Smalltalk) แต่มันไม่ได้เป็นกรณีใน Java

แก้ไข

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

class MyClass { ... }
class MySubClass extends MyClass { ... }

MyClass obj1 = new MyClass();
MySubClass obj2 = new MySubClass();

ob2 instanceof MyClass --> true

Class clazz1 = obj1.getClass();
Class clazz2 = obj2.getClass();

clazz2 instanceof clazz1 --> false

คุณสามารถสะท้อนการเรียนได้ แต่มันหยุดอยู่ตรงนั้น คุณไม่เรียกวิธีคงโดยใช้แต่ใช้clazz1.staticMethod() MyClass.staticMethod()วิธีการคงที่ไม่ผูกพันกับวัตถุและดังนั้นจึงไม่มีความคิดthisหรือsuperวิธีการคงที่ วิธีการคงที่เป็นฟังก์ชั่นระดับโลก; ผลที่ตามมาก็คือไม่มีความคิดของความหลากหลายและดังนั้นวิธีการเอาชนะไม่มีเหตุผล

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

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


5
แม้แต่ภาษาสมัยใหม่จำนวนมากเช่น Ruby มีวิธีการสอนแบบคลาสสิกและช่วยให้พวกเขาสามารถเอาชนะได้
Chandra Sekar

3
คลาสมีอยู่เป็นวัตถุใน Java ดูคลาส "คลาส" ฉันสามารถพูด myObject.getClass () และมันจะกลับมาฉันตัวอย่างของวัตถุระดับที่เหมาะสม
Jay

5
คุณได้รับ "รายละเอียด" ของชั้นเรียนเท่านั้นไม่ใช่ชั้นเรียน แต่ความแตกต่างนั้นลึกซึ้ง
ewernli

คุณยังคงมีคลาส แต่ถูกซ่อนอยู่ใน VM (ใกล้กับตัวโหลดคลาส) ผู้ใช้แทบจะไม่สามารถเข้าถึงได้
mathk

หากต้องการใช้clazz2 instanceof clazz1อย่างถูกต้องคุณสามารถใช้แทนclass2.isAssignableFrom(clazz1)ซึ่งฉันเชื่อว่าจะกลับมาจริงในตัวอย่างของคุณ
Simon Forsberg

14

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

Animal lassie = new Dog();
lassie.speak(); // outputs "woof!"
Animal kermit = new Frog();
kermit.speak(); // outputs "ribbit!"

แม้ว่าทั้งสองlassieและkermitถูกประกาศเป็นออบเจ็กต์ประเภทAnimalพฤติกรรมของพวกเขา (เมธอด.speak()) แตกต่างกันไปเนื่องจากการจัดส่งแบบไดนามิกจะผูกเฉพาะการเรียกเมธอด.speak()ไปยังการใช้งาน ณ เวลารันไทม์ - ไม่ใช่ในเวลาคอมไพล์

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


11

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

class Animal {
    public static void eat() {
        System.out.println("Animal Eating");
    }
}

class Dog extends Animal{
    public static void eat() {
        System.out.println("Dog Eating");
    }
}

class Test {
    public static void main(String args[]) {
       Animal obj= new Dog();//Dog object in animal
       obj.eat(); //should call dog's eat but it didn't
    }
}


Output Animal Eating

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


3
มันเป็นการปฏิบัติที่ไม่ดีที่จะเรียกวิธีการคงที่จากวัตถุ
Dmitry Zagorulkin

6

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


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

ริชาร์ดลองสมมุติว่าเมื่อฉันตอบคำถามนี้เมื่อ 4 ปีก่อนคำตอบเหล่านี้ส่วนใหญ่ยังไม่ได้โพสต์ดังนั้นอย่าเป็นคนโง่! ไม่จำเป็นต้องยืนยันว่าฉันไม่ได้อ่านอย่างละเอียด นอกจากนี้คุณไม่ได้อ่านว่าเรากำลังพูดถึงการเอาชนะด้วยความเคารพ Java เท่านั้น ใครสนใจสิ่งที่เป็นไปได้ในภาษาอื่น มันไม่เกี่ยวข้อง ไปหมุนรอบที่อื่น ความคิดเห็นของคุณไม่ได้เพิ่มคุณค่าใด ๆ ให้กับกระทู้นี้
Athens Holloway

6

ใน Java (และภาษา OOP หลายภาษา แต่ฉันไม่สามารถพูดได้ทั้งหมดและบางภาษาไม่มีสแตติกเลย) วิธีการทั้งหมดมีลายเซ็นคงที่ - พารามิเตอร์และประเภท ในวิธีการที่เสมือนพารามิเตอร์แรกบอกเป็นนัย ๆ : thisการอ้างอิงไปยังวัตถุเองและเมื่อเรียกจากภายในวัตถุคอมไพเลอร์เพิ่มโดยอัตโนมัติ

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

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

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

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

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

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

แก้ไข : หนึ่งความคิดเห็นอื่น ตอนนี้ฉันเห็นความแตกต่างและในขณะที่ฉันพัฒนา Java / C # ด้วยตนเองฉันสามารถเข้าใจว่าทำไมคำตอบที่คุณได้รับจากนักพัฒนา Java อาจสับสนถ้าคุณมาจากภาษาอย่าง Ruby staticวิธีการของJava ไม่เหมือนกับclassวิธีของRuby นักพัฒนา Java จะมีเวลาทำความเข้าใจกับเรื่องนี้เช่นเดียวกับคนที่ทำงานด้วยภาษาอย่าง Ruby / Smalltalk ฉันเห็นได้ว่าสิ่งนี้จะทำให้เกิดความสับสนอย่างมากจากความจริงที่ว่า Java ยังใช้ "วิธีการเรียน" เป็นวิธีอื่นในการพูดคุยเกี่ยวกับวิธีการคงที่ แต่คำเดียวกันนี้ใช้ทับทิมต่างกัน Java ไม่มีวิธีการเรียนสไตล์ Ruby (ขออภัย); Ruby ไม่ได้มีวิธีการคงที่ของรูปแบบ Java ซึ่งเป็นจริงเพียงแค่ฟังก์ชั่นรูปแบบขั้นตอนเก่าที่พบใน C.

โดยวิธีการ - ขอบคุณสำหรับคำถาม! ฉันเรียนรู้สิ่งใหม่สำหรับฉันในวันนี้เกี่ยวกับวิธีการเรียน (สไตล์ทับทิม)


1
แน่นอนไม่มีเหตุผลที่ Java ไม่สามารถส่งผ่านวัตถุ "คลาส" เป็นพารามิเตอร์ที่ซ่อนอยู่กับวิธีการคงที่ มันไม่ได้ออกแบบมาให้ทำ
jmucchiello

6

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

ตัวอย่าง : ลองมาดูกันว่าเกิดอะไรขึ้นถ้าเราพยายามเอาชนะเมธอดแบบสแตติก: -

class SuperClass {
// ......
public static void staticMethod() {
    System.out.println("SuperClass: inside staticMethod");
}
// ......
}

public class SubClass extends SuperClass {
// ......
// overriding the static method
public static void staticMethod() {
    System.out.println("SubClass: inside staticMethod");
}

// ......
public static void main(String[] args) {
    // ......
    SuperClass superClassWithSuperCons = new SuperClass();
    SuperClass superClassWithSubCons = new SubClass();
    SubClass subClassWithSubCons = new SubClass();

    superClassWithSuperCons.staticMethod();
    superClassWithSubCons.staticMethod();
    subClassWithSubCons.staticMethod();
    // ...
}
}

ผลลัพธ์ : -
SuperClass: inside staticMethod
SuperClass: inside staticMethod
SubClass: inside staticMethod

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


5

โดยทั่วไปแล้วมันไม่มีเหตุผลที่จะอนุญาตให้ 'แทนที่' ของวิธีการคงที่เนื่องจากไม่มีวิธีที่ดีในการพิจารณาว่าจะเรียกใช้งานที่ใดในรันไทม์ รับตัวอย่างพนักงานถ้าเราเรียก RegularEmployee.getBonusMultiplier () - ควรใช้วิธีการแบบไหน?

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


2
ฉันคิดว่ามันควรจะทำงานได้เหมือนฟังก์ชั่นเสมือนจริง ถ้า B ขยาย A และทั้ง A และ B มีฟังก์ชันเสมือนชื่อ doStuff คอมไพเลอร์รู้ว่าอินสแตนซ์ของ A ควรใช้ A.doStuff และอินสแตนซ์ของ B ควรใช้ B.doStuff ทำไมมันไม่ทำเช่นเดียวกันกับฟังก์ชั่นคงที่? ท้ายที่สุดคอมไพเลอร์รู้ว่าแต่ละคลาสของวัตถุเป็นตัวอย่างของอะไร
Jay

อืม ... เจวิธีจำเป็นต้องคงไม่ได้ (โดยทั่วไปไม่ได้) ที่เรียกว่าอินสแตนซ์ ...
Meriton

2
@ เมอร์ริตัน แต่ก็ง่ายกว่าใช่ไหม? หากวิธีการแบบคงที่เรียกว่าใช้ชื่อคลาสคุณจะใช้วิธีที่เหมาะสมสำหรับชั้นเรียน
CPerkins

แต่สิ่งที่สำคัญสำหรับคุณ หากคุณโทรหา A.doStuff () ควรใช้เวอร์ชันที่ถูกเขียนทับใน "B extends A" หรือเวอร์ชันที่ถูกเขียนทับใน "C extends A" และถ้าคุณมี C หรือ B คุณก็กำลังเรียกเวอร์ชันเหล่านั้นอยู่ดี ... ไม่จำเป็นต้องมีการเอาชนะ
PSpeed

@meriton: มันเป็นความจริงที่ว่าวิธีการแบบสแตติกมักจะไม่ถูกเรียกด้วยอินสแตนซ์ แต่ฉันคิดว่าเป็นเพราะการโทรแบบนี้ไม่มีประโยชน์อะไรเลยหากมีการออกแบบ Java ในปัจจุบัน! ฉันแนะนำว่าการออกแบบทางเลือกอาจเป็นแนวคิดที่ดีกว่า BTW ในความรู้สึกที่แท้จริงมากฟังก์ชั่นคงที่จะถูกเรียกด้วยอินสแตนซ์ค่อนข้างบ่อย: เมื่อคุณเรียกใช้ฟังก์ชั่นคงที่จากภายในฟังก์ชั่นเสมือนจริง จากนั้นคุณจะได้รับ this.function () เช่นอินสแตนซ์ปัจจุบัน
เจย์

5

โดยการเอาชนะเราสามารถสร้างลักษณะ polymorphic ขึ้นอยู่กับชนิดของวัตถุ วิธีการคงไม่มีความสัมพันธ์กับวัตถุ ดังนั้นจาวาไม่สามารถรองรับวิธีการคงที่ที่เอาชนะ


5

ฉันชอบและแสดงความคิดเห็นเป็นสองเท่าของ Jay ( https://stackoverflow.com/a/2223803/1517187 )
ฉันยอมรับว่านี่เป็นการออกแบบที่ไม่ดีของ Java
ภาษาอื่น ๆ อีกมากมายรองรับวิธีการคงที่ดังที่เราเห็นในความคิดเห็นก่อนหน้า ฉันรู้สึกว่า Jay มาที่ Java จาก Delphi เช่นฉัน
Delphi (Object Pascal) เป็นภาษาแรกที่ใช้ OOP
เห็นได้ชัดว่าหลายคนเคยมีประสบการณ์กับภาษานั้นมาตั้งแต่ก่อนหน้านี้เป็นภาษาเดียวที่เขียนผลิตภัณฑ์ GUI เชิงพาณิชย์ และ - ใช่เราทำได้ในวิธีการแทนที่ Delphi แบบคงที่ อันที่จริงวิธีการคงที่ใน Delphi เรียกว่า "วิธีการเรียน" ในขณะที่ Delphi มีแนวคิดที่แตกต่างกันของ "วิธีการ Delphi คงที่" ซึ่งเป็นวิธีการที่มีผลผูกพันก่อน ในการแทนที่วิธีการที่คุณต้องใช้การรวมล่าช้าให้ประกาศคำสั่ง "เสมือน" ดังนั้นมันจึงสะดวกและใช้งานง่ายและฉันคาดหวังสิ่งนี้ใน Java


3

มันจะทำอย่างไรดีเพื่อแทนที่วิธีคงที่ คุณไม่สามารถเรียกวิธีการคงที่ผ่านอินสแตนซ์

MyClass.static1()
MySubClass.static1()   // If you overrode, you have to call it through MySubClass anyway.

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


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

1
อันที่จริง Java อนุญาตให้สมาชิกแบบคงที่สามารถเข้าถึงได้ผ่านอินสแตนซ์: ดูตัวแปรแบบคงที่ใน Java
ริชาร์ด JP Le Guen

IDE สมัยใหม่ที่เหมาะสมจะสร้างคำเตือนเมื่อคุณทำเช่นนั้นดังนั้นอย่างน้อยคุณสามารถจับมันได้ในขณะที่ Oracle สามารถทำให้ความเข้ากันได้ย้อนหลังดำเนินต่อไป
Gimby

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

@RichieHH นั่นไม่ใช่สิ่งที่พวกเขาพูดเล่น คำถามคือทำไมมันได้รับอนุญาตให้เรียกvariable.staticMethod()แทนClass.staticMethod()ซึ่งเป็นตัวแปรที่มีประเภทการประกาศให้เป็นvariable Classฉันเห็นด้วยว่ามันคือการออกแบบภาษาที่ไม่ดี
fishinear

3

การเอาชนะใน Java นั้นหมายถึงว่าวิธีการเฉพาะนั้นจะถูกเรียกตามชนิด runtime ของวัตถุและไม่ได้อยู่ในประเภทเวลารวบรวมของมัน (ซึ่งเป็นกรณีที่มีวิธีคงที่แทนที่) เนื่องจากวิธีการแบบสแตติกเป็นวิธีการเรียนพวกเขาไม่ใช่วิธีการแบบอินสแตนซ์ดังนั้นพวกเขาจึงไม่มีอะไรเกี่ยวข้องกับความจริงที่การอ้างอิงชี้ไปที่วัตถุหรืออินสแตนซ์เนื่องจากเนื่องจากลักษณะของวิธีการแบบคงที่ คุณสามารถประกาศใหม่ในคลาสย่อย แต่คลาสย่อยนั้นไม่ทราบอะไรเกี่ยวกับวิธีการคงที่ของคลาสพาเรนต์เพราะอย่างที่ฉันบอกว่ามันเป็นคลาสเฉพาะที่คลาสนั้นถูกประกาศเท่านั้น การเข้าถึงพวกเขาโดยใช้การอ้างอิงวัตถุเป็นเพียงเสรีภาพพิเศษที่นักออกแบบของ Java และเราไม่ควรคิดที่จะหยุดการฝึกนั้นเมื่อพวกเขา จำกัด รายละเอียดและตัวอย่างเพิ่มเติม http://faisalbhagat.blogspot.com/2014/09/method-overriding-and-method-hiding.html


3

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

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

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

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

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


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

คงไม่ได้หมายความว่าเวลารวบรวมคงที่หมายความว่ามันถูกผูกไว้กับชั้นเรียนค่อนข้างแล้ววัตถุเฉพาะใด ๆ มันทำให้มากขึ้นถ้าไม่สมเหตุสมผลมากกว่าการสร้างโรงงานคลาสยกเว้น static Box.createBoxให้ความรู้สึกมากกว่าBoxFactory.createBoxและเป็นรูปแบบที่หลีกเลี่ยงไม่ได้เมื่อต้องการสร้างการตรวจสอบข้อผิดพลาดโดยไม่โยนข้อยกเว้น (ตัวสร้างไม่สามารถล้มเหลวพวกเขาสามารถฆ่ากระบวนการ / โยน ยกเว้น) ในขณะที่วิธีการคงสามารถกลับ null ในความล้มเหลวหรือแม้กระทั่งยอมรับการเรียกกลับประสบความสำเร็จ / ข้อผิดพลาดในการเขียนสิ่งที่ต้องการhastebin.com/codajahati.java
Dmitry

2

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


1
สวัสดี G4uKu3_Gaurav ขอขอบคุณที่ตัดสินใจร่วมให้ข้อมูล อย่างไรก็ตามโดยทั่วไปเราคาดหวังคำตอบที่ยาวและละเอียดกว่านี้
DJClayworth

@DJClayworth คุณควรไปที่ลิงค์นี้เพื่อรับรายละเอียดคำตอบ geeksforgeeks.org/
g1ji

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

1

ทางออกที่ง่าย: ใช้อินสแตนซ์เดี่ยว มันจะช่วยให้แทนที่และมรดก

ในระบบของฉันฉันมีชั้น SingletonsRegistry ซึ่งส่งคืนอินสแตนซ์สำหรับระดับที่ผ่าน หากไม่พบอินสแตนซ์จะถูกสร้างขึ้น

ชั้นเรียนภาษา Haxe:

package rflib.common.utils;
import haxe.ds.ObjectMap;



class SingletonsRegistry
{
  public static var instances:Map<Class<Dynamic>, Dynamic>;

  static function __init__()
  {
    StaticsInitializer.addCallback(SingletonsRegistry, function()
    {
      instances = null;
    });

  } 

  public static function getInstance(cls:Class<Dynamic>, ?args:Array<Dynamic>)
  {
    if (instances == null) {
      instances = untyped new ObjectMap<Dynamic, Dynamic>();      
    }

    if (!instances.exists(cls)) 
    {
      if (args == null) args = [];
      instances.set(cls, Type.createInstance(cls, args));
    }

    return instances.get(cls);
  }


  public static function validate(inst:Dynamic, cls:Class<Dynamic>)
  {
    if (instances == null) return;

    var inst2 = instances[cls];
    if (inst2 != null && inst != inst2) throw "Can\'t create multiple instances of " + Type.getClassName(cls) + " - it's singleton!";
  }

}

เจ๋งมากมันเป็นครั้งแรกที่ฉันได้ยินเกี่ยวกับภาษาการเขียนโปรแกรม Haxe :)
cyc115

1
สิ่งนี้ถูกนำมาใช้ที่ดีขึ้นมากใน Java เป็นวิธีการคงที่ในชั้นเรียนของตัวเอง; Singleton.get(). รีจิสทรีเป็นค่าใช้จ่ายในการสร้างต้นแบบและจะขัดขวาง GC ในชั้นเรียน
Lawrence Dol

ของคุณถูกต้องมันเป็นทางออกที่คลาสสิก ฉันจำไม่ได้ว่าทำไมฉันถึงเลือกรีจิสทรีอาจมีกรอบความคิดบางอย่างที่นำไปสู่ผลลัพธ์นี้
Raivo Fishmeister

1

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

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

High Cohesion - คลาสหนึ่งควรมีเพียงหนึ่งบทบาท ตัวอย่างเช่น: คลาสรถยนต์ควรผลิตวัตถุรถยนต์เท่านั้นไม่ใช่จักรยานรถบรรทุกเครื่องบิน ฯลฯ แต่คลาสรถยนต์อาจมีคุณสมบัติบางอย่าง (พฤติกรรม) ที่เป็นของตัวเองเท่านั้น

ดังนั้นในขณะที่ออกแบบภาษาโปรแกรมจาวา ผู้ออกแบบภาษาคิดว่าจะอนุญาตให้ผู้พัฒนาเก็บพฤติกรรมบางอย่างของชั้นเรียนให้กับตัวเองเท่านั้นโดยการทำให้วิธีการคงที่ในธรรมชาติ


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

public class Vehicle {
static int VIN;

public static int getVehileNumber() {
    return VIN;
}}

class Car extends Vehicle {
static int carNumber;

public static int getVehileNumber() {
    return carNumber;
}}

นี้เป็นเพราะที่นี่เรายังไม่ได้เอาชนะวิธี แต่เราเป็นเพียงอีกครั้งประกาศมัน Java อนุญาตให้ประกาศใหม่ของวิธีการ (คงที่ / ไม่คงที่)

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

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

public static final int getVehileNumber() {
return VIN;     }

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


1

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


เป็นเรื่องง่ายที่จะมีpublic abstract IBox createBox();อินเทอร์เฟซภายใน IBox อย่างไร Box สามารถใช้ IBox เพื่อแทนที่ createBox และมีการสร้างผลลัพธ์วัตถุใน IBox ที่ถูกต้องมิฉะนั้นคืนค่า null คอนสตรัคเตอร์ไม่สามารถส่งคืน "ว่าง" ดังนั้นคุณจึงถูกบังคับให้ (1) ใช้ข้อยกเว้นทุกที่ (สิ่งที่เราทำตอนนี้) หรือ (2) สร้างคลาสโรงงานที่ทำในสิ่งที่ฉันพูดไว้ก่อนหน้านี้ ของ Java (ซึ่งเราทำด้วย) วิธีการที่ไม่ได้ใช้งานแบบคงที่แก้ปัญหานี้
Dmitry

-1

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

เราสามารถเข้าถึงวิธีสแตติกของคลาสซูเปอร์พร้อมการอ้างอิงคลาสย่อยหากวิธีสแตติกนี้ไม่ได้ถูกซ่อนโดยวิธีสแตติกใหม่ที่กำหนดในคลาสย่อย

ตัวอย่างเช่นดูรหัสด้านล่าง: -

public class StaticMethodsHiding {
    public static void main(String[] args) {
        SubClass.hello();
    }
}


class SuperClass {
    static void hello(){
        System.out.println("SuperClass saying Hello");
    }
}


class SubClass extends SuperClass {
    // static void hello() {
    // System.out.println("SubClass Hello");
    // }
}

เอาท์พุท: -

SuperClass saying Hello

ดูJava oracle docsและค้นหาสิ่งที่คุณสามารถทำได้ในคลาสย่อยสำหรับรายละเอียดเกี่ยวกับการซ่อนเมธอดสแตติกในคลาสย่อย

ขอบคุณ


-3

รหัสต่อไปนี้แสดงให้เห็นว่าเป็นไปได้:

class OverridenStaticMeth {   

static void printValue() {   
System.out.println("Overriden Meth");   
}   

}   

public class OverrideStaticMeth extends OverridenStaticMeth {   

static void printValue() {   
System.out.println("Overriding Meth");   
}   

public static void main(String[] args) {   
OverridenStaticMeth osm = new OverrideStaticMeth();   
osm.printValue();   

System.out.println("now, from main");
printValue();

}   

} 

1
ไม่มันไม่; คงประเภทประกาศosmเป็นไม่ได้OverridenStaticMeth OverrideStaticMeth
Lawrence Dol

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