JAVA面向对象编程学习 (4)继承与多态

引言

面向对象程序设计语言有三大特性:封装、继承和多态性。继承是面向对象语言的重要特征之一,没有继承的语言只能被称作“使用对象的语言”。继承是非常简单而强大的设计思想,它提供了我们代码重用和程序组织的有力工具。

类是规则,用来制造对象的规则。我们不断地定义类,用定义的类制造一些对象。类定义了对象的属性和行为,就像图纸决定了房子要盖成什么样子。

一张图纸可以盖很多房子,它们都是相同的房子,但是坐落在不同的地方,会有不同的人住在里面。假如现在我们想盖一座新房子,和以前盖的房子很相似,但是稍微有点不同。任何一个建筑师都会拿以前盖的房子的图纸来,稍加修改,成为一张新图纸,然后盖这座新房子。所以一旦我们有了一张设计良好的图纸,我们就可以基于这张图纸设计出很多相似但不完全相同的房子的图纸来。

基于已有的设计创造新的设计,就是面向对象程序设计中的继承。在继承中,新的类不是凭空产生的,而是基于一个已经存在的类而定义出来的。通过继承,新的类自动获得了基础类中所有的成员,包括成员变量和方法,包括各种访问属性的成员,无论是public还是private。当然,在这之后,程序员还可以加入自己的新的成员,包括变量和方法。显然,通过继承来定义新的类,远比从头开始写一个新的类要简单快捷和方便。继承是支持代码重用的重要手段之一。

类这个词有分类的意思,具有相似特性的东西可以归为一类。比如所有的鸟都有一些共同的特性:有翅膀、下蛋等等。鸟的一个子类,比如鸡,具有鸟的所有的特性,同时又有它自己的特性,比如飞不太高等等;而另外一种鸟类,比如鸵鸟,同样也具有鸟类的全部特性,但是又有它自己的明显不同于鸡的特性。

如果我们用程序设计的语言来描述这个鸡和鸵鸟的关系问题,首先有一个类叫做“鸟”,它具有一些成员变量和方法,从而阐述了鸟所应该具有的特征和行为。然后一个“鸡”类可以从这个“鸟”类派生出来,它同样也具有“鸟”类所有的成员变量和方法,然后再加上自己特有的成员变量和方法。无论是从“鸟”那里继承来的变量和方法,还是它自己加上的,都是它的变量和方法。

database代码(硬编码)

1.database类的定义

package dome;

import java.util.ArrayList;

public class Database {
	ArrayList<CD> list=new ArrayList<CD>();
	ArrayList<DVD> listdvd=new ArrayList<DVD>();
	public void add(CD cd)
	{
		list.add(cd);
	}
	
	public void add(DVD dvd)
	{
		listdvd.add(dvd);
	}
	
	public void list()
	{ 
		for(CD cd:list)
		{
			cd.print();
		}
		for(DVD dvd:listdvd)
		{
			dvd.print();
		}
	}
	
	public static void main(String[] args) 
	{
		Database dbDatabase=new Database();
		dbDatabase.add(new CD("abc", "abc", 50, 230 , "abc"));
	}

}

2.CD类定义

package dome;

public class CD
{
	private String title;
	private String artist;
	private int numofTracks;
	private int playingtime;
	private boolean gotIt;
	
	public CD(String title, String artist, int numofTracks, int playingtime, String comment) 
	{
		this.title = title;
		this.artist = artist;
		this.numofTracks = numofTracks;
		this.playingtime = playingtime;
		this.comment = comment;
		this.gotIt=false;
		//source->构造的
	}
	private String comment;
	
	public static void main(String[] args) 
	{
		
	}
	public void print() {
		System.out.println("CD:"+title+":"+artist);
	}

}

3.DVD类定义

package dome;

public class DVD
{
	private String title;
	private String director;
	private int numofTracks;
	private int playingtime;
	private boolean gotIt;
	public void print() 
	{
		System.out.println("DVD:"+title+":"+director);
	}
	public DVD(String title, String director, int numofTracks, int playingtime, boolean gotIt) {
		super();
		this.title = title;
		this.director = director;
		this.numofTracks = numofTracks;
		this.playingtime = playingtime;
		this.gotIt = gotIt;
	}
	
	
}

以上的代码,代码重复性高,耦合性强,以后的维护较为困难。

继承

继承:某个类宣称直接得到父类的全部信息。这某个类还可以自定义内内部参数。

继承表达了一种is-a关系,就是说,子类的对象可以被看作是父类的对象。比如鸡是从鸟派生出来的,因此任何一只都可以被称作是一只鸟。但是反过来不行,有些鸟是鸡,但并不是所有的鸟都是鸡。如果你设计的继承关系,导致当你试图把一个子类的对象看作是父类的对象时显然很不合逻辑,比如你让鸡类从水果类得到继承,然后你试图说:这只本鸡是一种水果,所以这本鸡煲就像水果色拉。这显然不合逻辑,如果出现这样的问题,那就说明你的类的关系的设计是不正确的。Java的继承只允许单继承,即一个类只能有一个父类

子类与父类之间的关系

1.对理解继承来说,最重要的事情是,知道哪些东西被继承了,或者说,子类从父类那里得到了什么。答案是:所有的东西,所有的父类的成员,包括变量和方法,都成为了子类的成员,除了构造方法构造方法是父类所独有的,因为它们的名字就是类的名字,所以父类的构造方法在子类中不存在。除此之外,子类继承得到了父类所有的成员

2.但是得到不等于可以随便使用。每个成员有不同的访问属性,子类继承得到了父类所有的成员,但是不同的访问属性使得子类在使用这些成员时有所不同:有些父类的成员直接成为子类的对外的界面,有些则被深深地隐藏起来,即使子类自己也不能直接访问。下表列出了不同访问属性的父类成员在子类中的访问属性:
public:对所有人开放
protected:在父类中:只有包内其它类、自己和子类可以访问 在子类中:只有包内其它类、自己和子类可以访问
缺省:在父类中:只有包内其它类可以访问 在子类中:如果子类与父类在同一个包内:只有包内其它类可以访问,否则:相当于private,不能访问
private:在父类中:只有自己可以访问 在子类中: 不能访问

3.public的成员直接成为子类的public的成员,protected的成员也直接成为子类的protected的成员。Java的protected的意思是包内和子类可访问,所以它比缺省的访问属性要宽一些而对于父类的缺省的未定义访问属性的成员来说,他们是在父类所在的包内可见,如果子类不属于父类的包,那么在子类里面,这些缺省属性的成员和private的成员是一样的:不可见。父类的private的成员在子类里仍然是存在的,只是子类中不能直接访问我们不可以在子类中重新定义继承得到的成员的访问属性。如果我们试图重新定义一个在父类中已经存在的成员变量,那么我们是在定义一个与父类的成员变量完全无关的变量,在子类中我们可以访问这个定义在子类中的变量,在父类的方法中访问父类的那个。尽管它们同名但是互不影响。

4.在构造一个子类的对象时,父类的构造方法也是会被调用的,而且父类的构造方法在子类的构造方法之前被调用。在程序运行过程中,子类对象的一部分空间存放的是父类对象。因为子类从父类得到继承,在子类对象初始化过程中可能会使用到父类的成员。所以父类的空间正是要先被初始化的,然后子类的空间才得到初始化。在这个过程中,如果父类的构造方法需要参数,如何传递参数就很重要了。

5.子类的对象可以被当做父类的对象来使用
-赋值给父类的变量
-传递给需要父类对象的容器
-放进存放父类对象的容器里

extends关键字

我们把用来做基础派生其它类的那个类叫做父类、超类或者基类,而派生出来的新类叫做子类。Java用关键字extends表示这种继承/派生关系:

public class CD extends Item

这个语句的意思是是将CD作为Iteam的一个子类,正常的英语翻译是CD扩展了子类(字面意思理解),意思是从Item派生出来一个CD类
而在之前的Databse里面更改了数据结构:

	ArrayList<Item> list=new ArrayList<Item>();

这时候database不会直接与DVD类和CD类产生联系,而是通过item来产生联系。如果子类当中有父类中完全一样的变量,父类的变量不会被提及。
第一次重构后的代码

package dome;

import java.util.ArrayList;

public class Database {
	private ArrayList<Item> list=new ArrayList<Item>();
	
	public void add(Item item)
	{
		list.add(item);
	}
	
	public void list()
	{ 
		for(Item item:list)
		{
			item.print();
		}
	}
	
	public static void main(String[] args) 
	{
		Database dbDatabase=new Database();
		dbDatabase.add(new CD("abc", "abc", 50, 230 , "abc"));
	}

}

第二次重构:

为Item父类提供构造器方法

	protected String title;
	Item(String title)
	{
		this.title=title;
	}

在子类的构造方法中,通过super传递父类构造器所需要的参数,这一个原则保证了构造对象的顺利,如果我们使用了一个由父类继承得到子类的,我们必须保证父类的构造顺利。

父类的private成员函数只能在当前类中使用,外部类不可见, 私有访问修饰符是最严格的访问级别,所以被声明为 private 的方法、变量和构造方法只能被所属类访问,并且类和接口不能声明为 private。

声明为私有访问类型的变量只能通过类中公共的 getter 方法被外部类访问。

	
	public CD(String title, String artist, int numofTracks, int playingtime, String comment) 
	{
		super(title);
		this.artist = artist;
		this.numofTracks = numofTracks;
		this.playingtime = playingtime;
		this.comment = comment;
		this.gotIt=false;
		//source->构造的
	}

重构后database代码(继承改进)

CD类定义

package dome;

public class CD extends Item
{
	private String artist;
	private int numofTracks;
	//这两个是CD的特有参数
	
	//修改构造器方法
	public CD(String title, String artist, int numofTracks, int playingtime, String comment) 
	{
		super(title,playingtime,false,comment);
		this.artist = artist;
		this.numofTracks = numofTracks;
		//source->构造的
	}
	
	public void print() 
	{
		System.out.print("CD:");
		super.print();
		System.out.println(artist);
	}
	
	public static void main(String[] args) 
	{
		
	}

}

DVD类定义

package dome;

public class DVD extends Item
{
	private String director;
	public DVD(String title, String director, int numofTracks, int playingtime, boolean gotIt,String comment)
	{
		//public Item(String title, int playingtime,boolean gotIt,String Comment)
		super(title,playingtime,false,comment);
		this.director = director;
	}
	public void print()
	{
		System.out.print("DVD:");
		super.print();
		System.out.println(director);
	}
}

Item类定义

package dome;

public class Item 
{
	private String title;
	private int playingtime;
	private boolean gotIt;
	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 void print()
	{
		System.out.print(title+" ");//title是父类内private不拿
	}
	
}

多态变量和向上造型

子类型类似于类的层次,类型也构成了类型层次。子类所定义的类型是其超类的类型的子类型。
当把一个对象赋值给一个变量时,对象的类型必须与变量的类型相匹配,如:

Car myCar = new Car(); 

是一个有效的赋值,因为Car类型的对象被赋值给声明为保存Car类型对象的变量。但是由于引入 了继承,这里的类型规则就得叙述得更完整些:

一个变量可以保存其所声明的类型或该类型的任何子类型。

对象变量可以保存其声明的类型的对象,或该类型的任何子类型的对象。

Java中保存对象类型的变量是多态变量。“多态”这个术语(字面意思是许多形态)是指一个变量可以保存不同类型(即其声明的类型或任何子类型)的对象。
子类的对象可以赋值给父类的变量

多态变量

-1.Java中的对象变量是多态的,它们能保存不止一种类型的变量
-2.它们可以保存的是声明类型的对象,或者是声明类型的子类的对象
-3.当把子类的对象赋给父类的变量的时候,就发生了向上造型

造型Cast

造型的含义是:把一个类型的对象赋给另一个类型的变量(把一个类型当做另外一个类型来看待)
子类的对象可以赋值给父类的变量(应该要注意的是:Java中不存在对象对对象的赋值)这实际上就是之前说过的一种管理者理念,赋值对象变量实际上是修改了指向对象的指针(不正确,但是帮助我理解这个内容)。
父类的对象不能赋值给子类的变量

向上造型

1.拿一个子类的对象,当做父类的对象来用
2.向上造型是默认的,不需要运算符
3.向上造型总是安全的。

多态

如果子类的方法覆盖了父类的方法,我们也说父类的那个方法在子类有了新的版本或者新的实现覆盖的新版本具有与老版本相同的方法签名:相同的方法名称和参数表。因此,对于外界来说,子类并没有增加新的方法,仍然是在父类中定义过的那个方法。不同的是,这是一个新版本,所以通过子类的对象调用这个方法,执行的是子类自己的方法

覆盖关系(@override标签)

覆盖关系并不说明父类中的方法已经不存在了,而是当通过一个子类的对象调用这个方法时,子类中的方法取代了父类的方法,父类的这个方法被“覆盖”起来而看不见了。而当通过父类的对象调用这个方法时,实际上执行的仍然是父类中的这个方法。注意我们这里说的是对象而不是变量,因为一个类型为父类的变量有可能实际指向的是一个子类的对象。

通过父类的变量调用存在覆盖关系的函数时,会调用变量当时所管理的对象的所属的类的函数。\

注意要想做到覆盖,那么签名必须完全一致。

简单来说,在我们这个学习代码中的Item的方法中,我们定义了new了几个CD和DVD的变量弄进去了,我们看到的是Item,但是我们看到它执行了CD或者是DVD的方法,这种就叫做方法覆盖。

函数调用的绑定

当调用一个方法时,究竟应该调用哪个方法,这件事情叫做绑定。绑定表明了调用一个方法的时候,我们使用的是哪个方法。绑定有两种:一种是早绑定,又称静态绑定,这种绑定在编译的时候就确定了;另一种是晚绑定,即动态绑定。动态绑定在运行的时候根据变量当时实际所指的对象的类型动态决定调用的方法。Java缺省使用动态绑定

在成员函数中调用其他成员函数也是通过this这个对象来调用的。

类型系统

Object类

toString函数

将当前的Object转化为一个字符串来显示

equals()函数

比较两个对象的管理者是不是管理同一个对象。

定义一个新类型

package dome;

public class videoGame extends Item 
{
	private int numsofPlayers;
	
	public videoGame(String title, int playingtime, boolean gotIt, String Comment,int nums) 
	{
		super(title, playingtime, gotIt, Comment);
		this.numsofPlayers=nums;
	}
	public void print()
	{
		System.out.print("VideoGame:");
		super.print();
	}
}

除此之外,还可以在VideoGame上继续继承。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值