概念
面向对象程序设计语言有三大特性:封装、继承和多态性。继承是面向对象语言的重要特征之一,没有继承的语言只能被称作“使用对象的语言”。继承是非常简单而强大的设计思想,它提供了我们代码重用和程序组织的有力工具。
类是规则,用来制造对象的规则。我们不断地定义类,用定义的类制造一些对象。类定义了对象的属性和行为,就像图纸决定了房子要盖成什么样子。
一张图纸可以盖很多房子,它们都是相同的房子,但是坐落在不同的地方,会有不同的人住在里面。假如现在我们想盖一座新房子,和以前盖的房子很相似,但是稍微有点不同。任何一个建筑师都会拿以前盖的房子的图纸来,稍加修改,成为一张新图纸,然后盖这座新房子。所以一旦我们有了一张设计良好的图纸,我们就可以基于这张图纸设计出很多相似但不完全相同的房子的图纸来。
基于已有的设计创造新的设计,就是面向对象程序设计中的继承。在继承中,新的类不是凭空产生的,而是基于一个已经存在的类而定义出来的。通过继承,新的类自动获得了基础类中所有的成员,包括成员变量和方法,包括各种访问属性的成员,无论是public还是private。当然,在这之后,程序员还可以加入自己的新的成员,包括变量和方法。显然,通过继承来定义新的类,远比从头开始写一个新的类要简单快捷和方便。继承是支持代码重用的重要手段之一。
类这个词有分类的意思,具有相似特性的东西可以归为一类。比如所有的鸟都有一些共同的特性:有翅膀、下蛋等等。鸟的一个子类,比如鸡,具有鸟的所有的特性,同时又有它自己的特性,比如飞不太高等等;而另外一种鸟类,比如鸵鸟,同样也具有鸟类的全部特性,但是又有它自己的明显不同于鸡的特性。
如果我们用程序设计的语言来描述这个鸡和鸵鸟的关系问题,首先有一个类叫做“鸟”,它具有一些成员变量和方法,从而阐述了鸟所应该具有的特征和行为。然后一个“鸡”类可以从这个“鸟”类派生出来,它同样也具有“鸟”类所有的成员变量和方法,然后再加上自己特有的成员变量和方法。无论是从“鸟”那里继承来的变量和方法,还是它自己加上的,都是它的变量和方法。
例子
我们要设计一个简单的数据库来存放一些数据,例如存放一张CD的简单内容。
package dome;
import java.util.ArrayList;
public class Datebase {
private ArrayList<CD> listCD = new ArrayList<CD>();
private ArrayList<DVD> listDVD = new ArrayList<DVD>();
public void add(CD cd)
{
listCD.add(cd);
}
public void add(DVD dvd)
{
listDVD.add(dvd);
}
public void list()
{
for(CD cd: listCD)
{
cd.print();
}
for(DVD cd: listDVD)
{
cd.print();
}
}
public static void main(String[] args) {
Datebase db = new Datebase();
db.add(new CD("ABC", "guoqian" , 4, 56, "我很喜欢作者"));
db.add(new CD("EFG", "guoqian" , 5, 66, "我很喜欢作者"));
db.add(new DVD("ghj", "guoqian" , 112 , "我很喜欢导演"));
db.list();
}
}
package dome;
public class CD {
private String title;
private String artist;
private int numofTracks;
private int playingTime;
private boolean gotIt = false;
private String comment;
public CD(String title, String artist, int numofTracks, int playingTime, String comment) {
// super();
this.title = title;
this.artist = artist;
this.numofTracks = numofTracks;
this.playingTime = playingTime;
this.comment = comment;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
}
public void print() {
// TODO Auto-generated method stub
System.out.println("CD:"+ title +":"+artist);
}
}
package dome;
public class DVD {
private String title;
private String director;
private int playingTime;
private boolean gotIt = false;
private String comment;
public DVD(String title, String director, int playingTime, String comment) {
// super();
this.title = title;
this.director = director;
this.playingTime = playingTime;
this.comment = comment;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
}
public void print() {
// TODO Auto-generated method stub
System.out.println("DVD:"+ title+":"+director);
}
}
上面的方法可扩展行不强,对于CD和DVD这两种比较相似的类都重新进行了定义,如果使用继承的方法,将会大大提高程序的可扩展性。
我们把用来做基础派生其它类的那个类叫做父类、超类或者基类,而派生出来的新类叫做子类。Java用关键字extends表示这种继承/派生关系:
class ThisClass extends SuperClass {
//…
}
继承表达了一种is-a关系,就是说,子类的对象可以被看作是父类的对象。比如鸡是从鸟派生出来的,因此任何一只都可以被称作是一只鸟。但是反过来不行,有些鸟是鸡,但并不是所有的鸟都是鸡。如果你设计的继承关系,导致当你试图把一个子类的对象看作是父类的对象时显然很不合逻辑,比如你让鸡类从水果类得到继承,然后你试图说:这只本鸡是一种水果,所以这本鸡煲就像水果色拉。这显然不合逻辑,如果出现这样的问题,那就说明你的类的关系的设计是不正确的。Java的继承只允许单继承,即一个类只能有一个父类。
子类与父类关系
对理解继承来说,最重要的事情是,知道哪些东西被继承了,或者说,子类从父类那里得到了什么。答案是:所有的东西,所有的父类的成员,包括变量和方法,都成为了子类的成员,除了构造方法。构造方法是父类所独有的,因为它们的名字就是类的名字,所以父类的构造方法在子类中不存在。除此之外,子类继承得到了父类所有的成员。
但是得到不等于可以随便使用。每个成员有不同的访问属性,子类继承得到了父类所有的成员,但是不同的访问属性使得子类在使用这些成员时有所不同:有些父类的成员直接成为子类的对外的界面,有些则被深深地隐藏起来,即使子类自己也不能直接访问。下表列出了不同访问属性的父类成员在子类中的访问属性:
public的成员直接成为子类的public的成员,protected的成员也直接成为子类的protected的成员。Java的protected的意思是包内和子类可访问,所以它比缺省的访问属性要宽一些。而对于父类的缺省的未定义访问属性的成员来说,他们是在父类所在的包内可见,如果子类不属于父类的包,那么在子类里面,这些缺省属性的成员和private的成员是一样的:不可见。父类的private的成员在子类里仍然是存在的,只是子类中不能直接访问。我们不可以在子类中重新定义继承得到的成员的访问属性。如果我们试图重新定义一个在父类中已经存在的成员变量,那么我们是在定义一个与父类的成员变量完全无关的变量,在子类中我们可以访问这个定义在子类中的变量,在父类的方法中访问父类的那个。尽管它们同名但是互不影响。
在构造一个子类的对象时,父类的构造方法也是会被调用的,而且父类的构造方法在子类的构造方法之前被调用。在程序运行过程中,子类对象的一部分空间存放的是父类对象。因为子类从父类得到继承,在子类对象初始化过程中可能会使用到父类的成员。所以父类的空间正是要先被初始化的,然后子类的空间才得到初始化。在这个过程中,如果父类的构造方法需要参数,如何传递参数就很重要了。
修改后的程序:
package dome;
import java.util.ArrayList;
public class Datebase {
private ArrayList<Item> listItem = new ArrayList<Item>();
public void add(Item item)
{
listItem.add(item);
}
public void list()
{
for(Item item: listItem)
{
item.print();
}
}
public static void main(String[] args) {
Datebase db = new Datebase();
db.add(new CD("ABC", "guoqian" , 4, 56, "我很喜欢作者"));
db.add(new CD("EFG", "guoqian" , 5, 66, "我很喜欢作者"));
db.add(new DVD("ghj", "guoqian" , 112 , "我很喜欢导演"));
db.list();
}
}
package dome;
public class CD extends Item {
private String artist;
private int numofTracks;
public CD(String title, String artist, int numofTracks, int playingTime, String comment) {
super(title, playingTime, false, comment);
this.artist = artist;
this.numofTracks = numofTracks;
}
public static void main(String[] args) {
CD cd = new CD("ABC", "guoqian" , 4, 56, "我很喜欢作者");
cd.print();
}
}
package dome;
public class DVD extends Item{
private String title;
private String director;
private int playingTime;
private boolean gotIt = false;
private String comment;
public DVD(String title, String director, int playingTime, String comment) {
super(title, playingTime, false, comment);
this.director = director;
}
public static void main(String[] args) {
DVD dvd = new DVD("ghj", "guoqian" , 112 , "我很喜欢导演");
dvd.print();
}
public void print() {
System.out.println("DVD:");
super.print(); //调用父类的print
System.out.println(director);
}
}
package dome;
public class Item {
private String title;
private int playingTime;
private boolean gotIt = false;
private String comment;
public Item(String title, int playingTime, boolean gotIt, String comment) {
super();
this.title = title;
this.playingTime = playingTime;
this.gotIt = gotIt;
this.comment = comment;
}
public Item() {
}
public void print() {
System.out.println(title);
}
}
练习题
class A{
B b = new B();
A(){
System.out.print("A");
}
}
class B{
B(){
System.out.print("B");
}
}
public class C extends A{
B b = new B();
private C(){
System.out.print("C");
}
public static void main(String[] args) {
new C();
}
}
请问上述程序中输出的结果是什么
A.CBAA B.CBAB
C.BABC D.ABCA
------------------------------------------------------------------------------------------------------------------------------------------
相关知识:
1.new一个对象的实例时会调用这个对象的构造方法(函数)。
2.当一个对象继承一个父对象进行实例化时,必须在构造函数中先调用父类的构造方法,如果子类中没有通过书写super()来调用父类构造方法,则会默认调用父类的无参构造方法。
3.普通成员变量的实例化在构造方法之前执行。
源码分析:
首先声明了类A,A类中有一个B类的成员变量,并且进行了实例化。A类的构造方法(函数)中输出一个字母A。
然后声明了类B,B类中有一个构造方法,输出字母B。
最后声明了C类继承了A类,在C类中有一个B类的成员变量,并且进行了实例化。C类的构造方法输出字母C。
主函数main中对C类进行了实例化。
答案解析:
由于C类继承了A类,因此在C对象实例化过程中,会首先调用父类(A类)的构造方法。而在A中首先实例化了B,调用B类的构造方法,输出字母B,然后A的构造方法为输出字母A。回到C子类中,实例化B,调用B类构造方法输出B,最后输出本身的C。
因此最后输出的结果为 BABC. 答案选择C.
instanceof运算符
用于判断左边的对象是否属于右边的类,下面看一道题目:
对于以下代码段,4个输出语句中输出true的个数是( )。
class A{}
class B extends A{}
class C extends A{}
class D extends B{}
A obj = new D();
System.out.println(obj instanceof B);
System.out.println(obj instanceof C);
System.out.println(obj instanceof D);
System.out.println(obj instanceof A);
我们可以看出obj的实际类型为D,而D继承了B,所以obj属于B,obj不属于C,obj属于D,而对于最后一个而言,也是true,所以答案是3个。
参考文献:翁凯老师的慕课《面向对象程序设计——Java语言》