代码复用是面向对象程序设计语言锁提供的最了不起的优点之一。
新的类型可以由任意数量,任意类型的其他对象以任意可以实现新的类中想要的功能的方式组成。
因为是在使用现有的类合成新的其他对象的类,所以这种概念被称为组合(composition),如果组合是动态发生的,那么它通常被称为聚合(aggregation)。
在建立新类时,应该首先考虑组合,因为它更加简单灵活。如果采用这种方式,设计会变得更加清晰,一旦有了一些经验之后,便能够看出必须使用继承的场合了。
当源类(被称为基类,超类或父类)发生变动时,被修改的“副本”(被称为导出类,继承类或者子类)也会反应出变动。
可以创建一个基类来表示系统中某些对象的核心概念,从基类型中导出其他类型,来表示此核心可以被实现的各种不同方式。
当继承现有类型时,也就创建了新的类型。这个新的类型不仅包括现在类型的所有成员(尽管private成员被隐藏起来,并且不可访问),而且更重要的是它复制了基类的接口。也就是说,所有可以发送给基类对象的消息同时也可以发送到导出类对象。
通过继承而产生的类型等价性是理解面向对象程序设计方法内涵的重要门槛!
有两种方法可以使基类与导出类产生差异
继承应该只覆盖基类的方法(而不添加在基类中没有的新方法),在某种意义上,这是一种处理继承的理想方式。我们经常将这种情况下的基类与导出类之间的关系称为is-a(是一个)关系。
有时必须在导出类型添加新的接口元素,这样也就扩展了接口。这种情况我们可以描述为is-like-a(像是一个)关系。
这种能力可以极大地改善我们的设计,同时也降低软件维护的代价。
面向对象程序设计的最重要妙诀:编译器不可能产生传统意义上的函数调用。一个非面向对象编程的编译器产生的函数调用会引起所谓的 前期绑定 ,这个术语你可能以前从未听说过,可能从未想过函数调用的其他方式。这么做意味着编译器将产生对一个具体函数名字的调用,而运行时将这个调用解析到将要被执行的代码的绝对地址。然而在OOP(面向对象编程)中,程序直到运行时才能够确定代码的地址,所以当消息发送到一个泛化对象时,必须采用其他的机制。
当向对象发送消息时,被调用的代码直到运行时才能确定。编译器确保被调用方法的存在,并对调用参数和返回值执行类型检查(无法提供此类保证的语言被称为是弱类型的),但是并不知道将被执行的确切代码。
把将被导出类看做是它的基类的过程称为向上转型。
转型这个名称的灵感来自于模型铸造的塑模动作;而向上(up)这个词来源于继承图的典型布局方式;通常基类在顶部,而导出类在其下部散开。因此,转型为一个基类就是在继承图中向上移动,即“向上转型”。
正是因为多态才使得事情总是能够被正确处理。编译器和运行系统会处理相关的细节,你需要马上知道的只是事情会发生,更重要的是怎样通过它来设计。当向一个对象发送消息时,即使涉及向上转型,该对象也知道要执行什么样的正确行为。
容器(也称为集合,不过java类库以不同的含义使用“集合”这个术语,所以本书使用“容器”这个词),在任何需要时都可扩充自己以容纳你置于其中的所有东西。因此不需要知道将来会把多少个对象置于容器中,只需要创建一个容器对象,然后让它处理所有细节。
Java容器
我们可以在一开始使用LinkedList构建程序,而在优化系统性能时改用ArrayList。接口List所带来的抽象,把在容器之间进行转换时对代码产生的影响降低到最小限度。
参数化类型(范型) 在JavaSE5之前,容器存储的对象都只具有Java中的通用类型:Object。
单根继承结构意味着所有东西都是Object类型 ,所以可以存储Object的容器可以存储任何东西。这使得容器很容易被复用。
向上转型是安全的,例如Circle是一种Shape类型;但是不知道某个Object是Circle还是Shape,所以除非确切知道所要处理的对象的类型,否则向下转型几乎是不安全的。
参数化类型(范型)就是一个编译器可以自动定制用于特定类型上的类。例如,通过使用参数化类型,编译器可以定制一个只接纳和取出Shape对象的容器。
为了利用泛型的优点,很多标准类库构建都已经进行了修改。就像我们将要看到的那样,范型对本书中的许多代码都产生了重要的影响。
每个对象为了生存都需要资源,尤其是内存。当我们不再需要一个对象时,它必须被清理掉。使其占有的资源可以被释放和重用。
这种方式将存储空间分配和释放置于优先考虑的位置,某些情况下这样的控制非常有价值。但是,也牺牲了灵活性,因为必须在编写程序时知道对象确切的数量,生命周期和类型。
在这种方式中,直到运行时才知道需要多少对象,它们的生命周期如何,以及它们的具体类型是什么。
如果需要一个新对象,可以在需要的时刻直接在堆中创建。因为存储空间是在运行时被动态管理的,所以需要大量的时间在堆中分配存储空间,这可能要远远大于在堆栈中创建存储空间的时间。
动态方式有这样一个一般性的逻辑假设:对象趋向于变得复杂。所以查找和释放存储空间的开销不会对对象的创建造成重大冲击。动态方式所带来的更大的灵活性正是解决一般化编程问题的要点所在。
java完全采用动态内存分配方式。每当想要创建新对象时,就要使用new关键字来构建此对象的动态实例。
还有一个议题,就是生命周期。对于允许在堆栈上创建对象的语言,编译器可以确定对象存活的时间,并可以自动销毁它。
垃圾回收器非常有用,因为它减少了所必须考虑的议题和必须编写的代码。更重要的是,垃圾回收器提供了更高层的保障,可以避免暗藏的内存泄漏问题。
java的垃圾回收器被设计用来处理内存释放问题(尽管它不包括清理对象的其他方面)。垃圾回收器“知道”对象何时不再被使用,并自动释放对象占用的内存。
java中,一切都被视为对象,因此可采用单一的语法。尽管一切都看作是对象,但操作的标识符实际上是对象的一个“引用”。
例:遥控器(引用)操作电视机(对象)。
寄存器:最快的存储区,但由于寄存器的数量极其有限,所以根据需求进行分配。你不能直接控制,在程序中也感觉不到寄存器存在的任何迹象。
堆栈:位于通用RAM(随机访问存储器)中。堆栈指针若往下移则分配新内存,若往上移动则释放内存。
堆栈:就是STACK。实际上是只有一个出入口的队列,即后进先出(First In Last Out),先分配的内存必定后释放。一般由系统自动分配,存放函数的参数值,局部变量等,自动清除。
堆栈是每个函数进入的时候分一小块,函数返回的时候就释放了
局部变量放在堆栈中,所以函数返回,局部变量就全没了。
堆:一种通用的内存池(位于RAM区), 用来存放所有的Java对象。
当需要一个对象时,只需用new写一行简单的代码,当执行这行代码时,会自动在堆里进行存储分配。
用堆进行存储分配和清理可能比用堆栈进行存储分配需要更多的时间。
常量存储:常量值通常是直接存放在程序代码内部。
非RAM存储:如果数据完全存活于程序之外,那么它可以不受程序的任何控制,在程序没有运行也可以存在。例如:流对象和持久化对象。