面向对象三大特性---继承
什么是继承
继承的概念来源于现实生活,维基百科将其定义为:在所有权人死亡后,将其财产、债务、爵位、世袭官职等转移给一个或多个继承人继承。而在Java编程语言中,继承是面向对象的三大特性之一。
继承的本质是代码复用,当我们发现多个类之间存在相同的属性或行为时,可以将这些共同特征抽取到一个父类中,然后让具体的子类去继承这个父类。比如,我们可以定义一个Animal类作为父类, 然后让Dog Cat等具体的动物类作为子类来继承它。
让我们用实际例子来理解继承的使用场景:
// 父类:动物类
public class Animal {protected String name;public Animal(String name) {this.name = name;}public void eat() {System.out.println(name + "正在吃东西");}
}// 子类:猫类
public class Cat extends Animal {public Cat(String name) {super(name);}
}
运行结果:
public class Demo {public static void main(String[] args) {Animal animal = new Animal("旺财");Cat cat = new Cat("咪咪");animal.eat(); // 输出:旺财正在吃东西cat.eat(); // 输出:咪咪正在吃东西}
}
我们可以观察到,虽然没有在cat类写任何的属性和方法,但依然可以使用父类的属性和方法
单继承与多继承
单继承
Java采用单继承机制,即一个子类只能有一个直接父类。这种设计避免了许多复杂的问题,使得类的层次结构更加清晰。在上面的例子中,Dog
类和Cat
类都只继承了Animal
这一个父类。
多继承
一些编程语言(如C++)支持多继承,允许一个类同时继承多个父类。虽然这看起来更加灵活,但实际上会带来很多问题:
最典型的问题是菱形继承,假设我们有如下继承结构:
如果B和C都重写了A类的某个方法,那么D类继承这个方法时就会产生歧义:到底继承哪个父类的实现?俺不中嘞!
C++为了解决这个问题引入了虚继承,但这又增加了语言的复杂性。Java的设计者认为,既然多继承的使用场景相对较少,而带来的问题却很多,不如采用更简洁的单继承模式。
接口:多继承的替代方案
Java通过接口可以弥补单继承的不足。接口定义了一组方法签名,类可以实现多个接口,从而获得类似多继承的效果,但又避免了菱形继承的问题。
// 接口定义
public interface Flyable {void fly();
}public interface Swimmable {void swim();
}// 实现多个接口
public class Duck extends Animal implements Flyable, Swimmable {public Duck(String name, int age) {super(name, age);}@Overridepublic void fly() {System.out.println(name + "正在飞翔");}@Overridepublic void swim() {System.out.println(name + "正在游泳");}
}
继承的优缺点分析
优点
- 代码复用:子类可以继承父类的属性和方法,减少重复代码的编写。
- 扩展性:可以在不修改原有代码的基础上,通过继承来扩展功能。
- 多态性基础:继承为多态提供了基础,使得程序更加灵活。
- 代码组织:通过继承可以建立清晰的类层次结构,便于理解和维护。
缺点
- 耦合度增加:子类与父类之间存在强耦合关系,父类的变化会直接影响子类。
- 破坏封装性:子类需要了解父类的内部实现细节,违背了封装的原则。
- 维护困难:当父类发生变化时,所有子类都可能需要进行相应的修改,“牵一发而动全身”。
- 滥用风险:过度使用继承可能导致类层次过深,增加系统复杂性。
继承的最佳实践
- 遵循里氏替换原则:子类应该能够替换父类出现在程序中的任何地方,而不影响程序的正确性。
- **优先使用组合而非继承:**当类之间的关系是"has-a"而不是"is-a"时,应该使用组合而不是继承。
- 合理使用访问修饰符:
- 使用
protected
修饰符来保护父类的内部实现 - 谨慎使用
public
继承,避免暴露过多的实现细节
- **避免过深的继承层次:**一般建议继承层次不要超过3-4层,过深的继承层次会增加系统的复杂性和维护难度。
现代Java中的继承演进
Java 8的接口改进
从Java 8开始,接口可以包含默认方法(default method)和静态方法,这进一步增强了接口的功能,使其在某种程度上具备了多继承的特性:
public interface Drawable {void draw();// 默认方法default void print() {System.out.println("正在打印图形");}// 静态方法static void info() {System.out.println("这是一个可绘制接口");}
}
一句话总结
“把继承当成行为契约,而不是代码复用工具;子类签了契约,就必须能随时顶班。”