我们都知道java三大属性,封装、继承、多态。今天我们来详谈一下继承。
百度上这样说:继承是面向对象最显著的一个特性。继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力。也就是说继承能够扩展已有的类,达到代码可复用性。
- 继承之名称
继承通过关键字extends来实现,当类A继承了类B,那么我们称A为衍生类或者子类,称B为基类或父类。
/**
- A称之为基类或父类
*/
public class A {
A(){
}
}
/**
- B称之为衍生类或子类
*/
class B extends A{
B(){
}
}
- 继承的权利
当A继承了B
1.那么A(子类)拥有B(父类)非private的属性和方法—–继承所得来的(复用性)
2.A(子类)可以拥有自己的属性和方法—-对父类的扩展(扩展性)
3.A(子类)可以对B(父类)的方法进行覆写,而添加自己的业务—–方法相同,但业务操作可以不同(方法的覆写)。
通过子类对象调用属性或方法时,编译器会优先在当前类(即子类)中查找该属性或方法,假如没有再去父类中查找并调用。 所以如果子类和父类拥有相同的属性和方法时,通过new 子类对象调用的是子类中属性或方法。(当然如果是覆写父类的静态方法,就另当别论了,具体参考另一篇文章 关于静态static那些事)
/**
- A基类或父类
*/
public class A {
private int num = 1;//私有不能被继承
private void method1(){
System.out.println("私有的方法无法被继承");
}
public void method2(){
System.out.println("可以被继承");
}
public void method3(){
System.out.println("实现A的业务操作");
}
}
/**
- B继承A
*/
class B extends A {
private String a = "a";//拥有自己的属性
/**
* 覆写父类的方法method3()
*/
protected void method3(){
System.out.println("实现B的业务操作");
}
}
class Test {
public static void main(String[] args) {
B b = new B();
b.method2();//重用父类的方法method2()
b.method3();//调用的是子类的方法method3()
//b.method1();编译不通过无法调用,无法继承private的方法
}
}
- 多继承
java只支持单继承,不能多继承,也就是说不能一个类同时继承多个父类。试想一下,假如有两个类A和B,拥有相同名称的实例变量和相同名称的方法,当C同时继承了A和B并且没有覆盖A和B的方法时,那么C在引用实例变量或调用方法时该用哪个父类的呢?这就产生了歧义,所以在java中是禁止多继承的。
public class A {
int num = 1;
public void method(int num){
System.out.println("实现A的业务");
}
}
/**
- B继承A
*/
class B {
int num = 2;
public void method(int num){
System.out.println("实现B的业务");
}
}
/**
*C同时继承A,B
*/
class C extends A,B{//不支持多继承,编译不通过
}
- 继承之构造器
有两种情况子类无法通过继承得到,一种就是被private所修饰的属性和方法,另一种就是父类的构造器。我们知道当new 一个对象时,编译器会通过调用对应的构造器来完成对象的初始化。那么在继承中对象的初始化时怎么的呢?既然子类可以继承父类的属性和方法(private除外),那是不是父类的对象初始化必定在子类前面呢,答案是:肯定的。
public class Test {
public static void main(String[] args) {
new Son();
new Son("小明");
}
}
class Father {
Father(){
System.out.println("父类的无参构造函数");
}
Father(String a){
System.out.println("父类的有参构造函数");
}
}
class Son extends Father{
Son(){
//super();//编译器默认添加super(),并且是在子类构造器的首位置
System.out.println("子类的无参构造函数");
}
Son(String b){
//super();//编译器默认添加super(),并且是在子类构造器的首位置
System.out.println("子类的有参构造函数");
}
}
执行结果:
父类的无参构造函数
子类的无参构造函数
父类的无参构造函数
子类的有参构造函数
从上面的例子可以看出,当我们new Son()对象时,父类的无参构造器会优先被调用执行,然后再执行子类的构造方法,也就是说父类的对象优先初始化。这是因为当我们new Son()对象时,编译器会默认为我们添加一行代码super();放在子类构造器的首位置。所以会优先调用父类无参构造器,执行完后再调用子类无参构造函数。同理,new Son(“小明”)时编译器也会默认为我们添加一行代码super();放在子类构造器的首位置,所以有了如上结果。
当然上面的例子只是针对父类有无参构造函数的情况,编译器可以很容易地调用它们,因为不存
在具体传递什么自变量的问题那如果父类没有无参构造函数呢?(关于构造函数,已写过一篇进行详细介绍,这里不再多讲。请参考构造函数)这时就得我们手动调用。
public class Test {
public static void main(String[] args) {
new Son("小明");
}
}
class Father {
Father(String a){
System.out.println("父类String类型的参构造函数");
}
Father(int i){
System.out.println("父类int类型的参构造函数");
}
}
class Son extends Father{
Son(String b){
super(b);//手动显示调用
System.out.println("子类String类型的构造函数");
}
Son(String a, int i){
super(i);//手动显示调用
System.out.println("子类int类型的构造函数");
}
}
可以看出父类中没有无参构造函数,我们只能手动显示调用,而且手动显示调用时super中的类型在父类中必须存在。所以建议,当父类中存在有参构造函数时,我们都手动添加一个无参构造函数,避免不必要的错误和麻烦。
- 继承的缺点
我们都知道没任何东西都是完美无缺的,继承同样也存在一定的缺点。
1、耦合性:当一个类继承了另一个类时,就会有一定的依赖性。假如子类使用了父类的全局变量,当这个全局变量被改变时,那么所有用到这个变量的函数都被受到影响
2、 脆弱的基类问题:基础类被认为是脆弱的,是因为你在看起来安全的情况下修改基类,但是当从派生类继承时,新的行为也许引起派生类出现功能紊乱。
3、捕获基本构建器的违例:正如刚才指出的那样,编译器会强迫我们在衍生类构建器的主体中首先设置对基础类构建器的调用。这意味着在它之前不能出现任何东西,同时也会防止衍生类构建器捕获来自一个基础类的任何违例事件。显然,这有时会为我们造成不便。