- 浏览: 687503 次
- 性别:
- 来自: 上海
文章分类
- 全部博客 (254)
- java分布式应用架构 (22)
- SSH框架整合 (6)
- java web 学习笔记 (49)
- java 学习笔记 (56)
- struts 2 学习 (6)
- Hibernate学习 (10)
- spring 学习 (2)
- 客户端编程(javascript) (4)
- IDE使用 (13)
- 生命 人生 (6)
- 系统维护 (3)
- 技术篇 (10)
- MySql (2)
- J2ME (1)
- java网络编程 (4)
- 数据库 (5)
- C/C++ (8)
- Oracle (7)
- 软件测试 (0)
- 软件的安装和部署 (0)
- Java快讯 (1)
- swt (1)
- Flex (1)
- 软件工程 (1)
- PostgreSQL (1)
- sql server2000 (2)
- 嵌入式数据库sqlite (5)
- J2EE (1)
- XML (1)
- ibatis3(MyBatis) (6)
- Linux&Unix (1)
- velocity (1)
- 回报社会 (4)
- 软件项目管理 (3)
- android研究 (3)
- C# (2)
- Objective-C (1)
- 音乐 (0)
- webx (1)
- JMS (1)
- maven软件项目管理 (1)
- 分布式服务 (0)
- 云平台 (0)
- 分布式存储 (1)
- 分布式系统架构 (0)
- 移动互联网 (1)
- ZooKeeper (1)
最新评论
-
liyys:
楼主,可不可以发这个项目的源码工程出来分享一下,少了几个类。楼 ...
仿照Hibernate实现一个SQLite的ORM框架 -
liyys:
少了一些类的源码没有粘贴出来
仿照Hibernate实现一个SQLite的ORM框架 -
honglei0412:
我使用的是这种方式获取db文件的目录但是 URL p = Fi ...
使用sqlite注意事项 -
honglei0412:
大侠 能不能说明下DbFile您是怎么做的吗?
使用sqlite注意事项 -
ahack:
刚写完mapping才发现早就有人写好了。仔细一看还都是针对的 ...
仿照Hibernate实现一个SQLite的ORM框架
Chap1 对象简介
1.抽象的过程
Alan Kay总结了Smalltalk的五项基本特征。这些特征代表了纯的面向对象的编程方法:
(1).万物皆对象。将对象想成一种特殊的变量;它存储数据,而且还可以让你“提要求”,命令它进行某些操作。从理论上讲,你可以把所有待解决的问题中的概念性组件(狗,建筑,服务等)都标识成程序里的对象。
(2).程序就是一组相互之间传递消息的对象。你只要向那个对象“发一个消息”,就能向它提出要求。更确切的说,你可以这样认为,消息是调用专属某个对象的方法的请求。
(3).每个对象都利用别的对象来组建它自己的记忆。换言之,你通过将已有的对象打成一个包,来创建新的对象。由此,你可以将程序的复杂性,隐藏在对象的简单性之下。
(4).对象都有类型。任何对象都是某个类的实例(instance of a class)。用以区分类的最突出的特点就是“你能传给它什么消息?”
(5).所有属于同一类型的对象能接受相同的消息。这种互换性(substitutability)是OOP最强大的功能之一。
Booch还给对象下了个更为简洁的定义:
对象有状态,行为和标识。
这就是说,对象可以有内部数据(状态),有方法(因而产生了行为),以及每个对象都能同其它对象区分开来--具体而言,每个对象在内存里都有唯一的地址。
这句话或许有点太过了。因为对象还能存在于另一台及其上以及不同的内存空间中,此外还能保存在硬盘上。在这种情况下,对象的身份就不能用内存地址,而必须要用别的方法来确定。
2.可凭借多态性相互替换的对象
非OOP的编译器的做法称为前绑定(early binding)。编译器会产生那个名字的函数的调用,而连接器负责将这个调用解析成须执行的代码的绝对地址。在OOP中,不到运行的时候,程序没法确定代码的地址,所以向泛型对象发送一个消息的时候,就要用到一些特别的手段。
OOP语言用了后绑定(late binding)的概念。当你向某个对象送了一个消息后,不到运行时,系统不能确定到底该调用哪段代码。编译器只保证这个方法存在,并且检查参数和返回值的类型(不这么做的语言属于弱类型weakly typed),但是它并不知道具体执行的是哪段代码。
在有些语言中,你必须明确申明,某个方法要用到后绑定的灵活性(C++用virtual关键字)。在这些语言中,方法不是默认地动态绑定的。而动态绑定是 Java的缺省行为,因此无需添加什么额外的关键词就能获得多态性。
将派生类当作它的基类来用的过程称为上传(upcast),反之称为下传(downcast)。下传所需的运行时检查会引起程序运行效率的降低,也加重了编程的负担。解决方案就是参数化类型(parameterized type)机制,即泛型。
3.Collection和迭代器
ArrayList和LinkedList,都是简单的线性序列,具有相同的接口和外部行为。对于ArrayList,随机访问是一种时间恒定的操作。然而对于LinkedList,随机访问和选取元素的代价会很大。另一方面,如果要在序列中插入元素,LinkedList的效率会比ArrayList的高出许多。
=============
Chap2 万物皆对象
1.数据存在哪里
数据可以存储在以下六个地方:
(1).寄存器(registers)。这是反应最快的存储,因为它处在CPU里。但寄存器数量有限,由编译器分配,你不能直接控制。
(2).栈(stack)。位于常规内存区里,CPU可以通过栈指针对它进行直接访问。栈指针下移就创建新的存储空间,上移就释放内存空间。这是仅次于寄存器的最快、最有效率的分配内存的方法。由于Java编译器必须生成能控制栈指针上下移的代码,所以程序编译的时候,那些将被存储在栈中的数据的大小和生命周期必须是已知的。Java把对象的reference存放在栈里。
(3).堆(heap)。这是一段多用途的内存池,所有Java对象都保存在这里。在堆中分配空间时,编译器无需知道该分配多少空间,或数据会在堆里待多长时间。但是其速度比分配栈的慢些。
(4).静态存储(static storage)。这里“静态”的意思是“在固定的位置”(尽管还是在RAM里面)。静态存储里面的数据在整个程序运行期间都能访问到。可以用 static关键词指明对象的某个元素是静态的,但是Java对象本身是决不会放到静态存储中去的。
(5).固定存储(constant storage)。常量值通常直接放在程序里。有时常量还能为自己设置界限,这样在嵌入式系统中,就能选择是不是把它们放到ROM里面去。
(6).非内存的存储(Non-RAM storage)。如果数据完全独立于程序,那么即使程序不运行,它也应该还在。对象被转化成某种能保存在其它介质上的东西,要用的时候,又能在内存里重建。Java提供了轻量级persistence的支持。
特例:primitive类型
primitive(原始)类型的变量直接保存值,并且存储在栈中。
高精度的数值
Java还包括两个能进行高精度算术运算的类:BigInteger和BigDecimal。
作用域
int x = 12;
{
int x = 100;//illegal
}
2.创建新的数据类型:类
只有在“变量被用作类的成员”时,Java才能确保它获得默认值。本地变量,没有这种保障。
不管在哪种情况下,Java在传递对象的时候,实际上是在传递reference。
============
Chap3 控制程序流程
1.运算符
逗号运算符
Java里面,唯一一个把逗号当运算符用的地方是for循环。
String的+运算符
加号(+)用在String上的时候,如果表达式中有String,那么Java编译器会把其他的操作数都转换成String。
Java没有sizeof
C和C++的sizeof()用于获取数据要占用多少字节的内存,需要sizeof的主要原因是为了移植。相同的数据类型在不同的机器上占用的内存长度可能会不一样。
Java没有移植的问题,因此不需要sizeof,所有数据类型在所有的机器上都是相同的。
运算符的总结
在进行数学运算或混和赋值的时候,char,byte,short,都会先进行提升,运算结果也是int。如果要把结果赋给原先那个变量,就必须明确地进行类型转换。
除了boolean之外,所有的primitive类型都能被转换成其它的primitive类型。
2.执行控制
Java不允许把数字当作boolean用,尽管C和C++允许这么做(非零值表示true,零表示false)。
Java里,唯一能放标签的地方,就是在循环语句的外面。而且必须直接放--在循环语句和标签之间不能有任何东西。而这么做的唯一理由就是,你会嵌套多层循环或选择。因为通常情况下break和continue关键词只会中断当前循环,而用了标签后,就会退到label所在的地方。
label1:
outer-iteration{
inner-iteration{
break;//中断内循环,退到外循环
continue;//中断本次内循环,重新移到内循环开始处,执行下次内循环
continue label1;//中断本次外循环,移到外循环开始处,重新执行下次外循环
break label1;//退出外循环,执行循环以后的语句
}
}
如果退出循环或选择的同时,还要退出方法,可以直接使用return。
continue,break以及label的规则:
(1).普通的continue会退到内部循环的最开始,然后继续执行内部循环。
(2).带标签的continue会跳转到标签,并且重新进入直接跟在标签后面的循环。
(3).break会从循环的“底部溜出去”。
(4).带标签的break会从由这个标签标识的循环的“底部溜出去”。
在Java里能使用标签的唯一理由就是,在嵌套循环的同时要用break和continue退出多层循环。
3.switch
switch会根据整数表达式的值(可以是char)决定应该运行哪些代码。
找到匹配的值后,就会执行相应的case语句,不会再进行比较。通常case语句应该以break结束。否则会直接执行下一个case语句,而不会再次进行匹配。如果没有匹配的case,则执行default语句。
计算细节
将float或double转换成整数的时候,它总是将后面的小数截去。
Math.random()会生成一个double,值域是[0,1)。
============
Chap4 初始化与清理
1.用构造函数确保初始化
构造函数的名字必须与类的名字大小写完全相同。构造函数本身没有返回值,虽然new表达式会返回新创建对象的reference。
2.默认的构造函数
如果你写了一个没有构造函数的类,编译器会自动创建一个默认的构造函数(不带参数)。但是,只要你定义了构造函数,不管带不带参数,编译器就不会再自动合成默认构造函数了。
3.this关键字与构造函数
在类的构造函数中,可以用this(arg)调用另一个构造函数,但是不能调用两个构造函数(即this()在构造函数中只能出现0或1次)。此外,必须在程序代码的最前面调用构造函数。在非构造函数的方法里,不能用this()调用同一个类的构造函数。
4.static的含义
类的static方法只能访问其他的static方法和static数据成员,不能在static方法调用非static方法,但是反过来是可以的。
但是,如果可以传一个对象的reference给static方法,就可以通过这个reference调用非static方法和非static数据成员。但要达到这个目的,通常应该使用非static的方法。
5.清理:finalize和垃圾回收
java提供finalize()方法,垃圾回收器准备释放内存的时候,会先调用finalize()。
(1).对象不一定会被回收。
(2).垃圾回收不是拆构函数。
(3).垃圾回收只与内存有关。
(4).垃圾回收和finalize()都是靠不住的,只要JVM还没有快到耗尽内存的地步,它是不会浪费时间进行垃圾回收的。
6.成员的初始化
方法中的局部变量,使用前必须进行初始化,否则编译器会给出错误消息。
类的成员数据会被自动初始化,对于primitive变量,会被清零(char型也为0);对于对象reference,会被赋值为null。
7.指定初始化
对类成员数据进行初始化时,可以调用方法获取初始值,该方法如果有参数,参数不能是类的其他尚未初始化的数据成员。可以这样:
class Test{
int i=f();
int j=g(i);
}
不能这样:
class Test{
int j=g(i);//i尚未初始化
int i=f();
}
8.初始化的顺序
对类而言,初始化的顺序是由变量在类里定义的顺序决定的,变量的初始化会优先于任何方法,包括构造函数。
创建对象的过程(小结,以Dog类为例)
(1).第一次创建Dog类的对象(构造函数实际上是static方法),或者第一次访问Dog类的static的方法或字段的时候,Java解释器会搜寻classpath,找到Dog.class.
(2).装载了Dog.class之后,(创建了Class对象之后),会对所有的static数据进行初始化。这样第一个装载Class对象的时候,会先进行static成员的初始化。
(3).用new Dog()创建新对象的时候,Dog对象的构建进程会先在堆(heap)里为对象分配足够的内存。
(4).这块内存先被清零,自动把Dog对象的primitive类型的成员赋缺省值(对于数字是零,或是相应的boolean和char),将 reference设成null。
(5).执行定义成员数据时所作的初始化。
(6).执行构造函数。可能牵涉到继承关系的很多活动。
9.非静态的实例初始化
实例初始化(instance initialization)语句,除了没有static关键字,其他与静态初始化没有两样。这种语法为匿名内部类(anonymous inner class)的初始化提供了必不可少的支持。
10.数组的初始化
Java可以将一个数组赋给另外一个,实际上是复制reference.
可以用花括号括起来的对象列表初始化对象数组。有两种形式:
Integer[] a = {
new Integer(1),
new Integer(2),
};
Integer[] b = new Integer[]{
new Integer(1),
new Integer(2),
};
列表的最后一个逗号是可选的,这个特性能使长列表的维护工作变得简单一些。
===============
Chap5 隐藏实现
1.package:类库的单元
Java的源代码文件通常被称为编译单元(compilation unit),必须是一个以.java结尾的文件,其中必须有一个与文件名相同的public类(大小写也必须相同,但不包括.java扩展名)。每个编译单元只能有一个public类。
编译.java文件的时候,里面的每个类都会产生输出,文件名是类名,扩展名是.class。
创建独一无二的package名字
package语句必须是文件里的第一个非注释行。
如果使用JAR文件打包package,必须把文件名放到CLASSPATH里面。
冲突
如果两个import语句所引入的类库都包含一个同名的类,只要不写会引起冲突的代码(不使用这个同名的类),就一切OK,否则编译器会报错。
不论哪种对象,只要放进了String的表达式,就会被强制转化成该对象的String表示形式。如:
System.out.print(""+100);//Force it to be a String
2.Java的访问控制符
Java的一个访问控制符只管它所定义的这一项,而C++的访问控制符会一直管下去,直到出现另一个。
private:除非是用成员所在类的方法,否则一律不得访问。
package访问权限(package access,有时也称为“friendly”),是默认的访问权限,没有关键词。同属一个package的类都能访问这个成员,但是对于不属于这个 package的类来说,这个成员就是private的。
protected:继承的访问权限。protected也赋予成员package权限--同一个package里的其它类也可以访问protected 元素。除此之外,继承类(不管是否在同一个package)也可以访问protected成员。
public:访问接口的权限,任何类都能访问。
3.类的访问权限
类只有两种访问控制权,public和package。(inner class 可以是private或protected)
每个编译单元(文件)只能有一个public类,也可以没有public类(不常见)。public类的名字必须和编译单元文件名大小写完全相同。如果没有public类,就可以随意给编译单元文件起名。
对于package权限的类,通常应该将方法也设成package权限。
如果不写类的访问控制符,默认是package权限的。package中的任何一个类都能创建这个类的对象,但是package以外的类就不行了。但是,如果这个类有一个public 的static成员,那么即使客户程序员不能创建这个类的对象,他们也还可以访问这个static成员。
==============
Chap6 复用类
1.合成所使用的语法
对象的reference作为类的成员时,会被自动初始化为null。此外可以在三个时间对类的reference成员进行初始化:
(1)在定义对象reference的时候。这就意味着,在类的构造函数调用前,已经初始化完毕了。
private String a=new String("hello"), b="world";
(2)在类的构造函数。
(3)在即将使用对象reference之前。这被称为lazy或delayed initialization,如果创建对象的代价很大,或不是每次都需要创建对象,这种做法就能降低程序开销。
2.基类的初始化
当你创建一个派生类对象的时候,这个对象里面还有一个基类的子对象(subobject)。
对于默认构造函数,即无参构造函数,Java会让派生类的构造函数自动地调用基类的构造函数。基类会在派生类的构造函数调用它之前进行初始化。
对于带参数的构造函数,必须用super关键字以及合适的参数明确地调用基类构造函数,super()语句必须是派生类构造函数的第一条语句。
3.确保进行妥善的清理
不要依赖垃圾回收器去做任何与内存回收无关的事情。如果要进行清理,一定要自己写清理方法,别去用finalize()。
清理的顺序:先按照创建对象的相反顺序进行类的清理,然后调用基类的清理方法。
4.名字的遮盖(重载方法的覆盖)
如果Java的基类里有一个被重载了好几次的方法,那么在派生类里重新定义那个方法,是不会把基类里定义的任何一个给覆盖掉的。(在C++里,就会把基类方法全都隐藏起来)
5.用合成还是继承
合成用于新类要使用旧类的功能,而不是其接口的场合。继承则是要对已有的类做一番改造,以获得一个特殊版本,即将一个较为抽象的类改造成能适用于某些特定需求的类。
合成要表达的是“有(has-a)”关系,继承要表达的是一种“是(is-a)”关系。
判断该用合成还是继承时,可以问一下是不是会把新类上传给基类。如果必须上传,那么继承就是必须的。
6.final关键词
6.1 final的数据
常量能用于两种情况:(1)编译时的常量(compile-time constant),这样就再也不能改了;(2)运行时初始化的值,这个值你以后不想改了。
如果是编译时常量,编译器会把常量放到表达式中,可以降低运行时的开销。Java中这种常量必须是primitive的,要用final表示,这种常量的赋值必须在定义的时候进行。
当final修饰对象的reference时,表示reference是常量,初始化的时候,一旦将reference指向了某个对象,那么它就再也不能指向别的对象了。但是这个对象本身是可以修改的。
final int v1 = 10;//compile-time constants
final int v2 = rand.nextInt(100);//
final Value v3 = new Value();//v3的数据成员是可变的
final int[] arr={1,2,3};//arr的元素是可变的,如a[i]++;
空白的final数据(Blank finals),是指声明了final成员,却没有在声明时赋值。编译器会强制在构造函数中初始化final数据。通过使用带参数的构造函数,根据参数对空白的final数据进行初始化,可以在保持final数据不变性的同时,提供一定的灵活性。
可以把方法的参数声明为final的。
6.2 final方法
使用final方法有两个目的。第一,可以禁止派生类修改方法。第二,效率。对于final方法,编译器会把调用转换成“内联(inline)”,即用方法本身的拷贝来代替方法的调用。但是,如果方法很大,程序会很快膨胀,于是内联也不会带来什么性能的改善。
只有是基类接口里的东西才能被覆写。如果基类的方法是private的,那它就不属于基类的接口。即使在派生类里创建了一个同名的方法,它同基类中可能同名的private方法没有任何联系。
6.3 final类
把类定义成final的,可以禁止继承这个类。final类的数据可以是final的,也可以不是。final类的方法都隐含地变成final了。
7.初始化与类的装载
在传统的编程语言中,程序启动的时候都是一次装载所有的东西,然后进行初始化,接下来再开始执行。这些语言必须仔细控制初始化的顺序。
Java采用了一种新的装载模式。编译之后每个类都保存在它自己的文件里。不到需要的时候,这个文件是不会装载的。即“类的代码会在它们第一次使用的时候装载”,第一次访问static成员或创建对象的时候。
继承情况下的初始化=====
首次使用类的时候,装载器(loader)就会寻找类的.class文件,转载过程中,会依次追溯装载基类(不管是否创建基类对象,这个过程都会发生)。下一步,会执行“根基类(root base class)”的static初始化,然后是下一个派生类的static初始化,以此类推。
所有类都装载结束,就可以创建对象了。首先对象里所有成员数据会被初始化为缺省值,这个过程是一瞬间完成的,对象的内存会被设置成二进制0。然后开始构造基类,基类的构造过程及顺序与派生类相同,先对基类的变量按字面顺序进行初始化,再调用基类的构造函数(调用是自动发生的,但你可以用super关键字指定要调用基类的哪个构造函数)。之后会对派生类的变量按定义的顺序进行初始化,最后执行派生类构造函数其余代码。
=============
Chap7 多态性
1.方法调用的绑定
将方法的调用连到方法本身被称为“绑定(binding)”。当绑定发生在程序运行之前时(由编译器或连接器负责),被称作“前绑定(early binding)”。
“后绑定(late binding)”指在程序运行时,根据对象类型决定该绑定哪个方法。后绑定也被称为“动态绑定(dynamic binding)”或“运行时绑定(run-time binding)”。
除了static和final方法(private方法隐含有final的意思),Java的所有方法都采用后绑定。
2.错误:“覆写”private的方法
public class Parent{
private void f(){
//...
}
public static void main(String[] args){
Parent pa = new Child();
pa.f();//执行的是Parent.f()
}
}
class Child extends Parent{
public void f(){
//...
}
}
应该避免用基类的private的方法名去命名派生类中方法。
只有非private的方法才能被覆写。
3.继承与清理
清理的顺序应该与初始化的顺序相反。对数据成员而言,清理顺序就应该与声明的顺序相反。
在派生类的清理方法(比如dispose())的最后,应该调用基类的清理方法。
4.多态方法在构造函数中的行为
如果在构造函数里调用了动态绑定的方法,那么它会调用那个覆写后的版本。
abstract class Glyph{
abstract void draw();
Glyph(){
System.out.println("Glyph() before draw()");
draw();
System.out.println("Glyph() after draw()");
}
}
class RoundGlyph extends Glyph{
private int radius = 1;
RoundGlyph(int r){
radius = r;
System.out.println("RoundGlyph.RoundGlyph(),radius = " + radius);
}
void draw(){
System.out.println("RoundGlyph.draw(),radius = " + radius);
}
}
public class PolyConstructors{
public static void main(String[] args){
new RoundGlyph(5);
}
}
//输出========
//Glyph() before draw()
//RoundGlyph.draw(), radius = 0
//Glyph() after draw()
//RoundGlyph.RoundGlyph(), radius = 5
上面程序中,Glyph的构造函数调用了draw(),而这个调用最后落到了RoundGlyph.draw()。而此时radius的值还没有被初始化为1,还是0。
真正的对象初始化过程:
(1)类加载结束,并且static初始化结束后,在进行其它工作之前,分配给这个对象的内存会被初始化为二进制的0。
(2)构造基类,调用基类的构造函数。这时会调用被覆写的draw()方法(在调用RoundGlyph的构造函数之前),受第一步的影响,radius 的值还是0。
(3)数据成员按照声明的顺序进行初始化。
(4)调用派生类的构造函数的正文。
5.用继承来进行设计
一般准则:使用继承来表示不同的行为,使用成员数据(合成)来表示不同的状态。
6.总结
人们通常会把多态性同Java的那些非面向对象的特性相混淆,比如方法的重载,它常常会被当作面向对象的特性介绍给大家。千万别上当:不是后绑定的,就不是多态性。
===========
Chap8 接口与内部类
1.接口(interface)
interface也可以包含数据成员,但是它天生就是public,static和final的。
interface默认是package权限的,只能用于同一个package。也可以加上public(只有保存在同名文件里的interface才可以加)。
可以把interface里的方法声明成public的,但是即便不写public关键词,这些方法也自动是public的。当implements一个 interface的时候,必须把这个interface的方法定义成public的。
2.Java的“多重继承”
由于interface不带任何“实现”--也就是说interface和内存无关--因此不会有谁去阻挠interface之间的结合。
到底是用interface,还是用abstract类?只要基类的设计里面可以不包括方法和成员变量的定义,就应该优先使用interface。只有在不得不定义方法或成员变量的情况下,才把它改成abstract类。
3.合并接口时的名字冲突
在要合并的接口里面放上同名的方法,通常会破坏程序的可读性,应该避免。
4.用继承扩展interface
可以用继承往interface里添加新的方法,也可以把多个interface合并成一个新的interface。使用extends关键词,多个“基接口(base interface)”之间用逗号分割。
interface Vampire extends IMonster, IUndead{}
5.常量的分组
由于interface的数据成员自动就是public,static和final的,因此interface是一种非常方便的创建一组常量值的工具。这点同C和C++的enum很相似,但没有enum那样的类型安全。
6.接口的嵌套
接口既可以嵌套在类里,也可以嵌套在接口里面。
实现接口的时候,不一定要实现嵌套在里面的接口。同样,private接口只能在定义它的类里实现。
7.内部类(inner class)
内部类与合成是截然不同的。
除非是在“宿主类(outer class)”的非static方法里面,否则无论在哪里创建内部类的对象,都必须用OuterClassname.InnerClassName的形式来表示这个对象的类型。
8.内部类与上传
内部类可以被定义成private或protected,非内部类只可能是public或package权限的。
9.在方法和作用域里的内部类
在方法的某个作用域里定义的内部类,比如if语句里,并不意味着这个类的创建是有条件的--它会同别的东西一起编译。但是,这个类的访问范围仅限于定义它的那个作用域。除此之外,它同普通的类没有区别。
10.匿名内部类
public class Parcel6{
public Contents cont(){
return new Contents(){
private int i = 11;
public value(){return i;}
};//Semicolon required
}
}
上例中return new Contents(){...}语句表达的是:创建一个继承Contents的匿名类的对象。new语句所返回的reference对自动上传到 Contents。这个return语句是如下代码的简化形式:
class MyContents implements Contents{
private int i = 11;
public int value(){return i;}
}
return new MyContents();
这个匿名内部类是通过默认构造函数来创建Contents的,如果基类需要的是一个带参数的构造函数,可以直接将参数传给基类的构造函数,return new Contents(x){...}。
如果在定义匿名内部类的时候,要用到外面的对象,编译器会要求把这个参数的reference声明成final的。
public class Parcel8{
//Argument must be final to use inside anonymous inner class
public Destination dest(final String ds){
return new Destination(){
private String lbl = ds;
public String readLabel(){return lbl;}
};
}
}
不能在匿名内部类里创建构造函数(因为它根本就没有名字),但是可以通过“实例初始化(instance initialization)”(与static初始化对应),进行一些类似构造函数的操作。实际上实例初始化过程就是匿名内部类的构造函数。但它的功能是有限的,由于不能重载实例初始化,因此只能有一个构造函数。
public Base getBase(int i){
return new Base(i){
{
System.out.println("Inside instance initializer.");
}
public void f(){
System.out.println("In anonymous f().");
}
};
}
11.与宿主类的关系
内部类能访问宿主类的所有成员。内部类对象里存在一个隐蔽的指向宿主类对象的reference,由编译器处理。
12.嵌套类(静态内部类)
如果不需要这种“内部类对象和宿主类对象之间的”联系,可以把内部类定义成static的,通常被称作“嵌套类(nested class)”。嵌套类的意思是:
(1)无需宿主类的对象就能创建嵌套类的对象。
(2)不能在嵌套类的对象里面访问非static的宿主类对象。
此外,嵌套类同普通的内部类还有一点不同。普通的内部类的成员数据和方法只能到类的外围这一层,因此普通的内部类里不能有static数据,static 数据成员或嵌套类。但是,这些东西在嵌套类里都可以有。
嵌套类可以是interface的一部分。
每个类可以带一个供测试的main()方法,但编译后,会产生额外的代码。可以考虑在嵌套类里创建供测试的main()。编译后会产生单独的名称如 Test$NestedTester.class的文件,发布的时候可以删除这个文件。
13.引用宿主类的对象
在内部类里通过宿主类名字后面加句点再加this来表示宿主类对象的reference。比如类Sequence.Selctor里,可以用 Sequence.this来获取它所保存的宿主类Sequence对象的reference。
要在其他地方创建内部类的对象,就必须在new表达式里面给出宿主类对象的reference。如:
Parcel11 p = new Parcel11();
//Must use instance of outer class to create an instance of the inner class.
Parcel11.Contents c = p.new Contents();
14.在多层嵌套的类里向外访问
内部类的嵌套层次不是问题--它可以透明地访问它的各级宿主类的成员。
class MNA{
private void f(){}
class A{
private void g(){}
public class B{
void h(){
g();
f();
}
}
}
}
public class MultiNestingAccess{
public static void main(String[] args){
MNA mna = new MNA();
MNA.A mnaa = mna.new A();
MNA.A.B mnaab = mnaa.new B();
mnaab.h();
}
}
".new"语句指明了正确的作用域,因此无需在调用构造函数的语句里再限定类的名字了。
15.继承内部类
class WithInner(){
class inner{}
}
public class InheritInner extends WithInner.Inner{
//!InheritIner(){}//Won't compile
InheritIner(WithInner wi){
wi.super();
}
public static void main(String[] args){
WithInner wi = new WithInner();
InheritInner ii = new InheritInner(wi);
}
}
InheritInner继承的只是内部类,默认的构造函数不能通过编译。必须传递宿主类对象的reference。此外,必须在构造函数里面使用这种语法:enclosingClassReference.super();这样才能提供那个必须的reference,才能编译通过。
16.内部类可以被覆写吗
像覆写宿主类的方法那样去“覆写”内部类,是不会有实际效果的。但是,可以在继承类的内部类明确继承基类的内部类。
public class BigEgg2 extends Egg2{
public class Yolk extends Egg2.Yolk{
}
}
17.本地内部类(Local inner classes)
本地内部类是在代码段,通常是方法的正文部分创建的。本地内部类不能有访问控制符,因为它并不属于宿主类,但它可访问当前代码段的final变量,以及宿主类的所有成员。
由于本地内部类的名字在代码段(通常是一个方法)外面是没法访问的,因此选择本地内部类来代替匿名内部类的一个理由就是,需要一个有名字的构造函数,并要重载这个构造函数,因为匿名内部类只能进行实例初始化。另一个理由是,需要创建多个那种类的对象。
18.内部类的标识符
宿主类名字,加上"$",再加上内部类的名字。如果是匿名内部类,编译器会直接用数字来表示。
如:OuterClass$InnerClass.class,OuterClass$2.class。
19.为什么要有内部类?
每个内部类都可以独立的继承某个“实现(implementation)”。内部类能在事实上继承多个实体类或abstract类。可以把它当作彻底解决多重继承问题的办法,接口部分地解决了这个问题。
========
Chap9 用异常来处理错误
1.重抛异常
如果直接重抛当前的异常,则printStackTrace()所打印出来的那些保存在异常对象里的信息,还会指向异常发生的地方,不会被指到重抛异常的地点。如果要装载新的栈轨迹信息,你可以调用fillInStackTrace()。
try{
f();
} catch(Exception e){
e.printStackTrace();
//throw e;//17
throw e.fillInStackTrace();//18
}
也可以抛出一个与捕捉到的异常不同的异常。这么做的效果同使用fillInstackTrace()的差不多--异常最初在哪里发生的信息被扔了,现在里面保存的时抛出新异常的地点。
2.异常链
异常链可以在捕捉到一个异常并且抛出另一个异常的时候,仍然保存前一个异常的信息。
1.抽象的过程
Alan Kay总结了Smalltalk的五项基本特征。这些特征代表了纯的面向对象的编程方法:
(1).万物皆对象。将对象想成一种特殊的变量;它存储数据,而且还可以让你“提要求”,命令它进行某些操作。从理论上讲,你可以把所有待解决的问题中的概念性组件(狗,建筑,服务等)都标识成程序里的对象。
(2).程序就是一组相互之间传递消息的对象。你只要向那个对象“发一个消息”,就能向它提出要求。更确切的说,你可以这样认为,消息是调用专属某个对象的方法的请求。
(3).每个对象都利用别的对象来组建它自己的记忆。换言之,你通过将已有的对象打成一个包,来创建新的对象。由此,你可以将程序的复杂性,隐藏在对象的简单性之下。
(4).对象都有类型。任何对象都是某个类的实例(instance of a class)。用以区分类的最突出的特点就是“你能传给它什么消息?”
(5).所有属于同一类型的对象能接受相同的消息。这种互换性(substitutability)是OOP最强大的功能之一。
Booch还给对象下了个更为简洁的定义:
对象有状态,行为和标识。
这就是说,对象可以有内部数据(状态),有方法(因而产生了行为),以及每个对象都能同其它对象区分开来--具体而言,每个对象在内存里都有唯一的地址。
这句话或许有点太过了。因为对象还能存在于另一台及其上以及不同的内存空间中,此外还能保存在硬盘上。在这种情况下,对象的身份就不能用内存地址,而必须要用别的方法来确定。
2.可凭借多态性相互替换的对象
非OOP的编译器的做法称为前绑定(early binding)。编译器会产生那个名字的函数的调用,而连接器负责将这个调用解析成须执行的代码的绝对地址。在OOP中,不到运行的时候,程序没法确定代码的地址,所以向泛型对象发送一个消息的时候,就要用到一些特别的手段。
OOP语言用了后绑定(late binding)的概念。当你向某个对象送了一个消息后,不到运行时,系统不能确定到底该调用哪段代码。编译器只保证这个方法存在,并且检查参数和返回值的类型(不这么做的语言属于弱类型weakly typed),但是它并不知道具体执行的是哪段代码。
在有些语言中,你必须明确申明,某个方法要用到后绑定的灵活性(C++用virtual关键字)。在这些语言中,方法不是默认地动态绑定的。而动态绑定是 Java的缺省行为,因此无需添加什么额外的关键词就能获得多态性。
将派生类当作它的基类来用的过程称为上传(upcast),反之称为下传(downcast)。下传所需的运行时检查会引起程序运行效率的降低,也加重了编程的负担。解决方案就是参数化类型(parameterized type)机制,即泛型。
3.Collection和迭代器
ArrayList和LinkedList,都是简单的线性序列,具有相同的接口和外部行为。对于ArrayList,随机访问是一种时间恒定的操作。然而对于LinkedList,随机访问和选取元素的代价会很大。另一方面,如果要在序列中插入元素,LinkedList的效率会比ArrayList的高出许多。
=============
Chap2 万物皆对象
1.数据存在哪里
数据可以存储在以下六个地方:
(1).寄存器(registers)。这是反应最快的存储,因为它处在CPU里。但寄存器数量有限,由编译器分配,你不能直接控制。
(2).栈(stack)。位于常规内存区里,CPU可以通过栈指针对它进行直接访问。栈指针下移就创建新的存储空间,上移就释放内存空间。这是仅次于寄存器的最快、最有效率的分配内存的方法。由于Java编译器必须生成能控制栈指针上下移的代码,所以程序编译的时候,那些将被存储在栈中的数据的大小和生命周期必须是已知的。Java把对象的reference存放在栈里。
(3).堆(heap)。这是一段多用途的内存池,所有Java对象都保存在这里。在堆中分配空间时,编译器无需知道该分配多少空间,或数据会在堆里待多长时间。但是其速度比分配栈的慢些。
(4).静态存储(static storage)。这里“静态”的意思是“在固定的位置”(尽管还是在RAM里面)。静态存储里面的数据在整个程序运行期间都能访问到。可以用 static关键词指明对象的某个元素是静态的,但是Java对象本身是决不会放到静态存储中去的。
(5).固定存储(constant storage)。常量值通常直接放在程序里。有时常量还能为自己设置界限,这样在嵌入式系统中,就能选择是不是把它们放到ROM里面去。
(6).非内存的存储(Non-RAM storage)。如果数据完全独立于程序,那么即使程序不运行,它也应该还在。对象被转化成某种能保存在其它介质上的东西,要用的时候,又能在内存里重建。Java提供了轻量级persistence的支持。
特例:primitive类型
primitive(原始)类型的变量直接保存值,并且存储在栈中。
高精度的数值
Java还包括两个能进行高精度算术运算的类:BigInteger和BigDecimal。
作用域
int x = 12;
{
int x = 100;//illegal
}
2.创建新的数据类型:类
只有在“变量被用作类的成员”时,Java才能确保它获得默认值。本地变量,没有这种保障。
不管在哪种情况下,Java在传递对象的时候,实际上是在传递reference。
============
Chap3 控制程序流程
1.运算符
逗号运算符
Java里面,唯一一个把逗号当运算符用的地方是for循环。
String的+运算符
加号(+)用在String上的时候,如果表达式中有String,那么Java编译器会把其他的操作数都转换成String。
Java没有sizeof
C和C++的sizeof()用于获取数据要占用多少字节的内存,需要sizeof的主要原因是为了移植。相同的数据类型在不同的机器上占用的内存长度可能会不一样。
Java没有移植的问题,因此不需要sizeof,所有数据类型在所有的机器上都是相同的。
运算符的总结
在进行数学运算或混和赋值的时候,char,byte,short,都会先进行提升,运算结果也是int。如果要把结果赋给原先那个变量,就必须明确地进行类型转换。
除了boolean之外,所有的primitive类型都能被转换成其它的primitive类型。
2.执行控制
Java不允许把数字当作boolean用,尽管C和C++允许这么做(非零值表示true,零表示false)。
Java里,唯一能放标签的地方,就是在循环语句的外面。而且必须直接放--在循环语句和标签之间不能有任何东西。而这么做的唯一理由就是,你会嵌套多层循环或选择。因为通常情况下break和continue关键词只会中断当前循环,而用了标签后,就会退到label所在的地方。
label1:
outer-iteration{
inner-iteration{
break;//中断内循环,退到外循环
continue;//中断本次内循环,重新移到内循环开始处,执行下次内循环
continue label1;//中断本次外循环,移到外循环开始处,重新执行下次外循环
break label1;//退出外循环,执行循环以后的语句
}
}
如果退出循环或选择的同时,还要退出方法,可以直接使用return。
continue,break以及label的规则:
(1).普通的continue会退到内部循环的最开始,然后继续执行内部循环。
(2).带标签的continue会跳转到标签,并且重新进入直接跟在标签后面的循环。
(3).break会从循环的“底部溜出去”。
(4).带标签的break会从由这个标签标识的循环的“底部溜出去”。
在Java里能使用标签的唯一理由就是,在嵌套循环的同时要用break和continue退出多层循环。
3.switch
switch会根据整数表达式的值(可以是char)决定应该运行哪些代码。
找到匹配的值后,就会执行相应的case语句,不会再进行比较。通常case语句应该以break结束。否则会直接执行下一个case语句,而不会再次进行匹配。如果没有匹配的case,则执行default语句。
计算细节
将float或double转换成整数的时候,它总是将后面的小数截去。
Math.random()会生成一个double,值域是[0,1)。
============
Chap4 初始化与清理
1.用构造函数确保初始化
构造函数的名字必须与类的名字大小写完全相同。构造函数本身没有返回值,虽然new表达式会返回新创建对象的reference。
2.默认的构造函数
如果你写了一个没有构造函数的类,编译器会自动创建一个默认的构造函数(不带参数)。但是,只要你定义了构造函数,不管带不带参数,编译器就不会再自动合成默认构造函数了。
3.this关键字与构造函数
在类的构造函数中,可以用this(arg)调用另一个构造函数,但是不能调用两个构造函数(即this()在构造函数中只能出现0或1次)。此外,必须在程序代码的最前面调用构造函数。在非构造函数的方法里,不能用this()调用同一个类的构造函数。
4.static的含义
类的static方法只能访问其他的static方法和static数据成员,不能在static方法调用非static方法,但是反过来是可以的。
但是,如果可以传一个对象的reference给static方法,就可以通过这个reference调用非static方法和非static数据成员。但要达到这个目的,通常应该使用非static的方法。
5.清理:finalize和垃圾回收
java提供finalize()方法,垃圾回收器准备释放内存的时候,会先调用finalize()。
(1).对象不一定会被回收。
(2).垃圾回收不是拆构函数。
(3).垃圾回收只与内存有关。
(4).垃圾回收和finalize()都是靠不住的,只要JVM还没有快到耗尽内存的地步,它是不会浪费时间进行垃圾回收的。
6.成员的初始化
方法中的局部变量,使用前必须进行初始化,否则编译器会给出错误消息。
类的成员数据会被自动初始化,对于primitive变量,会被清零(char型也为0);对于对象reference,会被赋值为null。
7.指定初始化
对类成员数据进行初始化时,可以调用方法获取初始值,该方法如果有参数,参数不能是类的其他尚未初始化的数据成员。可以这样:
class Test{
int i=f();
int j=g(i);
}
不能这样:
class Test{
int j=g(i);//i尚未初始化
int i=f();
}
8.初始化的顺序
对类而言,初始化的顺序是由变量在类里定义的顺序决定的,变量的初始化会优先于任何方法,包括构造函数。
创建对象的过程(小结,以Dog类为例)
(1).第一次创建Dog类的对象(构造函数实际上是static方法),或者第一次访问Dog类的static的方法或字段的时候,Java解释器会搜寻classpath,找到Dog.class.
(2).装载了Dog.class之后,(创建了Class对象之后),会对所有的static数据进行初始化。这样第一个装载Class对象的时候,会先进行static成员的初始化。
(3).用new Dog()创建新对象的时候,Dog对象的构建进程会先在堆(heap)里为对象分配足够的内存。
(4).这块内存先被清零,自动把Dog对象的primitive类型的成员赋缺省值(对于数字是零,或是相应的boolean和char),将 reference设成null。
(5).执行定义成员数据时所作的初始化。
(6).执行构造函数。可能牵涉到继承关系的很多活动。
9.非静态的实例初始化
实例初始化(instance initialization)语句,除了没有static关键字,其他与静态初始化没有两样。这种语法为匿名内部类(anonymous inner class)的初始化提供了必不可少的支持。
10.数组的初始化
Java可以将一个数组赋给另外一个,实际上是复制reference.
可以用花括号括起来的对象列表初始化对象数组。有两种形式:
Integer[] a = {
new Integer(1),
new Integer(2),
};
Integer[] b = new Integer[]{
new Integer(1),
new Integer(2),
};
列表的最后一个逗号是可选的,这个特性能使长列表的维护工作变得简单一些。
===============
Chap5 隐藏实现
1.package:类库的单元
Java的源代码文件通常被称为编译单元(compilation unit),必须是一个以.java结尾的文件,其中必须有一个与文件名相同的public类(大小写也必须相同,但不包括.java扩展名)。每个编译单元只能有一个public类。
编译.java文件的时候,里面的每个类都会产生输出,文件名是类名,扩展名是.class。
创建独一无二的package名字
package语句必须是文件里的第一个非注释行。
如果使用JAR文件打包package,必须把文件名放到CLASSPATH里面。
冲突
如果两个import语句所引入的类库都包含一个同名的类,只要不写会引起冲突的代码(不使用这个同名的类),就一切OK,否则编译器会报错。
不论哪种对象,只要放进了String的表达式,就会被强制转化成该对象的String表示形式。如:
System.out.print(""+100);//Force it to be a String
2.Java的访问控制符
Java的一个访问控制符只管它所定义的这一项,而C++的访问控制符会一直管下去,直到出现另一个。
private:除非是用成员所在类的方法,否则一律不得访问。
package访问权限(package access,有时也称为“friendly”),是默认的访问权限,没有关键词。同属一个package的类都能访问这个成员,但是对于不属于这个 package的类来说,这个成员就是private的。
protected:继承的访问权限。protected也赋予成员package权限--同一个package里的其它类也可以访问protected 元素。除此之外,继承类(不管是否在同一个package)也可以访问protected成员。
public:访问接口的权限,任何类都能访问。
3.类的访问权限
类只有两种访问控制权,public和package。(inner class 可以是private或protected)
每个编译单元(文件)只能有一个public类,也可以没有public类(不常见)。public类的名字必须和编译单元文件名大小写完全相同。如果没有public类,就可以随意给编译单元文件起名。
对于package权限的类,通常应该将方法也设成package权限。
如果不写类的访问控制符,默认是package权限的。package中的任何一个类都能创建这个类的对象,但是package以外的类就不行了。但是,如果这个类有一个public 的static成员,那么即使客户程序员不能创建这个类的对象,他们也还可以访问这个static成员。
==============
Chap6 复用类
1.合成所使用的语法
对象的reference作为类的成员时,会被自动初始化为null。此外可以在三个时间对类的reference成员进行初始化:
(1)在定义对象reference的时候。这就意味着,在类的构造函数调用前,已经初始化完毕了。
private String a=new String("hello"), b="world";
(2)在类的构造函数。
(3)在即将使用对象reference之前。这被称为lazy或delayed initialization,如果创建对象的代价很大,或不是每次都需要创建对象,这种做法就能降低程序开销。
2.基类的初始化
当你创建一个派生类对象的时候,这个对象里面还有一个基类的子对象(subobject)。
对于默认构造函数,即无参构造函数,Java会让派生类的构造函数自动地调用基类的构造函数。基类会在派生类的构造函数调用它之前进行初始化。
对于带参数的构造函数,必须用super关键字以及合适的参数明确地调用基类构造函数,super()语句必须是派生类构造函数的第一条语句。
3.确保进行妥善的清理
不要依赖垃圾回收器去做任何与内存回收无关的事情。如果要进行清理,一定要自己写清理方法,别去用finalize()。
清理的顺序:先按照创建对象的相反顺序进行类的清理,然后调用基类的清理方法。
4.名字的遮盖(重载方法的覆盖)
如果Java的基类里有一个被重载了好几次的方法,那么在派生类里重新定义那个方法,是不会把基类里定义的任何一个给覆盖掉的。(在C++里,就会把基类方法全都隐藏起来)
5.用合成还是继承
合成用于新类要使用旧类的功能,而不是其接口的场合。继承则是要对已有的类做一番改造,以获得一个特殊版本,即将一个较为抽象的类改造成能适用于某些特定需求的类。
合成要表达的是“有(has-a)”关系,继承要表达的是一种“是(is-a)”关系。
判断该用合成还是继承时,可以问一下是不是会把新类上传给基类。如果必须上传,那么继承就是必须的。
6.final关键词
6.1 final的数据
常量能用于两种情况:(1)编译时的常量(compile-time constant),这样就再也不能改了;(2)运行时初始化的值,这个值你以后不想改了。
如果是编译时常量,编译器会把常量放到表达式中,可以降低运行时的开销。Java中这种常量必须是primitive的,要用final表示,这种常量的赋值必须在定义的时候进行。
当final修饰对象的reference时,表示reference是常量,初始化的时候,一旦将reference指向了某个对象,那么它就再也不能指向别的对象了。但是这个对象本身是可以修改的。
final int v1 = 10;//compile-time constants
final int v2 = rand.nextInt(100);//
final Value v3 = new Value();//v3的数据成员是可变的
final int[] arr={1,2,3};//arr的元素是可变的,如a[i]++;
空白的final数据(Blank finals),是指声明了final成员,却没有在声明时赋值。编译器会强制在构造函数中初始化final数据。通过使用带参数的构造函数,根据参数对空白的final数据进行初始化,可以在保持final数据不变性的同时,提供一定的灵活性。
可以把方法的参数声明为final的。
6.2 final方法
使用final方法有两个目的。第一,可以禁止派生类修改方法。第二,效率。对于final方法,编译器会把调用转换成“内联(inline)”,即用方法本身的拷贝来代替方法的调用。但是,如果方法很大,程序会很快膨胀,于是内联也不会带来什么性能的改善。
只有是基类接口里的东西才能被覆写。如果基类的方法是private的,那它就不属于基类的接口。即使在派生类里创建了一个同名的方法,它同基类中可能同名的private方法没有任何联系。
6.3 final类
把类定义成final的,可以禁止继承这个类。final类的数据可以是final的,也可以不是。final类的方法都隐含地变成final了。
7.初始化与类的装载
在传统的编程语言中,程序启动的时候都是一次装载所有的东西,然后进行初始化,接下来再开始执行。这些语言必须仔细控制初始化的顺序。
Java采用了一种新的装载模式。编译之后每个类都保存在它自己的文件里。不到需要的时候,这个文件是不会装载的。即“类的代码会在它们第一次使用的时候装载”,第一次访问static成员或创建对象的时候。
继承情况下的初始化=====
首次使用类的时候,装载器(loader)就会寻找类的.class文件,转载过程中,会依次追溯装载基类(不管是否创建基类对象,这个过程都会发生)。下一步,会执行“根基类(root base class)”的static初始化,然后是下一个派生类的static初始化,以此类推。
所有类都装载结束,就可以创建对象了。首先对象里所有成员数据会被初始化为缺省值,这个过程是一瞬间完成的,对象的内存会被设置成二进制0。然后开始构造基类,基类的构造过程及顺序与派生类相同,先对基类的变量按字面顺序进行初始化,再调用基类的构造函数(调用是自动发生的,但你可以用super关键字指定要调用基类的哪个构造函数)。之后会对派生类的变量按定义的顺序进行初始化,最后执行派生类构造函数其余代码。
=============
Chap7 多态性
1.方法调用的绑定
将方法的调用连到方法本身被称为“绑定(binding)”。当绑定发生在程序运行之前时(由编译器或连接器负责),被称作“前绑定(early binding)”。
“后绑定(late binding)”指在程序运行时,根据对象类型决定该绑定哪个方法。后绑定也被称为“动态绑定(dynamic binding)”或“运行时绑定(run-time binding)”。
除了static和final方法(private方法隐含有final的意思),Java的所有方法都采用后绑定。
2.错误:“覆写”private的方法
public class Parent{
private void f(){
//...
}
public static void main(String[] args){
Parent pa = new Child();
pa.f();//执行的是Parent.f()
}
}
class Child extends Parent{
public void f(){
//...
}
}
应该避免用基类的private的方法名去命名派生类中方法。
只有非private的方法才能被覆写。
3.继承与清理
清理的顺序应该与初始化的顺序相反。对数据成员而言,清理顺序就应该与声明的顺序相反。
在派生类的清理方法(比如dispose())的最后,应该调用基类的清理方法。
4.多态方法在构造函数中的行为
如果在构造函数里调用了动态绑定的方法,那么它会调用那个覆写后的版本。
abstract class Glyph{
abstract void draw();
Glyph(){
System.out.println("Glyph() before draw()");
draw();
System.out.println("Glyph() after draw()");
}
}
class RoundGlyph extends Glyph{
private int radius = 1;
RoundGlyph(int r){
radius = r;
System.out.println("RoundGlyph.RoundGlyph(),radius = " + radius);
}
void draw(){
System.out.println("RoundGlyph.draw(),radius = " + radius);
}
}
public class PolyConstructors{
public static void main(String[] args){
new RoundGlyph(5);
}
}
//输出========
//Glyph() before draw()
//RoundGlyph.draw(), radius = 0
//Glyph() after draw()
//RoundGlyph.RoundGlyph(), radius = 5
上面程序中,Glyph的构造函数调用了draw(),而这个调用最后落到了RoundGlyph.draw()。而此时radius的值还没有被初始化为1,还是0。
真正的对象初始化过程:
(1)类加载结束,并且static初始化结束后,在进行其它工作之前,分配给这个对象的内存会被初始化为二进制的0。
(2)构造基类,调用基类的构造函数。这时会调用被覆写的draw()方法(在调用RoundGlyph的构造函数之前),受第一步的影响,radius 的值还是0。
(3)数据成员按照声明的顺序进行初始化。
(4)调用派生类的构造函数的正文。
5.用继承来进行设计
一般准则:使用继承来表示不同的行为,使用成员数据(合成)来表示不同的状态。
6.总结
人们通常会把多态性同Java的那些非面向对象的特性相混淆,比如方法的重载,它常常会被当作面向对象的特性介绍给大家。千万别上当:不是后绑定的,就不是多态性。
===========
Chap8 接口与内部类
1.接口(interface)
interface也可以包含数据成员,但是它天生就是public,static和final的。
interface默认是package权限的,只能用于同一个package。也可以加上public(只有保存在同名文件里的interface才可以加)。
可以把interface里的方法声明成public的,但是即便不写public关键词,这些方法也自动是public的。当implements一个 interface的时候,必须把这个interface的方法定义成public的。
2.Java的“多重继承”
由于interface不带任何“实现”--也就是说interface和内存无关--因此不会有谁去阻挠interface之间的结合。
到底是用interface,还是用abstract类?只要基类的设计里面可以不包括方法和成员变量的定义,就应该优先使用interface。只有在不得不定义方法或成员变量的情况下,才把它改成abstract类。
3.合并接口时的名字冲突
在要合并的接口里面放上同名的方法,通常会破坏程序的可读性,应该避免。
4.用继承扩展interface
可以用继承往interface里添加新的方法,也可以把多个interface合并成一个新的interface。使用extends关键词,多个“基接口(base interface)”之间用逗号分割。
interface Vampire extends IMonster, IUndead{}
5.常量的分组
由于interface的数据成员自动就是public,static和final的,因此interface是一种非常方便的创建一组常量值的工具。这点同C和C++的enum很相似,但没有enum那样的类型安全。
6.接口的嵌套
接口既可以嵌套在类里,也可以嵌套在接口里面。
实现接口的时候,不一定要实现嵌套在里面的接口。同样,private接口只能在定义它的类里实现。
7.内部类(inner class)
内部类与合成是截然不同的。
除非是在“宿主类(outer class)”的非static方法里面,否则无论在哪里创建内部类的对象,都必须用OuterClassname.InnerClassName的形式来表示这个对象的类型。
8.内部类与上传
内部类可以被定义成private或protected,非内部类只可能是public或package权限的。
9.在方法和作用域里的内部类
在方法的某个作用域里定义的内部类,比如if语句里,并不意味着这个类的创建是有条件的--它会同别的东西一起编译。但是,这个类的访问范围仅限于定义它的那个作用域。除此之外,它同普通的类没有区别。
10.匿名内部类
public class Parcel6{
public Contents cont(){
return new Contents(){
private int i = 11;
public value(){return i;}
};//Semicolon required
}
}
上例中return new Contents(){...}语句表达的是:创建一个继承Contents的匿名类的对象。new语句所返回的reference对自动上传到 Contents。这个return语句是如下代码的简化形式:
class MyContents implements Contents{
private int i = 11;
public int value(){return i;}
}
return new MyContents();
这个匿名内部类是通过默认构造函数来创建Contents的,如果基类需要的是一个带参数的构造函数,可以直接将参数传给基类的构造函数,return new Contents(x){...}。
如果在定义匿名内部类的时候,要用到外面的对象,编译器会要求把这个参数的reference声明成final的。
public class Parcel8{
//Argument must be final to use inside anonymous inner class
public Destination dest(final String ds){
return new Destination(){
private String lbl = ds;
public String readLabel(){return lbl;}
};
}
}
不能在匿名内部类里创建构造函数(因为它根本就没有名字),但是可以通过“实例初始化(instance initialization)”(与static初始化对应),进行一些类似构造函数的操作。实际上实例初始化过程就是匿名内部类的构造函数。但它的功能是有限的,由于不能重载实例初始化,因此只能有一个构造函数。
public Base getBase(int i){
return new Base(i){
{
System.out.println("Inside instance initializer.");
}
public void f(){
System.out.println("In anonymous f().");
}
};
}
11.与宿主类的关系
内部类能访问宿主类的所有成员。内部类对象里存在一个隐蔽的指向宿主类对象的reference,由编译器处理。
12.嵌套类(静态内部类)
如果不需要这种“内部类对象和宿主类对象之间的”联系,可以把内部类定义成static的,通常被称作“嵌套类(nested class)”。嵌套类的意思是:
(1)无需宿主类的对象就能创建嵌套类的对象。
(2)不能在嵌套类的对象里面访问非static的宿主类对象。
此外,嵌套类同普通的内部类还有一点不同。普通的内部类的成员数据和方法只能到类的外围这一层,因此普通的内部类里不能有static数据,static 数据成员或嵌套类。但是,这些东西在嵌套类里都可以有。
嵌套类可以是interface的一部分。
每个类可以带一个供测试的main()方法,但编译后,会产生额外的代码。可以考虑在嵌套类里创建供测试的main()。编译后会产生单独的名称如 Test$NestedTester.class的文件,发布的时候可以删除这个文件。
13.引用宿主类的对象
在内部类里通过宿主类名字后面加句点再加this来表示宿主类对象的reference。比如类Sequence.Selctor里,可以用 Sequence.this来获取它所保存的宿主类Sequence对象的reference。
要在其他地方创建内部类的对象,就必须在new表达式里面给出宿主类对象的reference。如:
Parcel11 p = new Parcel11();
//Must use instance of outer class to create an instance of the inner class.
Parcel11.Contents c = p.new Contents();
14.在多层嵌套的类里向外访问
内部类的嵌套层次不是问题--它可以透明地访问它的各级宿主类的成员。
class MNA{
private void f(){}
class A{
private void g(){}
public class B{
void h(){
g();
f();
}
}
}
}
public class MultiNestingAccess{
public static void main(String[] args){
MNA mna = new MNA();
MNA.A mnaa = mna.new A();
MNA.A.B mnaab = mnaa.new B();
mnaab.h();
}
}
".new"语句指明了正确的作用域,因此无需在调用构造函数的语句里再限定类的名字了。
15.继承内部类
class WithInner(){
class inner{}
}
public class InheritInner extends WithInner.Inner{
//!InheritIner(){}//Won't compile
InheritIner(WithInner wi){
wi.super();
}
public static void main(String[] args){
WithInner wi = new WithInner();
InheritInner ii = new InheritInner(wi);
}
}
InheritInner继承的只是内部类,默认的构造函数不能通过编译。必须传递宿主类对象的reference。此外,必须在构造函数里面使用这种语法:enclosingClassReference.super();这样才能提供那个必须的reference,才能编译通过。
16.内部类可以被覆写吗
像覆写宿主类的方法那样去“覆写”内部类,是不会有实际效果的。但是,可以在继承类的内部类明确继承基类的内部类。
public class BigEgg2 extends Egg2{
public class Yolk extends Egg2.Yolk{
}
}
17.本地内部类(Local inner classes)
本地内部类是在代码段,通常是方法的正文部分创建的。本地内部类不能有访问控制符,因为它并不属于宿主类,但它可访问当前代码段的final变量,以及宿主类的所有成员。
由于本地内部类的名字在代码段(通常是一个方法)外面是没法访问的,因此选择本地内部类来代替匿名内部类的一个理由就是,需要一个有名字的构造函数,并要重载这个构造函数,因为匿名内部类只能进行实例初始化。另一个理由是,需要创建多个那种类的对象。
18.内部类的标识符
宿主类名字,加上"$",再加上内部类的名字。如果是匿名内部类,编译器会直接用数字来表示。
如:OuterClass$InnerClass.class,OuterClass$2.class。
19.为什么要有内部类?
每个内部类都可以独立的继承某个“实现(implementation)”。内部类能在事实上继承多个实体类或abstract类。可以把它当作彻底解决多重继承问题的办法,接口部分地解决了这个问题。
========
Chap9 用异常来处理错误
1.重抛异常
如果直接重抛当前的异常,则printStackTrace()所打印出来的那些保存在异常对象里的信息,还会指向异常发生的地方,不会被指到重抛异常的地点。如果要装载新的栈轨迹信息,你可以调用fillInStackTrace()。
try{
f();
} catch(Exception e){
e.printStackTrace();
//throw e;//17
throw e.fillInStackTrace();//18
}
也可以抛出一个与捕捉到的异常不同的异常。这么做的效果同使用fillInstackTrace()的差不多--异常最初在哪里发生的信息被扔了,现在里面保存的时抛出新异常的地点。
2.异常链
异常链可以在捕捉到一个异常并且抛出另一个异常的时候,仍然保存前一个异常的信息。
发表评论
-
Java编程中“为了性能”尽量要做到的一些地方
2012-09-14 10:33 12281. 尽量在合适的场合使 ... -
Java 5.0多线程编程
2012-08-17 19:16 1075概述 1:三个新加的多线程包 2:C ... -
Thread.setDaemon设置说明
2012-06-28 18:00 1162Thread.setDaemon的用法,经过学习以后了解: ... -
JVM运行时数据区
2012-03-07 13:15 1011JVM定义了若干个程序执行期间使用的数据区域。这个区域里的一些 ... -
使用ibatis防止sql注入
2011-12-19 19:59 4670为了防止SQL注入,iBatis模糊查询时也要避免使用$$来进 ... -
java的引用探讨
2011-12-18 16:03 1137Reference 是一个抽象类,而 SoftReferenc ... -
ClassLoader
2011-11-04 15:11 1202package org.liufei.neta.lib; ... -
仿照Hibernate实现一个SQLite的ORM框架
2011-09-30 20:06 3202那么先来看看使用 实体对象 package test; ... -
java压缩文件
2011-09-20 11:09 1302package org.liufei.net.util; ... -
获取客户机IP地址
2011-09-07 15:04 1041package org.liufei.jweb; imp ... -
Java读取本地机器MAC地址
2011-09-07 15:01 1310package org.liufei.jweb; imp ... -
获取IP地址
2011-09-07 13:41 2431public String getIpAddrByReques ... -
用CSS来美化Java桌面--Javacss
2011-09-02 12:14 1340CSS可以用来修饰HTML网页。 但你有没有想过,使用CSS ... -
java管理windows进程
2011-08-29 17:34 1790package org.zzuli.xmsb; /** ... -
java html工具
2011-08-29 17:26 1092package org.liufei.jweb.util; ... -
java将汉字转化为全拼
2011-08-29 17:24 1239package org.liufei.jweb.util; ... -
开源项目SVN源码地址
2011-08-22 16:20 3561多优秀的开源项目已经提供SVN源码签出了,无论是解疑还是学习, ... -
XML解析
2011-08-22 09:58 10321、DOM解析XML <?xml version=&q ... -
jdbc操作大观园
2011-08-09 17:22 1392最近公司使用jdbc和mybatis比较多,于是自己试着写了一 ... -
Windows XP系统总命令集合
2011-08-05 14:08 1086Windows XP系统总命令集合 winver----- ...
相关推荐
《Java编程思想》
《Java编程思想》是Java程序员领域的一本经典之作,由Bruce Eckel撰写,以其深入浅出的讲解方式和丰富的实例闻名。这本书对于想要深入理解Java语言的人来说,是一份宝贵的资源。"Thinking in Java",直译为“思考...
《Java编程思想》是 Bruce Eckel 的经典著作,第四版更是深入浅出地介绍了Java语言的核心概念和技术。这个压缩包包含的源代码是书中的示例程序,它们旨在帮助读者理解书中阐述的各种编程原理和实践。通过分析这些源...
《JAVA编程思想》是 Bruce Eckel 的经典著作,中文版为国内Java开发者提供了深入理解Java语言的宝贵资源。这本书全面而深入地介绍了Java编程的核心概念和技术,是学习和提升Java编程技能的重要参考资料。 本书主要...
java编程思想第四版用的额外的jar包,在http://www.mindviewinc.com/TIJ4/CodeInstructions.html中有详细的说明,可以自己一个个的去下,我这里都下好了,可以直接用,给个辛苦分,呵呵。
文旦含有java编程思想一书中前9章的PPT 还附有几个基础型的编程代码 如乘法表: public class Chengfabiao { public static void main(String[] args) { for (int i = 1; i ; i++) {// 从1开始循环到9 for ...
《Java编程思想》第四版是Java开发者必备的经典书籍之一,由Bruce Eckel撰写,深入浅出地介绍了Java语言的核心概念和技术。在这个压缩包文件中,包含的主要是与本书相关的类库资源,特别是针对"mindview.net"包的,...
《Java编程思想》是Java开发领域的一本经典著作,它深入浅出地介绍了Java语言的核心概念和编程技术。这本书以其详尽的解释、丰富的实例和严谨的逻辑深受程序员喜爱。以下将围绕标题和描述中的知识点进行详细阐述: ...
《Java编程思想》是一本由 Bruce Eckel 编著的经典Java教程,对于初学者和有经验的程序员来说,都是深入理解Java语言的重要参考书。这本书深入浅出地讲解了Java的核心概念,包括面向对象编程、泛型、并发、集合框架...
Java编程思想是深入理解并掌握Java这门编程语言的关键,其中源码的分析与学习尤为重要。这个压缩包包含了几个在Java编程中常见的关联库,这些库对于理解和实践Java编程思想有着重要作用。 首先,我们来看看`...
《Java编程思想》是 Bruce Eckel 的经典著作,这本书深入浅出地介绍了Java语言的核心概念和技术,被广大Java程序员视为学习Java的必备参考书。这个压缩包包含了书中的实例代码和习题答案,对于读者理解和掌握Java...
根据提供的文件信息,我们可以深入探讨《Java编程思想》这一主题中的关键知识点,这些知识点主要集中在类、对象、封装性、继承、多态等核心概念上。 ### Java编程思想概述 《Java编程思想》这本书深入浅出地介绍了...
《Java编程思想》是Java开发领域的一本经典著作,作者是Bruce Eckel。这本书深入浅出地介绍了Java语言的核心概念和技术,对初学者和有经验的开发者都极具价值。书中的内容涵盖了Java语言的各个方面,从基本语法到...
《Java编程思想》是 Bruce Eckel 的经典著作,它深入浅出地介绍了Java语言的核心概念和技术,对于初学者和有经验的程序员来说都是极好的学习资源。本压缩包包含两部分主要内容:Java编程思想的答案代码和英文版的...