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(),步骤是:
- VerboseDog d = new VerboseDog(); complier 类型 (动态类型) 与 runtime 类型 (动静类型) 均是 VerboseDog;
- 思考 Dynamic selection(呈现 Override 均思考 Dynamic Selection), 因为此时的 runtime 类型是 VerboseDog, 调用子类 Override 的 BarkMany(3)
- –> 调用 bark(),此时的 runtime 类型是 VerboseDog, 筹备调用子类的 Override bark(),因为子类中没有 bark()办法,应用继承父类的 bark()办法
- 打印三次 bark
假如管理者扭转了 Dog 类外部的函数实现, 然而在内部看来,其函数作用与原来仍雷同:
在这种状况下,依然依照之前的步骤调用子类的 barkMany():
- VerboseDog d = new VerboseDog(); complier 类型 (动态类型) 与 runtime 类型 (动静类型) 均是 VerboseDog;
- 思考 Dynamic selection, 因为此时的 runtime 类型是 VerboseDog, 调用子类 Override 的 BarkMany(3)
- –> 调用 bark(),此时的 runtime 类型是 VerboseDog, 筹备调用子类的 Override bark(),因为子类中没有 bark()办法,应用继承父类的 bark()办法
- 问题的要害来了 ,此时父类的 bark() 是
public void bark() {barkMany(1);
}
也就是调用父类的 bark()—> 调用 barkMany(1),因为此时的 runtime type 是 VerboseDog,barkMany(1)理论是要调用子类的 Override barkMany(),其中 1 作为参数传入子类的 barkMany()
@Override
public 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()……
从而造成有限循环!