《Think in Java》
第一章:对象导论
1.1 抽象过程
1)万物皆对象。
2)程序是对象的集合,它们通过发送消息来告诉彼此所要做的。
3)每个对象都有其他对象构成的存储,一个对象可以复用其他对象,从而使程序的复杂隐藏在对象之后。
4)每个对象都是其class的一个instance。
5)同一类(可以是implement 相同 interface,也可以是extend相同基类)的对象可以接受相同的信息,subsitutability可替代性。
1.2 每个对象都有一个接口
1)class是定义了特定特性和行为的一种数据类型,通过一个class创建的instance拥有相似的共性。
2)class的接口确定了对,某一特定对象,所能发出的请求,每一个可能的请求在class内都有一个方法与之相关联,当对对象发送请求时,与之相关联的方法就会被调用。
3)如上图,可以创建一个light对象,通过向其发送请求,light.on(),light对象收到请求,执行on()方法对应的代码。
1.3 每个对象都提供服务
1)因为程序本身就是提供服务,所以我们可以将对象理解为“服务提供者”,而编程者的目标就是去创建能够解决这一系列问题的对象。
2)询问自己:我需要解决哪些问题?我可以创建哪些对象解决这些问题?
3)将对象看作一个服务提供者的一个附带好处就是:我们通过一个对象提供一个服务,能够提高对象的内聚性,减少与其他对象之间的耦合,一个对象就可以很好地完成一个服务。
1.4 被隐藏的具体实现
1)可以将程序员分为‘类创建者’与‘客户端程序员’(使用类创建者创建的类)。
2)类创建者希望类使用者不去操作他所写类的内部部分,因为可能会导致bug,类创建者可以通过访问控制来限制customer的访问范围。
3)访问权限,就是规定了类与类之间的内容修改权限,public是指所以类都可以修改本类,private是只有本类内部可以修改本类,protected是指本类,一个包里的类,以及继承本类的类可以修改本类,以及默认的包访问权限,即一个包里类可以访问。
Public>protected>默认>private
1.5 复用的具体实现
1)组合(composition):使用现有的类来合成新类,“has-a“(拥有)关系。eg.汽车拥有引擎
2)聚合(
1.6继承
1)可以通过两种方法使基类与导出类产生差异:
<1>直接在导出类中添加方法
<2>导出类的方法覆盖(@override)基类的方法
2)“是一个“与”像一个“
[1]. 对于继承可能会存在一种争议:继承应该只使用<2>方法来使导出类与基类差异,因为,这样做就意味着,导出类与基类拥有相同的接口,结果就是可以用导出类完全替代一个基类,此时就可以说:导出类Is a 基类,两者拥有相同的方法。
[2]. 而对于通过<1>直接在导出类中添加新方法,此时导出类的接口改变,只能说:导出类 is-like-a 基类。
1.7 伴随多态的可互换对象
<1>多态
程序员经常想把一个对象当作其基类,不是本身所属的特殊类来对待,这样程序员就能编写出不依赖特定类型的代码。比如我们向circle类发送请求,我们就将其当成shape类,我们不需要关系其所属的特定类,因为所有shape子类都实现了shape类的绘制,擦除与移动的方法,我们不需要担心其如何实现。这就是多态。
<2>多态的可扩展性
通过多态,我们可以添加新的类型而不需要改写原先的调用代码,只需要新类继承,存在的类即可,便可以插入调用代码中使用。提高了代码的可复用与扩展性。
<3>后期绑定
java调用对象的方法,对于不同的对象,执行的代码是不一定相同的。对象作为参数时,因为有泛型的存在,操作代码是不能确定传入的对象的类型的。
在c语言中我们调用函数,在发生编译时就确定了被调用函数的位置和内容了。java中,当把一个消息传送给一个泛化对象时,java程序直到运行时才能确定代码的位置,这样的话,无法依照c语言的调用函数的方式来处理,所以提出了后期绑定。
后期绑定:当向对象发送消息时,被调用的代码只有在运行时才能确定。对象的方法被调用,对象通过,确定调用哪个方法的规则,在运行时确定调用的方法。
在编译阶段,编译器只会确保被调用的方法存在,并对调用参数和返回值的类型进行检查,java对象接受要调用一种方法的信息,但不知道确定将要执行的代码,而是使用一段特殊的代码来代替绝对地址,当运行时这段代码会根据被调用的对象中存储的信息来计算方法体的地址。
这样,根据这一段特殊的代码,每个对象都可以拥有不同的表现。
在java中,动态绑定是默认行为。
<4>向上转型(upcasting):将导出类看作它的基类。向上转型这个词来自于类图中,基类在导出类的上方,所以称为向上转型。
1.8单根继承结构
在java中所有的类都继承自一个类Object,这就是单根继承。
单根继承确保每一个对象都拥有一些相同的功能,所有对象都很容易在堆上创建,参数传递也得到了极大的优化。
1.9容器和参数化类型
容器:自动扩容
如果我们不知道在解决特定问题时需要多少个对象,或者它们存在多长时间,我们就无法给需要创建的对象腾出空间来。这时我们就可以使用容器,我们不需要知道我们需要创建多少个对象,我们可以在任何需要时都可以扩充足以容纳新对象的空间,我们只需要创建一个容器,然后它将处理所有的细节。
参数化类型(即泛型)
不使用泛型时,我们往容器中存储时,容器都会将存入的对象转换为基类Object,当我们取出时我们必须进行向下转型,转换为我们存入的类型,但是这个过程很容易出错。所有我们引入了泛型,具有泛型的容器知道它所存储的对象类型,从而不需要向下转型。
1.10对象的创建和生命周期
在使用对象时,对象的生成和销毁方式是十分关键的,因为对象是存储在堆上的,对象的生存会占用资源,尤其是内存资源,在我们不需要对象时,对象的空间需要释放。
怎么样控制对象的生命周期呢?
c++认为效率控制是最重要的议题,所以c++给程序员提供了选择的权力。为了追求最大的执行速度,对象的存储空间和生命周期在创建时确定,对象可以存储在堆栈或者静态存储区中。但是这种存储方式,牺牲了灵活性。
而java使用了动态内存分配方式。java在堆的内存池中动态地创建对象,当需要创建对象时就要使用new关键字来创建此对象的动态实例,c++在运行前就给对象腾出了空间,而Java则是在运行中才会知道需要什么对象,只有在相关代码执行的那一刻才能确定,因为存储空间是在运行时被动态管理的,所以需要在运行相关代码时花费时间去堆中创建存储空间。
对于c++的对象,其在堆栈上创建对象,知道对象的存活时间,所以可以自动销毁,但是这也带来了内存泄漏的可能。
而在java中,其在堆上创建对象,所以无法确定对象的销毁时间,对此,java提供了一种“垃圾回收器“的机制,它可以自动发现对象何时不再被需要,并且能够自动地销毁它。”垃圾回收器“机制避免了内存泄漏的问题。
java能够确定对象何时不被使用并且能够自动释放其内存,能够实现这一特性主要是因为:一,java所有的对象都继承自Object对象(单根继承),二,对象只能已一种方式创建(通过new在堆上创建)。
这俩特征,决定了Java编程的简单。
1.11异常处理
自编程语言问世以来,错误处理一直是最困难的问题之一,许多语言将异常处理交给程序员,异常处理主要依赖于程序员。
java的一个优点就是,一开始就内置了异常处理,并且强制程序员必须去使用,java将异常也当作一种对象,从错误的地点抛出,异常不能被忽略,必须在程序运行的某处得到处理,被专门设计用来处理特定类型错误的异常处理器捕获,异常处理就像和程序运行并行的,错误发生时执行另一个路径,不会干扰正常代码的执行。需要指出的是:异常提供了一种从错误状况进行可靠恢复的途径,现在遇到异常,程序员不止是只能退出程序,而且可以去矫正程序,并恢复程序继续执行,这些都有助于编出更健壮的程序。
1.12并发编程
在计算机中有一个基本概念,那就是在同一时刻处理多个任务的思想,起初,程序员通过计算机的底层技术,编写中断服务任务的程序,通过硬件中断来触发主程序的挂起,但是这种程序既费时又费力。
对于时间性强的程序有时中断程序是必须的,但是对于大部分程序来说,我们只想把程序分为多个可独立运行的部分,从而提高程序的响应能力。在程序中,这些彼此独立运行的部分称为线程,上述概念被称为“并发“。
并发最常见的就是用户界面,有触发程序挂起,等待触发,用户可以在按下按钮后迅速得到一个响应,而不用等待主程序完成。
通常,线程是一种只为单一处理器分配执行时间的手段。但是如果操作系统支持多个处理器,那么每个任务都可以指派给不同的处理器,并且它们是真正并行执行。由于程序在逻辑上被分为了线程,java程序员不需要关心系统是多处理器还是单处理器,如果机器拥有多个处理器,程序不需要特殊调整也能执行得更快。
通过线程的概念,并发变得简单,但是有一个隐患:共享资源。如果多个并行任务都要访问一个资源,那么就会出问题。例如,两个进程不能同时向一台打印机发送信息。为了解决这个问题,我们需要在使用共享资源时,将共享资源上锁,完成某项任务,然后释放资源锁,其他任务才能使用这项资源。
1.13java与internet
13.1 客户/服务器技术:中央计算机分发信息
有一个具有信息存储池的中央计算机,其上有分发信息的软件,就是服务器,客户计算机被允许在服务器中插入修改信息。
客户/服务器概念的关键在于信息存储池的位置位于中央。
客户向服务器请求数据,服务器给发回需要的文件,浏览器解析这个文件并显示。客户端只能发送请求,所有的处理都在服务器端,浏览器只是一个观察端,什么任务也不能做。此时,即使是一个拼写错误,也需要服务器去处理。
服务器分发HTML静态信息,浏览器显示静态信息,基本的HTML页面包含数据收集机制:文本输入框等等,它们可以将客户端的信息提交给服务器,这种提交动作通过所有的web服务器都提供的通用网关接口传递(common gateway interface CGI)传递,提交信息会告诉CGI怎么处理它,最常见的就是运行“cgi-bin”目录下的一个程序,然后CGI将结果传给服务器,服务器将信息回复给客户端。
现在很多服务器都是使用CGI技术,但这样的网站可能会因为功能的增加迅速变得复杂难以维护,而且CGI的响应速度依赖于发送的数据量大小,以及服务器和Internet的负载。
但是数据发送给服务器,服务器启动程序验证,再将结果返回给客户端,即使是小问题也需要重新提交,这样做会造成时间浪费,而且很不优雅。
解决方案就是客户端编程。
13.2客户端编程
将一些简单任务交给浏览器处理,而不是所有的都发给服务器。
下面对一些客户端编程方法给与描述:
<1>插件(plug-in)
浏览器生产商给浏览器留了一个后面,程序员编写一段脚本代码,客户端只需要下载一次,就可以给浏览器开发出新的功能。
<2>脚本语言
插件引发了脚本语言(scripting language)的开发,你可以将脚本程序插入HTML中,解释这种语言的插件在HTML页面被显示时自动激活。
如果你期待一种脚本语言web浏览器不需要任何插件就可以得到支持,那么它非JavaScript莫属。
但是JavaScript能力有限,仍然有其不能解决的问题,java就是用来解决这些硬骨头的(并发,数据库,网络编程)。
java通过applet以及使用java web start来进行客户端编程。
applet是只在浏览器中运行的小程序,它是作为网页的一部分而自动下载的(就像网页中的图片一样)。
当applet被激活后,它便执行一个任务,它提供了一种分发软件的方式,使得程序员只需要创建单一的程序,而只需要计算机拥有浏览器,且浏览器具有内置的java解释器,那么这个程序就能在浏览器上自动运行。
第二章 一切都是对象
2.1 用引用操纵对象
每种编程语言都有自己的操纵内存中元素的方式,或者直接操作元素,或者基于特殊的语法的间接表示(例如c里的指针)。
在java中一切都被视为对象,但操作的标识符实际上是对象的一个引用。
2.2必须由你创建所有对象
当我们创建了一个引用,我们希望它和一个对象相关联,通常用new关键字实现
2.2.1存储到什么地方
我们有5个地方可以存储数据
2.3永远不需要销毁对象
2.3.1基本变量和引用变量的作用域
{
String s = new String(“a string”);
}
java的作用域,和c语言一样,花括号就是作用域,在花括号里的基本变量和引用变量(例如s,s存储的是new出的对象的位置)只能用到花括号结束之前。
2.3.2对象的作用域
对象的作用域和基本变量是不同的。
如上,当代码运行到花括号结尾,引用变量s不在起作用了,但是s指向的对象仍然存在于堆中。java中,new创建的对象,只要需要,就会一直存在,当垃圾回收器检测到这个对象不再被需要时,就会释放这个对象的空间。
你只需要创建你的对象,一旦不需要,它就会自动消失。
2.4类
一个类有数据成员和方法。
数据成员
数据成员可以是引用变量或者基本变量。若某个成员变量是基本数据类型,java会赋给它默认值。但是在方法中,如果一个变量没有初始化,不会默认初始化的,而且会报错的。
成员变量随着对象的创建,和对象捆绑着产生在堆中。静态变量是类的属性,存储在方法区。
方法
方法名和参数列表合起来称为方法签名,可以唯一得标识出这个方法。
java中的方法只能作为类的一部分来创建,方法只有通过对象才能被调用。一切都是对象。
存储在方法区。
2.6.1类库的起名方式
使用程序员的域名的倒写的小写。
haidong556.top就是top.haidong556.utility.foibles
2.6.2导入类库
import java.util.ArrayList;
2.6.3 static关键字
当创建类时,只是在描述这个类的外形和行为,除非用new来创建这个类,否则是不会得到任何对象的,执行new时,对象的空间才被分配,方法才能被外界调用。
如果希望即使没有对象,类的方法也可以被调用。
如果希望为特定域分配单一存储空间,而不与其创建的对象数量关联,所有对象公用一个类的存储空间。
我们就需要创建static关键字。
2.8 使用javadoc生成注释文档
Javadoc通过提取注释,生成html格式的程序文档。·
3.7和equals()方法
基本数据类型通过判断相等。
对象,引用数据类型通过重写equals()方法判断相等。
3.8.1 短路
当使用逻辑运算符时,&&,||,一旦能够准确无误地确定整个表达式的值,就不再计算余下部分了。
eg. test(1)&&test(2)&&test(3);如果test(1)是假,则直接是假。
test(1)||test(2)||test(3); 如果test(1)是真,则直接是真。
3.9直接常量
向编译器表明字面量的类型。
后缀字符代表了数据类型。
100L 代表long类型数据
100F 代表flout类型数据
100D 代表Double类型数据
十六进制以0x开头
0x100
八进制以0开头
0100
3.11 移位操作符
Java中有“有符号右移“(>>)和”无符号右移“(>>>)
对char,byte,short类型的数据右移,会先将数据转换为int类型,再进行右移,然后截断数据。
3.14 Java不会自动窄化转换
while(x = y){
//…
}
对于c语言,这个循环会一直循环,而由于java不会自动将int类型转换为布尔值,所以会在编译时报错。
3.16 java中没有sizeof
因为所有的数据类型在所有机器中的大小是相同的,所有我们不用去考虑。
第五章 初始化与清理
5.1用构造器确保初始化
构造器就是类名,没有返回值,因为构造不需要返回。
在java中,“初始化“和”创建“捆绑在一起,两者不能分离。
5.2方法重载
在一个类中,参数列表不同而方法名相同,就是方法重载。
为了让方法名相同而参数列表不同的构造器同时存在,就必须用到方法重载。
5.2.1 区分重载方法
重载方法的名字相同,Java如何才能知道你指的是哪个呢?这就需要我们使用独一无二的参数列表。
5.2.2 涉及基本类型的重载
5.3默认构造器
如果你写的类中没有构造器,则编译器会自动给你创建一个默认构造器。
但如果你写了一个有参的构造器,则编译器就不会给你再创建一个默认无参构造器了。编译器会认为:“啊,你已写了一个构造器,所有你知道你在什么,你是刻意省略了默认构造器。“
5.3 this关键字
当对象的方法调用时,会暗自把对象作为参数传入方法,我们可以在方法中用this关键字调用对象,常使用return this;来返回对象实例。
5.4 static关键字
了解this之后,我们将可以更好地理解static方法。
static方法就是编译器没有将对象作为参数暗自返回的方法,所以在static内部不能调用非静态方法。反过来却是可以的。
有些人认为static方法不是“面向对象“的,因为它们具有全局函数的语义,使用static方法时,由于不存在this,所以不是通过”向对象发送消息“的方式来完成的。的确,要是在代码中出现了大量的static方法,就该重新考虑自己的设计了。
5.5 清理:终结处理和垃圾回收
1.何时需要手动使用finalize方法清理----尽量不去使用
当你的对象(并非使用new)获得了一块“特殊“的内存区域(数据库连接,打开文件资源,或者调用非Java方法(native方法)比如c的malloc()方法,由于垃圾回收只知道释放那些通过new分配的内存,所以它不会去回收这块内存。为了应对这种情况,java允许在类中定义一个名为finalize()的方法。
finalize工作原理:一旦垃圾回收器第一次准备回收对象时,就会调用其finalize方法,而不会回收对象,只有再下一次调用finalize方法时才会回收。
垃圾回收不能保证发生,除非内存濒临用完,具有不确定性。
如果程序执行结束,就像c语言malloc调用的空间一样,Java对象占用的空间资源也会还给操作系统。
5.5.4垃圾回收器工作原理
5.5.4.1引入:引用计数的垃圾回收原理
每个对象都有一个引用计数器,当有引用接至对象后,这个对象的计数器加一,当计数器为0时,删除对象。这个方法有一个很大的问题:如果两个对象互相引用,那么这俩对象的计数器永远的不会为0,永远不会被删除,就是这个问题导致java不去使用引入计数的垃圾回收机制。
5.5.4.2使用根追溯用来确定是否该删除
依据的思想:对于“活“的对象,一定能够追溯到其存活在堆栈或者静态存储区之中的引用。我们只需要遍历堆栈和静态存储区的所有引用,追踪它们的对象,然后是它们的对象所包含的对象,直到所有根植于堆栈和静态存储区的所有引用所形成的网络全部都被访问为止。这样就解决了”交互自引用的对象组“的问题了。
5.4.4.3三种垃圾回收方式
一.停止-复制(stop-and-copy):大量垃圾时使用
先停止程序,然后将堆中所有活的对象复制到另一个堆(块)中,没有复制的都是垃圾,在新堆中对象是一个挨着一个存储的。在这个过程中,我们需要修改,原先指向旧堆中的对象的引用,位于堆栈和静态存储区的引用可以直接被修改,但还有其他指向这些对象的引用,通过建立一个表格,将旧对象映射到新对象上,如果我们使用到了旧对象,就来查表,从而找到新对象。
缺点:效率低,内存需求大,有时没有产生垃圾或者很少,却也需要重新复制一遍。
二.标记-清理(mark-and-sweep)少量垃圾时使用
先使用根追溯遍历一遍并进行每个对象的引用数量标记,标记过程结束后,清理开始,将没有标记的对象都删除。
缺点:得到的是不连续的空间。
自适应垃圾回收策略
Java虚拟机中内存被分配为较大的“块“为单位,在停止-复制策略中,将旧对象复制到空闲的块中。内含大的对象的块不会被复制,只有那些小对象的块会被复制整理,当所有对象都很稳定时,Java虚拟机就会切换回”标记-清理“模式。
5.6成员初始化
方法中的局部变量必须进行赋值,负责会报错。
类中的基本类型的成员变量都会自动分配一个初始值(0,null)。
类中的引用变量需要程序员去初始化,负责就是null.使用时会报错。
5.6.1模板中的指定初始化
类成员变量可以直接赋值,成为类的模板值。
5.7构造器初始化
在类加载过程中的准备阶段给静态变量初始化,类加载过程中的初始化过程给成员变量初始化,而只有创建对象时才会使用构造器去初始化。
构造器的初始化可以覆盖类加载过程中的初始化。
5.7.2静态变量的初始化
无论创建多少个对象,静态变量只占据一份存储区域。
如果之前创建了(类加载过程中)静态变量的存储区域,之后就不会再初始化static了,因为类已经加载了,不会再进行类加载的准备阶段的静态变量初始化了。
5.7.3初始化总结
当JVM预测到要使用某个对象时,就会进行这个class文件的类加载,在类加载的准备阶段会将静态变量初始化,初始化阶段将会将成员变量初始化。当使用new创建对象时,虚拟机会给对象在堆上分配足够的空间,并且把这块空间清零,这就将对象的基本数据类型设置为0,而引用数据类型设置为null,然后进行初始化,最后进行构造器构造。
5.7.4成员变量的初始化
成员变量在类加载中的初始化初始化,是初始化了类的成员变量,其作为一个模板,对象初始化时参考这个类来进行初始化
5.8 数组初始化
数组是传递引用,只有一份数据在内存中存储。
5.9枚举类型——enum关键字
枚举是常量,用大写字母命名。
使用.ordinal()方法,查看声明顺序。
第六章 访问控制权限
不能改变的内容我们想要不让客户端程序员使用,而可以改变的内容我们希望客户端程序员使用,通过访问权限的设置,我们就可以实现这一功能。
6.1包:库单元
使用ArrayList的类,一种方法是使用全名:java.util.ArrayList
这使得程序变得冗长,我们可以导入该类:import java.util.*;这样我们¬¬¬¬¬就可以直接调用ArrayList类。
当编写一个Java源代码文件时,此文件通常被称为编译单¬¬元,这个编译单元已.java做为后缀名,这个编译单元内只有一个public类,该类的名称必须和编译单元名字相同。
java通过打包生成多个.class文件,通过Java解释器来解释。
6.1.2创建独一无二的包名
既然一个包并不是一个单一的文件,并且一个包由许多.class文件构成的,为了方便整理,我们可以将特定包的所有.class文件置于一个目录下。
如果我们需要发布我们的包,我们为了避免和网络上的其他包冲突,我们使用自己的域名的反写作为包名。
Java解释器的运行过程
首先找出环境变量CLASSPATH,CLASSPATH包含一个或多个目录,用作查找.class文件的根目录。从根目录开始,解释器将包的名称中的每个句点变为反斜杠,从而找到.class文件。
6.2Java访问权限修饰符
默认权限:包内
public:公有
private:类内
protected:包内,继承子类内。
第七章 复用类
有俩种方法,实现复用代码
第一种:在新类中产生现有类的对象,由于新类是由现有类的对象组成,所有这种方法称为组合。“has-a”是组合
第二种:继承。“is-a”是继承
7.1组合语法
7.2继承语法
可以为每个类创建一个public static void main()方法,用来测试这个类。
7.2.1初始化基类
默认在导出类的构造第一行调用无参基类构造,只有主动声明参数构造才会调用有参构造。
无参的基类构造器会在导出类的构造器的第一行隐式地调用。
对象使用构造器构造的过程是从基类为起点向导出类扩散的,所以基类在导出类构造器可以访问它之前,就已经完成了初始化。
带参数的基类构造器的调用:在导出类的构造器的第一行使用。
7.4.2重写
在导出类中重写基类的方法,并使用@Override进行注解。
7.7继承或者组合
如果必须向上转型,则继承是必须的;如果不需要,尽量使用组合。
7.8 final关键字
final经常指的是“这是不可改变的”。
final static基本类型用大写字母表示,并且字与字之间用下划线隔开。
一个final static的域只占据一段不能改变的存储空间。
final修饰变量,如果是基础数据变量,用final修饰其值不能改变,如果是引用对象数据,其引用的对象不能改变。
final修饰参数,Java允许在参数列表中以声明的方式将参数指明为final。这意味着你无法在方法中更改参数引用所指向的对象。这一特征主要用来向匿名内部类传递参数。
final修饰类,类不能继承
final修饰方法——禁止重写
使用final修饰方法出于俩种原因,第一是把方法锁定,以防任何继承类重写。第二是出于效率原因。如果将一个方法指明为final,那么就是同意编译器将针对该方法的所以调用的转为内嵌调用(但是这是落后的技术,JVM已经优化掉了,不需要去使用了)。所以现在只有想要明确禁止覆盖时才会将方法设置为final。
7.9类加载什么时候加载
类只会加载一次。类的class文件通常发生在第一次创建对象时,但是当访问static域或static方法时,也会发生类加载。
第八章 多态
静态方法和成员变量,编译看左边,运行看左边,声明决定。
非静态方法,编译看左边,声明决定,运行看右边,赋值决定。
List list=new LinkedList<>();
list.addFirst();//此时这条语句会爆红,显示没有这个方法,这就是编译看左边。
运行时候就看右边的虚方法表了(其中如果有覆盖)。
eg. (左边)Animal cat = new (右边)Cat();
声明决定了虚拟机认为这个对象的类型,但是其链接(指向的)的虚方法表(方法区的类属性)却是真实的类的类型的,可能已经实现了覆盖了。
方法具有多态,成员变量的访问没有,静态与多态无关。
8.1向上转型和忘记对象类型
接受基类作为参数,而不是那些特殊的导出类,即不管导出类的存在,只是与基类打交道,这就是多态。
8.2.1方法调用绑定
将一个方法同一个方法主体关联起来被称作绑定。若在程序执行前进行绑定,比如c,称为前期绑定。而在多态中,是在运行时根据对象的类型进行绑定的,称为后期绑定,也叫做动态绑定。
Java中除了static和final方法之外(private方法属于final方法),其他所有的方法都是后期绑定,final是前期绑定,是内嵌调用。
8.2.2产生正确的行为
只告诉对象消息,由对象决定使用那部分代码,由对象决定去做哪件事情。
8.2.3可扩展性
通过多态的基类接口,我们可以给方法添加新的对象。
多态是一种让程序员“将改变的事物和未变的事物分离开”的重要技术,我们添加新的对象不会影响原有的对象的实现方式。
5.2.4 缺陷:无法“覆盖”私有方法
private方法被自动认为是final方法(可以这样认为),所以无法被重写。并且,private方法对导出类不可见。
5.2.5多态中的域
子类对象创建时,会在一块内存上初始化一个父类的域和一个子类的域,当这个对象使用父类的引用时,显示的域是父类的域,当使用子类的引用指向这个对象时,显示的是子类的域。
但是这个不重要,因为我们会把域设置为private,转而用方法去访问,而方法是后期绑定的,所以可以确定地访问到父类或者子类的域。
5.2.6静态:静态与多态无关
多态是对象之间的关系,静态在类导入时已经构建好,是类的性质。
使用类名来调用静态方法和域。
8.3构造器和多态
构造器是默认static方法,并不具有多态性。
8.3.1构造器的调用顺序-先调用基类,再调用导出类
如果为了给基类传递参数,想要主动调用基类的构造器,就在导出类的主体的首行调用。如果没有主动调用,那么就会在导出类的构造器调用之前默默调用所以的基类的构造器,从基类到导出类逐层调用。
先调用基类构造器,再构造导出类的原因:
第九周 接口
9.1抽象类和抽象方法
通用接口建立起一种基本形式,以此表示所有导出类的共同部分。
我们创建抽象类是希望通过这个通用接口操作这一系列类。
包含抽象方法的类必须被限定为抽象类。
9.2接口interface
接口表示:“所有实现了该特定接口的类看起来都是这样的。”
接口也可以包含域,但是这些域隐式地是static和final的,应该使用大写字母命名。
要让一个类遵循某个特定接口,需要使用implements关键字,它表示:“interface只是它的外貌,现在我要声明它是如何工作的。”
第十章 内部类
将一个类的定义放在另一个类的定义的内部,就是内部类。
第十一章,持有对象
容器的核心特点:自动扩容
为什么要使用容器来持有对象?
如果一个程序只包含数量固定并且生命周期都是已知的的对象,那么这是一个简单的程序,就不需要考虑容器了。但是,通常,程序只有在运行中符合某些条件才去创建对象,这些对象的数量和生命周期是不确定的,此时我们就需要使用容器了。
所以使用容器的其一条件就是,程序会在过程中创建对象。
在程序运行中,我们可以将任意数量的对象放置到容器中,并且不用担心容器应该设置多大,因为容器都能自动扩容。
11.1泛型和类型安全的容器
ArrayList可以当作“可以自动扩充自身尺寸的数组”来对待。
我们可以将不同类型的对象放入一个容器中,但是我们取出对象时需要我们强制转换,所以我们通常使用ArrayList来存储一类的对象,此时向里放入Orange对象就会在编译时报错。
11.2基本概念
容器的用途就是保存对象,将其划分为两个不同的概念: