第 1 章 对象入门

Smalltalk 的五大基本特征

(1) 所有东西都是对象

(2) 每个程序都是一大堆对象的组合;通过消息的传递一个对象可告诉另一个对象该做什么

(3) 每个对象都有自己的存储空间可容纳其他对象,换句话说,通过封装一个现有的对象还可生成一个新对象

(4) 每个对象都有一种类型

(5) 同一类所有对象都能接收相同的消息

第 2 章 万事万物皆对象

一.所有对象都必须由你建立

1 . 存储在哪里

1) 寄存器:我们在程序中无法控制; 速度最快 , 数据位于处理器的内部

2) stack :存放基本类型的数据和对象的 reference ,但对象本身不存放在 stack 中,而是存放在 Heap 中;位于 RAM 中;处理器通过 堆栈指针访问,下移创建新内存,上移释放内存

3) Heap :存放用 new 产生的数据

4) Static storage :存放在对象中用 static 定义的静态成员

5) Constant storage :存放常量;需严格保护的常量,可考虑放入 ROM 中

6) NON-RAM :硬盘等永久存储空间

2 . 特例:基本型别

基本类型数据存放在 Stack 中,存放的是数据。而产生对象时,只把对象的 reference 存放在 stack 中,用于指向某个对象,对象本身存放在 Heap 中。

Java 提供了两个类,专门用于进行高精度运算 BigInteger 和 BigDecimal

3 . Java 中的数组

当你产生某个存储对象的数组时,真正产生的其实是存储 reference 的数组。引数组建立后,其中的每一个 reference 都会被自动设为 null ,表示“不指向任何对象”。

4 . 作用域 Scope

作用域是由花括号的位置决定的。需要说明的是,在java中,如下代码是不允许的。

{

int x = 12;

{

int x = 96; /* illegal */

}

}

二.建立新的数据型别: Class

1 . 数据成员和函数

1.1 基本成员的缺省值

1 ) 当 class 的某个成员属于基本型别时,即使你没有为它提供初值, Java 仍保证它有一个缺省值。

2 ) 只有当变量身份是 class 内的成员时, Java 才保证为该变量提供初值。

*:浮点型数默认的是 double 型

三.函数( Mehtods ),引数( arguments ),返回值( return values )

1 . 引数列

当引数传递的是对象时,传递的是对象的 reference 。

*: packcage 的命名 以小写字母为标准; Java 的设计者鼓励程序员反转使用自己的 Internet 域名,因为它们肯定是独一无二的。

四.开始构建Java 程序

import java.util.*;注意*只能包含当前目录下的所有类不能包含子目录中的类。

五.注解用内嵌式文档

Java 提供两种注解风格: /*XXXX*/ 、 //XXXX

javadoc : /**   */ ; 两种方式来使用 javadoc : 嵌入的 HTML 或 使用文档标记

文档标记 Doc tags 是一些以 @ 开头的命令,置于注释行的起始处,但最前头的 * 会被忽略。

@see 采用超链接的形式指向其他文档。后面跟上:类名、完整类名或者完整类名 # 方法名;

@version 版本信息

@author 作者信息

@since 是从该代码的哪个版本开始启用

@param 参数名 说明

@return 说明

@throws 完整类名 说明

@deprecated

第 3 章 控制程序流程

一. 使用 Java 运算符

1. 函数引数如果为对象类型,是传递引用的

2. 关系运算符

1. ) 当对两个对象运用关系运算符进行比较时,比较的是 object reference ,如:

Integer n1 = new Integer(3);

Integer n2 = new Integer(3);

System.out.println(n1==n2);

结果为 false ,因为两个 object reference ( n1 和 n2 )值是不同的

2 ) quals() 的缺省行为也是拿 reference 来比较。不过 Java 中的 class 大多覆写了 equals 方法,如:

Integer n1 = new Integer(3);

Integer n2 = new Integer(3);

System.out.println(n1.quals(n2));// 值为 true

2 . 逻辑运算符

1 ) 只能将 and 、 or 、 not 施用于 boolean 值身上。如果逻辑运算符两边的值存在 non-boolean 值,将会出错,如:

int test1 = 1;

System.out.println((test && 1<2);// 编辑出错, test 是 non-boolean 值

*: 操作逻辑运算符时,可能会遇到一种名为短路的情况。

3 . 位运算符

按位运算符可与等号 = 联合使用,以便同时进行运算和赋值, &= , |= 和 ^= 都是合法的,由于 ~ 是一元运算符,所以不可与 = 联合使用。

4 .位移运算符

如果所操作的位移对象是 char 、 byte 、 short ,位移动作发生之前,其值会先被晋升为 int ,运算结果会是 int 。

二.流程控制

1 . 迭代( iteration )

1.1 逗号运算符

逗号运算符只能用于 for 循环的控制表达式中的 initialization 和 step 两部分中,如: for(int i=0, j=I+1; I<5; i++, j=I*2)

1.2 break 和 continue

break 表示退出循环; continue 表示退出本次循环,回来循环起始位置。

1.3 label

label 只有放在迭代语句之前才起作用,在 label 和迭代语句之间插入任何语句都不会起作用。

2 . Switch

switch 中的选择器必须是 int 或 char 型,如:

float i = 2;

switch ( i )// 将出错,因为 i 不是 int 或 char 之一

3 . 计算细节

1 ) 从 float 或 double 转为整数值,总是以完全舍弃小数的方式进行。

4 . Math.random() 的输出范围是 [0, 1) 。

第 4 章 初始化和清理

一.以构造函数( constructor )确保初始化的进行

如果某个 class 具备构造函数, Java 便会在对象生成之际,使用者有能力加以操作之前,自动调用其构造函数,于是便能名确保初始化动作一定被执行。

二.函数重载( Method overloading )

1 . 区分重载函数

由于只能从函数名和函数的引数列来区分两个函数,而重载函数具有相同的函数名称,所以每个重载函数都必须具备独一无二的引数列。

2 . default 构造函数

1 ) default 构造函数是一种不带任何引数的构造函数。如果你所开发的 class 不具任何构造函数,编译器会自动为你生成一个 default 构造函数。

2 ) 如果你自行定义了任何一个构造函数(不论有无引数),编译器就不会为你生成 default 构造函数。

3 ) 如果定义了一个 class ,如

class Bush{

Bush(int I){}

}

当想用 new Bush(); 来产生 class 的实例时,会产生错误。因为在定义 class 时已定义了构造函数,所以编译器就不会为 class 生成 default 构造函数。当我们用 new Bush() 来产生实例时,会尝试调用 default 构造函数,但在 class 中没有 default 构造函数,所以会出错。如:

class Sundae

{

Sundae(int i) {}

}

public class IceCream

{

public static void main(String[] args)

{

//Sundae x = new Sundae(); 会编译出错,无构造函数 Sundae()

Sundae y = new Sundae(1);

}

}

*:在定义一个 class 时,如果定义了自己的构造函数,最好同时定义一个 default 构造函数

3 . 关键字 this

1 ) this 仅用于函数之内,能取得“唤起此一函数“的那个 object reference 。

2 ) 在构造函数中,通过 this 可以调用同一 class 中别的构造函数,如

public class Flower{

Flower (int petals){}

Flower(String ss){}

Flower(int petals, Sting ss){

//petals++; 调用另一个构造函数的语句必须在最起始的位置

this(petals);

//this(ss); 会产生错误,因为在一个构造函数中只能调用一个构造函数

}

}

**: 1 )在构造调用另一个构造函数,调用动作必须置于最起始的位置

2 )不能在构造函数以外的任何函数内调用构造函数

3 )在一个构造函数内只能调用一个构造函数

4 . Static 的意义

无法在 static 函数中调用 non-static 函数(反向可行)。为什么不能呢,我们看下面的例子。

假设能在 static 函数中调用 non-static 函数,那么( a )处就将出错。因为在没有产生 Movie class 实例之前,在就不存在 Movie class 内的 name 实例,而在 getName() 中却要使用 name 实例,显然的错误的。

class Movie{

String name = “”;

Movie(){}

public Movie(String name) { this.name = name; }

public static String getName() { return name; }

}

public class Test{

public static void main(String[] args){

// 下面两名先产生实例后再调用 getName() 没有问题

//Movie movie1 = new Movie(“movie1”);

//String name1 = movie1.getName();

// 下面一行将出错

//String name2 = Movie.getname();   ( a )

}

}

三.清理( cleanup ):终结( finalization )与垃圾回收( garbage collection )

1 )你的对象可能不会被回收

只有当程序不够内存时,垃圾回收器才会启动去回收不再被使用的对象的内存空间。某个对象所占用的空间可能永远不会被释放掉,因为你的程序可能永远不会逼近内存用完的那一刻,而垃圾回收器完全没有被启动以释放你的对象所占据的内存,那些空间便会在程序终止时才一次归还给操作系统;

finalize() ,理想情况下: 一旦垃圾收集器准备好释放由对象占用的存储空间,便首先调用 finalize() , 而且只有在下一次垃圾收集过程中才会真正回收对象的内存。

2 )垃圾收集器的工作原理

在 Java 中为heap对象分配存储空间可达到其他语言在stack中创建存储空间差不多的速度。

不同的垃圾收集器 GC 机制:a、最简单速度也最慢的一种 GC 技术叫作“引用计数”。很少使用。缺点:假如对象相互进行了循环引用,尽管没有非零的引用计数存在,仍有可能是真正的垃圾。b、 JVM 采用了一种自适应的垃圾收集机制。一种技术便是“停止和拷贝”,程序首先停止运行,每个存活的对象都从一个堆拷贝到另一个堆,实现了对新堆空间的压缩。

另一种机制叫作“标记和清除”,要求从堆栈和静态存储区域开始对所有引用进行追踪最后找出所有活着的对象。假如知道自己已经没有垃圾或者只有少量垃圾换用它便可得到更高的效率。

3 ) 只有在采用原生函数( native methods )时,才使用 finalize() 。

四.成员初始化( member initialization )

1 ) 函数中的变量不会被自动初始化,如

void f(){

int i;

i++;

}

将发生编译错误,因为 i 没有被初始化。

2 ) class 的数据成员会被自动初始化,具体情况如下:

基本型别: boolean : false 、 char : null ( \u0000 )、 byte : 0 、 short : 0 、 int : 0 、

long : 0 、 float : 0 、 double : 0

对象( reference ): null

1 . 初始化次序

1 ) 所有变量一定会在任何一个函数(甚至是构造函数)被调用之前完成初始化

2 ) 在产生一个 class 的对象(包含 static 成员的 class 的代码被装载)时,首先自动初始化 class 中的 static 成员变量,再执行所有出现于 static 数据定义处的初始化动作,最后执行 static block ,所有这些初始化操作只在第一次生成该对象时进行。

3 ) 自动初始化 class 中的其它成员变量。

4 ) 执行所有出现于数据定义处的初始化动作。如: int i=1 ;的执行顺序是先把 I 自动初始化为 0 ,再执行数据定义处的初始化动作,初始化为 1 。

5 ) 执行 non-static block

6 ) 调用构造函数。

例:

class Cup{

Cup(int marker){

System.out.println("Cup(" + marker + ")");

}

void f(int marker){

System.out.println("f(" + marker + ")");

}

}

class Cups{

static Cup c1 = new Cup(11);

static Cup c2;

Cup c3 = new Cup(33);

Cup c4;

{

c3 = new Cup(3);

c4 = new Cup(4);

}

static{

c1 = new Cup(1);

c2 = new Cup(2);

}

Cups(){

System.out.println("Cups()");

}

}

 

public class ExplicitStatic{

public static void main(String[] args){

System.out.println("Inside main()");

Cups.c1.f(99);

}

static Cups x = new Cups();

static Cups y = new Cups();

}

结果为:

Cup(11)

Cup(1)

Cup(2)

Cup(33)

Cup(3)

Cup(4)

Cups()

Cup(33)

Cup(3)

Cup(4)

Cups()

Inside main()

f(99)

2 . Array 的初始化

1 ) 定义数组时不能指定大小。如 int[4] iArr = {0, 1, 2, 3}; ,由于指定了数组的大小,会编译出错。

2 ) 数组只是存放 reference 的数组。 Array 与 non-array 的结构图如下:

a )对于基本型别数据,存放的是数据。如

int i = 5;

b )对于 class 变量,存放的是 reference ,这个 reference 指向一个存有 class 实例的内存空间。如 :   String s = “hello”;

变量 s 存放的是一个 reference ,这个 reference 指向一个存有 String 实例的内存空间。

c )对于基本型别数组,存放的是 reference 数组,数组中的每一个 reference 都指向一个 class 实例的内存空间。如

int[] ia = {10, 11, 12};

数组 ia 存放的是一个 reference 数组,数组中的每一个 reference 都指向一个的 int 实例的内存空间。

d )对于 class 数组,存放的是 reference 数组,数组中的每一个 reference 都指向一个的 class 实例的内存空间。如

String[] sa = {“hello1”, “hello2”, “hello3”};

数组 sa 存放的是一个 reference 数组,数组中的每一个 reference 都指向一个的 String 实例的内存空间。

3 ) 任何数组都要进行初始化,使用没有进行初始化的数组会产生运行时错误,如:

int[] iArr;

System.out.pritnln(iArr[0]);// 产生错误,因为 iArr 还未初始化

数组初始化可在任何地方,可用以下方法来对数组进行初始化:

a ) int[] iArr = {1,1,1,1};// 数组的长度为 {} 元素的个数

b ) int i = 10;

int[] iArr = new int[i];// 数组的长度可为变量(这在 C/C++ 中不行)

System.out.println(iArr[0]);//iArr[0] 是一个 int ,自动初始化值为 0

Integer[] iArr2 = new Integer[i];

System.out.println(iArr2[0]);//iArr[0] 是一个 reference ,自动初始为 null

 

I ) 对于基本型别数组, new 产生的是用于存放数据的数组;否则,产生的只是存放 reference 的数组。

II ) new 可用来初始化基本型别的数组,但不能产生 non-array 的基本型别数据。

c ) int[] iArr = new int[]{1,1,1,1};

Integer[] iArr2 = new Integer[]{new Integer(1), new Integer(2)};

3 . 多维数组( Multidimensional ) arrays

多维数组每一维的大小可以不一样,如:

Integer[][][] a5;

a5 = new Integer[3];

for(int i=0; i<a5.length; i++)

a5[i] = new Integer[i+1];

for(int j=0; j<a5[i].length

a5[i][j] = new Integer[i+j+1];

第 5 章 隐藏实现细节

*: package 语句必须作为文件的第一个非注释语句出现.

一 .Java 访问权限饰词( access specifiers )

Java 有 public 、 protect 、 friendly 、 private 四种访问权限,并且这四访问权限的访问范围越来越小。

1 . friendly

1 ) 果一个 class 内的数据成员或方法没有任何权限饰词,那么它的缺省访问权限就是 friendly 。同一个 package 内的其它所有 classes 都可以访问 friendly 成员,但对 package 以外的 classes 则形同 private 。

2 )  对于同一个文件夹下的、没有用 package 的 classes , Java 会自动将这些 classes 初见为隶属于该目录的 default package ,可以相互调用 class 中的 friendly 成员。如以下两个 class 分别在同一个文件夹的两个文件中,虽然没有引入 package ,但隶属于相同的 default package 。

class Sundae{

// 以下两个方法缺省为 friendly

Sundae(){}

Void f() {System.out.println(“Sundae.f()”);

}

public class IceCream{

public static void main(String[] args){

Sundae x = new Sundae();

x.f();

}

}

2 . public :可以被任何 class 调用

3 . private : private 成员只能在成员所属的 class 内被调用,如:

class Sundae{

private Sundae(){}// 只能在 Sundae class 中被调用

Sundae(int i) {}

static Sundae makASundae() {

return new Sundae();

}

}

public class IceCream{

public static void main(String[] args){

// Sundae class 中构造函数 Sundae() 是 private ,所以不能用它进行初始化

//Sundae x = new Sundae();

Sundae y = new Sundae(1);//Sundae(int) 是 friendly ,可以在此调用

Sundae z = Sundae.makASundae();

}

}

4 . protected :具有 friendly 访问权限的同时,又能被 subclass (当然包括子孙类,即子类的子类)所访问。即,既能被同一 package 中的 classes 访问,又能被 protected 成员所在 class 的 subclass 访问。

 

二. Class 的访问权限

1 . Class 同样具有 public 、 protect 、 friendly 、 private 四种访问访问权限:

1)  public :在任何地方都可被使用

每个编译单元文件都只能有一个 public 类; public 类的名字必须与包含了编译单元的那个文件的名字完全相符;

2 ) protect 、 private :除了它自己,没有任何 class 可以使用,所以 class 不能是

protected 或 private ( inner class 除外)

3 ) friendly :同一个 package 中的 classes 能用

2 . 如何调用构造函数被声明为 private 的 class

1 ) 用 static 函数

2 ) 用 Singteton 模式

class Soup{

private Soup(){}

// ( 1 )静态函数方法

public static Soup makeSout(){

return new Soup();

}

// ( 2 ) The "Singleton" pattern:

private static Soup ps1 = new Soup();

public static Soup access(){

return ps1;

}

public void f(String msg){

System.out.println("f(" + msg + ")");

}

}

public class Lunch{

public static void main(String[] args){

//Soup priv1 = new Soup(); 编译错误

Soup priv2 = Soup.makeSout();

Soup priv3 = Soup.access();

priv2.f("priv2");

priv3.f("priv3");

}

第 6 章 重复运用 classes

一.继承( inheritance )

1 、在 derived class 中 overriding 某个函数时,只能覆写 base class 中的接口,即 base class 中的 public 或 protected 或 friendly 函数。如果试图 overriding 一个 private 函数,虽然编译通过,但实际上你只是在 derived class 中添加了一个函数。

2、在派生类的构造函数中 Java 会自动插入对基类构造函数的调用。从上到下依次执行。

 

2 . Super 的使用

1 )通过关键字 super 可以调用当前 class 的 superclass (父类)。调用所在位置任意。

2 )通过 super 来调用 superclass 中的成员时,调用的是最近成员。

3 . Base class 的初始化

2.1 当你产生 derived class 对象时,其中会包含 base class 子对象( subobject )。这个子对象就和你另外产生的 base class 对象一模一样。

2.2 通过 super() 可调用 base class 的构造函数,但必须放在构造函数的第一行,并且只能在构造函数中运用。

2.3 初始化顺序为:

1 ) 加载代码( .class 文件)

2 ) 初始化 class 的静态成员,初始化顺序了“从里到外”,即从 base   class 开始。

3 ) 在 derived class 的构造函数中调用 base class 的构造函数。

如果在 derived class 的构造函数中没有通过 super() 显式调用调用 base class 的构造函数,编译器会调用 bass class 的 default 构造函数并自动生成相应的调用语句,从而产生一个 base class 实例。如果在 derived class 的构造函数中通过 super() 显示调用了父类的构造函数,则调用所指定的构造函数。调用构造函数的调用顺序是“从里到外”。

4 ) 调用 derived class 的构造函数。

**:当 base class 没有 default 构造函数时,必须在 derived class 的构造函数中通过 super 显示调用 base class 的构造函数。

例:下面代码的初始化过程为:

1 ) 装载 ExplicitStatic 的代码(装载 ExplicitStatic.class 文件)。

2 ) 发现 ExplicitStatic 有关键字 extends ,装载 ExplicitStatic 的 base   class 的代码(装载 Cleanser.class 文件)。

3 ) 发现 Cleanser 有关键字 extends ,装载 Cleanser 的 base   class 的代码(装载 Base.class 文件)。

4 ) 初始化 Base class 中的静态成员。

5 ) 初始化 Cleanser class 中的静态成员。

6 ) 初始化 ExplicitStatic class 中的静态成员。

如果把( c )处的代码注释掉,那么初始化工作到此就结束了。

7 ) 在 ExplicitStatic class 的构造中调用 super("ExplicitStatic") (在 ExplicitStatic class 的构造函数中显式调用父类的构造函数),试图产生一个 Cleanser class 实例。

8 ) 由于 Cleanser class 又是继承自 Base class ,会在 Cleanser class 的构造函数中通过 super() (由于没有显式调用父类的构造函数,所以自动调用父类的 default 构造函数)调用父类的构造函数,试图产生一个 Cleanser class 实例。

9 ) 产生一个 Base class 实例。先初始化成员变量,再调用构造函数。

10 ) 回到 Cleanser class ,产生一个实例。首先初始化 Cleanser class 中的成员数据,再执行构造函数 Cleanser(String a) 中的其余部分。

11 ) 回到 ExplicitStatic class ,产生一个实例。首先初始化 ExplicitStatic class 中的成员数据,再执行构造函数 ExplicitStatic () 中的其余部分( System.out.println( “ ExplicitStatic() ” ) )。

class Base{

static int s1 = prt("s1 initialized.", 11);

int i1 = prt("i1 initialized.", 12);

Base(){

System.out.println("Base()");

System.out.println("s1 = " + s1 + " ,i1 = " + i1);

}

static int prt(String s, int num) {

System.out.println(s);

return num;

}

}

class Cleanser extends Base{

static int s2 = prt("s2 initialized.", 21);

int i2 = prt("i2 initialized.", 22);

Cleanser(){

System.out.println("Cleanser()");

System.out.println("s2 = " + s2 + " ,i2 = " + i2);

}

Cleanser(String a){

//super(); ( b )

System.out.println("Cleanser(" + a + ")");

System.out.println("s2 = " + s2 + " ,i2 = " + i2);

}

}

public class ExplicitStatic extends Cleanser{

static int s3 = prt("s3 initialized.", 31);

int i3 = prt("i3 initialized", 31);

ExplicitStatic(){

super("ExplicitStatic");

System.out.println("ExplicitStatic()");// ( a )

System.out.println("s3 = " + s3 + " ,i3 = " + i3);

}

public static void main(String[] args){

ExplicitStatic x = new ExplicitStatic();// ( c )

}

}

结果:

s1 initialized.

s2 initialized.

s3 initialized.

// 如果把( c )处的代码注释掉,输出结果到此为止,不会输出下面结果

i1 initialized.

Base()

s1 = 11 ,i1 = 12

i2 initialized.

Cleanser(ExplicitStatic)         // ( a )处结果

s2 = 21 ,i2 = 22

i3 initialized

ExplicitStatic()

s3 = 31 ,i3 = 31

代码及结果中的( a )说明了是先产生当前 class 的 base class 的实例,否则在 derived class 中无法调用 base class 的成员。在调用 Cleanser class 的构造函数 Cleanser(String a) 时,在 Cleanser(String a) 中没有用 super 显式调用 Base class 的构造函数,所以系统会自动生成调用 Base class 的 default 构造函数的语句,如( b )。

4 . 组合与继承之间的快择

1 )继承表示的是一种“ is-a (是一个)”的关系,如货车是汽车中的一种;组合表示的是一种“ has-a (有一个)”的关系,如汽车有四个轮子。

2 )是否需要将新的 class 向上转型为 base class 。

5 . 在继承中的访问权限

protect 变量能被子孙类所调用。如 Base class 中的 baseS 能被 Cleanser class 和 ExplicitStatic class 调用。

class Base{

protected String baseS = "Base";

Base(){System.out.println("Base()");}

}

class Cleanser extends Base{

protected String baseS = "Cleanser";

public String s = new String("Cleanser");

Cleanser(){

System.out.println("Cleanser(): " + s);

}

Cleanser(String a){

System.out.println("Cleanser(" + a + "): s = " + s );

}

}

public class ExplicitStatic extends Cleanser{

String s2 = s;

String baseS = super.baseS;

ExplicitStatic(){

super("ExplicitStatic");

System.out.println("ExplicitStatic():s2 = " + s2 + ", baseS = " + baseS + ",super.baseS = " + super.baseS);

baseS = "ExplicitStatic";

System.out.println("baseS = " + baseS + " , super.baseS = " + super.baseS);

}

public static void main(String[] args){

ExplicitStatic x = new ExplicitStatic();

}

}

结果:

Base()

Cleanser(ExplicitStatic): s = Cleanser

ExplicitStatic():s2 = Cleanser, baseS = Cleanser, super.baseS = Cleanser

baseS = ExplicitStatic , super.baseS = Cleanser

二.关键字 final

1 . Final data

1.1   final data

1 )当基本型别被定义为 final ,表示它的数据值不能被改变。如

final int i = 9;

i++;// 编译错误,不能改变 I 的值

2) 当 object   reference 被定义为 final 时,不能改变的只是 reference 而不是对象本身。如

class Value{

int i = 1;

}

public class ExplicitStatic extends Cleanser{

public static void main(String[] args){

final Value v = new Value();//v.i = 1

v.i++;//v.i = 2

//v = new Value();

}

}

由于 v 为 final ,所以不能通过 new Value() 使 v 重新指向一个对象;但是 v 所指向的对象的值是可以改变的( v.i++ )。

1.2   blank finals

我们可以将数据成员声明为 final 但不给予初值,这就是 blank finals 。但 blank finals 必须且只能在构造函数中进行初始化。

public class ExplicitStatic {

final int ib;

final int i = 1;

ExplicitStatic()

{

ib = 2;// ( a )

//i = 3;  ( b )

System.out.println("i = " + i + ", ib = " + ib);

}

public static void main(String[] args){

ExplicitStatic ex = new ExplicitStatic();

}

}

ib 为 blank finals ,所以可以在构造函数中进行初始化。如果把( a )处的代码注释掉,则 ib 没有初值,编译出错。而 i 在定义处已进行了初始化,则不能改变 i 的值,( b )处的代码编译错误。

**:非 blank   finals 成员即使在构造函数中也不能更改其值

2 . Final   methods

1 )被声明为 final 的函数不能被覆写

2 ) class 中所有 private 函数自然而然会是 final 。

3 . Final   classes

1 )当一个 class 被声明为 final 时,表示它不能被继承,但 class 的数据成员不是 final ,可以被改变。如

class SmallBrain{}

 

final class Dinosaur{

int i = 7;

int j = i;

SmallBrain x = new SmallBrain();

void f(){};

}

 

// 不能继承 final 函数

//class Further extends Dinosaur{}

 

public class ExplicitStatic{

public static void main(String[] args){

Dinosaur n = new Dinosaur();

n.f();

n.i = 40;//final class 中的 non-final 数据成员可以被改变

n.j++;

}

}

2 ) final   class 中的所有函数也都自然是 final ,因为没有人能够加以覆写。

第7章 多态

对于面向对象的程序语言多态是其第三种最基本的特征前两种是数据抽象与继承.

一、再论向上转型( upcasting )

将某个 object   reference 视为一个“ reference   to   base   type “的动作,称为向上转型。

二、深入理解

将一个方法调用同一个方法主体连接到一起就称为绑定 Binding 。 若在程序运行前执行绑定,就叫作早期绑定。在运行时间进行以对象的类型为基础,后期绑定也叫作动态绑定或运行时间绑定。方法声明成 final ,它可关闭动态绑定,或者告诉编译器不需要进行动态绑定。

三、抽象类和方法

抽象方法:只含有一个声明没有方法主体,包含了抽象方法的一个类叫作抽象类。即使不包括任何 abstract 方法亦可将一个类声明成抽象类。

四 .Abstract class和Abstract methods

1.如果一个class中存在abstract class,则class也必须被声明为abstract class。

2.abstract class不能被实例化。

3.   如果base class是一个abstract class,那么derived class必须实现base class中所有的abstract methods;否则,derived class也必须被声明为abstract class。

五、 构造函数的调用遵照下面的顺序

(1) 调用基类构造函数

(2) 按声明顺序调用成员初始化模块

(3) 调用派生构造函数主体

六、在程序运行时对类型进行检查的行为叫作运行时间类型标识, Run-Time Type Identification RTTI 。

七.其它要点

1 .      纯粹继承与扩充

纯粹继承:只有 base   class 所建议的函数,才被 derived   class 加以覆写。

扩充:除了覆写 base   class 的函数,还实现了自己的函数

第8 章 接口和内部类

一、接口

规定一个类的基本形式方法名参数列表以及返回类型等等,只是不规定方法主体。

接口中,数据类型的数据成员默认为 static 和 final ,方法默认为 public

1.  如果实现接口的 class 未实现接口中的所有函数,则这个 class 必须被声明为 abstract   class ,而接口中未被实现的函数在这个 class 中为 abstract   class 。

2.  接口中的所有函数自动具有public访问权限,所以实现某个接口时,必须将承袭自该接口的所有函数都定义为public

3.  接口中的数据成员自动成为static和final

4.  多重继承

1) derived class可以同时继承多个interface和一个abstract或concrete base class。

2) 如果derived class所继承的具体类具有与interfaces相同的函数,则可在derived class不实现那个函数,因为会调用base class中的该函数。

5.  嵌套的interfaces

嵌套的interfaces可以在定义该内部接口的外部类(接口)之外被使用(但内隐类不行)。

被声明为private的接口不能在class外被使用。

虽然 A class 含有接口,但它仍可被实例化。

内隐类的作用域为定义该内隐类的外部类内。

当接口嵌套于接口中,

1) 嵌套于接口中的接口自动为 public ,且只能为 public 。

2) 当实现某个接口时,无需实现其中嵌套的接口。

3 ) Private 接口无法在其所定义的 class 之外被实现。

二. Inner classes(内隐类)

1 .内隐类的基本用法

1 )如果要在外围 class 的 non-static 函数之外产生一个 inner class 对象,得以 OuterClassName.InnerClassName 的形式指定该对象的型别。而在 non-static 函数内则不用。

2)对于non-static inner class,在外围class的non-static函数可以通过new产生一个inner class对象。但要在非non-static函数产生一个inner class对象,则一定要关联到其enclosing class的某个对象。

3)inner class的向上转型,当把一个inner class对象向上转型成为interface时,我们得到的只是一个reference。

2.匿名内部类

1 ) 试图定义一个匿名内部类,并想使用在匿名内部类外部定义的一个对象,则编译器要求外部对象为 final 属性。

2)创建从 Contents 派生出来的匿名类的一个对象,由 new 表达式返回的引用会自动向上强制转型成一个 Contents 引用。

return new Contents() {

private int i = 11;

public int value() { return i; }

};

* : 内部类拥有对封装类所有元素的访问权限

*:non-inner classes无法被声明为private或protected,只能是public或 ” friendly ” .

3.内隐类与外围enclosing  class的连接关系

3.1 non-static inner class

1)  inner class可以访问enclosing class的所有成员(包括private成员),就像inner class自己拥有这些成员一样。即inner class天生具有对enclosing class的所有成员的访问权力。

2)  inner class对象被产生时,一定要关联到其enclosing class的某个对象(这个enclosing class对象就是Inner class对象的制造者)。建构inner class对象的同时,得有其enclosing class对象的reference才行。原因:因为inner class可以访问enclosing class的所有成员,那么当产生一个inner class时,编译器会自动为inner class对象添加一个指向enclosing class对象的reference(这个reference是隐藏的)。所以Inner class被产生时,一定要关联到其enclosing class的某个对象。

3)  同一个enclosing class对象产生出来的inner class对象访问的是同一个enclosing class对象中的成员。

4)       非静态内部类,指向外部类对象的链接是用一个特殊的 this 引用来完成.

5)       继承inner class的类,不能有default构造函数,在构造函数中,不仅要传入一个reference指向outer object,还要在构造函数中,写enclosingClassReference.super();

6)       可考虑用一个静态内部类来容纳自己的测试代码,发布时候,去掉名为xxx $Tester 的类,避免会产生大量额外的编译码。

7)       在创建多重嵌套内部类的对象,xxxx .new yyy 语法可new 子类对象。

3.2 static inner class

1)       要想创建静态内部类的一个对象,不需要一个外部类对象.

2)       不能从静态内部类的一个对象中访问一个外部类对象.

4.内部类的用处

1)       Closures() and Callbacks(回调)

2)       inner classes和control framework

 

第 9 章 对象的容纳

一.

集合框架的祖宗: Collection

历史集合

新集合

无序集: Set

有序集: List

映射:Dictionary

映射:Map

AbstractSet

SortedSet

AbstractList

AbstractSequentialList

Hashtable

AbstractMap

SortedMap

历史集合

新集合

LinkedList

WeakHashMap

IdentityHashMap

HashMap

TreeMap

HashSet

TreeSet

Vector

ArrayList

LinkedHashSet

 

Stack

 

 

Properties

 

 

LinkedHashMap

 

 

实现了两种定制的数据结构:哈希表(包括HashSet和HashMap)和平衡二叉树(包括TreeSet和TreeMap)。

二、历史实现 vs. 新实现

Enumeration vs. Iterator

    Enumeration是一个传统的集合遍历工具,在新的JCF中使用的是Iterator,Iterator同样具有遍历功能,还包含一个remove()方法来删除当前得到的元素。

Dictionary vs. Map

    Dictionary是一个现在已经被标记为deprecated的类,实现了老版本中的映射功能,现在已经完全被Map取代。它们的区别是:Dictionary中key和value不能为null,但Map却允许空的关键字和值,这一点直接影响到它们的后代:Hashtable和HashMap。

Vector vs. ArrayList

    Vector和ArrayList是数组在JCF中的体现,Vector和ArrayList就是一种可以动态增长的数组。Vector是历史实现,它和ArrayList的主要区别在于,Vector是同步集合(或者说是线程安全的),但ArrayList并不是同步的,由于同步需要花一定的代价,所以ArrayList看起来要比Vector的存取访问效率更高。关于同步我们下面还将要谈到。

Hashtable vs. HashMap

    Hashtable是Dictionary的子类,属于历史实现,而HashMap是Map的子类,是新实现。它们的区别除了上面所说的key和value是否可以为空之外,也有同步的差别,Hashtable是同步的,但HashMap不是。Hashtable的一个著名的子类Properties我们可是经常会用到的。

ArrayList

由一个数组实现的List,可对元素进行速度非常快的随机访问。但用它在List中部插入或删除元素的时候速度却比较慢。ListIterator 通常只能用于在一个ArrayList 中来回 “ 遍历 ” 而不要用它插入或删除元素 —— 那应该是LinkedList 的事情。

LinkedList

提供优化的顺序访问性能同时可以高效地在List 中部进行插入和删除操作,但在进行随机访问时速度却相当慢,此时应换用ArrayList。

Set 接口

添加到Set的每个元素都必须是独一无二的,Set不会添加重复的元素。添加到Set里的对象必须定义equals() 以树立对象的 “ 唯一 ” 性,Set拥有与Collection 完全相同的接口,Set接口并不保证自己会按任何特定的顺序来容纳元素。

HashSet

假如在一个Set中的搜索速度是至关重要的就应考虑用HashSet。同时Object 还必须定义hashCode()。

TreeSet

排好序的一种Set,采用树形结构,这样一来就可从Set里提取出一个固定顺序的元素序列。

三、深入Map

1.Map接口

     维持 “ 键值 ” 对应关系对以便根据一个键查找到相应的值。

2.HashMap

基于一个散列表实现,用它代替Hashtable,针对 “ 键值 ” 对的插入和检索,这种形式具有最稳定但不是最好的性能,可通过构造函数设置散列表的 “ 容量 ” 与 “ 负载比 ” ,从而对性能进行调整。

3.TreeMap

在一个 “ 红黑 ” 树的基础上实现。查看键或者 “ 键值 ” 对时,它们会按固定的顺序排列(取决于Comparable 或Comparator)。TreeMap 最大的好处就是我们得到的是已排好序的结果TreeMap 是提供了subMap()方法的唯一一种Map,用它可返回树的一部分(子映射)。


 

java 对象序列化学习笔记

    序列化的过程就是对象写入字节流和从字节流中读取对象。将对象状态转换成字节流之后,可以用java.io包中的各种字节流类将其保存到文件中,管道到另一线程中或通过网络连接将对象数据发送到另一主机。对象序列化功能非常简单、强大,在RMI、Socket、JMS、EJB都有应用。对象序列化问题在网络编程中并不是最激动人心的课题,但却相当重要,具有许多实用意义。

一:对象序列化可以实现分布式对象。主要应用例如:RMI要利用对象序列化运行远程主机上的服务,就像在本地机上运行对象时一样。

二:java对象序列化不仅保留一个对象的数据,而且递归保存对象引用的每个对象的数据。可以将整个对象层次写入字节流中,可以保存在文件中或在网络连接上传递。利用对象序列化可以进行对象的“深复制”,即复制对象本身及引用的对象本身。序列化一个对象可能得到整个对象序列。

  从上面的叙述中,我们知道了对象序列化是java编程中的必备武器,那么让我们从基础开始,好好学习一下它的机制和用法。

 

    java序列化比较简单,通常不需要编写保存和恢复对象状态的定制代码。实现java.io.Serializable接口的类对象可以转换成字节流或从字节流恢复,不需要在类中增加任何代码。只有极少数情况下才需要定制代码保存或恢复对象状态。这里要注意:不是每个类都可序列化,有些类是不能序列化的,例如涉及线程的类与特定JVM有非常复杂的关系。

 

序列化机制:

 

序列化分为两大部分:序列化和反序列化。序列化是这个过程的第一部分,将数据分解成字节流,以便存储在文件中或在网络上传输。反序列化就是打开字节流并重构对象。对象序列化不仅要将基本数据类型转换成字节表示,有时还要恢复数据。恢复数据要求有恢复数据的对象实例。ObjectOutputStream中的序列化过程与字节流连接,包括对象类型和版本信息。反序列化时,JVM用头信息生成对象实例,然后将对象字节流中的数据复制到对象数据成员中。下面我们分两大部分来阐述:

 

 

处理对象流:

(序列化过程和反序列化过程)

 

  java.io包有两个序列化对象的类。ObjectOutputStream负责将对象写入字节流,ObjectInputStream从字节流重构对象。

    我们先了解ObjectOutputStream类吧。ObjectOutputStream类扩展DataOutput接口。

writeObject()方法是最重要的方法,用于对象序列化。如果对象包含其他对象的引用,则writeObject()方法递归序列化这些对象。每个ObjectOutputStream维护序列化的对象引用表,防止发送同一对象的多个拷贝。(这点很重要)由于writeObject()可以序列化整组交叉引用的对象,因此同一ObjectOutputStream实例可能不小心被请求序列化同一对象。这时,进行反引用序列化,而不是再次写入对象字节流。

下面,让我们从例子中来了解ObjectOutputStream这个类吧。

 

// 序列化 today's date 到一个文件中.

    FileOutputStream f = new FileOutputStream("tmp");

    ObjectOutputStream s = new ObjectOutputStream(f);

    s.writeObject("Today");

    s.writeObject(new Date());

    s.flush();

 

   现在,让我们来了解ObjectInputStream这个类。它与ObjectOutputStream相似。它扩展DataInput接口。ObjectInputStream中的方法镜像DataInputStream中读取Java基本数据类型的公开方法。readObject()方法从字节流中反序列化对象。每次调用readObject()方法都返回流中下一个Object。对象字节流并不传输类的字节码,而是包括类名及其签名。readObject()收到对象时,JVM装入头中指定的类。如果找不到这个类,则readObject()抛出ClassNotFoundException,如果需要传输对象数据和字节码,则可以用RMI框架。ObjectInputStream的其余方法用于定制反序列化过程。

例子如下:

 

//从文件中反序列化 string 对象和 date 对象

    FileInputStream in = new FileInputStream("tmp");

    ObjectInputStream s = new ObjectInputStream(in);

    String today = (String)s.readObject();

    Date date = (Date)s.readObject();

 

 

定制序列化过程:

 

 

序列化通常可以自动完成,但有时可能要对这个过程进行控制。java可以将类声明为serializable,但仍可手工控制声明为static或transient的数据成员。

例子:一个非常简单的序列化类。

 

public class simpleSerializableClass implements Serializable{

    String sToday="Today:";

    transient Date dtToday=new Date();

}

 

序列化时,类的所有数据成员应可序列化除了声明为transient或static的成员。将变量声明为transient告诉JVM我们会负责将变元序列化。将数据成员声明为transient后,序列化过程就无法将其加进对象字节流中,没有从transient数据成员发送的数据。后面数据反序列化时,要重建数据成员(因为它是类定义的一部分),但不包含任何数据,因为这个数据成员不向流中写入任何数据。记住,对象流不序列化static或transient。我们的类要用writeObject()与readObject()方法以处理这些数据成员。使用writeObject()与readObject()方法时,还要注意按写入的顺序读取这些数据成员。

关于如何使用定制序列化的部分代码如下:

 

//重写writeObject()方法以便处理transient的成员。

public void writeObject(ObjectOutputStream outputStream) throws IOException{

    outputStream.defaultWriteObject();//使定制的writeObject()方法可以

                        利用自动序列化中内置的逻辑。

    outputStream.writeObject(oSocket.getInetAddress());

    outputStream.writeInt(oSocket.getPort());

}

//重写readObject()方法以便接收transient的成员。

private void readObject(ObjectInputStream inputStream) throws IOException,ClassNotFoundException{

    inputStream.defaultReadObject();//defaultReadObject()补充自动序列化

    InetAddress oAddress=(InetAddress)inputStream.readObject();

    int iPort =inputStream.readInt();

    oSocket = new Socket(oAddress,iPort);

    iID=getID();

    dtToday =new Date();

}

 

 

完全定制序列化过程:

 

如果一个类要完全负责自己的序列化,则实现Externalizable接口而不是Serializable接口。Externalizable接口定义包括两个方法writeExternal()与readExternal()。利用这些方法可以控制对象数据成员如何写入字节流.类实现Externalizable时,头写入对象流中,然后类完全负责序列化和恢复数据成员,除了头以外,根本没有自动序列化。这里要注意了。声明类实现Externalizable接口会有重大的安全风险。writeExternal()与readExternal()方法声明为public,恶意类可以用这些方法读取和写入对象数据。如果对象包含敏感信息,则要格外小心。这包括使用安全套接或加密整个字节流。到此为至,我们学习了序列化的基础部分知识。关于序列化的高级教程,以后再述。