概念说明
概念:
面向对象程序设计有三大特性:封装、继承和多态性。继承是面向对象语言的重要特性之一,没有继承的语言只能被称为“使用对象的语言”。继承是非常简单而强大的设计思想,它提供了我们重用和程序组织的有力工具。
类是规则,用来制造对象的规则。我们不断的定义类,用定义的类制造一些对象。类定义类对象的属性和行为,就像图纸决定了房子要盖成什么样子。
解释:
一张图纸可以盖很多的房子,它们都是相同的房子,但坐落在不同的地方,会有不同的人住在里面。假如现在我们想盖一座新房子,和以前盖的房子很相似,但稍微有点不同。任何一个建筑师都会拿以前盖的房子的图纸来,稍加修改,成为一张新图纸,然后盖出这座房子。所以一旦我们有了一张设计良好的图纸,我们就可以基于这张图纸设计出很多相似但不完全相同的房子的图纸来。
基于已有的设计创造新的设计,就是面向对象编程设计中的继承。在继承中,新的类不是凭空产生的,而是基于一个已经存在的类而定义出来的。通过继承,新的类自动获得了基础类中的所有成员,包括成员变量和方法,包括各种访问属性的成员,无论public还是private。当然,在这之后,程序员还可以加入自己的新的成员,包括变量和方法。显然,通过继承来定义新的类,远比从头开始写可以新的类要简单快捷和方便。继承是支持代码重用的重要手段之一。
举例:
类这个词有分类的意思,具有相似特性的东西可以归为一类。比如所有的鸟都有一些共同特性:有翅膀、下蛋等等。鸟的一个子类,比如鸡,具有鸟的所有的特性,同时又有它自己的特性,比如飞不太高等等;而另外一个鸟类,比如鸵鸟,同样具有鸟类的全部特性,但又有它自己的明显不同于鸡的特性。
如果我们用程序设计的语言来描述这个鸡和鸵鸟的关系问题,首先有一个类叫做“鸟”,它具有一些成员变量和方法,从而阐述了鸟所应该具有的特征和行为。然后一个“鸡”类可以从这个“鸟”类派生出来,它同样也具有“鸟”类所有的成员变量和方法,然后再加上自己特有的成员变量和方法。无论是从“鸟”那里继承来的变量和方法,还是它自己加上的,都是它的变量和方法。
实例说明
以下的代码是一个媒体数据库的雏形(每一段代码都是一个.java文件):
//Database.java(主文件)
import java.util.ArrayList;
public class Database {
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) {
Database db = new Database();
db.add(new CD("ab", "cd", 4, 60, "..."));
db.add(new CD("ef", "gh", 4, 60, "..."));
db.add(new DVD("hi", "jk", 60, "..."));
db.list();
}
}
CD中print()函数被注释的结果:
item:ab
item:ef
DVD:hi:jk
CD中print()函数未被注释的结果:
CD:ab:cd
CD:ef:gh
DVD:hi:jk
由此可见,子类可以重写父类中的成员,当调用子类的成员时,先去子类中找对应成员,如果没有,才去父类中找。
这就是覆盖(@Override),但父类的那个方法本身不受影响,在父类中依然可以调用。
//(CD.java)CD类型媒体数据存储
public class CD extends item{ //继承自item类(extends item) //所有类默认继承自Object类
private String title;
private String artist; //作者
private int numofTracks; //音轨数量
public CD(String title, String artist, int numofTracks, int playingTime, String comment) {
super(title, playingTime, false, comment); //super自动调用父类的构造函数,且super只能写在第一行
this.artist = artist; //super将传入的参数传给父类(item)
this.title = title;
this.numofTracks = numofTracks;
}
// //@Override
// public void print() { //将注释取消后CD类型的对象再使用print()方法,会调用此方法
// System.out.println("CD:" + title + ":" + artist);
// }
public static void main(String[] args) {
}
}
一个构造函数里面不能调用两次super(),super()必须写在第一行,因为程序初始化顺序为:1)父类定义初始化;2)父类构造器初始化;3)子类定义初始化;4)子类构造器初始化。
//(DVD.java)DVD类型媒体数据存储
public class DVD extends item{
private String title;
private String director; //导演
private int playintTime; //作品时间
private boolean gotIt = false;; //是否可提
private String comment; //注释
public DVD(String title, String director, int playintTime, String comment) {
super();
this.title = title;
this.director = director;
this.playintTime = playintTime;
this.comment = comment;
}
public static void main(String[] args) {
DVD dvd = new DVD("abc", "a", 3, "...");
dvd.print();
}
public void print() {
System.out.println("DVD:" + title + ":" + director);
}
}
//item.java(和CD和DVD的关系相当于鸡类和鸟类的关系)
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可以理解为具体的对象,每一个对象都对应一套值,
this.title = title; //它调用父类的函数时,会调用父类中相应的对象的成员的值
this.playingTime = playingTime;
this.gotIt = gotIt;
this.comment = comment;
}
public item(){
//定义一个空的构造函数,满足DVD的需求
}
public void print() {
System.out.println("item" + ":" + title);
}
public static void main(String[] args) {
}
}
父类的私有变量只能通过父类中public的方法调用,子类不可以直接调用。
关系图解
继承item的类也可以是其它类的父类,如item下有一个Game类,Game的子类可以是VideoGame、ChessGame等等。
item和其它继承它的类的关系图:
继承中类的声明(public)
对理解继承来说,最重要的事情是,知道哪些东西被继承了,或者说,子类从父类那里得到了什么。答案是:所有的东西,所有的父类的成员,包括变量和方法,都成为了子类的成员,除了构造方法。构造方法是父类所独有的,因为它们的名字就是类的名字,所以父类的构造方法在子类中不存在。除此之外,子类继承得到了父类所有的成员。
但是得到不等于可以随便使用。每个成员有不同的访问属性,子类继承得到了父类所有的成员,但是不同的访问属性使得子类在使用这些成员时有所不同:有些父类的成员直接成为子类的对外的界面,有些则被深深地隐藏起来,即使子类自己也不能直接访问。下表列出了不同访问属性的父类成员在子类中的访问属性:
public的成员直接成为子类的public的成员,protected的成员也直接成为子类的protected的成员。Java的protected的意思是包内和子类可访问,所以它比缺省的访问属性要宽一些。而对于父类的缺省的未定义访问属性的成员来说,他们是在父类所在的包内可见,如果子类不属于父类的包,那么在子类里面,这些缺省属性的成员和private的成员是一样的:不可见。
父类的private的成员在子类里仍然是存在的,只是子类中不能直接访问。我们不可以在子类中重新定义继承得到的成员的访问属性。如果我们试图重新定义一个在父类中已经存在的成员变量,那么我们是在定义一个与父类的成员变量完全无关的变量,在子类中我们可以访问这个定义在子类中的变量,在父类的方法中访问父类的那个。尽管它们同名但是互不影响。
在构造一个子类的对象时,父类的构造方法也是会被调用的,而且父类的构造方法在子类的构造方法之前被调用。在程序运行过程中,子类对象的一部分空间存放的是父类对象。因为子类从父类得到继承,在子类对象初始化过程中可能会使用到父类的成员。所以父类的空间正是要先被初始化的,然后子类的空间才得到初始化。在这个过程中,如果父类的构造方法需要参数,如何传递参数就很重要了。