ThinkInJava

第 1 章 对象入门

面向对象编程(OOP)具有多方面的吸引力。对管理人员,它实现了更快和更廉价的开发与维护过程。对分析
与设计人员,建模处理变得更加简单,能生成清晰、易于维护的设计方案。对程序员,对象模型显得如此高
雅和浅显。此外,面向对象工具以及库的巨大威力使编程成为一项更使人愉悦的任务。

1.1 抽象的进步

五大基本特征:

  1. 所有东西都是对象
  2. 程序是一大堆对象的组合
  3. 每个对象都有自己的存储空间,可容纳其他对象。
  4. 每个对象都有一种类型
  5. 同一类所有对象都能接收相同的消息

    1.2 对象的接口

    所有对象——尽管各有特色——都属于某一系列对象的一部分,这些对象具有通用的特征和行为。
    在面向对象的程序设计中,尽管我们真正要做的是新建各种各样的数据“类型”(Type),但几乎所
    有面向对象的程序设计语言都采用了“class”关键字。当您看到“type”这个字的时候,请同时想到
    “class”;反之亦然。
    如何利用对象完成真正有用的工作呢?必须有一种办法能向对象发出请求,令其做一些实际的事情,比如完
    成一次交易、在屏幕上画一些东西或者打开一个开关等等。每个对象仅能接受特定的请求。我们向对象发出
    的请求是通过它的“接口”(Interface)定义的,对象的“类型”或“类”则规定了它的接口形式。“类
    型”与“接口”的等价或对应关系是面向对象程序设计的基础。
    例子

    1.3 实现方案的隐藏

    “接口”(Interface)规定了可对一个特定的对象发出哪些请求。然而,必须在某个地方存在着一些代码,
    以便满足这些请求。这些代码与那些隐藏起来的数据便叫作“隐藏的实现”
    Java 采用三个显式(明确)关键字以及一个隐式(暗示)关键字来设置类边界:public,private,
    protected 以及暗示性的friendly(default)。若未明确指定其他关键字,则默认为后者。

“public”(公共)意味着后续的定义任何人均可使
用。

“private”(私有)意味着除您自己、类型的创建者以及那个类型的内部函数成员,其
他任何人都不能访问后续的定义信息。private 在您与客户程序员之间竖起了一堵墙。若有人试图访问私有成员,就会得到一个编译期错误。

“friendly (default)”(友好的)涉及“包装”或“封装”(Package)的概念——
即 Java 用来构建库的方法。若某样东西是“友好的”,意味着它只能在这个包装的范围内使用(所以这一访问级别有时也叫作“包装访问”)。

“protected”(受保护的)与“private”相似,只是一个继承的类可访问受保护的成员,但不能访问私有成员。

1.4 方案的重复使用

为重复使用一个类,最简单的办法是仅直接使用那个类的对象。但同时也能将那个类的一个对象置入一个新类。我们把这叫作“创建一个成员对象”。新类可由任意数量和类型的其他对象构成。无论如何,只要新类
达到了设计要求即可。这个概念叫作“组织”——在现有类的基础上组织一个新类。有时,我们也将组织称
作“包含”关系,比如“一辆车包含了一个变速箱”。

继承的频繁使用会大大增加程序的复杂程度。相反,新建类的时候,首先应考虑“组织”对象;这样做显得更加简单和灵活。利用对象的组织,我们的设计可保持清爽。

1.5 继承:重新使用接口

在 Java 语言中,继承是通过 extends 关键字实现的 使用继承时,相当于创建了一个新类。这个新类不仅包含了现有类型的所有成员(尽管private 成员被隐藏起来,且不能访问),但更重要的是,它复制了基础类的方法。

1.5.1 改善基础类

重写(Override)覆盖继承到的那个方法,那个方法仍然没有放弃。
重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。例如: 父类的一个方法申明了一个检查异常 IOException,但是在重写这个方法的时候不能抛出 Exception 异常,因为 Exception 是 IOException 的父类,只能抛出 IOException 的子类异常。
在面向对象原则里,重写意味着可以重写任何现有方法。

重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
只能重载构造函数
overwrite 重写继承到的那个方法的代码,原方法被放弃
重写与重载之间的区别
区别点| 重载方法| 重写方法
参数列表| 必须修改 |一定不能修改
返回类型 |可以修改| 一定不能修改
异常 |可以修改| 可以减少或删除,一定不能抛出新的或者更广的异常
访问| 可以修改| 一定不能做更严格的限制(可以降低限制)

总结:方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载是一类中多态性的一种表现。

重写与重载

1.5.2 等价与类似关系

等价我们完全能够将衍生类的一
个对象换成基础类的一个对象!可将其想象成一种“纯替换”。在某种意义上,这是进行继承的一种理想方
式。此时,我们通常认为基础类和衍生类之间存在一种“等价”关系

类似新类型拥有旧类型的接口,但也包含了其他函数,所以不能说它们是完全等价的

1.6 多形对象的互换使用

继承最终会以创建一系列类收场,所有类都建立在统一的接口基础上

1.6.1 动态绑定

“多态”(Polymorphic)也叫“动态绑定”(Dynamic Binding)同时也叫“迟绑定”(Late Binding)。

动态绑定是指“在执行期间(而非编译期间)判断所引用对象的实际类型,根据其实际类型调用其相应的方法。”
多态必备的三个条件1、有继承2、有重写3、父类引用指向子类对象
多态

1.6.2 抽象的基础类和接口

abstarct class

interface(接口)关键字将抽象类的概念更延伸了一步,它完全禁止了所有的函数定义。“接口”是一种相当有效和常用的工具。另外如果自己愿意,亦可将多个接口都合并到一起

abstarct class在Java语言中体现了一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在”is a”关系,即父类和派生类在概念本质上应该是相同的。对于interface 来说则不然,并不要求interface的实现者和interface定义在概念本质上是一致的,仅仅是实现了interface定义的契约而已。

1.7 对象的创建和存在时间

java对象需要的数据位于哪儿,如何控制对象的“存在时间”呢?

比如 String bb= new String ();new出来的对象放在堆内存中了,bb这个是局部变量放在栈内存

new出来的都放在heap 堆里

stack 是存放局部变量的

data segment 是存放静态变量和字符串常量的

code segment 是存放代码的

  1. 栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。
  2. 栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据可以共 享,详见第3点。堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要 在运行时动态分配内存,存取速度较慢。

基本类型(primitive types),共有8种,即int, short, long, byte, float, double, boolean,char出于追求速度的原因,就存在于栈中.

栈有一个很重要的特殊性,就是存在栈中的数据可以共享

如Integer, String, Double等将相应的基本数据类型包装起来的类。这些类数据全部存在于中,Java用new()语句来显示地告诉编译器,在运行时才根据需要动态创建,因此比较灵活,但缺点是要占用更多的时间。

基本数据类型,存储在栈中,“abc”存储在栈中,只要是使用new一个对象,则存储在堆中,而且在栈中,在新建一个新的变量或对象时,会首 先看一下是否已经存在,存在则不创建新的。那在栈中的数据是否不涉及垃圾回收呢。那这部分内容,如何处理呢,还有就是,对象的作用域,程序存储在哪儿呢
对于栈中的数据来说,当超过变量的作用域的时候,会消失。哦,明白了,那种栈的形式

实例化后,也就是用了new运算符号,在堆内存建立一个对象,对象是一块区域,里面包含着每个属性

只要是用new()来新建对象的,都会在堆中创建,而且其字符串是单独存值的,即使与栈中的数据相同,也不会与栈中的数据共享。

当比较包装类里面的数值是否相等时,用equals()方法;当测试两个包装类的引用是否指向同一个对象时,用==

java对象需要的数据位于哪儿

一个内存池中动态创建对象,该内存池亦叫“堆”或者“内存堆”

若需一个新对象,只需在需要它的时候在内存堆里简单地创建
它即可。由于存储空间的管理是运行期间动态进行的,所以在内存堆里分配存储空间的时间比在堆栈里创建
的时间长得多(在堆栈里创建存储空间一般只需要一个简单的指令,将堆栈指针向下或向下移动即可)。由于动态创建方法使对象本来就倾向于复杂,所以查找存储空间以及释放它所需的额外开销不会为对象的创建造成明显的影响。

程序员可用两种方法来破坏一个对象:用程序化的方式决定何时破坏对象,或者利用由运行环境提供的一种“垃圾收集器”特性,自动寻找那些不再使用的对象,并将其清除。当然,垃圾收集器显得方便得多,但要求所有应用程序都必须容忍垃圾收集器的存在,并能默许随垃圾收集带来的额外开销。

1.7.1 集合与迭代器

如果事先不知道需要多少个对象,或者它们的持续时间有多长,那么也不知道如何保存那些对象。既然如此,怎样才能知道那些对象要求多少空间呢?事先上根本无法提前知道,除非进入运行期。

在需要的时候,集合会自动扩充自己,以便适应我们在其中置入的任何东西。所以我们事先不必知道要在一个集合里容下多少东西。只需创建一个集合,以后的工作让它自己负责好了。

根据自己的需要选择适当的类型。其中包括集合、队列、散列表、树、堆栈等等

。如果想对集合中的一系列元素进行操纵或比较,而不是仅仅面向一个,这时又该怎么办呢?

办法就是使用一个“迭代器”(Iterator),它属于一种对象,负责选择集合内的元素,并把它们提供给迭代器的用户。

有两方面的原因促使我们需要对集合作出选择。

首先,集合提供了不同的接口类型以及外部行为。堆栈的接口与行为与队列的不同,而队列的接口与行为又与一个集(Set)列表的不同。利用这个特征,我们解决问题时便有更大的灵活性。

其次,不同的集合在进行特定操作时往往有不同的效率。最好的例子便是矢量(Vector)列表(List)的区别。它们都属于简单的序列,拥有完全一致的接口和外部行为。但在执行一些特定的任务时,需要的开销
却是完全不同的。对矢量内的元素进行的随机访问(存取)是一种常时操作;无论我们选择的选择是什么,需要的时间量都是相同的。但在一个链接列表中,若想到处移动,并随机挑选一个元素,就需付出“惨重”
的代价。而且假设某个元素位于列表较远的地方,找到它所需的时间也会长许多。但在另一方面,如果想在序列中部插入一个元素,用列表就比用矢量划算得多。

1.7.2 单根结构

java所有对象是继承于Object

单根结构中的所有对象(比如所有 Java 对象)都可以保证拥有一些特定的功能。

利用单根结构,我们可以更方便地实现一个垃圾收集器

1.7.3 集合库与方便使用集合

参数化类型 Java generic泛型 声明的类型参数在使用时用具体的类型来替换

1.7.4 清除时的困境:由谁负责清除?

如何才能知道什么时间删除对象呢?用完对象后,系统的其他某些部分可能仍然要发挥作用。同样的问题也会在其他大量场合出现

在 Java 中,垃圾收集器在设计时已考虑到了内存的释放问题(尽管这并不包括清除一个对象涉及到的其他方面)。垃圾收集器“知道”一个对象在什么时候不再使用,然后会自动释放那个对象占据的内存空间。采用这种方式,另外加上所有对象都从单个根类Object 继承的事实,而且由于我们只能在内存堆中以一种方式创建对象,所以Java 的编程要比 C++的编程简单得多。

垃圾收集器对效率及灵活性的影响

1.8 违例控制:解决错误

“违例控制”将错误控制方案内置到程序设计语言中,有时甚至内建到操作系统内。这里的“违例”(Exception)属于一个特殊的对象,它会从产生错误的地方“扔”或“掷”出来。随后,这个违例会被设计用于控制特定类型错误的“违例控制器”捕获。

1.9 多线程

Java 也提供了有限的资源锁定方案。它能锁定任何
对象占用的内存(内存实际是多种共享资源的一种),所以同一时间只能有一个线程使用特定的内存空间。为达到这个目的,需要使用synchronized 关键字。其他类型的资源必须由程序员明确锁定,这通常要求程序员创建一个对象,用它代表一把锁,所有线程在访问那个资源时都必须检查这把锁。

volatile

应用场景:检查一个应用执行关闭或中断状态。因为此关键字拒绝了虚拟对一个变量多次赋值时的优化从而保证了虚拟机一定会检查被该关键字修饰的变量的状态变化。

CountDownLatch

应用场景:控制在一组线程操作执行完成之前当前线程一直处于等待。例如在主线程中执行await()方法阻塞主线程,在工作线程执行完逻辑后执行countDown()方法。

synchronized:java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。

一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。

三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。

四、第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。

五、以上规则对其它对象锁同样适用.

1.10 序列化对象有限永久性保存

serilizable