栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

面向对象程序设计——Java语言 2

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

面向对象程序设计——Java语言 2

继承与多态

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

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

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

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

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

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

4.1 继承

 

 

先做一个Database类。

private ArrayList listCD = new ArrayList();

提示错误,点黄色小灯泡,提示:Create class’CD’      

有了CD的类,如何把这些东西初始化起来呢?先把这些成员变量做好以后,在eclipse里边,Source菜单里边Generate Constructor using Fields.. 用这些所有的字段来帮我们建一个构造器,这个构造函数需要这么多的参数,然后帮我们把所有的这些成员变量都初始化好了。gotIt可以不打勾,给一个默认值就行。

Database这边需要有一个add函数。还需要一个函数,列出所有的cd。

程序执行结果:

在这里边放的是我们自定义的一个类CD。

我们想扩充一下这个database,我们想往里面放另外一种东西,比如说那种叫做DVD的。

我们想在里面放DVD,怎么做呢?

我们得要有另外的一个类,那个类是表达DVD的,然后我们把这个DVD的东西也放到这里边,让这个资料库也能够管理这个DVD,那我们来试试看是不是可以这样子。

我们得另外有一个用来放DVD的那么一个Arraylist。我们把它叫做ArrayList()。

    private ArrayList listDVD = new ArrayList();

报错信息显示没有DVD,那创建一个吧。

 

于是我们又有了一个 DVD,然后DVD里有什么呢?其实DVD和cd也蛮像的,那我们不妨从cd这里借一些东西。

往这个database里面加DVD的话,就还得有另外一个函数叫做add,然后呢它接收的是DVD,这叫做函数的重载,有两个函数,它们的名称是一样的,但是它们的参数表是不一样的,这是重载。

 

这个database。 它里面有两个容器,一个容器放CD,一个容器放DVD,当然这个成员变量其实是一个管理者,所以是外面有一个容器,那个容器里面呢,放了很多管理着很多cd的东西,然后呢,另外那个容器里面呢,又管理着很多DVD的东西,所以现在其实大概是这种结构。

 

这样的代码,功能是可以的,做到了我们要的那个功能,能够保存cd的,能够保存DVD的,能够都列出来,但是这个代码是有问题,那么这样的一个资料库程序有什么问题呢?

很多地方都一模一样。不一样的仅仅只是一个是CD,一个是DVD,而cd和DVD本身又有很多相似的地方,我们看到在这里面出现了大量的代码复制,回想我们刚才把这些东西做出来的过程,就是拷贝粘贴,然后稍微修改一下,在代码当中出现这样的代码复制是代码质量不良的一种表现,因为有了这样的代码复制之后,就意味着将来我们要去维护这样的代码是不容易的,如果我们发现print做的不好,希望改改print,那我们所有的print都要改,而且这样的代码不具有可扩展性,如果我们要增加一种新的类型,比如说我们要增加一种这个MP3的这个U盘,然后我们就得为它做一个新的类。然后我们就要在这个database里面为它增加新的add函数。为它增加新的Arraylist,为它在list函数里面增加一段新的循环,我们要做很多工作。

怎么来改进这件事情呢?我们看到在我们的代码里面,CD和DVD很相似,我们能不能从这两个类里头提取一些东西,来做一个公共的东西,这个公共的东西可以表达CD或者DVD。然后我们让database管那个公共的东西,而不是管这里的cd或者DVD。

我们先来试试看,做这样的事情,说我们在这个里头呢,有一个新的class,我们把它叫做Item。然后呢,我们说CD是一种特殊的Item,extends Item。

说我们的DVD呢,它也是extends Item。

我们做了一个新的类,叫做item,在这个类里面什么东西都没有,我们定义了一个空的叫做print的函数,然后其他什么都没有了,然后cd呢,跟原来不一样的地方是我们加了extends Item,我们说这个CD类它扩展了Item,这是一个英文单词叫做extend,意思是扩展完了以后呢,我们还得加上s,class CD extends Item 是一个陈述句,第三人称单数,动词要加s。

做完这个之后这个CD就成为Item的一个子类,这就叫做继承。

有了一个Item类了,然后这个CD是Item的一个子类,同样的DVD也是 class DVD extends Item,他是Item那个类的一个子类。

这4个类的关系,有一个类是Item,从这个Item派生出来两个子类,一个叫做CD,一个叫做DVD,它们都是Item的子类。那个Database只认识叫做Item的类,在database里面有一个数据结构Arraylist,那个Arraylist是放Item的。所以这4个类成为这样的一个关系,Database没有直接和CD,DVD打交道。Database是管那个Item的,他里边有一个放Item的容器,他的list函数会去遍历那个Item,他的add函数会把东西往Item里边加。

也就是说什么呢,我们的父类定义了这么一个函数之后,在cd这个子类当中,它天然的就继承得到了那东西。所以,继承得到什么?父类有什么,他就得到什么,所有东西在子类当中都是存在的,在子类当中都是可能可以用的,之所以说的可能是因为还涉及到访问权限的问题,一会儿我们再仔细说这个事儿。

作为继承,从语法上来说,就是某个类宣称自己extends另外一个类了,OK,它就从那个父类得到继承了,继承得到什么东西呢,父类所有的东西它都有了。然后它还可以再增加新东西,当然我们现在这个程序还不是完美的,我们还要做一些事情。

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

class ThisClass extends SuperClass {

    //…

}

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

4.2 子类父类的关系

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

但是得到不等于可以随便使用。每个成员有不同的访问属性,子类继承得到了父类所有的成员,但是不同的访问属性使得子类在使用这些成员时有所不同:有些父类的成员直接成为子类的对外的界面,有些则被深深地隐藏起来,即使子类自己也不能直接访问。下表列出了不同访问属性的父类成员在子类中的访问属性:

父类成员访问属性

在父类中的含义

在子类中的含义

public

对所有人开放

对所有人开放

protected

只有包内其它类、自己和子类可以访问

只有包内其它类、自己和子类可以访问

缺省

只有包内其它类可以访问

如果子类与父类在同一个包内:只有包内其它类可以访问

否则:相当于private,不能访问

private

只有自己可以访问

不能访问

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

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

我们用了继承把他改造过了,从Database的角度看好像还不错,你看现在我们的Database呢,只有一个容器,只有一个add函数,list函数里面只有一个for循环。可是如果我们去看cd和DVD,我们仍然看到很多重复的,还存在代码复制的情况。要把所有这些重复东西都消除掉,怎么做呢?在CD和DVD这两个子类里面相同的东西,我们可以把它提取出来,放到他们的父类Item里边去。

错误提示The field Item.title is not visible

父类当中说是private的东西,就只有父类自己才能用,子类虽然是从父类继承的,虽然从父类继承得到了title,但是不能直接用,因为这是private。

解决办法法,既然title是Item的,应该让这个Item来对title做赋值,做初始化。为Item做一个构造器,他会去做title的初始化。然后再CD类里,在CD的构造器里的第一句话super(title);把title传过去。

为Item做一个构造器,去做title的初始化。

原则就是当我们去构造构造一个子类的对象的时候,他首先要确保他的父类所拥有的那些成员变量得到恰当的初始化,恰当的初始化指的是两件事情,定义初始化和构造器,我们之前看到过,如果又有定义初始化又有构造器,那么定义初始化先做,然后做构造器,那么当你现在有子类,有父类的时候呢,它一定会保证父类的那个部分,定义初始化、构造器会先做,做完之后才轮到子类自己的定义初始化和构造器,这样的一个执行顺序永远是存在的。

不管你是不是主动的用super去传递参数,去指定要调用哪一个父类的构造器,这个事情一定会做的,如果你没有super,那么他就去找那个没有参数的父类的构造器,如果有super,那么就根据super的这个参数去寻找恰当的。

这段程序继续完善,把CD和DVD两个类所有共同的东西都搬到Item。

修改前的程序:

修改后的程序:

4.3 多态变量和向上造型

类定义了类型,DVD类所创建的对象的类型就是DVD。类可以有子类,所以由那些类定义的类型可以有子类型。在demo的例子中,DVD类型就是Item类型的子类型。

子类型类似于类的层次,类型也构成了类型层次。子类所定义的类型是其超类的类型的子类型。

当把一个对象赋值给一个变量时,对象的类型必须与变量的类型相匹配,如:

    Car myCar = new Car(); 

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

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

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

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

 

子类的对象可以被当作父类的对象来使用。

比如说子类的对象可以赋给父类的变量,子类的对象可以传递给需要父类对象的函数。然后子类的对象也可以放在用来存放父类对象的那种容器里头。

 

比如说如果我们这儿有 Vehicle,表达的是车辆,那么从Vehiclel类我们派生出了两种车辆,一种是Car,一种是Bicycle,他们都是交通工具,他们都是车辆。所以如果我们有一个 Vehicle的变量,它可以获得的是我们制造出来的Vehicle的对象,也可以是Car的对象,也可以是 Bicycle的对象,子类的对象总是可以安全的去交给父类。

 

在Database里面我们的容器是一个放Item的容器,我们可以在里面放CD,也可以放DVD。我们的容器是一个 ArrayList

private ArrayList listItem = new ArrayList();

所以它是用来放Item,父类的对象的。

当我们做了一个CD,交给add函数之后,add函数认为它拿到的是一个item,他并不关心 item究竟是item呢还是CD还是DVD,他不关心。任何Item及其子类都是可以由 item的变量来管理的。

public static void main(String[] args) {

       Database db = new Database();

       db.add(new CD("abc1", "abc2", 4, 61,". . ."));

public class Database {

    private ArrayList listItem = new ArrayList();

    public void add(Item item) {

       listItem.add(item);

    }

Debug

我们step into去看 add的过程,我们拿到的 item是个什么样的东西?我们看到item里有artist,有 title有numberOfTracks,至少numberofTracks就表明它一定是个CD。

在Java当中,所有的对象变量都是多态的。多态什么意思?就是多种形态,这个时候他可以放这个变量,这个类型的变量,过一个时候他又可以放另外一个类型的变量,就像我们这个 Item,放Item的变量。我现在放的是个CD,一会放的是个DVD,这没有问题。所以在Java的系统里头,所有的对象变量全部都是多态变量,就是多种形态都可以存在的。

我们认为这个多态变量是有两个类型,一个类型叫做它的声明类型或者叫做静态类型,就是静态的就是字面上看的出来的类型。比如说item的那个变量,在这个函数里头 item变量, Item是它的声明类型,那么这是静态的,一眼就能看出来的。动态类型是什么?当程序运行到这儿的时候,它里头管理的是一个什么类型的对象,那么它的动态类型就是什么?所以每一个Java的对象变量都具有两个类型,一个是他的声明类型,一个是它的动态类型,有的时候可能是一致的,有的时候可能是不一致的。

多态变量的意思就是这个变量运行的时候,在具体某一个时刻,他所管理的那个对象的类型是会变化,这叫做多态变量。

当我们把一个子类的对象,赋给这么一个多态变量的时候,就发生了一件事情,叫做向上造型。你看我们在这儿做了一个CD的对象出来,把它交给 Item,这个多态变量,item的静态类型是Item,小写item的静态类型是Item,我们实际做的不是Item的对象,是CD的对象,是他子类的对象。在这个时候把子类的对象交给父类的一个变量,就是静态类型为父类的一个变量,这个时候发生的事情就叫做向上造型。

有一个string的s一开始它等于hello,后来我们又说s等于bye。

这里边发生了什么?在s等于bye这句话的时候,是说原本 s管理者是管理着hello的。后来s去管另外一个叫做bye的对象,如果我们画图的话,是说我有一个s这是一个对象的管理者,一开始我们让他管了一个对象,对象里面的内容是hello,后来我们做说s等于bye,这句话的意思是我另外有了一个对象,那个对象里面的内容是bye,然后让s你去管它,你不要再管那个 hello了,这叫做s等于bye,并不是说我要用bye的内容去替换原来的hello的内容。

 

我们可以在第32行String s ="hello";这个地方设个断点,然后debug进来,现在我们在这我们s变量还不存在对不对?于是我们 step into,step into之后s有了,我们可以看一下现在这个s是ID为19的一个s它的内容是hello。

 

然后我们再要做下面的这一句,s等于bye。点击“Step Into”按钮。做完之后,s变成了bye,并且ID变成了23,这表明s已经指向另外一个对象,所以 Java的变量类型就是这样子起作用的。你不可能让一个对象去修改另外一个对象的值,如果采用赋值运算符的话,赋值运算符做的事情就是让这个变量去指向了另外一个对象,去管理了另外一个对象,这是赋值。那么在这个过程当中就可能发生造型,就是让静态类型为某个类型的变量去管理了动态类型和它的静态类型不符的那个对象,这就叫做造型。

但如果反过来把一个父类的对象交给一个子类的变量,这样是不行的。

我们做一个Item的对象,Item,new的时候要给他一堆东西,我们给他一个string,一个int,一个boolean,还有一个string,我们把这个Item构造出来,然后呢做一个他的子类CD的对象,我们让他去等于那个 Item,立刻就不行了。

出了一个错误,他说类型不匹配,Type mismatch: cannot convert from Item to CD 不能把Item转换成CD。为什么?因为 item这个管理者所管理的是一个Item的类型的东西,你现在把它交给了CD,CD是子类,Item是父类,子类的变量,不能够去接收父类的对象。

cd是new了一个CD做出来的,我们也给一些东西,完了以后我们说那个 item等于 cd,这是可以的。把子类的对象赋给父类的变量是安全的。

 

有一个绕过去的办法是我们在前面加这么一个东西,圆括号里面写上CD,表明说我们要强迫 item成为一个CD,然后去交给cc,于是编译就给你过了,运行也是能过的。

 

假如把item =cd;注释掉之后,运行时报错。出了一个叫做java.lang.ClassCastException: demo.Item cannot be cast to demo.CD类造型异常,这个 item不能够被cast成 CD。对因为item里面实际现在 item,变量里面实际放的是一个item,这个东西不能够被造型成一个CD,所以你用这种造型的方式,用圆括号里面加上类型的名字,去把一个变量强制造型成另外一个类型,然后赋给那个对象赋给那个变量。这种事情他不总是安全的,取决于你那个变量,被造型的那个变量当时实际管理的类型究竟是什么。

 

在第5行item=(Item)cd;这个地方在这个地方设置断点跟踪一下。

向上造型是不需要去写(Item)这个东西,但是写了也不会有错,写不写都是可以的。

说在这个过程当中,item得到的cd是什么?为了说明问题,我们还是把造型运算符给它放上去。然后我们来看看在这个过程当中 CD有没有被换掉?我们来debug一下。我们看到这个 cd呢,他的类型是 CD,那么item当然类型是Item,然后现在我们要做item=(Item)cd这个赋值了,做完了之后item的类型成为CD了,就是它的动态类型是CD了,它所指的所管理的对象仍然是CD,尽管我们在这儿有一句说,我们要把 CD造型成Item,那只是说我们要把 CD当作一个Item来看待,并不是要把 CD转变成一个item的对象。所以我们把 cast翻译叫做造型。

尽管这个 cast其实和那个double变int那个 cast在英文里头是同一个单词,但是呢,在中文里头我们把它分开成两个不同的术语,一个叫做类型转换,对于基本类型来说int double这样的基本类型,那是类型转换。对于对象类型来说,这件事情的叫做造型。那么所谓的向上造型,拿一个子类的对象当做父类的对象来用,这就叫做向上造型,为什么叫向上?是因为是这样的,你看我们在画这个类的关系的图当中,一般总是这样画,上面是父类,然后呢说下面是子类,总是这个子类放在下面,父类放在上面,所以当你把一个子类的对象当做父类的对象来看待的时候,这就成了向上造型。所以向上造型这个词汇是这么来的。

4.4 多态

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

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

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

在前面继承这个部分,我们看了这个database之后,在Item里面有print,CD和DVD里面也有print,然后我们在database的 那个list函数里面,我们是有一个 Item类型的变量,让它去做了print的动作,结果看起来 CD或者DVD的print被调用了,这个事情是怎么实现?

Item类中

    在这个list函数里面这个 for循环,每一次拿到了list,item里面的一个item,第1次是个CD,第2次是个CD,第3次是个DVD,第4次是个video game。然后我们让这个 item去做print,结果看起来好像并不是item那个类的print被调用了,因为 item类的 printe就是说输出了那个 title,而我们实际看到的东西不止title,看起来CD的DVD的video game的print函数都被调用了。

当我们通过变量去调用函数的时候,比如说item点print就通过变量去调用函数,选择哪一个函数来调用?这个事情叫做绑定。绑定有两种,一种叫做静态绑定,一种叫做动态绑定。静态绑定就是你这个变量的声明类型是什么?我就调用这个声明类型里头的那个函数,而动态绑定呢就是你当时所管理的实际管理的对象的类型是什么,就调用那个类型,所以这叫做动态绑定,因为你在静态的时候在编译的时候不知道,对于编译器来说,item点print这一行,在编译的时候他并不知道到底这个时候item管的是什么,所以只有运行的时候才知道,运行的时候才知道的东西,那就是动态的。

对于像Java这样的程序语言来说,面向对象的语言来说,默认所有的绑定都是动态绑定。那么我们在一个成员函数里面去调别的成员函数,因为它实际上可以被体现为this点什么,所以它也是一种动态绑定。所以所有的成员函数的调用都应该被看作是一种动态绑定。

如果在子类和父类当中,我们发现了存在的那么一对函数,他们的名称相同,参数表也相同,他们就构成了我们叫做覆盖关系。子类的这个函数,把父类的函数覆盖掉了。比如说对于print来说,CD里面的print就覆盖了 item的 print。我们通过一个item的变量去调用print函数的时候,它会根据实际到底是哪一个类来决定到底要用哪一个类的一个print的书。所以这个关系 这个printe,cd里的print和item里面的print,他们之间的关系就叫做覆盖。

4.5 类型系统__Object类

 

java实现了一个单根结构,就是说在java当中所有的类,不管你愿意不愿意,不管你声明不声明,这些类一定都是一个叫做object类的子类, object就是Java的类型系统当中的 root,那个根,所以这个叫做单根结构。几乎所有的oop语言都实现了这样一个单根结构。除了c++,所以Java也是这样有单根结构的。在这个图中,String这是系统内部当中的类。Person,Vehicle这些是我们自己做的类,他们都是继承自Object的,如果你这个类是直接从object得到继承的,不需要写extends,他默认就是object类的子类。

 

我们讲继承是说如果从那个类继承,那么那个类的所有的public的东西,就是你的public的东西,那个类里的所有东西都是你的东西。Object这个类给了我们一些什么东西,其实我们之前就遇到过两个。一个就是那个 toString(),我们怎么能够知道 object有些什么样的函数是我们可以用的呢?一个是这样做,既然Object是一个类,那我们不妨说我们有一个Object的对象o等于new一个 Object,完了以后我们就可以说o点,于是他就会列出所有Object的东西。

Object o= new Object();

 

public static void main(String[] args) {

              // TODO Auto-generated method stub

              CD cd =new CD("a", "b", 2, 2,". . .");

              System.out.println(cd.toString());

       }

 

用System.out.println(cd.toString());去输出一下 CD的 toString。CD是有toString的,因为它是Object的继承来的,object有toString,所以它就有toString了。

我们来看一下这个时候是什么形式?一运行他输出了一个看上去很奇怪的东西,说demo里面的CD, demo的CD@150bd4d,后面这个东西可以大致认为这是它在内存当中的一个地址或者是一个编号之类的,前面就是这个类的名字。

但实际上如果这个地方需要一个字符串,可以直接用这个对象,不需要加上点toString,他会知道在这个地方需要调用你的toString去把它转成String。类似的如果说现在有个String s = "aa"+cd,这个加法就是能做的,因为编译器发现说在加法的左边是一个字符串,右边是一个不是字符串的东西,它就会自动地去寻找一个方案,把那个不是字符串的东西转变成字符串儿。对于一个对象来说,那么那个方案就是toString。

想要让这种手段能够给我们产生一个有意义的表达这个 CD的对象的一个字符串,就得自己去写一个toString,可是怎么去把toString做出来,当然也可以自己敲代码。 还有另外一个办法,eclipse里可以用 source菜单里的 Generate toString(),直接就有产生一个toString。

 

Generate一个toString(),然后他就会说你这个toString()要把哪一些字段 toString过去,要把哪些字段放在toString里面,它列出了他自己的这些 fields,artists和number of tracks,这是CD自己的,你还可以再点开来看继承得到的那些东西你要不要在toString里面包含一下其他的函数,事实上这里还会涉及到这个toString指的是如果Item这个类有一个toString的话,他会帮我们去把Item的那个toString的结果也包含到我们现在cd的toString里头去,我们把勾点上我们来看一下。

这些都可以是默认的,我们来ok掉看看它给我们产生了什么?它给我们产生了这样一个函数,public的toString,返回一个string。然后 return的把一串的东西给它加起来,是吧?这当中包括了super的toString,也就是Item类的那个toString的结果,最后形成了一个。那么如果我们把这个程序现在来运行一下,我们就会看到相对比较好看的结果了,

但是他还是会列出这个,这个是谁列出来的?Item的那个toString对吧?因为我们没有去做item的那个toString函数。

@Override

    public String toString() {

       return "CD [artist=" + artist + ", numofTracks=" + numofTracks

              + ", toString()=" + super.toString() + "]";

    }

 

另外又做了一个CD的对象cd1然后一模一样的采用相同的方式去构造参函数的参数去构造出来的,这两个CD里头的值应该是一样的。可是如果去输出来看一下CD点 equals cd1的结果是false

System.out.println(cd.equals(cd1));

 

 

false的理由是我们没有做自己的equals,我们现在在使用的是object的equals,所以我们需要有一个自己的equals能够去判断他们两个的值是否真的相同,对于Object公共父类来说,他没有任何可能去知道他的某一个子类到底长什么样子,所以对于object的equals来说,他其实做的事情就是去判断这两个管理者管理的是不是同一个对象,我们要怎么做呢?

 

我们得写一个自己的CD类的equals。同样的右键点一下source,可以用 override/implement Methods他是列出父类有什么样的函数,然后问你要重新改写哪个函数?那么我们看 object里头有equals我们打上勾。Ok。他就给我们做了一个equals函数出来,这个equals函数返回一个boolean,会拿一个object过来,他也会用 super也就是Item的equals来判断这俩是否相同。现在他是做成这个样子的,我们可以把它改一改。

我们需要判断的事情是说我们想要知道,比如说这里头是有artist的,如果两个artist是相同的,我们就认为它们是同一个,所以我们来说 artist点 equals 那个obj里头的等等。

 

@Override

    public boolean equals(Object obj) {

       CD cc = (CD) obj;        //向下造型

       return artist.equals(cc.artist);

    }

obj里头就没有artist。因为 equals函数的原型说它是以object作为参数来作为输入的。换句话说,当我们后面在main里面,我们要说CD equals(cd1)的时候,我们把cd1传给equals函数,在equals里头cd1就不再是一个 CD了。而是一个 object。这时候该怎么办?我们可以做 CD的对象,cc等于你看用这个方法向下造型,CD cc = (CD)obj 造型造完了以后,cc表达的是一个 CD,我们就可以用它的 artist来做判断,然后把这个值返回回去。如果我们这么做了,我们再来运行,我们就能得到true了,因为他用了我们自己的equals。

 

这个Override是干什么用的?我们是用eclipse的功能帮我们去做了Override帮我们去产生了equals和toString函数,然后它在这两个函数前头都加了。 @Override。这个东西的意思是他告诉编译器,下面这一行所在的那个函数,是一个覆盖了父类的函数。换句话说,它必须和父类的函数具有完全相同的函数签名或者函数原型,也就是函数的名字、参数表必须一样,并且必须都是public,如果我们有一个不相同,就会认为是错误,比如说我们说这东西不是public,错误就来了。他说这个东西不能够从Object的类里头 ,Object的equals函数是public,你不能降低了它的visibility。

 

Multiple markers at this line

       - overrides java.lang.Object.equals

       - Cannot reduce the visibility of the inherited method from Object

 

好,我们把public放回去,如果我们把 object换成了 CD类型行不行?也不行。

 

Multiple markers at this line

       - The method equals(CD) of type CD must override or implement a supertype method

       - Method breakpoint:CD [entry] - equals(Object)

 

他说类型不能够改,所以这还得是object。假如我们现在是这样,我们把 Override给去了,去掉了之后,当然我们把Object改成CD就ok了。

然后我们试着说我们 equals要加点东西,比如说我们再来个int的变量i,我们也不管这i干什么用,总之这个equals函数和Object的equals函数就长得不一样了,对不对?

然后我们现在再来运行的,我们得到了false,得到了false的理由,原因是因为当我们这个时候再做equals的时候,他其实根本就没有进到这个equals来,因为 这个equals是不带 int的,所以他又回到了 object那个地方去,而没有进入到我们自己的写的那个equals里头。这是因为把 @Override给去掉了。这个时候编译器就不能替你检查了。其实你的equals函数就并不会去取代 Object里头的那个equals函数。

 

在我们现在这个媒体资料库的这个程序基础上,如果我们有一种新的媒体要加入进来,那会是非常容易的。为什么?因为你不需要对database这个类做什么改动,你也不需要对Item做什么改动,你要做的事情就是增加一个新的类那个类,从Item得到继承,所有的事情都顺理成章了。我们现在只要说我们新建一个类,然后比如说我们有一个叫做 VideoGame,我们说这个类它是继承自Item的,那么我们可以在Superclass中写上Item,不需要到源代码里面去改,我们说类就继承自Item, 我们就会得到Videogame就是继承Item的。

然后我们可能在Videogame里面有一些新的成员变量,比方说我们需要有一个表达 numberOfplayers有多少个玩家?我们需要有一个这样的东西,接着我们可以继续用eclipse的功能,我们需要做一个构造器,那么这个时候我们需要构造器。

 

我们要一个构造器是从父类来的,所以它会处理父类的这个构造器的这些情况。于是他就给我们做了一个构造器,这个构造器有父类要的title,playingTime gotIt和comment这些参数,然后通过super会传给他。

还有一个numberOfPlayers,很简单,我们可以说我们在这还有另外一个参数。再来修改一下,说有一个 number,然后做完了这个事情之后,我们可以说我们的numberOfplayers = number。 于是我们的构造器也有了。

我们还需要为他做那个 print函数。所以这里通过 Source    Override/implement Methods来做 print。于是我们就有了一个print,那么他会去调用父类的print,在那个之前我们会需要他输出一个输出他自己的比如说叫做 VideoGame 。 System.out.print("VideoGame :");剩下的事就由 Item的 那个print,这样我们就把一个新的VideoGame给做好了,这就是一种新的媒体类型。

 

那么对于database来说,要不要动呢? databasee的 ArrayList Item 是可以放CD,DVD的。现在既然VideoGame 是一种Item,当然它也能放 。database的add是add一个Item的,既然VideoGame是一种Item,所以他也能够接受。那么对于这个main来说,我们在里面就可以好放这个新的这种类型,比如说我们要add一个new的 VideoGame,我们要给他一些比如说一些参数。

    db.add(new VideoGame("ddd",10,true,"...",4));

程序运行结果:

 

最后VideoGame也放进去了,在现在的这种database的架构底下,我们要增加一个新的媒体类型,会变得非常的容易,增加一种新的媒体类型,只要增加 Item类的一个新的子类就可以了。

对Database来说,add函数, print函数是完全不需要动的。这种特性我们叫做可扩展性。你的代码不需要经过修改就可以扩展去适应新的数据,新的内容,这叫做可扩展性。如果说你的代码经过修改可以去适应新的,那不叫做可扩展性,那就是可维护性,这是两个不同的术语。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/459051.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号