Thinking in Java 第1章:对象入门
面向对象编程(OOP)
所有编程语言的最终目的都是提供一种“抽象”方法。面向对象的程序设计是一大进步,我们将问题空间中的元素以及它们在方案空间的表示物称作“对象”(Object)。所有东西都是对象。程序是一大堆对象的组合。每个对象都有自己的存储空间,可容纳其他对象。每个对象都有一种类型。一个类最重要的特征就是“能将什么消息发给它?”。 同一类所有对象都能接收相同的消息。
我们向对象发出的请求是通过它的“接口”(Interface)定义的,对象的“类型”或“类”则规定了它的接口形式。“类型”与“接口”的等价或对应关系是面向对象程序设计的基础。接口定义了一类对象能接收的信息,做哪些事情。
“接口”(Interface)规定了可对一个特定的对象发出哪些请求。在某个地方存在着一些代码,以便满足这些请求。这些代码与那些隐藏起来的数据便叫作“隐藏的实现”。“接口”(Interface)重要一点是让牵连到的所有成员都遵守相同的规则。进行访问控制,第一个原因是防止程序员接触他们不该接触的东西。第二个原因是允许库设计人员修改内部结构,不用担心它会对客户程序员造成什么影响。一个继承的类可访问受保护的成员,但不能访问私有成员。
为重复使用一个类,最简单的办法是仅直接使用那个类的对象。但同时也能将那个类的一个对象置入一个新类。我们把这叫作“创建一个成员对象”。新类可由任意数量和类型的其他对象构成。这个概念叫作“组织”——在现有类的基础上组织一个新类。有时,我们也将组织称作“包含”关系,比如“一辆车包含了一个变速箱”。
新类的“成员对象”通常设为“私有”(Private),使用这个类的客户程序员不能访问它们。这样一来,我们可在不干扰客户代码的前提下,从容地修改那些成员。也可以在“运行期”更改成员,这进一步增大了灵活性。后面要讲到的“继承”并不具备这种灵活性,因为编译器必须对通过继承创建的类加以限制。
轻易的运用继承是非常笨拙的,会大大增加程序的复杂程度。首先应考虑“组织”(类包含)对象;这样做得更加简单和灵活。利用对象的组织,我们的设计可保持清爽。
在Java语言中,继承是通过extends关键字实现的 使用继承时,相当于创建了一个新类。这个新类不仅包含了现有类型的所有成员(尽管private成员被隐藏起来,且不能访问),但更重要的是,它复制了基础类的接口。也就是说,可向基础类的对象发送的所有消息亦可原样发给衍生类的对象。若只是简单地继承一个类,并不做其他任何事情,来自基础类接口的方法就会直接照搬到衍生类。这意味着衍生类的对象不仅有相同的类型,也有同样的行为,这一后果通常是我们不愿见到的。
改变基础类一个现有函数的行为。我们将其称作“改善”那个函数。
继承只改善原基础类的函数,衍生类型就是与基础类完全相同的类型,都拥有完全相同的接口,此时,我们通常认为基础类和衍生类之间存在一种“等价”关系;但在许多时候,我们必须为衍生类型加入新的接口元素,所以不仅扩展了接口,也创建了一种新类型。我们将其称作“类似”关系;
通常,继承最终会以创建一系列类收场,所有类都建立在统一的基础接口上。
对这样的一系列类,我们可以将衍生类的对象当作基础类(完全相同)的一个对象对待。
把衍生类型(子类)当作它的基本类型(父类)处理的过程叫作“Upcasting”(上溯造型)。在面向对象的程序里,通常都要用到上溯造型技术。这是避免去调查准确类型的一个好办法。
将一条消息发给对象时,如果并不知道对方的具体类型是什么,但采取的行动同样是正确的,这种情况就叫作“多形性”(Polymorphism)(多态性)。对面向对象的程序设计语言来说,它们用以实现多形性的方法叫作“动态绑定”。编译器和运行期系统会负责对所有细节的控制;我们只需知道会发生什么事情,而且更重要的是,如何利用它帮助自己设计程序。
设计程序时,我们经常都希望基础类只为自己的衍生类提供一个接口,而不是实际创建基础类的一个对象,为达到这个目的,需要把那个类变成“抽象”的——使用abstract关键字。
亦可用abstract关键字描述一个尚未实现的方法,指出:“这是适用于从这个类继承的所有类型的一个接口函数,但目前尚没有对它进行任何形式的实现。抽象方法也许只能在一个抽象类里创建。继承了一个类后,那个方法就必须实现,否则继承的类也会变成“抽象”类。
interface(接口)关键字将抽象类的概念更延伸了一步,它完全禁止了所有的函数定义。“接口”是一种相当有效和常用的工具。另外如果自己愿意,亦可将多个接口都合并到一起(不能从多个普通class或abstract class中继承)。
备注:这是一段个人的总结:
interface(接口)更倾向于表明这个类是能干什么事,也就是说这类的类实现了哪些方法及继承与此接口的类必需要实现哪些方法;接口更像一个规范文件(协议)。
abstract(抽象)更倾向于表明一类类的共有特性,由基础类或一个接口衍生的一系列类的共有特性,所以不仅仅表明这个类能干哪些事。
也就是说 interface(接口)的方法都可以在接口中无实现,只规定了子类必须需要实现哪些方法,而 abstract(抽象)可以把子类相同实现的方法在抽象类里实现,子类各自特别且必须的方法用抽象方法规定然后在子类里各自实现。
abstract(抽象)可以看为一种跟 public、static 同级别的修饰符(修饰了类(class)和方法),而 interface(接口)可以看为一种跟 class 同级别的修饰符,标志着质的改变。
interface(接口)可以说是对 abstract(抽象)的抽象。
interface(接口)帮助了对象的分层,各组件之间的松耦合。
interface 语法要求:
1、接口可以包含:静态常量、方法(抽象)、内部类(接口、枚举)、默认方法(类方法)
2、接口只可以继承多个父接口(之间用英文逗号“,”隔开),不能继承类。
3、接口修饰符可以是 public 或者省略,如果省略了 public 访问控制符,则默认采用包权限访问控制符,即只有相同包结构下才可以访问该接口。
4、接口内的所有成员都是 public 访问权限,可省略,如果指定则只可使用 public 访问修饰符。
5、接口的成员变量均为静态常量,不管是否使用 public static final 修饰符都认为只可做如此修饰。
6、接口的方法只能是抽象、类方法、默认方法,如果不是默认方法,系统将自动为普通方法增加 public abstract 修饰符,且不能有方法实现(方法体),类方法、默认方法必须有方法实现(方法体)。
7、定义默认方法,需要用 default 修饰且默认添加 public 修饰 ,默认方法不能用 static 修饰,所以不能直接用接口来调用默认方法,由接口的实现类的实例来调用这些默认方法。
8、类方法需用 static 修饰 ,可用接口直接调用。
9、接口的内部类(接口、枚举)默认采用 public static 修饰符。
10、接口可看做为特殊的类,受限于一个源文件只能有一个 public 修饰。
11、接口不能创建实例,作为声明引用类型变量时此接口必须有其实现类。
abstract 语法要求:
1、有抽象方法的类只能是抽象类
2、抽象类里可没有抽象方法有普通方法
3、抽象类不能被实例化
4、抽象类可以包含:成员变量、方法(普通、抽象)、构造器、初始化快、内部类(接口、枚举)、抽象类的构造器不能用于创造实列,主要用于被其子类调用
5、抽象不能修饰变量(成员、局部),不能修饰构造器,抽象类里的构造器只能是普通的构造器。
总结如下:
1、抽象类与普通类相比“一得一失”:1、“得”可包含抽象方法。2、“失”无法被实列化。
2、抽象类是用来被它的子类继承的,方法由子类实现。而 final 修饰的类不能被继承, final 修饰的方法不能被重写,所以 final 和 abstract 是互斥的。
3、static 修饰方法是属于这个类本身的(类Class和实列Object的区别),如果该方法被定义成抽象方法,通过类调用的时候也会出错,因为调用了一个没有方法实体的方法,所以 static 和 abstract 是互斥的(非绝对,可以同时修饰内部类)。
4、abstract 修饰的方法必须被之类重写才有意义,因此 abstract 方法不能是私有的,所以 private 和 abstract 是互斥的。
从技术角度说,OOP(面向对象程序设计)只是涉及抽象的数据类型、继承以及多形性,但另一些问题也可能显得非常重要。
最重要的问题之一是对象的创建及破坏方式。对象需要的数据位于哪儿,及对象的“存在时间”。
“继续器”(Iterator),它属于一种对象,负责选择集合内的元素,并把它们提供给继承器的用户。它存在于所有集合中
不同的集合在进行特定操作时往往有不同的效率。
所有类最终是从单独一个基础类继承(Object)。他们最终都属于相同的类型。
所有对象都在内存堆中创建,可以极大简化参数的传递。
可以更方便地实现一个垃圾收集器。
由于运行期的类型信息肯定存在于所有对象中,所以永远不会遇到判断不出一个对象的类型的情况。这对系统级的操作来说显得特别重要,比如违例控制;
为了使这些集合能够重复使用,或者“再生”,Java提供了一种通用类型,以前曾把它叫作“Object”。所以容纳了Object的一个集合实际可以容纳任何东西。这使我们对它的重复使用变得非常简便。
但由于集合只能容纳Object,所以在我们向集合里添加对象句柄时,它会上溯造型成Object,这样便丢失了它的身份或者标识信息。再次使用它的时候,会得到一个Object句柄,而非指向我们早先置入的那个类型的句柄。
(↑↑↑↑↑解释上面两个段落:为了复用将集合元素的类型设置为Object,当向集合中添加元素的时候会因为 Upcasting 而丢失 元素的实际类型,以至于无法调用元素的实际有用接口)
我们再次用到了造型(Cast)。下溯造型成一种更“特殊”的类型。这种造型方法叫作“下溯造型”(Downcasting)。
在从一个集合提取对象句柄时,必须用某种方式准确地记住它们是什么,以保证下溯造型的正确进行。
我们可以采用“参数化类型”。
(↑↑↑↑↑解释上面段落:集合泛型的原因)
每个对象都要求资源才能“生存”,其中最令人注目的资源是内存。如果不再需要使用一个对象,就必须将其清除,以便释放这些资源,以便其他对象使用。
问题1:如何才能知道什么时间删除对象呢?
垃圾收集器“知道”一个对象在什么时候不再使用,然后会自动释放那个对象占据的内存空间。
2.垃圾收集器对效率及灵活性的影响
代价就是运行期的开销。
我们不能确定它什么时候启动或者要花多长的时间。这意味着在Java程序执行期间,存在着一种不连贯的因素。所以在某些特殊的场合,我们必须避免用它——比如在一个程序的执行必须保持稳定、连贯的时候。
错误必然发生
它们严重依赖程序员的警觉性
“违例控制”将错误控制方案内置到程序设计语言中,有时甚至内建到操作系统内。这“违例控制”将错误控制方案内置到程序设计语言中,有时甚至内建到操作系统内。
违例不能被忽略,“掷”出的一个违例不同于从函数返回的错误值,那些错误值或标志的作用是指示一个错误状态,是可以忽略的。
注意违例控制并不属于一种面向对象的特性
在计算机编程中,一个基本的概念就是同时对多个任务加以控制。许多程序设计问题都要求程序能够停下手头的工作,改为处理其他一些问题,再返回主进程。
要求将问题划分进入独立运行的程序片断中,使整个程序能更迅速地响应用户的请求。在一个程序中,这些独立运行的片断叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理”。
最开始,线程只是用于分配单个处理器的处理时间的一种工具。但假如操作系统本身支持多个处理器,那么每个线程都可分配给一个不同的处理器,真正进入“并行运算”状态。
从程序设计语言的角度看,多线程操作最有价值的特性之一就是程序员不必关心到底使用了多少个处理器。程序在逻辑意义上被分割为数个线程;假如机器本身安装了多个处理器,那么程序会运行得更快,毋需作出任何特殊的调校。
一个问题:临界资源!一些支持共享,不支持并行的资源,需要在线程使用期间必须进入锁定状态。(比如“屏幕”是个共享资源,但是不能同时播放两个画面。)
Java中对多线程处理的支持是在对象这一级支持的,所以一个执行线程可表达为一个对象。Java也提供了有限的资源锁定方案。它能锁定任何对象占用的内存(内存实际是多种共享资源的一种),所以同一时间只能有一个线程使用特定的内存空间。为达到这个目的,需要使用 synchronized 关键字。
Java8移除永久代
- (1) 对象是什么?(怎样将自己的项目分割成一系列单独的组件?)
- (2) 它们的接口是什么?(需要将什么消息发给每一个对象?)
整个过程可划分为四个阶段:
-
阶段0:拟出一个计划、
-
阶段1:要制作什么?
- 在上一代程序设计中(即“过程化或程序化设计”),这个阶段称为“建立需求分析和系统规格”。
- 最有价值的工具就是一个名为“使用条件”的集合。
-
阶段2:如何构建?
- 此时可考虑采用一种特殊的图表工具:“统一建模语言”(UML)。
- 包含的各类对象在外观上是什么样子,以及相互间是如何沟通的。
-
阶段3:开始创建
-
阶段4:校订