第四章:多态

多态性是允许你向父对象设置成为和一个或更多的它的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同方式运作。

4 - 1:Java中的多态

多态:对象的多种形态。多种形态有两方面,一个是引用多态,一个是方法多态。

先看引用多态。我们在写面向对象程序的时候,我可以使用一个父类的引用,指向一个本类的对象。我们之前在创建对象的时候都是使用的这种方式。我们现在运用多态的特性还可以这样来做:父类的引用可以指向子类的对象。这样的话,我们的父类的引用既可以指向本类,又可以指向子类,这不就是多种形态?我们把这种形式叫做引用多态。我们来看一下。

打开编程工具,创建一个Java Project,名字我就起project4,其他默认(我是Java1.8的)就行,然后在里面src中创建一个父类Animal,并指定包名com.imooc,接着再在这个包里面创建一个Animal子类Dog类,记着要选择Dog继承的类,最后在这个包里面生成一个测试类Initial类,在这边打上勾(这个类是main函数)。我们刚才说过,多态的特性第一点是父类的引用可以指向本类对象,在main函数第一行输入Animal obj1 = new Animal();,obj1引用的是本类的Animal类对象。我们多态还有特点就是可以使用父类引用指向子类对象:在第二行输入Animal obj2 = new Dog();,此时,同样类型的一个引用,我既可以指向本类,也可以指向子类。这就是引用的多种形态。我们把这种情况叫做多态。但大家一定要注意,我们不能使用一个子类引用去指向父类的对象,如:Dog是子类,Animal是父类,如果这样写:Dog obj3 = new Dog();,这样编译器就会报错。

再看方法多态。如果我创建的是父类的本类对象,那么我在调用这个方法的时候,调用的肯定是父类的方法,但父类引用的是子类对象,我们在执行的时候,调用的方法就是子类的方法。当然,这个方法如果子类没有重写的话,它调用的是继承的方法。

如,我在刚才Animal类里,添加一个eat方法,输出动物具有吃的能力,同时在子类里重写这个方法,输出狗是吃肉的,那么,我们在测试函数里,如果我用的是obj1,用的eat方法,这样,我们来运行一下:

这里可以看到,执行的是动物具有吃的能力,是父类中的方法,如果用obj2调用eat方法,调用的是狗是吃肉的,就是子类重写的方法。就是,同样都是父类引用,指向不同对象的时候,我们发现,调用方法的时候,是不同的方法,此时我们把这种情况叫做方法的多态。

其实方法的多态还有一种情况,我在包里再创建一个子类Cat,继承Animal,但我Cat类里面没写从父类继承过来的方法,然后创建一个子类对象并运用我们刚才所讲多态特性Animal obj3 = new Animal();,这里我调用一个eat方法,是子类继承父类的方法:

还有一种特殊情况下,多态特性是不能使用的:如果我在子类里添加一个独有的方法,并不是从父类继承来的,比如,在Dog里加一个watchDoor方法,输出狗具有看门的能力,这是Dog子类独有的方法,这样,父类里是没有的,此时,我在我们的main函数当中,就不能通过父类的引用调用子类的watchDoor方法。

现在我们来做一个练习,来描述我们的交通工具,交通工具分为汽车、轮船、飞机等类型,这些交通工具都有共同的特性,就是运输客人,并且有自己的运输方式。因为汽车运输方式是在陆地上,轮船是在海上,飞机是在空中。我能不能结合这些特性写一程序,尝试创建5个以上生活当中交通工具,并同时查看他们的运输客人方式。如:有大巴,能载客40人;有轮船,能载客200人;有飞机,能载客300人等。

 

4 - 2:多态中的引用类型转换

我们在写Java程序的时候,经常对我们的引用类型进行转换。对引用类型转换主要分为两个,一个是向上类型转换,就是将小类型转换到大类型,我们把这种转换叫做隐式转换或自动转换;一个是向下类型转换,就是从大类型到小类型,这种转换我们叫强制类型转换。

注意,向上类型转换是不存在风险的,比如,我把一个杯子里的水倒到一个比杯子大的壶里,正常来讲肯定没问题,计算机会自动帮我们做,所以我们把它叫做自动类型转换。而向下类型转换是存在风险的,把一个壶里的水倒到一个杯子里,如果壶里的水多,杯子就盛不下,这样杯子就会溢出,就会有风险。但程序员愿意做这件事,我们也可以强制计算机做这件事。

避免溢出风险:我们可以用instanceof这个关键字来解决引用对象的类型,避免类型转换的安全性问题。

首先在main函数中创一个子类对象,用子类的引用:,我们现在也可以用父类的引用指向这个对象:,这个时候,也是没有问题的。这算是一个“自动类型提升”,或“向上类型转换”。上面说了,这种操作是不存在问题的。如果我再次将这个父类引用转换成子类引用,这样是存在风险的,在编译器这关我们就过不去,前面会打叉:

虽然我们知道,这个父类引用指向的就是这个dog对象,但编译器也认为,这种转换是存在风险的。但我们允许向下类型转换,我们可以在animal前面加一个小括号,里面加上我们的子类类型:,这样,我们是可以强制转换的。这就是向下类型转换。但这样做是存在风险的,就像壶往杯里倒水一样,那就要看杯子能不能装的进去了。还有就是,我用Dog引用了一个dog对象,转换成animal没问题,我再把animal用Cat类来引用,此时,编译器不会报错,但运行起来就会出错了(我们内存里面添加的是Dog的类,但我们是按Cat类来走的,肯定会出错):

我们遇到上述问题就有点棘手了,还好我们有instanceof关键字,通过instanceof关键字,可避免类型转换的安全性问题,它会返回一个布尔值,通常我们会用这个布尔值跟if语句配合使用。相面我写了instanceof与if的使用:

这段代码的意思是,如果animal这个对象跟Cat类里面的元素相同,就执行Cat cat = (Cat)animal;这句话,否则跳过。

 

4 - 3:Java中的抽象类

我们写程序是,还会用一个特殊类型,就是抽象类。抽象类与普通类最大的关系是,抽象类定义时前面会多出来一个关键字,abstract

抽象类什么时候用呢?1、在某些情况下,某个父类只是知道其子类应该包含怎样的方法,但无法准确知道这些子类如何实现那些方法。抽象类是用来约束子类必须有哪些方法,而并不关注子类如何实现。2、从多个具有相同特征的类中抽象出一个抽象类,以这个抽象类作为子类的模板,从而避免了子类设计的随意性。

抽象类作用:限制规定子类必须实现某些方法,但不关注细节。

使用规则:1、一定要用关键字abstract修饰一下。2、abstract定义抽象方法时,只有声明,没有实现。3、包含抽象方法的类一定是抽象类。4、抽象类中可包含普通方法,也可没有抽象方法。5、抽象类不能直接创建,可以定义引用变量。指向子类对象。

我们现在基本属每人都有手机,每个手机最起码都具备两个功能,打电话、发短信。我们现在知道了,定义一个父类叫Telephone,这个父类是用来约束子类的,那我就可以把它定义成抽象类:

它约束子类都有打电话和发短信的能力,在抽象类里约束子类就可以在抽象类里定义两个方法,记得要加上abstract修饰,记住,抽象方法没有方法体,最后用分号结束

早些年的电话这边我们建一个类:CellPhone,要继承上面的抽象类,它会自动实现抽象类里的方法:

这里我们在方法里输出一下,call方法里输出早年电话是通过键盘来打电话,message方法里输出早年电话是通过键盘来发短信

我们现在用的都是智能电话,创建一个类继承Telephone,叫SmartPhone,并实现Telephone里面的方法,在call里输出智能电话通过语音打电话,message方法里输出智能电话通过影语音发短信

注意上面这两张图都有叉号,但不用管,没什么问题。1、CellPhone类型的方法call()必须覆盖或实现超类型方法。2、

再写一个测试类,Initial,加入main函数,紧接着,我就可以用抽象类的父类引用,来指向CellPhone,然后,我就可以打电话、发短信了。也可以指向SmartPhone打电话,发短信了。

思考:现有一个Shape图形类,用Rectangle矩形和Circle圆形子类,求图形周长和面积。

4 - 4:练习题

下面关于抽象类定义合法的是( )

A、class Animal { abstract void grow(); }

B、abstract Animal { abstract void grow(); }

C、class abstract Animal { abstract void grow(); }

D、abstract class Animal { abstract void grow(); }

 

答案:D。解析:包含抽象方法的类一定是抽象类,抽象类和抽象方法都需要添加关键字 abstract,且顺序为 abstract class

 

4 - 5:Java中的接口

1、接口:接口可以理解为一种特殊的类,有全局常量和公共的抽象方法组成。

类是一种具体实现体,而接口定义了某一批类所需要遵守的规范,接口不关心这些类的内部数据,也不关心这些类里的方法的实现细节,它只规定这些类里必须提供某些方法,满足我们的实际需要。也就是说,接口就是一种规范,是用来约束类的,

2、接口定义:和类定义不同,定义接口不再使用class关键字,使用的是interface关键字。

语法:

[修饰符] abstract interface 接口名[extends 父接口1,父接口2...]
{
	零到多个常量定义...
	零到多个抽象方法定义...
}

首先前面是它的修饰符,这里注意,接口就是用来被继承、被实现的,修饰符一般建议用public。不能用private和protected修饰接口。因为接口里面是常量或方法,所以在interface前面最好加个abstract,如果我们自己并没有把它显示的写出来,系统会自动帮我们加上。接口可以继承多个父接口,我们的类在Java世界里是单继承的,但接口是可以多继承的。我们在继承这个地方用了中括号,中括号就是说,你可以继承父接口,也可以不继承。然后跟我们定义类一样,有个大括号,里面我们就可以定义一些常量和抽象方法。

常量:接口中的属性是常量,即如果定义时不添加public static final修饰符,系统也会自动加上。

方法:接口中的方法是抽象方法,总是使用,即使定义时不添加public abstract修饰符,系统也会自动加上。

3、使用接口

一个类可以实现一个或多个接口,实现接口时使用的是implements关键字。Java中一个类只能继承一个父类,是不够灵活的,可通过实现多个接口做补充。

继承父类实现接口的语法为:

[修饰符] class 类名 extends 父类 implements 接口1,接口2...
{
	类体部分//如果继承了抽象类,需要实现继承的抽象方法;要实现接口中的抽象方法。
}

修饰符看大家用来做什么,写什么就好,然后是类名,之后这个类要继承父类,之后可以继承一个或多个接口。多个接口之间用逗号分隔开。这个类的具体部分,如果继承了抽象类,我们就必须继承抽象类里的抽象方法,如果我遵守了一个接口,我就必须实现接口中的抽象方法。

语法规则:如果要继承父类,继承父类必须在实现接口之前。

我们前面写过一个类,Telephone类,它的子类有CelPhone和SmartPhone,都可以发短信和打电话。现在智能手机又有了一些新功能,比如玩游戏和PSP那样。但是,PSP在中国很少人会购买,也不会用作为打电话来使用,这样,用Telephone做父类显然不合适。但是用接口就可以描述这个特征。

我们在包里新建一个接口(interface):

然后再给它起名,就叫IPlayGame,给接口命名时,一定要在前面加一个I,以至于区分和我们的类文件名不一样:

如果这个接口还想继承其他的父接口,上面的图上面有Add...,就可以添加其他的父接口了。因为现在就这一个接口,直接点Finish就行。点完以后:

其实这里隐藏了abstract关键字,在interface前面,当我们在定义接口里面的方法的时候,它也省掉了一个abstract关键字:

我们智能手机能玩游戏,所以咱们让它实现这个接口。这时候我们要用到个关键字:implements:

输入这个后我们看待前面有个叉,我们点一下叉,里面会有Add implemented methods,双击这个,里面就会多出来一个方法:

我们就在里面输出具有玩游戏的功能,就可以了。

然后我们再创建一个PSP类,起名就叫Psp,按照Java驼峰命名法来命名。这个类不是Telephone的子类,但它具有游戏的特性,我们就把这个接口给它:

在这里的接口中也输出具有玩游戏的功能就好了。

这样,这个接口就可以把这两个类里面的东西放在一起。

这个怎么应用呢:在Initial里面,我们可以直接用接口的方式来引用有接口里面东西的对象:

接口在使用当中,还经常与匿名内部类配合使用。匿名内部类就是没有名字的内部类,多用于关注实现而不关注实现类的名称。

语法格式:

Interface i = new Interface(){
	public void method(){
		System.out.println("匿名内部类实现接口的方式");
	}
};

 

注意最后有分号!

在代码里看一下:

首先在Initial里面new了一个IPlayGame,并在内部写接口的实现部分:

这时候鼠标停在后面的IPlayGame上,他会出提示:Add unimplemented methods,单击,出现后面代码:

就在这里输出一句话:使用匿名内部类的方式实现接口。最终,我们可以通过接口的引用,调用内部类的方法:

还有一种方法可以达成这样的效果,直接new,不用“引用”:

这样的结果也是:

 

4 - 6:练习题

在接口中定义方法 geta() ,正确的是( )

A、void geta() { }

B、private void geta() ;

C、void geta() ;

D、protected void get() ;

 

答案:C。解析:接口中方法不能有方法体,同时方法的访问修饰符不能是 private 和 protected

 

4 - 7:UML简介

1、UML概念:Unified Modeling Language(UML),又称统一建模语言或标准建模语言。是一个支持模型化和软件系统开发的图形化语言,为软件开发的所有阶段提供模型化和可视化支持。

2、UML图示:UML2.2中共定义了14种图示(diagrams)。

3、常用UML图

①用例图(The Use Case Diagram)用例图能够以可视化的方式,表达系统如何满足所收集的业务规则,以及特定的用户需求等信息。

比如在餐馆里面,左边普通的用户可以吃东西(Eat Food)、支付他吃的东西(Pay for Food)、喝9(Drink Wine);右边的厨师能做饭(Cook Food),这两边的人所做的事不一样,中间是我们要系统提供的功能,人不一样,功能不同。

②序列图(The Sequence Diagram):序列图主要用于按照交互发生的一系列顺序,显示对象之间的这些交互。

比如上面这幅图,我去银行里取钱,这个取钱的过程是用户和系统之间发生的关系⚣。首先用户要表明身份,输入用户名密码或拿着身份证,然后柜台窗口接待了我们并让我们输入取钱的钱数,他会让写一张确认单,账户后台进行处理;处理完毕,授权合法并签字,他才会分发货币。这幅图表述的是用户和系统对象交互环节,每一步这幅图都会表现出来。

③类图/类别图(The Class Diagram):UML类图、业务逻辑和所有支持结构一同被用于定义全部的代码结构。

这幅图就是Animal和Dog的关系,Dog是继承Animal的。图片左边加号表示公有的,减号表示私有的。我们先开Animal这个类。它主要分为3个部分:最上面是这个类的类名,中间是这个类的属性,第三个部分是这个类的方法(或这个对象拥有的能力)。再看Dog类,这里面自动继承Animal的属性和方法,这里,我们重写了(Override)eat的方法。

4、UML建模工具:Visio、Rational Rose、PowerDesign三种建模工具应用最广。

Rational Rose是IBM的:https://getintopc.com/softwares/development/rational-rose-enterprise-edition-free-download-6136315/

Visio是Office的一个组件,在Microsoft官方能搜到。

PowerDesigner也是建模工具,这个也可以搜到。

我们现在用PowerDesigner这个软件来看一下如何设计一个UML图(这个要收费,我在百度搜的16.5汉化破解版)。

打开PowerDesigner,右上角菜单栏有个创建(文件下面): 创建,可以创建各种模型类型,这里我们主要创建一个OO模型(面向对象模型),语言选择Java:

当然PowerDesigner还支持很多其他语言,这里我们选择的是类视图(Diagram里面选Class Diagram),然后我们的空白的面板就创建好了。

我们写接口的时候有一个小程序,有Telephone的抽象类、两个子类、另外有Psp游戏机,SmartPhone和PSP都有游戏机的特征,我们用接口来描述,这种情况来用UML来设计一下:

首先这里方框里有个Class:

点击这个按钮,在这里点4下:

在Class3和Class4下中间有接口来表示:

 

在这里创建一个Interface:

我们把视图放大:

 

首先双击第一个类:

重命名Name为Telephone,在Abstract后面打勾,然后phone可以打电话和发短信,我们点Operations,就是方法,里面我们输入send和call然后在A下面打勾,这个A就是Abstract:

这里父类就创建好了。下面子类与Telephone是继承关系,我们要点这个:

按住鼠标左键进行连线,连到Telephone上:

空心箭头描述的就是继承关系。双击Class2和3进行编辑,在CellPhone里,Name定位CellPhone,Operations里面选择Inherited,里面就会有send和call方法,因为CellPhone里面重写了,我们要加上Override:

对于SmartPhone也是:

这时候SmartPhone和PSP有共同特征,我们用接口来描述。双击接口,Name叫IPlayGame,接口里面描述了个方法,叫playGame。这里是public,默认是abstract。这个返回值是void。

要让类实现接口,这里面有个Realization(实现),然后让SmartPhone和Class4连向它:

继承关系是实线,实现关系是虚线。这是SmartPhone描述的就不严谨了,它还要加一个playGame的方法,我们再在SmartPhone里面选择Operations,里面就会有一个选项:To be Implemented...,选择,你实现里面的那个方法,点Implement,就可以了。同理,在Class4中,也实现这个方法,就可以了:

最后:

现在我们这个打电话发短信的返回值是int类型,我们只需要修改父类就行了:

PowerDesigner除了能写UML,还能根据这个图生成代码,编辑好UML后,选择语言,选择Generate Java Code...,弹出的窗口点确定,就会自动根据图形生成Java代码了。

不过里面方法的内容要我们自己去补充了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值