1.extends

后面咱们学习的继承是从interface继承,应用implements关键字来定义与接口的档次关系,当初咱们心愿能够继承class,应用extends关键字定义类的档次关系。
比方当初有一个class RotatingSLList,是继承class SLList

咱们能够在class申明中设置这种继承关系,应用extends关键字如下:

public class RotatingSLList<Item> extends SLList<Item>

即定义了"is-a"的关系

该RotatingSLList除了能应用SLList的全副method之外,其额定作用是将元素右旋(元素之间程序不变):
比方[5 10 15 20],以20为枢轴,右旋其右边所有元素后变成[20 5 10 15]实现办法:

public void rotateRight() {    Item x = removeLast();    addFirst(x);}

通过应用extends关键字,子类继承父类的所有成员。“成员”包含:

  • 所有实例和动态变量
  • 所有办法method
  • 所有嵌套类

留神构造函数不是继承的,子类不能间接拜访父类的公有成员(private)。
除此之外,子类还能够自定义一些其余的method,变量等等,也能够Override属于父类的method

super关键字

通过super关键字,能够让子类应用父类中的办法,应用 super. 拜访,比方某子类应用并Override SLList中的removeLast()办法:

    @Override    public Item removeLast() {        Item x = super.removeLast();        deletedItems.addLast(x);        return x;    }

构造函数非继承

正如咱们后面提到的,子类继承父类,其中包含实例和动态变量,办法和嵌套类的所有成员,但不包含构造函数。当咱们给子类写构造函数初始化时,须要先思考父类的构造函数,一个形象的例子是,假如咱们有两个类:

public class Human {...}public class TA extends Human {...}

如果咱们运行上面的构造函数:

TA(){somebody = new TA();}

那么首先必须发明一个人类。而后该人类才能够被赋予 TA 的品质。如果不先创立人类,就构建 TA 则毫无意义。
因而,子类在构造函数初始化之前须要先调用父类的构造函数,应用super():

TA() {    super();    somebody = new TA();}

假如你在子类构造函数初始化的时候并没有加super(),Java会隐式地帮你主动先调用父类的构造函数,然而它只会调用无参数的版本,比方:

public VengefulSLList(Item x) {    deletedItems = new SLList<Item>();}

只管子类的构造函数是含参数版本,因为没有调用super(x),Java仍会主动调用父类的无参数版的构造函数,要想正确调用父类含参的构造函数:

public VengefulSLList(Item x) {    super(x);    deletedItems = new SLList<Item>();}

请留神
Java不容许应用 super.super,请查看this link

Java 中的每个class都是 Object class或extends Object class的后辈。也就是说每个类都继承了Object class:
Class Object is the root of the class hierarchy. Every class has Object as a superclass. All objects, including arrays, implement the methods of this class.

public class A extends Object extends B(){......}

因而在任何类中都能够调用Object class中存在的一些method:
.equals(Object obj), .hashCode(), and toString()
see more detaile
然而interface 并没有 extends Object class
see more detail


2.封装

(本段是精校Google翻译)
封装是面向对象编程的根本准则之一,也是程序员用来抵挡最大敌人--复杂性 的办法之一。治理复杂性是咱们在编写大型程序时必须面临的次要挑战之一。

反抗复杂性的一些工具次要包含档次形象(形象阻碍!)和一个被称为“设计改革”的概念。围绕这个想法,程序应该构建成模块化的、可交互的局部,即在不毁坏零碎的状况下与外界进行替换。此外,暗藏其他人不须要的信息是治理大型零碎时的另一种根本办法。

封装的本源在于“向内部暗藏信息”的概念。以细胞来解释封装,细胞的内部结构可能极其简单,由染色体、线粒体、核糖体等组成,但它却齐全封装在一个模块中——形象掉了外部的复杂性。

在计算机科学术语中,一个模块能够定义为一系列办法,它们作为一个整体协同工作以执行一个工作或一组相干工作。这可能相似于示意List的class。当初,如果模块的实现细节在外部被暗藏,并且与其交互的惟一办法是通过接口文档,那么该模块则被称为封装。


3.Implementation Inheritance如何突破封装

假如咱们有一个封装好的Dog interface,蕴含:

  • bark()
  • barkMany()

其函数实现如下图所示,当初有一个子类VerboseDog,通过implementation继承Dog,并Override barkMany()函数:

当初调用VerboseDog的barkMany(),步骤是:

  1. VerboseDog d = new VerboseDog(); complier类型(动态类型)与runtime类型(动静类型)均是VerboseDog;
  2. 思考Dynamic selection(呈现Override均思考Dynamic Selection),因为此时的runtime类型是VerboseDog,调用子类Override的BarkMany(3)
  3. -->调用bark(),此时的runtime类型是VerboseDog,筹备调用子类的Override bark(),因为子类中没有bark()办法,应用继承父类的bark()办法
  4. 打印三次 bark

假如管理者扭转了Dog类外部的函数实现,然而在内部看来,其函数作用与原来仍雷同:

在这种状况下,依然依照之前的步骤调用子类的barkMany():

  1. VerboseDog d = new VerboseDog(); complier类型(动态类型)与runtime类型(动静类型)均是VerboseDog;
  2. 思考Dynamic selection,因为此时的runtime类型是VerboseDog,调用子类Override的BarkMany(3)
  3. -->调用bark(),此时的runtime类型是VerboseDog,筹备调用子类的Override bark(),因为子类中没有bark()办法,应用继承父类的bark()办法
  4. 问题的要害来了,此时父类的bark()是
public void bark() {barkMany(1);}

也就是调用父类的bark()--->调用barkMany(1),因为此时的runtime type是VerboseDog,barkMany(1)理论是要调用子类的Override barkMany(),其中1作为参数传入子类的barkMany()

@Overridepublic void barkMany(int N) {    System.out.println("As a dog, I say: ");    for (int i = 0; i < N; i += 1) {           bark();    }}

而后子类的barkMany()调用父类的bark(),父类的bark()又再次调用子类的barkMany()......
从而造成有限循环!