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()
@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()......
从而造成有限循环!