Java编程思想 读书笔记(更新中)
更新日志
- 8.4 1-2章
- 8.6 3-4章
Ch1 对象导论
面向对象程序设计(Object-oriented Programming)
1.1 抽象过程
Alan Kay总结了第一个成功的面向对象语言、同事也是Java所基于的语言之一的Smalltalk的五个基本特性:
- 万事万物姐对象
- 程序是对象的集合,它们通过发送消息来告知彼此所要做的
- 每个对象都有自己的由其他对象所构成的存储
- 每个对象都拥有其类型
- 某一特定类型的所有对象都可以接收同样的消息
1.2 每一个对象都有一个接口
- 面向对象程序设计的挑战之一,就是在问题空间的元素和解空间的对象之间创建一对一的映射
- 接口确定了对某一特定对象所能发出的请求
1.3 每个对象都提供服务
- 当试图开发或者理解一个程序设计时,最好的方法之一就是将对象想象为“服务提供者”
- 将对象看作是服务提供者还有一个附带的好处:有助于提高对象的内聚性
- 在良好的面向对象设计中,每个对象都可以很好的完成一项任务,但是它并不试图做更多的事
1.4 被隐藏的具体实现
- public:表示紧随其后的元素对任何人都是可用的
- private:表示除类型创建者和类型的内部方法之外的任何人都不能访问
- protected:与private作用相当,差别仅在于继承的类可以访问protected成员但是不能访问private成员
- 默认访问权限:当没有使用前面的访问指定词时,它将发挥作用,这种权限通常被称为包访问权限。在这种权限下,类可以访问在同一个包(库构件)中的其他类的成员;但是在包之外,这些成员就如同指定了private一样
1.5 复用具体实现
- 代码复用是面向对象程序设计语言所提供的最了不起的优点之一
- 最简单的复用某个类的方式就是直接使用瓜类的一个对象,此外也可以将那个类的一个对象置于某个新的类中。我们称其为“创建一个成员对象”。新的类可以由任意数量、任意类型的其他对象以任意可以实现新的类中想要的功能的方式所组成。因为是在使用现有的类合成新的类,所以这种概念被称为组合(composition),如果组合是动态发生的,那么它通常被称为聚合(aggregation)
1.6 继承
- 当父类发生变动时,被修改的子类也会反映出这些变动
- 有两种方法可以使基类与导出类产生差异:
- 在导出类中添加新的方法
- 重写父类方法(overriding)
- “is - a” / “is - like - a”
1.7 伴随多态的可互换对象
- 一个非面向对象变成的编译器产生的函数调用会引起所谓的前期绑定,这么做意味着编译器将产生对一个具体函数名字的调用,而运行时将这个调用解析到将要被执行的代码的绝对地址
- 但是在OOP中,程序直到运行时才能够确定代码的地址,为了解决这个问题,OOP使用了后期绑定的概念,当向对象发送消息时,被调用的代码知道运行时才能确定。编译器确保被调用的方法存在,并对调用参数和返回值执行类型检查,但是并不知道将被执行的确切代码
- 为了执行后期绑定,Java使用一小段特殊的代码来替代绝对地址调用。这段代码使用在对象存储的信息来计算方法体中的地址
- 在一些语言中,必须明确的声明希望某个方法具备后期绑定属性所带来的灵活性(如C++是使用virtual关键字),但是Java中动态绑定是默认行为
1.8 单根继承结构
- 所有的类都继承自单一的基类 - Object (除了C++以外的所有OOP语言)
- 单根继承的好处:
- 另一种结构能够更好的适应C模型,但是当要进行完全的面向对象程序设计时,则必须构建自己的继承体系,使得它可以提供其他OOP语言内置的便利。并且在所获得的任何新类库中,总会用到一些不兼容的接口,这就需要花力气(可能还需要通过多重继承)来使新接口融入你的设计中。
- 单根继承结构能够保证所有对象都具备某些功能
- 单根继承结构使得垃圾回收器的实现变得容易许多,由于所有对象都保证具有其类型信息,因此不会因无法确定对象的类型而陷入僵局
1.9 容器
如果不知道在解决某个特定问题时需要多少个对象,或者它们将存活多久,那么就要将它们存储在容器中
List、Map、Set等
- List:用于存储序列
- Map:也被称为关联数组,用来建立对象之间的关联
- Set:每种对象类型只有一个
ArrayList 和 LinkedList的区别
- 访问元素,ArrayList中是一个花费固定时间的操作,而LinkedList则需要在列表中移动,访问越靠近表尾的元素花费的时间越长
- 插入元素,LinkedList的开销比ArrayList小
- 两者都实现了Serializable Clonable Iterable\
1.10 对象的创建和生命期
- C++认为效率控制是最重要的议题,为了追求最大的执行速度,对象的内存空间和生命周期可以在编写程序时确定,这可以通过将对象置于堆栈或静态存储区域内来实现
- 另一种方式是,在被称为堆(heap)的内存池中动态地创建对象。在这种方式中,直到运行的时候才知道需要多少对象,它们的生命周期如何,以及他们的具体类型是什么,这些问题的答案只能在程序运行时相关代码被执行到的那一刻才能确定。如果需要一个新对象,可以在需要的时刻直接在堆中创建。因为存储空间是运行时被动态管理的,所以需要大量的时间在堆中分配存储空间,这可能要远远大于在堆栈中创建存储空间的时间。在堆栈中创建存储空间和释放存储空间通常各需要一条汇编指令即可,分别对应将栈顶指针向下移动和将栈顶指针向上移动。创建堆存储空间的时间依赖于存储机制的设计
- 动态方式有这样的一个一般性的逻辑假设:对象趋向于变得复杂,所以查找和释放存储空间的开销不会对对象的创建造成重大冲击
- Java完全采用了动态内存分配方式,每当想要创建新对象时,就要使用new关键字来构造此对象的动态实例
- 对象生命周期,对于允许在堆栈上创建对象的语言,编译器可以确定对象存活的时间,并可以自动销毁它;然而如果是在堆上创建对象,编辑器就会对它的生命周期一无所知。在像C++这样的语言中,必须通过编程的方式来确定何时销毁对象,可能会因为没有正确处理而导致内存泄露;Java则提供了“垃圾回收器”机制,可以自动发现对象何时不再被使用
1.11 异常处理
- 异常处理不是面向对象的特性——尽管在面向对象语言中异常常常被表示为一个对象。异常处理在面向对象语言出现之前就已经存在了
1.12 并发编程
- 最初的方式使用有关机器底层的只是来编写中断服务程序,主进程的挂起是通过硬件中断来触发的
- 线程
1.13 Java与Internet
- Java解决了在万维网上的程序设计问题
- web是什么
- 客户端/服务器计算技术
- web就是一台巨型服务器
- 客户端编程
- 服务器端编程
Ch2 一切都是对象
2.1 用引用操作对象
尽管一切都看做对象,但操纵的标识符实际上是对象的一个“引用”(reference)
String s;
这里创建的只是引用,并不是对象。如果此时向s发送一个消息,就会返回一个错误(一般编译器都会在编写代码的时候就会提示这个错误),这是因为此时s实际上没有与任何事物相关联。正确的做法是:即创建一个引用的同时便进行初始化
2.2 必须由你创建所有对象
有五个不同的地方可以存储数据:
- 寄存器:最快的存储区,位于处理器内部,根据需求进行分配
- 堆栈:位于通用RAM(随机访问存储器)中,通过堆栈指针可以从处理器获得直接支持。堆栈指针若向下移动,则分配新的内存;若向上移动,则释放那些内存
- 堆:一种通用的内存池(也位于RAM中),用于存放所有的Java对象。
- 堆与堆栈不同之处:编译器不需要知道存储的数据在堆里存活多长时间。因此,在堆里分配存储有很大的灵活性。当需要一个对象的时候,只需要new以下,执行这段代码时会自动在堆里进行存储分配。这种灵活性的代价是:用对进行存储分配和清理相比于堆栈需要更长的时间。
- 常量存储:常量值通常直接存放在程序代码内部,这样做是安全的,因为它们永远不会被改变
- 非RAM存储:如果数据完全存活于程序之外,那么他可以不受程序的任何控制,在程序没有运行时也可以存在(把对象转化成可以存放在其他媒介上的事物,在需要时恢复成常规的基于RAM的对象)
- 流对象:对象转化成字节流,通常被发送给另一台机器
- 持久化对象:对象被存放于磁盘上,因此即使程序终止,他们仍可以保持自己的状态
特例:基本类型
new将对象存储在“堆”里,但是像基本类型这样的小的、简单的变量不是很有效。也就是说,不用new来创建,而是创建一个并非是引用的“自动”变量,这个变量直接存储“值”,并置于堆栈中。
基本类型 | 大小 | 最小值 | 最大值 | 包装器类型 |
---|---|---|---|---|
boolean | Boolean | |||
char | 16-bit | Unicode 0 | Unicode 2^16-1 | Character |
byte | 8 bits | -128 | +127 | Byte |
short | 16 bits | -2^15 | +2^15-1 | Short |
int | 32 bits | -2^31 | +2^31-1 | Integer |
long | 64 bits | -2^63 | +2^64-1 | Long |
float | 32 bits | IEEE754 | IEEE754 | Float |
double | 64 bits | IEEE754 | IEEE754 | Double |
void | Void |
Short.MAX_VALUE: 32767
Integer.MAX_VALUE: 10位数
Long.MAX_VALUE: 19位数
Float.MAX_VALUE: 38位数
Double.MAX_VALUE: 308位数
BigInteger / BigDecimal: 任意大小/精度
Java中的数组
- C/C++中数组就是内存快
- Java确保数组会被初始化,而且不能在它的范围之外被访问
2.3永远不需要销毁对象
{
int x = 1; //available: x
{
int y = 2; //available:x,y
}
//avaliable:x
}
{
String s = new String("Hello");
} //s消失,但是s指向的String对象依然存在,占据内存空间,之后会被垃圾回收器回收
2.4 创建新的数据类型:类
基本成员默认值:
基本类型 | 默认值 |
---|---|
boolean | false |
char | ‘\u0000’(null) |
byte | (byte)0 |
short | (short)0 |
int | 0 |
long | 0L |
float | 0.0f |
double | 0.0d |
在使用之前忘了对对象进行赋值,java中是错误,c++是警告
2.7 第一个Java程序
- 编译:
javac Hello.java
- 运行:
java Hello.java
2.8 注释
- javadoc只能为public和protected成员进行文档注释
- private和包内可访问成员的注释会被忽略掉
- 可以用-private进行标记使得private成员的注释也包括在内
Ch3 操作符
自动递增和递减
- 前缀递增和前缀递减(++a、–a):先执行运算,再生成值
- 后缀递增和后缀递减(a++、a—):先生成值,再执行运算
关系操作符
==
和!=
比较的是对象的引用- 比较两个对象的实际内容
equals()
- 基本类型直接使用==/!=
- 自己写的类要重写equals()
逻辑操作符
- 只能用于布尔值上
- 如果在应该使用String值的地方使用了布尔型,那会自动转换为true / false
- 短路:一旦能够明确无误地确定整个布尔表达式的值,就不会再计算表达式余下的部分了
直接常量
- long:加上后缀L / l
- float:加上后缀F / f
- 十六进制数:加上前缀0X / 0x
位操作符
- 与: 1 & 1 = 1, 其余0
- 或: 0 | 0 = 0, 其余1
- 异或: 1 ^ 0 = 1, 0 ^ 1 = 1, 其余0
- 非: ~1 = 0, ~0 = 1
&=、|=、^=都是合法的,~=不合法,因为~是一元操作符
位移操作符
- 运算对象也是二进制的“位”
- 左位移操作符(<<):按照操作符右侧指定的位数将操作符左边的操作数向左移动(在低位补0)
- “有符号”右移操作符(>>):按照操作符右侧指定的位数将操作符左边的操作数向右移动
- 若符号为正,则在高位插入0
- 若符号为负,则在高位插入1
- “无符号”右移操作符(>>>):无论正负,在高位均插入0
三元操作符
boolean-exa? value0: value1;
- 如果boolean-exp(布尔表达式)的结果为true,结果就是value0
- 如果boolean-exp(布尔表达式)的结果为false,结果就是value1
类型转换
- 当float、double转换为整数型是,总是对该数字执行截尾操作
- 四舍五入,使用Math.rount()方法
Ch4 控制执行流程
for-each可用于任何Iterable对象
使用标签
标签只能放在迭代语句之前(标签之后必须是迭代语句)
示例:i:0-10,j:0-10,第一个i*j=20的情况
label: for (int i = 0; i < 10; i++) { for (int j = 0; j < 10; j++) { System.out.println("i = " + i + ", j = " + j + ", i * j = " + i * j); if (i * j == 20) { break label; } } }