复用代码是Java众多因人注目的功能之一.但要想成为极具革命性的语言,仅仅能够复制代码并对之加以改变是不够的.它还必须能够做更多的事情.
1. 组合语法
假设你需要某个对象,它要具有多个String对象
/几个基本类型数据,以及另一个类的对象.对于非基本类型的对象,必须将其引用置于新的类中,但可以直接定义基本类型数据:
|
|
toString()
:每一个非基本类型的对象都有一个toString()
方法,而且当编译器需要一个String而你却只有一个对象时,该方法便会被调用.所以在SprinklerSystem.toString()
的表达式中:"source = " + source;
编译器会得知你将要一个String对象同WaterSource相加.由于只能将一个String对象和另一个String对象相加,因此编译器会告诉你:我将调用toString()
,把source转换为一个String!所以,每当想要所创建对象的类具备这样的行为时,仅需要编写一个toString()
方法即可.
编译器并不是简单地为每一个引用都创建默认对象.如果想初始化这些引用,可以在代码中的下列位置进行:
- 在定义对象的地方.这意味着它们总是能够在构造器被调用之前被初始化.
- 在类的构造器中.
- 就在正要使用这些对象之前,这种方式称为惰性初始化.在生成对象不值得及不必每次都生成对象的情况下.
|
|
2. 继承语法
当创建一个类时,总是在继承,因此,除非已明确指出要从其它类中继承,否则就是在隐式地从Java的标准根类Object
进行继承.
在继承的过程中,需要声明”新类与旧类相似”.需要用extends
实现.
|
|
Java中用super
关键字表示超类的意思,当前类就是从超类继承来的.为此,表达式super.scrub()
将调用基类版本的scrub()
方法.
2.1 初始化基类
|
|
在构建过程是从基类”向外”扩散的,所以基类在导出类构造器可以访问它之前,就已经完成了初始化.即使你不为Catroon()
创建构造器,编译器也会为你合成一个默认构造器,该构造器将调用基类的构造器.
2.2 带参数的构造器
如果没有默认的基类构造器,或者想调用一个带参数的基类构造器,就必须要用关键字super
显式地编写调用基类构造器的语句,并且配以适当的参数列表.
|
|
如果不在BoardGame()
中调用基类构造器,编译器会无法找到符合Game()
形式构造器.而且,调用基类构造器必须是你在导出类构造器中要做的第一件事.Implicit super constructor BoardGame() is undefined. Must explicitly invoke another constructor
3. 代理
因为我们将一个成员对象置于所有构造的类中(就像组合),但与此同时我们在新类中暴露了该成员对象的所有方法(就像继承).例如,太空船需要一个控制模块:
|
|
构造太空船的一种方式是使用继承:
然而,SpaceShip
并非真正的SpaceShipControls
类型,即便你可以”告诉”SpaceShip
向前运动forward()
.更精确地讲,Spaceship
包含SpaceShipControls
,与此同时,SpaceShipControls
的所有方法在SpaceShip
中都暴露了出来.代理解决了此难题:
|
|
我们使用代理时可以拥有更多的控制力,因为我们可以选择只提供在成员对象中的方法的某个子集.
4. 结合使用组合和继承
|
|
4.1 确保正确清理
如果你想要某个类清理一些东西,就必须显式地编写一个特殊方法来做这件事,并要确认客户程序员知晓他们必须要调用这一方法.
|
|
关键字try
表示,下面的块是保护区,其中一项特殊处理就是无论try块是怎样退出的,保护区后面的finally
子句中的代码总是要被执行!
4.2 名称屏蔽
|
|
Java SE5新增加了@Override
注解,当想要覆盖某个方法适,可以选择添加这个注解,在你不留心重载并非覆写了该方法时,编辑器会生成一条错误信息.
5. 在组合和继承之间选择
组合技术通常用于想在新类中使用现有类的功能而非它的接口这种形式.
在继承的时候,使用某个现有类,并开发一个它的特殊版本
6. protected 关键字
在实际项目中,经常会想要将某些事物尽可能对这个世界隐藏起来,但仍然允许导出类的成员访问它们.
关键字protected
指明白”就类用户而言,这是private的,但对于任何继承于此类的导出类或其他任何位于同一个包内的类来说,它却是可以访问的.”(protected提供了包内访问权限)
|
|
7. 向上转型
能够向基类发送的所有信息同样也可以向导出类发送.如果Instrument类具有一个play()
方法,那么wind
乐器也同样具备.我们可以准确地说wind
对象也是一种类型的instrument
.
|
|
这种将Wind
引用转换为Instrument
引用的动作,我们称之为向上转型
.
7.1 为什么称之为向上转型
由导出类转换为基类,在继承图上是向上移动的,因此一般称为向上转型
.由于向上转型是从一个较专业类型向通用类型转换,所以总是很安全.在向上转型的过程中,类接口中唯一可能发生的丢失方法,而不是获取它们.
7.2 再论组合与继承
自己是否需要从新类向基类进行向上转型.如果必须向上转型,则继承是必要的;如果不需要,则应当好好考虑自己是否需要用到继承!
8. final 关键字
这是无法改变的.
8.1 final 数据
在Java中,这类常量必须是基本数据类型,并且以关键字final表示.在对这个常量进行定义的时候,必须对其进行赋值.
一个既是static
又是final
的域只占据一段不能改变的存储空间.
既是static
又是final
的域将用大写表示,并使用下划线分隔各个单词
定义为static
则强调只有一份,定义为final
则说明它是常量.并且static
在装载的时候已经被初始化,并不是每次创建新对象时才初始化.
- 空白final
|
|
必须在域的定义处或者每个构造器中用表达式对final
进行赋值,这正是final
域在使用前总是被初始化的原因.
- final参数
|
|
方法f()
和g()
展示了当前类型的参数指明为final
时所出现的结果:你可以读参数,但却无法修改参数.
8.2 final方法
使用final
方法的原因有两个:
- 把方法锁定,以防任何继承类修改它的含义.
- 效率.
final和private关键字
类中所有的private
方法都隐式地指定为final
.由于无法取用private
方法,所以也就无法覆盖它.可以对private
方法添加final
修饰词,但这不能给方法增加任何额外的意义.
|
|
8.3 final类
当将某个类的整体定义为final
,就表明了你不打算继承该类,而且也不允许别人这样做.final
是禁止继承的,所以final
类中所有的方法都隐式指定为final
的.
8.4 有关final的忠告
如果将一个方法制定为final
,可能会妨碍其他程序员在项目中通过继承来复用你的类,而这只是因为你没有想到它会以这种方式被运用.
9. 初始化及类的加载
加载发生于创建类的第一个对象之时,但是当访问static
域或static
方法时,也会发生加载.构造器也是static
方法.
9.1 继承与初始化
|
|
在Beetle
上运行java时,所发生的第一件事情就是试图访问Bettle.main()
(一个static方法),于是加载器开始启动并找出Bettle
类的编译代码.在对它进行加载的过程中,编译器注意到它由一个基类(这是由关键字extends
得知),于是它继续进行加载.
10. 总结
继承和组合都能从现有的类型生成新的类型,组合一般是将现有的类型作为新类型底层实现的一部分来加以复用,而继承复用的是接口.
在使用继承时,由于导出类具有基类接口,因此它可以向上转型为基类.