第
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
章 对象的容纳
一.
实现了两种定制的数据结构:哈希表(包括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,恶意类可以用这些方法读取和写入对象数据。如果对象包含敏感信息,则要格外小心。这包括使用安全套接或加密整个字节流。到此为至,我们学习了序列化的基础部分知识。关于序列化的高级教程,以后再述。