Dec 28, 2010

Thinking in Java 4th

一切皆对象,原生类型除外
对象皆以引用操作
函数皆属于类,并且只能从对象调用(static 函数可以从类调用)

对象存储于堆,引用存储于栈
可以有空引用 null

存储:速度由快到慢
寄存器



原生类存储于栈

原生类占用的内存大小在不同系统中是一致的,因此 Java 具有很好的移植性,也没有 sizeof 函数

array 是保证初始化的。创建对象 array,本质上是创建一个引用 array,并初始化为 null。
原生类 array 初始化为 0.

垃圾指 new 创建的、没被引用的对象。Java 有垃圾回收机制,因此不用手动销毁(destroy)对象(回收内存)。但是必须手动清理对象占用的非内存资源,以及不是操作符 new 分配的内存——调用c/c++程序分配的内存。

scope 用于规定一个引用或原生类的生存时间。引用跳出 scope 被销毁,但它指向的对象却还存在的,直到被 gc 回收。gc 会回收那些由 new 创建的、又没有被引用(引用被销毁了)的对象。

Java 不允许在当前 scope 隐藏更大的 scope 的变量。

成员变量称为 field,成员函数称为 method。

field 保证自动初始化。即使用户没有初始化,则初始化为0。
局部变量不保证初始化。

函数传递为引用,原生类除外。

静态变量可以用对象或类指涉,且最好用类指涉。

java.lang 是默认 import 的。

Javadoc 格式 /** Embed HTML or @docs_tags */

string 的连接用 +

对象皆以引用操作,因此赋值为引用的复制,而不是对象的复制。==和!= 是比较引用,而不是引用的对象。如果要比较引用的对象,要用 equals() method。但是equals的默认行为就是比较引用,因此要覆盖 equals()

Random 类很好,可以产生随机数。nextInt, nextFloat, nextDouble, nextLong

非 boolean 不能进行 boolean 操作。

数值
123L 123l: long
123D 123d: double
123F 123f: float
0X123 0x123: 16进制
0123:8进制
12e3: 12*10^3
转换,double 转为 float 必须显式
会失去精度,必须加后缀,比如 float f = 1e-4f

所有数值都是有符号的,Java 没有无符号数值,但是有无符号位移>>>
<<, >> 为带符号位移

Foreach:
for(item: array),这个格式也可以用于支持 iterable 的对象
注意 string 不是 array,必须转化为 array,toCharArray()

Java 没有goto,但是 break和continue可以跳到某一个 label,实现类似于 goto 的功能。

this:
1. method 都有一个秘密的变量:当前操作的对象的引用,也就是 this。静态方法没有这个变量,因此也不能调用 this。
2. this 还用于将当前对象传递给另一个方法。
3. 构造函数内的 this 有特殊含义,表示调用其他重载的构造函数

garbage collection 的特点
1. 对象并不一定会被垃圾回收
2. 垃圾回收不是析构对象
3. 垃圾回收只和内存有关
若程序执行时,内存始终不紧张,gc 就没有必要;程序完成后,内存整体归还给系统,因此对象不一定会被垃圾回收。Java 只有在执行gc之前才调用对象的finalize,因此 finalize 不一定会被执行。 finalize 不是c++ 的析构函数,Java 程序员必须自己清理对象占用的非内存资源。
gc 只和内存有关,finalize 可以说是 gc 的一部分,因此也只和内存有关。

finalize 的两个应用场合
1. 回收不是new分配的内存,因为java可以调用c/c++,c/c++又可以调用其他程序,这些程序可以分配内存
2. termination condition: 判断对象回收之前的合理条件,显示臭虫,但是注意finalize 不一定会被执行。

初始化:
1. 定义时初始化是一定执行的:member 可以在定义的地方初始化,如果不提供定义时初始化,默认初始化为0,对象成员初始化为 null,使用null引用不会触发编译时错误,只会触发运行时错误。

局部变量如果不提供定义时初始化,不保证初始化。使用没经过初始化的局部变量,触发编译时错误。

非静态成员,定义时初始化按照他们在类内定义的顺序,可以调用函数来初始化,前提是该函数没有用到还没有定义时初始化的变量。

定义时初始化的局限是初始化值是固定的。
2. 构造函数初始化,发生在定义时初始化之后
3. 静态变量的初始化方法和非静态变量一样。静态变量先于非静态变量进行定义时初始化。
4. Explicit static initialization
static {
}
和  non-static instance initialization
{
}

这二者都是定义时初始化

5. array 默认初始化为0

访问控制,和c++相比多了package access,即属于同一个包可以访问
类的访问控制只有两种 public 或 package
类成员的访问控制由松到紧有 public protected package private
没有设定访问控制,则默认为 package access,子类不能访问父类的 package access 成员,除非两个类在同一个 package.


package:
库单元
如果没有设定package,则实际上归为"unnamed"或default package

代码组织
一个 .java 文件包含有且只有一个 public class,和若干个非 public 类;且文件名必须和 public 类名字相同。编译器编译.java文件,得到若干个.class文件,每一个对应一个类。
一个程序或库包含一批.class文件,这些.class文件可以打包并压缩成.jar文档。Java解释器负责寻找、载入和解释这些文件。

package命令
一般为域名的倒转,再加上具体的逻辑层次,比如 com.blogspot.noteonx.math.geometry
noteonx.blogspot.com 为域名,math.geometry为逻辑层次。package的名字和文件夹层次对应,上面包的文件必须在文件夹 top-path/com/blogspot/noteonx/math/geometry 里面。
为了让java解释器找到文件,必须把 top-path 加入环境变量 CLASSPATH。
如果库打包为jar,必须把 jar 的绝对路径加入 CLASSPATH。

Jar 文件包含一系列 zipped 文件,以及一个描述这些文件的 manifest 文件。

不要重定义继承而来的 field

代码重用:
1. 组合
2. 继承
3. 代理(介于组合和继承之间,A代理C,即A包含一个C对象,且提供C的接口,但A不是C),java 不直接支持代理

重载(overload)父类的函数,并不遮掩父类的版本。如果是覆盖,可以通过 super.f() 调用

final 可以用于field, method, class
final field:
1. 编译时常量(类似于 c++ 中 const int),并且一般定为static。良好的风格,static final field 用全大写,下划线分割单词,如 static final double COMPRESS_RATIO
2. 运行时初始化后不改变。亦即允许 blank final,定义时不初始化
3. final 用于引用,指引用关系不变,对象可以被改变。

函数都是传递引用,final 参数只是常引用,不同于C++ const A&

final method: 防止继承类修改实现。final class 不允许继承

多态两个陷阱
1. private 方法不能覆盖,因此隐含 final 的意思。只能覆盖类的接口,private 不属于接口。
2. 最好不要覆盖 fields 和 static 方法不能覆盖。这二者是静态绑定,没有多态

抽象类和抽象方法,冠以 abstract
抽象方法不提供实现(不能提供,C++则允许提供)。抽象类可以不包含抽象方法。子类如果不提供父类抽象方法的实现,推定为抽象类,需冠以 abstract。

接口 interface
只提供接口,不提供实现(不能提供)。
Java 不允许多继承,但 interface 提供了类似于多继承的方式。一个类只能继承一个父类,但可以实现多个接口,该类对象可以 upcast 到 Interface。
接口可以有 filed,但只允许 static, final field.

内类,inner class,不同于嵌套类
内类不同于组合,也就是说内类并非包含类的成员,包含类对象并不包含一个内类对象。
内类的方法可以访问包含类的所有成分。
一般来说包含类有生成内类对象的方法。用户可以调用这个方法。
内类method可以通过 Enclosing.this 类获得包含类对象的引用,this 则为自身对象的引用。

Under the hood:
创建内类对象,必须要通过一个包含类对象(static inner class 为类似于C++的嵌套类,不需要包含类对象)。创建内类对象时,该对象记住了创建它的包含类的链接,因此可以访问包含类的成员。
创建内类对象的几种情况:

1. private inner class:用户使用只能调用包含类返回内类对象的方法。
2. public inner class 则不然,可以用过 Enclosing.Inner 指涉,但是必须指定包含类对象en, Enclosing.Inner in = new en.new Inner() 可以生成 inner object。
3. 包含类的方法可以直接指涉 Inner,不用加 Enclosing.Inner: Inner in = new Inner();


Array: 不能改变长度
Container:

  1. Collection: 可以用 foreach 语法,包含 List, Queue, Set 等
  2. Map

注意 Container 不能储存原生类,原生类只能用 Array 储存

List, Queue, Set 都是抽象类,不是具体实现,但是一般使用这些接口就可以了。这样以后更改具体实现不用改变代码。但是如果需要更具体的实现的方法,则不能用通用接口。

java.util 包含 Arrays 类和 Collections 类,提供函数多个元素操作
Arrays.asList() 一般通过Arrays.asList()构造一个List,也可以直接把Arrays.asList()作为一个List,但是不同改变长度。
Collections.addAll()

explicit type argument specification

List<Snow> snow4 = Arrays.<Snow>asList(new Light(), new Heavy());


Iterator 应该是轻量型对象,创建成本低
Iterator 支持
1. 调用 collection.iterator() 获得 iterator
2. 调用 iterator.next() 获得下一个 object
3. 判断是否有下一个 hasNext()
4. remove() 删除 iterator 上一次返回的对象,调用 remove 之前必须有调用 next()
Iterator 的位置应该视为items 之间的缝隙,而不是 item 的位置
| Item | Item | Item | Item | ,item 的index 为 0,1,2,3
| 的位置为iter的位置,index 为 0,1,2,3,4

功能更多的Iterator
ListIterator 双向的

Array 不能改变长度
Array 是一个对象,它有 length member 和 [] 函数;Array identifier 实际上是一个指向 arrry object 的引用。Array 既可以保存对象,也可以保存原生类。保存对象就是对象的引用存在 Array object 中,原生类则直接保存在Array object 中。

返回Array可以直接返回,因为实际上是返回引用。

多维 Array: 例如 int [][][]
解析
int [], int[][] 都是对象,因此 int[][][] 先是 int[][] 的 array,每个 int[][] 是 int[] 的 array。

RTTI: run time type information。多态的 paradigm 是使用一个通用的接口,所有具体类型都有这个接口。但是有时候需要特殊类的接口。或者需要判断具体的类型来进行某些特殊处理(这不能可以通过覆盖函数实现吗?)。

"Class" 对象
Java 为在编译一个类的时候,为该类生成一个 Class 对象,用于保存该类的信息,存在 .class 文件里(待确认)。
类的装载不是执行程序时,而是该类第一次被用到的时候,也就是该类的静态成员第一次被指涉的时候(构造函数默认是静态的)。装载器先检查该类的Class 对象是否装载好了,如果没有,则
1. 装载:寻找字节代码,并创建 Class 对象
2. 连接:确认该类的字节代码,为静态成员分配内存,解析该类引用到的其他类
3. 初始化:如有父类,初始化父类,执行静态成员初始化

Generics
erasure: Java 实现 generics 的原理,是将具体类型都当作Object,因此类型信息都被删掉了,这称为 erasure.
不能使用原生类做为类型参数,可以使用 autoboxing 类作为类型参数。
得到类A的 Class 对象
static Class.forName("A")
a.getClass()


Container 实际上储存的都是 Object,但是从其中取出会自动 cast 为具体类型。

0 comments: