继承

I. 继承概述

1. 什么是继承
 子类继承父类的特征行为,使得子类具有父类的各种属性和方法。或子类从父类继承方法,使得子类具有父类相同的行为。
继承的特点:
* 在继承关系中,父类更通用、子类更具体。父类具有更一般的特征和行为,而子类除了具有父类的特征和行为,还具有一些自己特殊的特征和行为。
* 在继承关系中。父类和子类需要满足is-a的关系。子类是父类。

2. 为什么要继承
 使用继承可以有效实现代码复用避免重复代码的出现。当两个类具有相同的特征(属性)和行为(方法)时,可以将相同的部分抽取出来放到一个类中作为父类,其它两个类继承这个父类。

3.如何实现继承
 在Java中,用extends关键字来表示一个类继承了另一个类。在父类中只定义一些通用的属性和方法。子类继承父类的属性和方法,子类中可以定义特定的属性和方法。或子类重新定义父类的属性、重写父类的方法可以获得与父类不同的功能。

接下来以一个例子来说明:

/**
 * 继承的演示:
 * 工人和学生都是人,
 * 他们有共同的属性:年龄和姓名
 *     有共同的行为:吃饭
 * 他们还有各自特有的属性:工人有公司
 *                      学生有学校
 *        各自特有的行为:工人工作
 *                      学生学习
 */

/**
* @Date: 2017/11/11
* @Time: 下午9:02
* @Description: 父类
*/
class Person {
    String name;
    int age;
    void eat() {
        System.out.println("人可以吃饭");
    }
}

/**
* @Date: 2017/11/11
* @Time: 下午9:08
* @Description: 学生类
*/
class Student extends Person {
    String school;
    void study() {
        System.out.println("学生学习");
    }
}

/**
* @Date: 2017/11/11
* @Time: 下午9:09
* @Description: 工人类
*/
class Worker extends Person {
    String company;
    void work() {
        System.out.println("工人工作");
    }
}

/**
 * @Author: 落脚丶
 * @Date: 2017/11/11
 * @Time: 下午9:02
 * @Description: 继承演示
 */
public class InheritanceDemo {
    public static void main(String[] args) {
        Student student = new Student();
        student.age = 20; // 具有父类的属性年龄
        student.name = "Jean"; // 具有父类的属性姓名
        student.school = "No1 Middle School"; // 子类独有的属性
        System.out.println(
                "学生的年龄:" + student.age + "\n" +
                "学生的姓名:" + student.name +"\n" +
                "学生所在学校:" + student.school
        );
        student.eat(); // 调用父类的方法
        student.study(); // 调用子类特有方法
    }
}
/**
 * Output
 * 学生的年龄:20
 * 学生的姓名:Jean
 * 学生所在学校:No1 Middle School
 * 人可以吃饭
 * 学生学习
 */

上面仅仅是继承的基本概念,下面我们再来探究一下继承的细节。


II. 继承的细节

  • 单继承与多重继承
    Java支持单继承,不直接支持多继承。即,一个子类只能有一个直接父类,不能有多个直接父类。
class A {
    void show() {
        System.out.println("a");
    }
}
class B {
    void show() {
        System.out.println("b");
    }
}
class C extends A,B {}

上面这中情况是不允许的,我们也很容易能看出来如果可以这么做带来的问题:如果类C同时继承了类A和类B,那么当C调用show()方法时,就会出现不确定性。

Java支持多重继承,即下面的情况是被允许的。

class A {}

class B extends A {}

class C extends B {}
  • 成员变量

由第一个例子我们可以知道,子类继承了父类的成员变量,并且可以直接使用。子类还可以定义自己的成员变量。不过,这里会出现一个特殊的情况:子父类中有同名的成员变量
看下面的例子:

class A {
    int tem = 1;
    int num = 4;
}

class B extends A {
    int num = 5;
    void show() {
        System.out.println("num = " + num + "\n" +
                "tem = " + tem
        );
    }
}

public class InheritanceDemo {
    public static void main(String[] args) {
        B b = new B();
        b.show();
    }
}
/**
 * Output
 * num = 5
 * tem = 1
 */

由上面例子可以看出,子类B的对象调用show方法时,输出num = 5;首先我们要明确的一点是虽然子类和父类中都有变量num,但是它们不是同一个变量。所以,并不是子类的num重新定义的了父类num的值。这涉及到作用域的问题,子类优先调用子类的变量。如果子类和父类中有同名的变量,我们还想调用父类的变量,可以使用super关键字。

class A {
    int tem = 1;
    int num = 4;
}

class B extends A {
    int num = 5;
    void show() {
        System.out.println("num = " + super.num + "\n" +
                "tem = " + tem
        );
    }
}

public class InheritanceDemo1 {
    public static void main(String[] args) {
        B b = new B();
        b.show();
    }
}
/**
 * Output
 * num = 4
 * tem = 1
 */

这样,就可以使用父类的成员变量了。

  • 成员方法
    正常情况下,子类可以调用从父类中继承的方法。
class A {
    void show1() {
        System.out.println("A show() run");
    }
}

class B extends A {
    void show2() {
        System.out.println("B show() run");
    }
}

public class InheritanceDemo {
    public static void main(String[] args) {
        B b = new B();
        b.show1();
        b.show2();
    }
}
/**
 * Output
 * A show() run
 * B show() run
 */

我们现在重点要说的是当子父类中出现相同的方法时,这时就是出现重写(override)。(也称覆盖覆写,因为这时父类的方法不能再由子类对象通过“.”运算符直接被调用,就像被覆盖了一样。)
方法的重写有三点需要注意:

  1. 在子类中可以根据需要对从基类中继承来的方法进行重写。
  2. 重写的方法和被重写的方法必须具有相同方法名称、参数列表和返回类型。
  3. 重写方法不能使用比被重写的方法更严格的访问权限。

接下来我们依次说明一下这三点。
第一点说明当我们继承父类的方法时,发现父类的方法不能满足我们的要求,我们可以在子类中对父类的方法进行重写。即在子类中写一个和父类一模一样的方法。这里需要注意的一点是,这个方法必须时从父类继承过来的。

class A {
    private void show() {
        System.out.println("A show() run");
    }
}

class B extends A {
    public void show() {
        System.out.println("B show() run");
    }
}

public class InheritanceDemo {
    public static void main(String[] args) {
        B b = new B();
        b.show();
    }
}
/**
 * Output
 * B show() run
 */

我们知道,当方法声明为private权限时,是不能被子类继承的。上面的情况,子类并未从父类中继承show()方法,所以该情况不是重写。

第二点就是我之前讲的,必须是同一个方法。不光要有相同的方法名,还要有相同的参数列表。满足这两个要求的情况下,返回值类型不一样本身在Java中就是不被允许的。所以,同一个函数必须满足这三个要求。

class A {
    public void show(int num) {
        System.out.println("A show() run" + num);
    }
}

class B extends A {
    public void show() {
        System.out.println("B show() run");
    }
}

public class InheritanceDemo {
    public static void main(String[] args) {
        int a = 0;
        B b = new B();
        b.show(a);
        b.show();
    }
}
/**
 * Output
 * A show() run0
 * B show() run
 */

上面这种情况并没有发生重写,可以看到,对象b依然可以调用父类的show方法。

第三点,重写函数的权限不能比父类中的权限更严格。

class A {
    public void show() {
        System.out.println("A show() run");
    }
}

class B extends A {
    void show() {
        System.out.println("B show() run");
    }
}

public class InheritanceDemo1 {
    public static void main(String[] args) {
        B b = new B();
        b.show();
    }
}

即上面这种情况是不能被允许的。编译器被报错:



那么如果我们还需要调用父类的show方法,可以在子类中通过super调用。

class A {
    public void show() {
        System.out.println("A show() run");
    }
}

class B extends A {
    public void show() {
        super.show();
        System.out.println("B show() run");
    }

}

public class InheritanceDemo1 {
    public static void main(String[] args) {
        B b = new B();
        b.show();
    }
}
/**
 * A show() run
 * B show() run
 */
  • 构造方法
    先看一个简单的例子:
class A {
    A() {
        System.out.println("A is running");
    }
}

class B extends A {
    B() {
        System.out.println("B is running");
    }
}

public class InheritanceDemo1 {
    public static void main(String[] args) {
        B b = new B();
    }
}
/**
 * A is running
 * B is running
 */

我们可以看出,当我们初始化子类对象时,父类的构造方法也会被调用。这是为什么呢?原来,在子类的构造方法(不管有没有参数)中会隐式调用父类的空参构造方法。即,实际情况是这样的

class B extends A {
    B() {
        super();
        System.out.println("B is running");
    }
}
super();

该语句写不写都是存在的。此处需要注意的是,默认调用的是父类的空参构造方法,如果父类中没有空参构造方法就会报错。即,下面这种情况是不被允许的。

class A {
    A(int a) {
        System.out.println("A is running");
    }
}

class B extends A {
    B() {
        System.out.println("B is running");
    }
}

public class InheritanceDemo1 {
    public static void main(String[] args) {
        B b = new B();
    }
}

其实这也是很容易理解的,子类构造之前为什么要先调用父类的构造方法呢?因为,子类要继承父类的成员,所以在继承之前,应当对父类先初始化。这样我们就明白了,子类构造之前,一定是要先构造父类的。只是在默认情况下调用了空参构造。其实在更多的情况下,应该是由人为指定的。并且super语句必须放在第一行。如果子类的一个构造方法中使用了this语句调用其他构造方法,此时该构造函数中super语句就没有了。

class A {
    A(int a) {
        System.out.println("A is running" + a);
    }
}

class B extends A {
    B() {
        super(4);
        System.out.println("B is running");
    }
}

public class InheritanceDemo1 {
    public static void main(String[] args) {
        B b = new B();
    }
}
/**
 * A is running4
 * B is running
 */
  • 子类实例化过程
    我们先来看下面这个例子
class A {
    A() {
        show();
    }
    void show() {
        System.out.println("A show");
    }
}

class B extends A {
    int num = 8;
    B() {
        super();
        System.out.println("B constructor..." + num);
    }
    void show() {
        System.out.println("B show..." + num);
    }
}

public class InheritanceDemo1 {
    public static void main(String[] args) {
        B b = new B();
        b.show();
    }
}
/**
 * Output
 * B show...0
 * B constructor...8
 * B show...8
 */

这个结果时有的人可能会很奇怪,那我们就来分析一下实例化的过程。
当new一个B的对象时:

  1. 首先会隐式(默认)初始化num = 0;
  2. 然后调用B的构造方法B();
  3. 进入构造方法后,首先执行super(),即调用父类构造方法;
  4. 父类(其实也有父类Object这里先忽略)的构造方法调用show()方法,注意这个show()方法是子类的show()方法。因为整个过程的主体是子类,调用时,子类的show()方法重写了父类的show()方法。所以会出现第一个输出的结果;
  5. 当父类的构造方法结束后,接着对子类的变量进行显式初始化,此时num = 8;
  6. 然后构造方法中再输出num的时候买就会出现第二句输出的情况。
  • final关键字与继承
    当用final修饰一个类时,表明这个类不能被继承。也就是说,如果一个类你永远不会让它被继承,就可以用final进行修饰。final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容

  • 一、继承 当两个事物之间存在一定的所属关系,即就像孩子从父母那里得到遗传基因一样,当然,java要遗传的更完美,这...
    玉圣阅读 1,014评论 0 2
  • 1. 继承 一个类可以继承另一个类的方法,属性和其它特性。当一个类继承其它类时,继承类叫子类,被继承类叫超类(或父...
    路飞_Luck阅读 399评论 0 1
  • 一、继承 1. 概述 继承是面向对象的重要特征之一,当多个类中存在相同的属性和行为时,将这些内容抽取到单独一个类中...
    陈凯冰阅读 346评论 0 2
  • 月明星稀的晚上,北风呼啸,星星冷得都不见踪影,此时的小狐狸在茅屋冷得瑟瑟发抖,思念着狐狸妈妈做的饭菜,自...
    陈小胖啊阅读 220评论 0 0
  • 必须先吐槽东东! 光棍节购物开始啦,作为光棍大军中的一员,必须得好好过过这个我的专属节日,最好的庆祝非购物莫属。作...
    小小野丫头阅读 162评论 3 3