Java ไม่อนุญาตให้มีการสืบทอดหลายรายการ แต่อนุญาตให้ใช้หลายอินเตอร์เฟสได้ ทำไม?
Java ไม่อนุญาตให้มีการสืบทอดหลายรายการ แต่อนุญาตให้ใช้หลายอินเตอร์เฟสได้ ทำไม?
คำตอบ:
เพราะอินเตอร์เฟซที่ระบุเพียงแค่สิ่งที่ชั้นจะทำไม่ว่ามันจะทำมัน
ปัญหาเกี่ยวกับการสืบทอดหลาย ๆ ครั้งคือสองคลาสอาจกำหนดวิธีการที่แตกต่างกันในการทำสิ่งเดียวกันและคลาสย่อยไม่สามารถเลือกได้ว่าจะเลือกแบบใด
อาจารย์คนหนึ่งในวิทยาลัยของฉันอธิบายให้ฉันฟังแบบนี้
สมมติว่าฉันมีชั้นหนึ่งซึ่งเป็นเครื่องปิ้งขนมปังและอีกชั้นหนึ่งซึ่งเป็นนิวเคลียร์ พวกเขาทั้งสองอาจมีการตั้งค่า "ความมืด" พวกเขาทั้งสองมีวิธีเปิด () (อันหนึ่งมีการปิด () อีกอันหนึ่งไม่ได้) ถ้าฉันต้องการสร้างคลาสที่เป็นคลาสย่อยของทั้งสองนี้ ... อย่างที่คุณเห็นนี่เป็นปัญหาที่ทำให้ใบหน้าของฉันระเบิดที่นี่จริงๆ .
ดังนั้นหนึ่งในประเด็นหลักคือถ้าคุณมีสองผู้ปกครองชั้นพวกเขาอาจมีการใช้งานที่แตกต่างกันของคุณสมบัติเดียวกัน - หรืออาจเป็นสองคุณสมบัติที่แตกต่างกันด้วยชื่อเดียวกันเช่นในตัวอย่างของผู้สอนของฉัน จากนั้นคุณต้องจัดการกับการตัดสินใจว่าจะใช้คลาสย่อยใด มีวิธีจัดการกับสิ่งนี้อย่างแน่นอน - C ++ ทำเช่นนั้น - แต่นักออกแบบของ Java รู้สึกว่าสิ่งนี้จะทำให้สิ่งที่ซับซ้อนเกินไป
อย่างไรก็ตามด้วยอินเทอร์เฟซคุณกำลังอธิบายบางสิ่งบางอย่างในชั้นเรียนที่สามารถทำได้แทนที่จะยืมวิธีของชั้นเรียนอื่นในการทำบางสิ่งบางอย่าง อินเทอร์เฟซหลายรายการมีโอกาสน้อยที่จะทำให้เกิดข้อขัดแย้งที่ยุ่งยากซึ่งต้องแก้ไขมากกว่าคลาสแม่
เนื่องจากการรับมรดกมากเกินไปแม้ว่าคุณจะไม่สามารถพูดว่า "เฮ้วิธีการนั้นดูมีประโยชน์ฉันจะขยายคลาสนั้นด้วย"
public class MyGodClass extends AppDomainObject, HttpServlet, MouseAdapter,
AbstractTableModel, AbstractListModel, AbstractList, AbstractMap, ...
คำตอบของคำถามนี้อยู่ในการทำงานภายในของ java compiler (constructor chaining) ถ้าเราเห็นการทำงานภายในของคอมไพเลอร์ java:
public class Bank {
public void printBankBalance(){
System.out.println("10k");
}
}
class SBI extends Bank{
public void printBankBalance(){
System.out.println("20k");
}
}
หลังจากรวบรวมลักษณะนี้:
public class Bank {
public Bank(){
super();
}
public void printBankBalance(){
System.out.println("10k");
}
}
class SBI extends Bank {
SBI(){
super();
}
public void printBankBalance(){
System.out.println("20k");
}
}
เมื่อเราขยายคลาสและสร้างวัตถุของมันโซ่คอนสตรัคเตอร์หนึ่งจะทำงานจนถึงObject
คลาส
โค้ดด้านบนจะทำงานได้ดี แต่ถ้าเรามีคลาสอีกคลาสหนึ่งที่เรียกว่าคลาสCar
ที่ขยายBank
และหนึ่งคลาสไฮบริด (หลายมรดก) เรียกว่าSBICar
:
class Car extends Bank {
Car() {
super();
}
public void run(){
System.out.println("99Km/h");
}
}
class SBICar extends Bank, Car {
SBICar() {
super(); //NOTE: compile time ambiguity.
}
public void run() {
System.out.println("99Km/h");
}
public void printBankBalance(){
System.out.println("20k");
}
}
ในกรณีนี้ (SBICar) จะล้มเหลวในการสร้างตัวสร้างโซ่ ( รวบรวมความกำกวมเวลา )
สำหรับส่วนต่อประสานสิ่งนี้ได้รับอนุญาตเพราะเราไม่สามารถสร้างวัตถุของมันได้
สำหรับแนวคิดใหม่ของการdefault
และstatic
วิธีการกรุณาดูค่าเริ่มต้นในอินเตอร์เฟซ
หวังว่านี่จะแก้ปัญหาการค้นหาของคุณ ขอบคุณ
การใช้หลายอินเตอร์เฟสนั้นมีประโยชน์มากและไม่ก่อให้เกิดปัญหากับผู้ใช้ภาษาหรือโปรแกรมเมอร์ ดังนั้นจึงได้รับอนุญาต การสืบทอดหลายอย่างพร้อมกันยังมีประโยชน์สามารถทำให้เกิดปัญหาร้ายแรงกับผู้ใช้ ( เพชรแห่งความตายที่น่ากลัว) และสิ่งที่คุณทำกับการสืบทอดหลายอย่างสามารถทำได้โดยการจัดองค์ประกอบหรือใช้คลาสภายใน ดังนั้นจึงห้ามมิให้มีการรับมรดกหลายครั้งเนื่องจากทำให้เกิดปัญหามากกว่าผลกำไร
ToyotaCar
และHybridCar
ทั้งสองอย่างนั้นได้มาจากCar
และล้มล้างCar.Drive
และถ้าPriusCar
สืบทอดทั้งสองอย่างแต่ไม่ได้แทนที่Drive
ระบบจะไม่มีวิธีระบุสิ่งที่เสมือนCar.Drive
ควรทำ การเชื่อมต่อหลีกเลี่ยงปัญหานี้โดยหลีกเลี่ยงเงื่อนไขที่เอียงด้านบน
void UseCar(Car &foo)
; ไม่สามารถคาดหวังได้ว่าจะรวมถึงความไม่ลงรอยกันระหว่างToyotaCar::Drive
และHybridCar::Drive
(เนื่องจากบ่อยครั้งที่ไม่ควรทราบหรือไม่สนใจว่ามีประเภทอื่น ๆอยู่ ) ภาษาสามารถทำได้เช่นเดียวกับ C ++ ต้องการรหัสที่ToyotaCar &myCar
ต้องการส่งไปยังUseCar
ต้องส่งไปยังHybridCar
หรืออย่างใดอย่างหนึ่งToyotaCar
แต่เนื่องจาก ((รถยนต์) (HybridCar) myCar) .Drive` และ((Car)(ToyotaCar)myCar).Drive
จะทำสิ่งต่าง ๆ นั่นหมายความว่า upcasts ไม่ได้รักษาตัวตน
คุณสามารถค้นหาคำตอบที่ถูกต้องสำหรับแบบสอบถามนี้ได้ในหน้าเอกสารของ oracle เกี่ยวกับการสืบทอดหลายแบบ
การสืบทอดหลายสถานะ: ความสามารถในการรับช่วงจากหลายคลาส
เหตุผลหนึ่งที่ภาษาจาวาไม่อนุญาตให้คุณขยายมากกว่าหนึ่งคลาสคือการหลีกเลี่ยงปัญหาการสืบทอดหลายสถานะซึ่งเป็นความสามารถในการรับช่วงจากหลายคลาส
หากอนุญาตให้ใช้การสืบทอดหลายอย่างและเมื่อคุณสร้างวัตถุโดยการสร้างคลาสนั้นขึ้นมานั้นวัตถุนั้นจะสืบทอดเขตข้อมูลจากซูเปอร์คลาสทั้งหมดของคลาสนั้น มันจะทำให้เกิดปัญหาสองประการ
การสืบทอดหลายแบบของการใช้งาน:ความสามารถในการสืบทอดการกำหนดวิธีการจากหลายคลาส
ปัญหาด้วยวิธีนี้: ชื่อ conflic TS และความคลุมเครือ หากคลาสย่อยและซูเปอร์คลาสมีชื่อวิธีการเดียวกัน (และลายเซ็น) คอมไพเลอร์ไม่สามารถกำหนดเวอร์ชันที่จะเรียกใช้
แต่จาวาสนับสนุนการสืบทอดหลายประเภทนี้ด้วยวิธีการเริ่มต้นซึ่งได้รับการแนะนำตั้งแต่การเปิดตัว Java 8 คอมไพเลอร์ Java มีกฎบางอย่างเพื่อกำหนดวิธีการเริ่มต้นที่คลาสใดใช้
อ้างถึง SE post ด้านล่างสำหรับรายละเอียดเพิ่มเติมเกี่ยวกับการแก้ไขปัญหาเพชร:
อะไรคือความแตกต่างระหว่างคลาสนามธรรมและอินเตอร์เฟสใน Java 8?
การสืบทอดหลายประเภท: ความสามารถของคลาสที่จะใช้มากกว่าหนึ่งอินเตอร์เฟส
เนื่องจากส่วนต่อประสานไม่มีฟิลด์ที่ไม่แน่นอนคุณจึงไม่ต้องกังวลกับปัญหาที่เกิดจากการสืบทอดหลายสถานะที่นี่
ว่ากันว่าสถานะของวัตถุนั้นถูกอ้างถึงด้วยความเคารพในทุ่งนาและมันจะกลายเป็นคลุมเครือถ้ามีการสืบทอดคลาสมากเกินไป นี่คือลิงค์
http://docs.oracle.com/javase/tutorial/java/IandI/multipleinheritance.html
Java รองรับการสืบทอดหลายรายการผ่านอินเตอร์เฟสเท่านั้น คลาสสามารถใช้อินเตอร์เฟสจำนวนเท่าใดก็ได้ แต่สามารถขยายคลาสได้เพียงคลาสเดียวเท่านั้น
ไม่รองรับการสืบทอดหลายรายการเนื่องจากจะนำไปสู่ปัญหาเพชรร้ายแรง อย่างไรก็ตามมันสามารถแก้ไขได้ แต่มันนำไปสู่ระบบที่ซับซ้อนดังนั้นผู้ก่อตั้ง Java จึงถูกสืบทอดหลายมรดก
ในกระดาษสีขาวที่ชื่อว่า“ Java: ภาพรวม” โดย James Gosling ในเดือนกุมภาพันธ์ 1995 ( ลิงก์ ) ให้แนวคิดว่าทำไมมรดกหลายรายการไม่ได้รับการสนับสนุนใน Java
ตามที่กอสลิง:
"JAVA ละเว้นจำนวนมากที่ไม่ค่อยได้ใช้, เข้าใจไม่ดี, ทำให้เกิดความสับสนในคุณลักษณะของ C ++ ซึ่งในประสบการณ์ของเรานั้นทำให้เกิดความเศร้าโศกมากกว่าประโยชน์ซึ่งส่วนใหญ่ประกอบด้วยตัวดำเนินการโอเวอร์โหลด (แม้ว่าจะมีวิธีการโอเวอร์โหลด)
ด้วยเหตุผลเดียวกัน C # ไม่อนุญาตให้สืบทอดหลาย ๆ อัน แต่อนุญาตให้คุณใช้หลายอินเตอร์เฟส
บทเรียนที่เรียนรู้จาก C ++ w / การสืบทอดหลายอย่างคือมันนำไปสู่ปัญหามากกว่าที่มันคุ้มค่า
อินเทอร์เฟซคือสัญญาของสิ่งที่คลาสของคุณต้องนำไปใช้ คุณไม่ได้รับฟังก์ชั่นใด ๆ จากอินเทอร์เฟซ การสืบทอดทำให้คุณสามารถสืบทอดการทำงานของคลาสแม่ (และในหลาย ๆ การสืบทอดซึ่งอาจทำให้เกิดความสับสนอย่างมาก)
การอนุญาตให้ส่วนต่อประสานหลายชุดช่วยให้คุณใช้รูปแบบการออกแบบ (เช่นตัวปรับต่อ) เพื่อแก้ไขปัญหาประเภทเดียวกันกับที่คุณสามารถแก้ไขได้โดยใช้การสืบทอดหลายตัว แต่ในลักษณะที่เชื่อถือได้และคาดการณ์ได้มากขึ้น
D1
และD2
ทั้งสองสืบทอดมาจากB
และแต่ละฟังก์ชั่นแทนที่f
และถ้าobj
เป็นตัวอย่างของประเภทS
ที่สืบทอดมาจากทั้งสองD1
และD2
แต่ไม่แทนที่f
แล้วหล่ออ้างอิงS
ถึงD1
ควรจะให้สิ่งที่มีการf
ใช้D1
แทนที่และการหล่อB
ไม่ควร เปลี่ยนที่ ในทำนองเดียวกันการอ้างอิงS
ถึงD2
ควรให้สิ่งที่มีการf
ใช้การD2
แทนที่และการส่งไปยังB
ไม่ควรเปลี่ยนสิ่งนั้น หากภาษาไม่จำเป็นต้องอนุญาตให้มีการเพิ่มสมาชิกเสมือน ...
เนื่องจากหัวข้อนี้ไม่ปิดฉันจะโพสต์คำตอบนี้ฉันหวังว่านี่จะช่วยให้ใครบางคนเข้าใจว่าทำไม java ไม่อนุญาตให้สืบทอดหลายรายการ
พิจารณาคลาสต่อไปนี้:
public class Abc{
public void doSomething(){
}
}
ในกรณีนี้คลาส Abc ไม่ขยายสิ่งที่ถูกต้องหรือไม่ ไม่เร็วนักโดยนัยคลาสนี้ขยายคลาส Object คลาสพื้นฐานที่อนุญาตให้ทุกอย่างทำงานใน java ทุกอย่างเป็นวัตถุ
ถ้าคุณพยายามที่จะใช้ชั้นบนคุณจะเห็นว่าคุณ IDE ช่วยให้คุณสามารถใช้วิธีการที่ชอบ: equals(Object o)
, toString()
ฯลฯ แต่คุณไม่ได้ประกาศวิธีการที่พวกเขามาจากชั้นฐานObject
คุณสามารถลอง:
public class Abc extends String{
public void doSomething(){
}
}
นี่เป็นเรื่องปกติเพราะชั้นเรียนของคุณจะไม่ขยายโดยนัยObject
แต่จะขยายออกString
เพราะคุณพูดว่า พิจารณาการเปลี่ยนแปลงต่อไปนี้:
public class Abc{
public void doSomething(){
}
@Override
public String toString(){
return "hello";
}
}
ตอนนี้ชั้นเรียนของคุณจะกลับมา "สวัสดี" ถ้าคุณเรียก toString ()
ทีนี้ลองนึกภาพชั้นเรียนต่อไปนี้:
public class Flyer{
public void makeFly(){
}
}
public class Bird extends Abc, Flyer{
public void doAnotherThing(){
}
}
อีกครั้งระดับFlyer
นัยขยายวัตถุซึ่งมีวิธีการที่toString()
ชั้นใด ๆ ที่จะมีวิธีการนี้มาตั้งแต่พวกเขาทั้งหมดขยายObject
ทางอ้อมดังนั้นถ้าคุณโทรtoString()
จากBird
ที่toString()
Java จะมีการใช้งาน? จากAbc
หรือFlyer
? นี้จะเกิดขึ้นกับการเรียนใด ๆ ที่พยายามที่จะขยายสองคนหรือมากกว่าชั้นเรียนเพื่อหลีกเลี่ยงชนิดของ "วิธีการปะทะกัน" นี้พวกเขาสร้างความคิดของอินเตอร์เฟซโดยทั่วไปคุณอาจคิดว่าพวกเขาเป็นระดับนามธรรมที่ไม่ขยายวัตถุทางอ้อม เนื่องจากเป็นนามธรรมพวกเขาจะต้องดำเนินการโดยชั้นซึ่งเป็นวัตถุ (คุณไม่สามารถทำการเชื่อมต่ออินเทอร์เฟซคนเดียวได้พวกมันจะต้องถูกนำไปใช้โดยคลาส) ดังนั้นทุกอย่างจะยังคงทำงานได้ดี
เพื่อแยกคลาสต่าง ๆ จากส่วนต่อประสานคำสำคัญที่ใช้ถูกสงวนไว้สำหรับส่วนต่อประสานเท่านั้น
คุณสามารถใช้อินเทอร์เฟซใด ๆ ที่คุณชอบในคลาสเดียวกันได้เนื่องจากพวกเขาไม่ได้ขยายสิ่งใดเป็นค่าเริ่มต้น (แต่คุณสามารถสร้างอินเทอร์เฟซที่ขยายอินเทอร์เฟซอื่น แต่อีกครั้งอินเทอร์เฟซ "พ่อ" จะไม่ขยายวัตถุ ") ดังนั้น เพียงแค่อินเทอร์เฟซและพวกเขาจะไม่ต้องทนทุกข์ทรมานจาก " วิธีการรวบรวมลายเซ็นต์วิธีการ " หากพวกเขาทำคอมไพเลอร์จะส่งคำเตือนให้คุณและคุณจะต้องเปลี่ยนวิธีการลงนามเพื่อแก้ไขมัน (Signature = method name + params + return type) .
public interface Flyer{
public void makeFly(); // <- method without implementation
}
public class Bird extends Abc implements Flyer{
public void doAnotherThing(){
}
@Override
public void makeFly(){ // <- implementation of Flyer interface
}
// Flyer does not have toString() method or any method from class Object,
// no method signature collision will happen here
}
เนื่องจากอินเทอร์เฟซเป็นเพียงสัญญา และคลาสจริงๆแล้วเป็นที่เก็บข้อมูล
ตัวอย่างเช่นสองคลาส A, B มีวิธีการเดียวกัน m1 () และคลาส C ขยายทั้ง A, B
class C extends A, B // for explaining purpose.
ตอนนี้คลาส C จะค้นหาคำจำกัดความของ m1 อันดับแรกจะค้นหาในชั้นเรียนหากไม่พบจากนั้นจะตรวจสอบชั้นผู้ปกครอง ทั้ง A, B มีคำจำกัดความดังนั้นที่นี่ความกำกวมเกิดขึ้นซึ่งคำนิยามที่ควรเลือก ดังนั้น JAVA ไม่สนับสนุนการใช้งานหลายอย่างในตัว
Java ไม่รองรับการสืบทอดหลายรายการเนื่องจากเหตุผลสองประการ:
Object
คลาส เมื่อได้รับมรดกจากซุปเปอร์คลาสมากกว่าหนึ่งคลาสย่อยจะได้รับความกำกวมในการได้มาซึ่งคุณสมบัติของคลาสอ็อบเจ็กต์super()
ให้สร้างคลาสอาหารมื้อเย็น ถ้าชั้นเรียนมีมากกว่าหนึ่งคลาสชั้นยอดก็จะสับสนดังนั้นเมื่อคลาสหนึ่งขยายจากซุปเปอร์คลาสมากกว่าหนึ่งเราจะได้รับข้อผิดพลาดในการคอมไพล์เวลา
ยกตัวอย่างเช่นกรณีที่คลาส A มีเมธอด getSomething และคลาส B มีเมธอด getSomething และคลาส C ขยาย A และ B จะเกิดอะไรขึ้นถ้ามีคนเรียก C.getSomething ไม่มีวิธีในการกำหนดวิธีการโทร
โดยทั่วไปอินเตอร์เฟสจะระบุว่าเมธอดใดที่คลาสการใช้งานจำเป็นต้องมี คลาสที่ใช้หลายอินเตอร์เฟสนั้นหมายถึงคลาสนั้นต้องใช้เมธอดจากอินเตอร์เฟสทั้งหมด Whci จะไม่นำไปสู่ปัญหาใด ๆ ตามที่อธิบายไว้ข้างต้น
พิจารณาสถานการณ์ที่ Test1, Test2 และ Test3 เป็นสามคลาส คลาส Test3 สืบทอดคลาส Test2 และ Test1 ถ้าคลาส Test1 และ Test2 มีวิธีการเดียวกันและคุณเรียกมันจากอ็อบเจ็กต์คลาสย่อยจะมีความกำกวมในการเรียกเมธอดของคลาส Test1 หรือ Test2 แต่ไม่มีความคลุมเครือดังกล่าวสำหรับอินเทอร์เฟซ
Java ไม่รองรับการสืบทอดหลายแบบ, การสืบทอดหลายหน้าและแบบไฮบริดเนื่องจากปัญหาความกำกวม:
Scenario for multiple inheritance: Let us take class A , class B , class C. class A has alphabet(); method , class B has also alphabet(); method. Now class C extends A, B and we are creating object to the subclass i.e., class C , so C ob = new C(); Then if you want call those methods ob.alphabet(); which class method takes ? is class A method or class B method ? So in the JVM level ambiguity problem occurred. Thus Java does not support multiple inheritance.
ลิงค์อ้างอิง: https://plus.google.com/u/0/communities/102217496457095083679
ในลักษณะที่เรียบง่ายที่เราทุกคนรู้เราสามารถสืบทอด (ขยาย) หนึ่งคลาส แต่เราสามารถใช้อินเทอร์เฟซมากมาย .. นั่นเป็นเพราะในอินเทอร์เฟซที่เราไม่ให้การใช้งานเพียงแค่พูดฟังก์ชั่น สมมติว่าถ้าจาวาสามารถขยายชั้นเรียนจำนวนมากและผู้ที่มีวิธีการเดียวกัน .. ในจุดนี้ถ้าเราพยายามที่จะเรียกวิธีการเรียนในชั้นเรียนซุปเปอร์ย่อยสิ่งที่วิธีการคิดว่าจะทำงาน ??, คอมไพเลอร์รับสับสน ตัวอย่างเช่น: - พยายามที่จะขยายหลาย แต่ใน อินเตอร์เฟซวิธีการเหล่านั้นไม่ได้มีร่างกายที่เราควรใช้ผู้ที่อยู่ในชั้นย่อย .. พยายามที่จะดำเนินการหลาย ๆ ดังนั้นไม่ต้องกังวล ..
* นี่เป็นคำตอบง่ายๆตั้งแต่ฉันเป็นผู้เริ่มต้นใน Java *
พิจารณามีสามชั้นX
, และY
Z
ดังนั้นเรากำลังสืบทอดเช่นX extends Y, Z
และทั้งสองY
และZ
มีวิธีการที่alphabet()
มีประเภทผลตอบแทนและข้อโต้แย้งเดียวกัน วิธีการนี้alphabet()
ในการY
พูดกับแสดงตัวอักษรแรกและวิธีการในอักษรZ
กล่าวว่าการแสดงผลอักษรสุดท้าย ดังนั้นที่นี่มาคลุมเครือเมื่อถูกเรียกโดยalphabet()
X
ไม่ว่าจะเป็นการแสดงตัวอักษรตัวแรกหรือตัวสุดท้าย ??? ดังนั้นจาวาไม่สนับสนุนการสืบทอดหลายอย่าง ในกรณีของอินเทอร์เฟซให้พิจารณาY
และใช้Z
เป็นอินเตอร์เฟส ดังนั้นทั้งสองจะมีการประกาศของวิธีการalphabet()
แต่ไม่ได้นิยาม มันจะไม่บอกว่าจะแสดงตัวอักษรตัวแรกหรือตัวอักษรตัวสุดท้ายหรืออะไร แต่จะประกาศวิธีการalphabet()
. ดังนั้นจึงไม่มีเหตุผลที่จะเพิ่มความกำกวม X
เราสามารถกำหนดวิธีการกับสิ่งที่เราต้องการระดับภายใน
ดังนั้นในคำนิยามในอินเทอร์เฟซจะทำหลังจากการใช้งานจึงไม่มีความสับสน