复用代码是Java众多引人注目的功能之一。但想要成为极具革命性的语言,仅仅能够复制代码并对之加以改变是不够的,它还必须能够做更多的事情。
组合:只在新的类中产生现有类的对象。
继承:按照现有类的类型来创建新类。
toString()很特殊。每一个非基本类型的对象都有一个toString()方法,而且当编译器需要一个String而你却只有一个对象时,该方法便会被调用。
初始化引用,可以在代码中的下列位置进行:
除非已明确指出要从其他类中继承,否则就是在隐式地从Java的标准根类Object进行继承。
extends关键字 自动得到基类中所有的域和方法。
可以为每个类都创建一个main()方法。这种在每个类中都设置一个main()方法的技术可使每个类的单元测试都变得简便易行。而且在完成单元测试之后,也无需剔除main(),可以将其留待下一次测试。
即使是一个程序含有多个类,也只有命令行调用的那个类的main()方法会被调用。即使一个类只具有包访问权限,其public main()仍然是可访问的。
Cleanser中的所有方法必须是public的。其他包中的某个类若要从Cleanser类中继承,则只能访问public成员。所以,为了继承,一般的规则是将所有数据成员都指定为private,将所有的方法指定为public(稍后将会学到,protected成员也可以借助导出类来访问)。
super关键字
构建过程是从基类“向外”扩散的,所以基类在导出类构造器可以访问它之前,就已经完成了初始化。
调用基类构造器必须是你在导出类构造器中要做的第一件事。
第三种关系称为代理,Java并没有提供对它的直接支持。这是继承与组合的中庸之道,因为我们讲一个成员对象置于所要构造的类中(就像组合),但与此同时我们在新类中暴露该成员对象的所有方法(就像继承)。
虽然编译器强制你去初始化基类,而且要求你要在构造器起始处就要这么做,但是它并不监督你必须将成员对象也初始化,因此在这一点上你自己必须时刻注意。
Java没有C++中析构函数的概念。
保护区(guarded region),这意味着它需要被特殊处理。其中一项特殊处理就是无论try块是怎样退出的,保护区后面的finally子句中的代码总是要被执行的。
在清理方法中,还必须注意对基类清理方法和成员对象清理方法的调用顺序,以防止某个子对象依赖于另一个子对象的情形发生。
亲自处理垃圾清理时,得多做努力并多家小心。一旦设计垃圾回收,能够信赖的事就不会很多了。最好的办法是出了内存以外,不能依赖垃圾回收器去做任何事。如果需要进行清理,最好是编写你自己的清理方法,但不要使用finalize().
如果Java的基类拥有某个已被多次重载的方法名称,那么在导出类中重新定义该方法名称并不会屏蔽其在基类中的任何版本(这一点与C++不同)。
即使子类引入一个新的重载方法(在C++中若要完成这工作则需要屏蔽基类方法),超类的所有重载方法都是可用的。使用与基类完全相同的特征签名和返回类型来覆盖具有相同名称的方法,是一件及其平常的事。
Java SE 5 新增加了@Override注解,他并不是关键字,但是可以把他当关键字使用。@Override可以防止你在不想重载时意外地进行了重载。
组合技术通常用于想在新类中使用现有类的功能而非它的接口这种情形。
在继承的时候,使用某个现有类,并开发一个它的特殊版本。通常这意味着你在使用一个通用类,并为了某种特殊需要而将其特殊化。
“is-a”的关系用继承来表达,“has-a”的关系则是用组合来表达。
关键字protected指明“就类用户而言,这是private的,但对于任何继承于此类的导出类或其他位于同一包内的类来说,它却是可以访问的。”(protected也提供了包内访问权限。)
传统的类继承图的绘制方法
由导出类转型成基类,在继承图上是向上移动的,因此一般称为向上转型。向上转型是从一个较专用类型向较通用类型转换,所以总是很安全的。唯一可能发生的事情是丢失方法。
在面向对象编程中,生成和使用程序代码最有可能采用的方法就是直接将数据和方法包装进一个类中,并使用该类的对象。也可以运用组合技术使用现有类来开发新的类;而继承技术其实是不太常用的。慎用继承技术,其使用场合仅限于你确信使用该技术确实有效的情况。到底改用组合还是用继承,一个最清晰的判断方法就是问一问自己是否需要从新类向基类进行向上转型。如果必须向上转型,则继承是必要的。
一个永不改变的编译时常量
一个在运行时被初始化的值,而你不希望它被改变。
在Java中,这类常量必须是基本数据类型,并且以final关键字表示。在对这个常量进行定义的时候,必须对其进行赋值。
一个既是static又是final的域只占据一段不能改变的存储空间。
对于基本类型,final使其数值恒定不变;而对于对象引用,final使其引用恒定不变。然而对象其本身确实可以被修改的,Java并未提供使任何对象恒定不变的途径(但可以自己编写类以实现)。这一限制适用于数组,也就是对象。
定义为public,则可以用于包之外;定义为static,则强调只有一份;定义为final,则说明它是一个常量。请注意,带有恒定初始值(即,编译期常量)的final static基本类型全用大写字母命名,并且字与字之间用下划线隔开。
我们不能因为某数据是final的就认为在编译时可以知道它的值。比如final数据赋值使用了随机数的情况就不能在编译期知道。
static的,在装载时初始化,而不是每次创建新对象时都初始化。
空白final是指被声明final但又未给定初值的域。无论什么情况,编译器都确保空白final在使用前必须被初始化。
必须在域的定义处或者每个构造器内用表达式对final赋值,这正是final域在使用前总是被初始化的原因所在。
Java允许在参数列表中以声明的方式将参数指明为final。这意味着你无法在方法中更改参数引用所指向的对象。
参数被指明final时,你可以读参数,但却无法修改它。这一特性主要用来向匿名内部类传递数据。
原因有二:一,把方法锁定,以防止任何继承类修改它的含义。确保继承中使方法行为保持不变,并且不会被覆盖。二,过去建议使用final方法的第二个原因是效率。 内嵌调用 最近的Java版本中,虚拟机(特别是HotSpot技术)可以探测到这些情况,并优化去掉这些效率反而降低的额外内嵌调用。所以不再需要final方法来进行优化了。
“覆盖”只有在某方法是基类的接口的一部分时才会出现。即,必须能将一个对象向上转型为它的基本类型并调用相同方法。如果某方法是private,它就不是基类的接口的一部分。它仅是一些隐藏于类中的程序代码,只不过是具有相同的名称而已。如果在导出类中以相同名称生成一个public、protected或包访问权限方法的话,该方法就不会产生基类中出现的“仅具有相同名称”的情况。此时你并没有覆盖该方法,而仅是生成了一个新的方法。由于private方法无法触及而且能有效隐藏,所以除了把它看成是因为它归属的类的组织结构的原因而存在外,其他任何事物都不需要考虑到它。
某个类整体定义为final时,就表明你不打算继承该类,而且也不允许别人这么做。
final类的域可以根据个人意愿选择是不是final。无论类是否被定义为final,相同的规则都适用于定义为final的域。
Vector和Stack
Hashtable
类的代码在初次使用时才加载。这通常是指加载发生于创建类的第一个对象之时,但是当访问static域或static方法时,也会发生加载。
初次使用之处也是static初始化发生之处。所有static对象和static代码都会在加载时依程序中的顺序(即,定义类时的书写顺序)而依次初始化。当然,定义为static的东西只会被初始化依次。