Tôi đang đọc "Hướng dẫn Java" (lần thứ 2). Tôi vừa xem qua phần Giao diện (một lần nữa), nhưng vẫn chưa hiểu cách Giao diện Java mô phỏng đa kế thừa. Có lời giải thích rõ ràng hơn những gì trong sách không?
Tôi đang đọc "Hướng dẫn Java" (lần thứ 2). Tôi vừa xem qua phần Giao diện (một lần nữa), nhưng vẫn chưa hiểu cách Giao diện Java mô phỏng đa kế thừa. Có lời giải thích rõ ràng hơn những gì trong sách không?
Câu trả lời:
Giả sử bạn có 2 loại thứ trong miền của mình: Xe tải và Nhà bếp
Xe tải có phương thức driveTo () và phương thức Kitchens a cook ().
Bây giờ, giả sử Pauli quyết định bán pizza từ phía sau xe tải giao hàng. Anh ấy muốn một thứ mà anh ấy có thể lái xeTo () và nấu ăn () với.
Trong C ++, anh ấy sẽ sử dụng đa kế thừa để làm điều này.
Trong Java, điều đó được coi là quá nguy hiểm nên bạn có thể kế thừa từ một lớp chính, nhưng bạn có thể "kế thừa" các hành vi từ các giao diện, dành cho tất cả các mục đích và mục đích, các lớp trừu tượng không có trường hoặc triển khai phương thức.
Vì vậy, trong Java, chúng tôi có xu hướng triển khai đa kế thừa bằng cách sử dụng các ủy quyền:
Pauli phân loại một chiếc xe tải và thêm một nhà bếp vào chiếc xe tải trong một biến thành viên được gọi là nhà bếp. Anh ta cài đặt giao diện Kitchen bằng cách gọi cook.cook ().
class PizzaTruck extends Truck implements Kitchen {
Kitchen kitchen;
public void cook(Food foodItem) {
kitchen.cook(foodItem);
}
}
Anh ấy là một người đàn ông hạnh phúc vì bây giờ anh ấy có thể làm những việc như;
pizzaTruck.driveTo(beach);
pizzaTruck.cook(pizzaWithExtraAnchovies);
Được rồi, câu chuyện ngớ ngẩn này là để làm rõ rằng nó không phải là mô phỏng của đa kế thừa, nó là đa kế thừa thực sự với điều kiện là bạn chỉ có thể kế thừa hợp đồng, chỉ kế thừa từ các lớp cơ sở trừu tượng trống được gọi là giao diện.
(cập nhật: với sự xuất hiện của các giao diện phương thức mặc định bây giờ cũng có thể cung cấp một số hành vi được kế thừa)
public void cook()
) mà cả Kitchen và PizzaTruck sẽ triển khai.
default
các phương thức. Giao diện vẫn không có nhà nước chẳng hạn, nhưng họ làm có phương pháp một lớp có thể kế thừa.
Có thể bạn đang nhầm lẫn vì bạn xem cục bộ đa kế thừa, trong điều kiện một lớp kế thừa chi tiết triển khai từ nhiều lớp cha mẹ. Điều này là không thể trong Java (và thường dẫn đến lạm dụng trong các ngôn ngữ có thể xảy ra).
Các giao diện cho phép đa kế thừa các kiểu , ví dụ a class Waterfowl extends Bird implements Swimmer
có thể được sử dụng bởi các lớp khác như thể nó là a Bird
và như thể là a Swimmer
. Đây là ý nghĩa sâu xa hơn của đa kế thừa: cho phép một đối tượng hoạt động giống như nó thuộc một số lớp khác nhau không liên quan cùng một lúc.
Đây là một cách để đạt được đa kế thừa thông qua các giao diện trong java.
Đạt được gì?
class A mở rộng B, C // điều này không thể thực hiện được trong java trực tiếp nhưng có thể đạt được gián tiếp.
class B{
public void getValueB(){}
}
class C{
public void getValueC(){}
}
interface cInterface{
public getValueC();
}
class cChild extends C implemets cInterface{
public getValueC(){
// implementation goes here, call the super class's getValueC();
}
}
// Below code is **like** class A extends B, C
class A extends B implements cInterface{
cInterface child = new cChild();
child.getValueC();
}
đưa ra hai giao diện bên dưới ...
interface I1 {
abstract void test(int i);
}
interface I2 {
abstract void test(String s);
}
Chúng tôi có thể triển khai cả hai điều này bằng cách sử dụng mã bên dưới ...
public class MultInterfaces implements I1, I2 {
public void test(int i) {
System.out.println("In MultInterfaces.I1.test");
}
public void test(String s) {
System.out.println("In MultInterfaces.I2.test");
}
public static void main(String[] a) {
MultInterfaces t = new MultInterfaces();
t.test(42);
t.test("Hello");
}
}
Chúng ta KHÔNG THỂ mở rộng hai đối tượng, nhưng chúng ta có thể triển khai hai giao diện.
Các giao diện không mô phỏng đa kế thừa. Những người tạo Java đã coi đa kế thừa là sai, vì vậy không có điều đó trong Java.
Nếu bạn muốn kết hợp chức năng của hai lớp thành một - hãy sử dụng thành phần đối tượng. I E
public class Main {
private Component1 component1 = new Component1();
private Component2 component2 = new Component2();
}
Và nếu bạn muốn hiển thị các phương thức nhất định, hãy xác định chúng và để chúng ủy quyền cuộc gọi cho bộ điều khiển tương ứng.
Ở đây các giao diện có thể hữu ích - nếu Component1
triển khai giao diện Interface1
và Component2
triển khai Interface2
, bạn có thể xác định
class Main implements Interface1, Interface2
Vì vậy, bạn có thể sử dụng các đối tượng thay thế cho nhau khi ngữ cảnh cho phép nó.
Nó khá đơn giản. Bạn có thể triển khai nhiều hơn một giao diện trong một kiểu. Vì vậy, ví dụ, bạn có thể có một triển khai của List
đó cũng là một thể hiện của Deque
(và Java thì có ... LinkedList
).
Bạn chỉ không thể kế thừa các triển khai từ nhiều cha mẹ (tức là mở rộng nhiều lớp). Các khai báo (chữ ký phương thức) không có vấn đề gì.
Bạn biết không, xuất phát từ quan điểm của một nhà phát triển JavaScript đang cố gắng hiểu chuyện quái gì đang xảy ra với thứ này, tôi muốn chỉ ra một vài điều và ai đó hãy cho tôi biết tôi đang thiếu điều gì ở đây nếu tôi cách đánh dấu.
Các giao diện thực sự đơn giản. Đơn giản một cách ngu ngốc. Chúng thật ngu ngốc, đơn giản đến điên cuồng như mọi người nghĩ ban đầu, đó là lý do tại sao có rất nhiều câu hỏi trùng lặp về chủ đề chính xác này bởi vì một lý do để sử dụng chúng đã không rõ ràng bởi những người cố gắng tạo ra nhiều hơn chúng và ở đó đang bị lạm dụng phổ biến trong mọi cơ sở mã phía máy chủ Java mà tôi đã từng tiếp xúc.
Vì vậy, tại sao bạn muốn sử dụng chúng? Hầu hết thời gian bạn sẽ không. Bạn chắc chắn sẽ không muốn sử dụng chúng mọi lúc như nhiều người vẫn nghĩ. Nhưng trước khi tôi đến khi bạn muốn, hãy nói về những gì chúng KHÔNG.
Các giao diện KHÔNG:
Chúng thực sự đơn giản như bạn nghĩ về cái nhìn đầu tiên. Mọi người luôn lạm dụng một cách ngu ngốc nên thật khó để hiểu được điểm mấu chốt là gì. Nó chỉ là xác nhận / thử nghiệm. Khi bạn đã viết một cái gì đó phù hợp với một giao diện và hoạt động, việc xóa mã "triển khai" đó sẽ không phá vỡ bất cứ điều gì.
Nhưng nếu bạn đang sử dụng các giao diện đúng cách, bạn sẽ không muốn xóa nó vì có nó ở đó cung cấp cho nhà phát triển tiếp theo một công cụ để viết một lớp truy cập cho một tập hợp cơ sở dữ liệu hoặc dịch vụ web khác mà họ muốn phần còn lại của ứng dụng của bạn tiếp tục. sử dụng vì họ biết lớp của họ sẽ không thành công cho đến khi họ có được giao diện hoàn chỉnh 100% như mong đợi. Tất cả các giao diện làm là xác thực lớp của bạn và xác nhận rằng trên thực tế bạn đã triển khai một giao diện như bạn đã hứa. Chỉ có bấy nhiêu thôi.
Chúng cũng có thể di động. Bằng cách hiển thị các định nghĩa giao diện của bạn, bạn có thể cung cấp cho những người muốn sử dụng mã chưa phơi sáng của bạn một tập hợp các phương pháp để tuân theo để các đối tượng của họ sử dụng nó một cách chính xác. Họ không phải triển khai các giao diện. Họ có thể chỉ cần ghi chúng vào một mẩu giấy ghi chú và kiểm tra lại. Nhưng với giao diện, bạn có nhiều sự đảm bảo sẽ không có gì cố gắng hoạt động cho đến khi nó có một phiên bản giao diện phù hợp được đề cập.
Vì vậy, bất kỳ giao diện nào không có khả năng được triển khai nhiều lần? Hoàn toàn vô dụng. Đa kế thừa? Ngừng vươn tới cầu vồng đó. Java tránh chúng là có lý do ngay từ đầu và các đối tượng tổng hợp / tổng hợp dù sao cũng linh hoạt hơn theo nhiều cách. Điều đó không có nghĩa là các giao diện không thể giúp bạn mô hình hóa theo những cách mà đa kế thừa cho phép nhưng nó thực sự không phải là kế thừa theo bất kỳ hình dạng hoặc hình thức nào và không nên được nhìn nhận như vậy. Nó chỉ đảm bảo rằng mã của bạn sẽ không hoạt động cho đến khi bạn triển khai tất cả các phương pháp mà bạn đã thiết lập.
Y
cũng có thể chấp nhận tham chiếu đến X
. Mối quan hệ như vậy sẽ có ích lợi gì nếu không thể thay thế được?
Nó không phải là một mô phỏng của đa kế thừa. Trong java, bạn không thể kế thừa từ hai lớp, nhưng nếu bạn triển khai hai giao diện "có vẻ như bạn được thừa kế từ hai lớp khác nhau" vì bạn có thể sử dụng lớp của mình như bất kỳ giao diện nào trong hai giao diện của bạn.
Ví dụ
interface MyFirstInteface{
void method1();
}
interface MySecondInteface{
void method2();
}
class MyClass implements MyFirstInteface, MySecondInteface{
public void method1(){
//Method 1
}
public void method2(){
//Method 2
}
public static void main(String... args){
MyFirstInterface mfi = new MyClass();
MySecondInterface msi = new MyClass();
}
}
Điều này sẽ hoạt động và bạn có thể sử dụng mfi và msi, nó có vẻ giống như một đa kế thừa, nhưng không phải vì bạn không thừa kế bất cứ thứ gì, bạn chỉ viết lại các phương thức công khai được cung cấp bởi các giao diện.
Bạn cần phải chính xác:
Java cho phép đa kế thừa giao diện, nhưng chỉ kế thừa duy nhất việc triển khai.
Bạn thực hiện đa kế thừa giao diện trong Java như sau:
public interface Foo
{
String getX();
}
public interface Bar
{
String getY();
}
public class MultipleInterfaces implements Foo, Bar
{
private Foo foo;
private Bar bar;
public MultipleInterfaces(Foo foo, Bar bar)
{
this.foo = foo;
this.bar = bar;
}
public String getX() { return this.foo.getX(); }
public String getY() { return this.bar.getY(); }
}
Nhân tiện, lý do tại sao Java không triển khai đa kế thừa đầy đủ là vì nó tạo ra sự mơ hồ. Giả sử bạn có thể nói "A mở rộng B, C", và sau đó cả B và C đều có một hàm "void f (int)". A kế thừa triển khai nào? Với cách tiếp cận của Java, bạn có thể triển khai bất kỳ số lượng giao diện nào, nhưng các giao diện chỉ khai báo một chữ ký. Vì vậy, nếu hai giao diện bao gồm các hàm có cùng chữ ký, tốt thôi, lớp của bạn phải triển khai một hàm với chữ ký đó. Nếu giao diện bạn thừa kế có các chức năng với các chữ ký khác nhau, thì các chức năng không liên quan gì đến nhau, vì vậy không có vấn đề gì về xung đột.
Tôi không nói đây là cách duy nhất. C ++ thực hiện đa kế thừa thực sự bằng cách thiết lập các quy tắc ưu tiên mà việc triển khai sẽ thắng. Nhưng các tác giả của Java đã quyết định loại bỏ sự mơ hồ. Tôi không biết vì niềm tin triết học rằng điều này tạo ra mã sạch hơn hay vì họ không muốn làm thêm tất cả các công việc.
Không công bằng khi nói rằng các giao diện 'mô phỏng' đa kế thừa.
Chắc chắn, kiểu của bạn có thể triển khai nhiều giao diện và hoạt động đa hình như nhiều kiểu khác nhau. Tuy nhiên, bạn rõ ràng sẽ không kế thừa hành vi hoặc triển khai theo cách sắp xếp này.
Nhìn chung, hãy nhìn vào thành phần mà bạn nghĩ rằng bạn có thể cần đa kế thừa.
HOẶC Một giải pháp tiềm năng để đạt được một thứ gì đó đa kế thừa là giao diện Mixin - http://csis.pace.edu/~bergin/patterns/multipleinheritance.html . Sử dụng cẩn thận!
Họ không.
Tôi nghĩ rằng sự nhầm lẫn đến từ việc mọi người tin rằng việc triển khai một giao diện tạo thành một hình thức kế thừa nào đó. Nó không; việc thực hiện có thể đơn giản là để trống, không có hành vi nào bị buộc bởi hành vi hoặc được đảm bảo thông qua bất kỳ hợp đồng nào. Một ví dụ điển hình là giao diện Clonable, mặc dù ám chỉ đến rất nhiều chức năng tuyệt vời, nhưng lại xác định rất ít nên về cơ bản nó vô dụng và có khả năng nguy hiểm.
Bạn thừa hưởng những gì bằng cách triển khai một giao diện? Bong bóng! Vì vậy, theo ý kiến của tôi, hãy ngừng sử dụng từ giao diện và kế thừa trong cùng một câu. Như Michael Borgwardt đã nói, giao diện không phải là một định nghĩa mà là một khía cạnh.
Bạn thực sự có thể "kế thừa" từ nhiều lớp cụ thể nếu chúng tự triển khai các giao diện. innerclasses
giúp bạn đạt được điều đó:
interface IBird {
public void layEgg();
}
interface IMammal {
public void giveMilk();
}
class Bird implements IBird{
public void layEgg() {
System.out.println("Laying eggs...");
}
}
class Mammal implements IMammal {
public void giveMilk() {
System.out.println("Giving milk...");
}
}
class Platypus implements IMammal, IBird {
private class LayingEggAnimal extends Bird {}
private class GivingMilkAnimal extends Mammal {}
private LayingEggAnimal layingEggAnimal = new LayingEggAnimal();
private GivingMilkAnimal givingMilkAnimal = new GivingMilkAnimal();
@Override
public void layEgg() {
layingEggAnimal.layEgg();
}
@Override
public void giveMilk() {
givingMilkAnimal.giveMilk();
}
}
Tôi muốn chỉ ra điều gì đó khiến tôi khó chịu ở phía sau, đến từ C ++, nơi bạn cũng có thể dễ dàng kế thừa nhiều triển khai.
Có một giao diện "rộng" với nhiều phương thức có nghĩa là bạn sẽ phải triển khai nhiều phương thức trong các lớp cụ thể của mình và bạn không thể chia sẻ chúng dễ dàng giữa các triển khai.
Ví dụ:
interface Herbivore {
void munch(Vegetable v);
};
interface Carnivore {
void devour(Prey p);
}
interface AllEater : public Herbivore, Carnivore { };
class Fox implements AllEater {
...
};
class Bear implements AllEater {
...
};
Trong ví dụ này, Fox và Bear không thể chia sẻ một triển khai cơ sở chung cho cả phương thức giao diện của nó munch
và devour
.
Nếu các triển khai cơ sở trông như thế này, chúng tôi có thể muốn sử dụng chúng cho Fox
và Bear
:
class ForestHerbivore implements Herbivore
void munch(Vegetable v) { ... }
};
class ForestCarnivore implements Carnivore
void devour(Prey p) { ... }
};
Nhưng chúng ta không thể kế thừa cả hai điều này. Các triển khai cơ sở cần phải là các biến thành viên trong lớp và các phương thức được định nghĩa có thể chuyển tiếp đến đó. I E:
class Fox implements AllEater {
private ForestHerbivore m_herbivore;
private ForestCarnivore m_carnivore;
void munch(Vegetable v) { m_herbivore.munch(v); }
void devour(Prey p) { m_carnivore.devour(p); }
}
Điều này sẽ khó sử dụng nếu các giao diện phát triển (tức là hơn 5-10 phương thức ...)
Một cách tiếp cận tốt hơn là xác định một giao diện như một tập hợp các giao diện:
interface AllEater {
Herbivore asHerbivore();
Carnivore asCarnivore();
}
Điều này có nghĩa là Fox
và Bear
chỉ phải triển khai hai phương thức này và các giao diện và các lớp cơ sở có thể phát triển độc lập với AllEater
giao diện tổng hợp liên quan đến các lớp thực thi.
Ít khớp theo cách này hơn, nếu nó phù hợp với ứng dụng của bạn.
Tôi không nghĩ là họ làm.
Kế thừa cụ thể là một mối quan hệ định hướng triển khai giữa các triển khai. Các giao diện hoàn toàn không cung cấp bất kỳ thông tin triển khai nào mà thay vào đó xác định một kiểu. Để có tính kế thừa, bạn cần phải kế thừa cụ thể một số hành vi hoặc thuộc tính từ một lớp cha.
Tôi tin rằng có một câu hỏi ở đây cụ thể về vai trò của giao diện và đa kế thừa, nhưng tôi không thể tìm thấy nó bây giờ ...
Thực sự không có mô phỏng đa kế thừa trong Java.
Đôi khi mọi người sẽ nói rằng bạn có thể mô phỏng đa kế thừa bằng Giao diện vì bạn có thể triển khai nhiều hơn một giao diện cho mỗi lớp và sau đó sử dụng thành phần (thay vì kế thừa) trong lớp của bạn để đạt được các hành vi của nhiều lớp mà bạn đang cố gắng kế thừa đầu tiên là.
Nếu nó có ý nghĩa trong mô hình đối tượng của bạn, tất nhiên bạn có thể kế thừa từ một lớp và triển khai 1 hoặc nhiều giao diện.
Có những trường hợp đa kế thừa trở nên rất tiện dụng và khó thay thế bằng các giao diện mà không cần viết thêm mã. Ví dụ: có những ứng dụng Android sử dụng các lớp bắt nguồn từ Activity và các lớp khác từ FragmentActivity trong cùng một ứng dụng. Nếu bạn có một tính năng cụ thể muốn chia sẻ trong một lớp chung, trong Java, bạn sẽ phải sao chép mã thay vì để các lớp con của Activity và FragmentActivity dẫn xuất từ cùng một lớp SharedFeature. Và việc triển khai kém các generic trong Java cũng không giúp ích được gì vì việc viết như sau là bất hợp pháp:
public class SharedFeature<T> extends <T extends Activity>
...
...
Không có hỗ trợ cho đa kế thừa trong java.
Câu chuyện về việc hỗ trợ đa kế thừa sử dụng giao diện là điều mà các nhà phát triển chúng tôi đã đúc kết ra. Giao diện mang lại sự linh hoạt hơn các lớp cụ thể và chúng tôi có tùy chọn triển khai nhiều giao diện bằng cách sử dụng một lớp. Đây là theo thỏa thuận, chúng tôi đang tuân thủ hai bản thiết kế để tạo một lớp.
Điều này đang cố gắng tiến gần hơn đến đa kế thừa. Những gì chúng tôi làm là triển khai nhiều giao diện, ở đây chúng tôi không mở rộng (kế thừa) bất cứ thứ gì. Lớp thực thi là lớp sẽ thêm các thuộc tính và hành vi. Nó không nhận được việc triển khai miễn phí từ các lớp cha. Tôi chỉ đơn giản nói rằng, không có hỗ trợ cho đa kế thừa trong java.
Không, Java không hỗ trợ đa kế thừa. Không sử dụng lớp cũng không sử dụng giao diện. Tham khảo liên kết này để biết thêm thông tin https://devsuyed.wordpress.com/2016/07/21/does-java-support-multiple-inheritance
Tôi cũng phải nói rằng Java không hỗ trợ đa kế thừa.
Bạn phải phân biệt ý nghĩa giữa từ khóa extends
và implements
từ khóa trong Java. Nếu chúng ta sử dụng extends
, chúng ta thực sự đang kế thừa lớp sau từ khóa đó. Nhưng, để làm cho mọi thứ trở nên đơn giản, chúng ta không thể sử dụng extends
nhiều hơn một lần. Nhưng bạn có thể triển khai nhiều Giao diện như bạn muốn.
Nếu bạn triển khai một giao diện, không có khả năng bạn bỏ lỡ việc triển khai tất cả các phương thức trong mỗi giao diện (Ngoại lệ: triển khai mặc định của các phương thức giao diện được giới thiệu trong Java 8) Vì vậy, bây giờ bạn hoàn toàn nhận thức được những gì đang xảy ra với những thứ mà bạn đã nhúng vào lớp mới của mình.
Thực ra tại sao Java không cho phép đa kế thừa, đa kế thừa làm cho mã hơi phức tạp. Đôi khi, hai phương thức của các lớp cha có thể xung đột do có các chữ ký giống nhau. Nhưng nếu bạn buộc phải thực hiện tất cả các phương pháp theo cách thủ công, bạn sẽ hiểu đầy đủ về những gì đang xảy ra, như tôi đã đề cập ở trên. Nó làm cho mã của bạn dễ hiểu hơn đối với bạn.
Nếu bạn cần thêm thông tin về các giao diện Java, hãy xem bài viết này, http://www.geek-programmer.com/introduction-to-java-interfaces/
Giữa hai lớp Java nhiều Kế thừa trực tiếp là không thể. Trong trường hợp này java khuyến nghị Sử dụng để giao diện và khai báo phương thức bên trong giao diện và thực hiện phương thức với lớp Con.
interface ParentOne{
public String parentOneFunction();
}
interface ParentTwo{
public String parentTwoFunction();
}
class Child implements ParentOne,ParentTwo{
@Override
public String parentOneFunction() {
return "Parent One Finction";
}
@Override
public String parentTwoFunction() {
return "Parent Two Function";
}
public String childFunction(){
return "Child Function";
}
}
public class MultipleInheritanceClass {
public static void main(String[] args) {
Child ch = new Child();
System.out.println(ch.parentOneFunction());
System.out.println(ch.parentTwoFunction());
System.out.println(ch.childFunction());
}
}