Thinking in Java
java的经典名著,无需过多解释,没仔细研读过,别说你学过java,随手翻翻也感觉受益匪浅,java是我喜欢的语言之一,Thinking in Java也是我看过的最好的技术书籍之一。第1章 对象导论
面向对象的三要素:封装、继承、多态。
封装是将事物抽象为类,隐藏具体实现。命令式语言是对汇编语言的抽象,汇编是对底层机器的抽象,面向对象语言是对所解决问题的抽象。
两种方法使基类和导出类产生差异:子类添加新方法、子类复写父类中的方法。
在处理类型的层次结构时,把对象当做基类的对象来处理,这样代码就能脱离具体类型。面向对象函数调用采用后期绑定,相对象发送消息时,被调用的代码直到运行时才能知道调用方法的绝对地址,编译器只确保方法存在和类型检查。后期绑定即动态绑定,这是实现多态的前提。面向基类编程的过程称为向上转型,多态可以使事物总是被正确的处理。
单根继承性:java所有的类都继承自单一的基类Object,单根继承性使得所有的对象都可以执行一些基本的操作。
向上转型是安全的,参数化类型,即泛型消除了向下转型的犯错误的可能。
对象数据的两种存储方式:堆栈(stack)和堆(heap)。C++的对象数据存放在堆栈中,这样得到了最大的执行速度,丧失了灵活性,对象的存储空间和生命周期都在程序编写时确定。java对象数据在堆内存池中动态创建,垃圾回收机制在对象不被使用时自动销毁,释放内存。
原始的web服务(服务器—浏览器)是一个简单的单项过程,即客户端向服务器发出一个请求,服务器返回一个文件,客户端解读文件。html只提供简单的 数据收集的作用,数据提交通过CGI(common gateway interface)传递 。客户端变成合理利用了客户机的资源,客户端编程的方法:插件、脚本语言。
applet提供了一中软件分发的方法,最终由于微软的封杀,被人遗忘。Flex以及Silverlight是一种applet的备选方案。Silverlight只局限于windows平台。移动平台似乎不喜欢插件,由于苹果的封杀,Flash穷头陌路。但是存在于98%的web浏览器上的Flash player,还有整个Adobe的多媒体生态环境的支撑,Flex短时间内还将是一个不错的富客户端解决方案。插件比脚本语言的优点在于开发者在编程时无需考虑浏览器的相关性。但是浏览器无插化是个趋势,html5、CSS3和javascript的完美结合将会是移动和PC平台web前段统一的解决方案。
第2章 一切都是对象
java对象操作的方式是引用。
数据存储方式:
- 寄存器,速度最快,不能直接控制,按需分配,C/C++可以向编译器建议寄存器分配方式。
- 堆栈,位于通用RAM(随机访问存储器),速度仅次于寄存器,但是要使用它,程序创建时系统必须知道存储在堆栈中的所有项的确切生命周期,这限制了程序的灵活性,所以java只将对象的引用和基本类型信息存放在堆栈中,这也是鼓励使用基本类型的原因。
- 堆,通用的内存池(也位于RAM),用于存放java的所有对象数据,不同于堆栈的是,new一个对象后,当程序执行时才在堆中进行存储分配,当然这种灵活性的代价是用堆存储分配和清理要比堆栈消耗的时间多。
- 常量存储,常量值通常直接存放在程序代码内部,由于他们不会被改变,所以是安全的。有时嵌入式系统中分离的常量也存放在ROM(只读存储器)中。
- 非RAM存储,数据也可以完全存活于程序之外,这样可以相对独立于程序,例如流对象和持久化对象。在流对象中对象转化成字节流发送到其他机器。在持久化对象中,对象被存放在磁盘上,这种存储方式的好处是对象存放在其他媒介上,在需要时可以恢复,例如JDBC、Hibernate。
当申明一个事物是static时,这个域或方法不与对象实例相关联。通过类可以直接调用static方法或域,一个static字段对每个类来说只有一份存储空间,非static字段每个对象对应一份存储空间。static方法在对象未创建前可以调用(main()方法)。
javadoc的命令都只能在“/*”注释出现时,同样结束于“/”。 一个例子:
/** 计算阶乘。
* 执行方法为 java Factorial [param]
* @author Calvin
* @author www.mceiba.com
*/
public class Factorial{
/** 主函数
* @param args 计算阶乘的参数
*/
public static void main(String[] args){
int input=Integer.parseInt(args[0]);
//int input=4;
double result=factorial(input);
System.out.println(result);
}
/** 函数体 */
public static double factorial(int x){
if(x<0)
return 0.0;
double fact=1.0;
while(x>1){
fact=fact*x;
x-=1;
}
return fact;
}
}
需要注意一点的是中文情况下使用javadoc命令需要设置字符集:
javadoc -encoding UTF-8 -charset UTF-8 Factorial.java
第3章 操作符
不可忽略的赋值语句:基本数据类型的赋值分配了新的存储空间,因为基本类型存储的是实际的数值;对一个对象的操作实际上是对对象引用的操作,因此对象的赋值只相当于给对象起了一个别名。将一个对象传递给方法,实际上传递的是一个引用,就如同C/C++中的变量名前加&
,改变的是方法之外的对象。
C++和java中的随机数生成:C++中用rand()
生成随机数,其实是伪随机数,因为它默认种子为1,不接收参数,每次产生的随机数序列是一样的,要传递种子产生随机数用srand()
,常用当前时间作为种子,即srand(time(0))
;java刚好相反,提供了一个产生随机数的类Random
,而默认就是以当前时间为种子的,要是想产生相同的随机数序列,可以指定固定的种子。
1 import static java.lang.System.out;
2 import java.util.*;
3 public class Rand{
4 public static void main(String[] args){
5 Random rand=new Random(3);
6 for(int i=0; i<10; i++){
7 //产生1~100之间的随机数
8 out.println((rand.nextInt(100)+1)+"\t");
9 }
10 for(int i=0; i<10; i++){
11 //nextFloat产生float类型的随机数
12 out.println(rand.nextFloat()+"\t");
13 }
14 }
15 }
自增自减的规律:前缀先生成值,后缀后生成值。
测试对象等价性的问题,关系操作符==
和!=
用于基本数据类型时比较的值得大小,用于对象时比较的是对象的引用,要比较对象的值用equals()
,但是不适合基本类型。实际上默认的equals()
方法(它是Object的方法)并不能比较两个对象的值,只是大多数的类都重写了该方法,猜一下下边程序的结果:
1 int a=1;int b=1;
2 out.println(a==b);
3 Integer aa=new Integer(1);
4 Integer bb=new Integer(1);
5 out.println(aa==bb);
6 out.println(aa.equals(bb));
7 aa=bb;
8 out.println(aa==bb);
9 Value va=new Value();
10 Value vb=new Value();
11 va.v=vb.v=1;
12 out.println(va.equals(vb));
13 out.println(va.v==vb.v);
如果你猜的没错,那结果应该是true, false, true, true, false, true
。
直接常量后面的后缀字符(l, f, d
及大写)标志了它的类型,java整数默认只支持十进制、十六进制(0x, 0X
)、八进制(0
),不能直接表示二进制,但是任意整数都可以使用Integer.toBinaryString()
来显示二进制结果。
java的移位操作:
1 int s=8;
2 out.println(s<<2);
3 int t=-8;
4 out.println(t>>2);
5 int r=-8;r>>>=2; //无符号右移,高位插0
6 out.println(r);
值得注意的一点,对于布尔值按位操作符于逻辑操作符效果是一样的,只是按位操作符不会短路,逐位取反操作~
只对符号位也起作用,如 ~1 = -2
,想得到期望的结果,可以~(-1)
。
第4章 控制执行流
java中不允许数字代表布尔值。java中唯一用到逗号操作符的是在for循环的控制表达式中,使用逗号操作符时,可以在for语句中定义多个变量,或执行多条步进语句,但是必须有相同的类型。
return, continue, break
:return作用范围最大,可以跳出当前方法;普通的continue和break只能跳出当前循环;带标签的continue和break可以跳到标签位置。java中唯一需要用标签的地方就是嵌套循环。switch使用限制较多,选择因子只能是int或char这样的整数值,case后的break是可选的,但是会执行直到遇到break为止,default后的break是多余的。
1 //! 错误用法 out.println(True+4);
2 out.println((int)2.9);
3 out.println(Integer.MAX_VALUE+1);
4 //!if(1) out.println("1");
5 //逗号操作符
6 Random random=new Random();
7 for(int j=0, rand=0; ; rand=random.nextInt(10), out.println(rand), j++)
8 if(rand>7)
9 break;
10 int array[]={1,2,3,4,5,6,7,8,9,10};
11 for(int rand: array){
12 out.print(rand+"\t");
13 }
14 //无穷循环
15 while(true){
16 double rand=Math.random();
17 out.println(rand);
18 if(rand>0.9)
19 break;
20 }
21 //标签及跳转
22 int i=0;
23 outer:
24 for(;;){
25 inner:
26 for(; i<10; i++){
27 out.print(i+"\t");
28 if(i==2) {out.println("continue"); continue;}
29 if(i==3) {out.println("break"); i++; break;}
30 if(i==7) {out.println("continue inner"); continue inner;}
31 if(i==8) {out.println("break outer"); break outer;}
32 }
33 }
34 label:
35 for(i=97;;i++){
36 char ch=(char)i;
37 switch(ch){
38 case 'a':
39 case 'e':
40 case 'm': out.print("m\t"); break;
41 case 'n': out.print("n");
42 case 'p': out.print("p"); break;
43 case 'z': out.print("end"); break label;
44 default: out.print("*\t");
45 }
46 }
第5章 初始化与清理
构造器&初始化
在java中初始化和创建是捆绑在一起的,但是概念上将他们是彼此独立的。构造器没有返回值,这与返回值为空(void)不同。创建对象时实际上是返回一个对象的引用,而同时自动调用了构造器,如果构造器有返回值,就会与创建对象时返回的对象引用冲突,因为这两个过程是捆绑在一起的,没有办法分离,我想这就是构造器没有返回值的原因。
方法重载可以通过参数裂变来区分不同的方法,甚至参数顺序的不同也能区别两个方法,返回值的不同不能区分方法,因为忽略返回值的情况下会产生歧义。涉及基本类型的重载中,如果传入的方法类型小于声明中的方法类型,实际的数据类型会被提升(char可以提升为int),相反的过程不能通过编译。
自己创建构造器(无论有参无参),编译器都不会自动创建构造器。
this关键字表示对当前对象的引用,只能在内部使用,this可以用在返回当前的对象、将当前对象传递给其它方法、以及在构造其中调用构造器。使用this调用构造器需要注意:
- 使用this只能调用一个构造器,这是因为创建一个对象时也只能调用一个构造器来初始化,当然这个构造器可以自己实现,或者再调用其他构造器。
- 构造器调用必须放在起始位置,否则编译报错。
- 构造器只能是自动调用,或者由构造器调用,普通方法不能调用构造器,因为对象创建的生命周期中,构造方法只能调用一次,而普通方法只有在对象创建以后才能调用,而这时构造方法已经调用过了,就不能再调用。
1 import static java.lang.System.out;
2 import java.util.*;
3
4 class Person{
5 public void eat(Apple apple){
6 Apple peeled=apple.getPeeled();
7 out.println(peeled.getState());
8 out.println("yummy!");
9 }
10 }
11 class Peeler{
12 static Apple peel(Apple apple){
13 apple.setState();
14 return apple;
15 }
16 }
17 class Apple{
18 private String state="unpeel";
19 public Apple getPeeled(){
20 return Peeler.peel(this);
21 }
22 public void setState(){
23 this.state="peeled";
24 }
25 public String getState(){
26 return this.state;
27 }
28 }
29 public class Test{
30 public static void main(String[] args){
31 class Leaf{
32 Leaf(){};
33 Leaf(int j){
34 out.println("get an int: "+j);
35 }
36 Leaf(String s){
37 out.println("get a String: "+s);
38 }
39 Leaf(int j, String s){
40 this(j);
41 //!this(s);
42 out.println("get a String: "+s);
43 }
44 int j=0;
45 Leaf increment(){
46 j++;
47 return this;
48 }
49 void print(){
50 out.println("j= "+j);
51 }
52 }
53 Leaf la=new Leaf();
54 la.increment().increment().increment().print();
55 Leaf lea=new Leaf(3, "leaf");
56
57 new Person().eat(new Apple());
58 }
59 }
终极处理&垃圾回收
java的垃圾回收器只会释放由new分配的内存,特殊的内存释放(如java中调用其他语言的代码),可以使用默认的finalize()
函数(继承自Object),垃圾回收器回收动作发生时首先调用finalize()
方法。
与 Java 不同,C++ 支持局部对象(基于栈)和全局对象(基于堆)。因为这一双重支持,C++ 提供了自动构造和析构,这导致了对构造函数和析构函数的调用,(对于堆对象)就是内存的分配和释放。在 Java 中,所有对象都驻留在堆内存,不存在局部对象,因此不需要析构来销毁局部对象。finalize()
不同于C++的析构函数,JVM不一定会调用它,所以是不可靠的。使用System.gc()
可以触发运行垃圾回收器,垃圾回收器会努力回收垃圾释放内存,但这并不意味着一定会执行finalize()
。
java垃圾回收机制的策略是:程序濒临存储空间用完的时刻,垃圾回收器才会执行以释放内存,如果直到程序执行结束垃圾回收器也没有释放内存,那么随着程序的退出,内存会自动释放,交给操作系统。
使用finalize()
的例子:
1 class Book{
2 boolean checkedout=false;
3 Book(boolean checkout){
4 checkedout=checkout;
5 }
6 void checkin(){
7 checkedout=false;
8 }
9 protected void finalize(){
10 if(checkedout){
11 out.println("Error: checked out!");
12 }
13 }
14 }
15 Book novel=new Book(true);
16 novel.checkin();
17 //Book boo=new Book(true);
18 //boo.finalize();
19 new Book(true);
20 //如果不使用匿名对象,System.gc()不一定触发finalize(),
21 //因为垃圾回收器不确定对象是否“存活”
22 System.gc();
垃圾回收器的工作原理
垃圾回收器有效地提高了对象的创建速度,因为GC运行时一边释放内存,一边使堆中的对象存储更紧密,对对象进行重新排列,提高存取速度(“堆指针”更接近地址入口)。
java的自适应垃圾回收技术:JVM进行监视,如果所有对象都很稳定,垃圾回收器的效率降低的话,就切换到标记-清扫方式;同样,JVM跟踪标记-清扫的效果,要是堆内出现很多碎片,就会切换回停止-复制方式。
初始化
java尽力保证所有变量在使用前都初始化,对于局部变量未初始化的调用会产生编译错误。类的成员变量在对象创建时会得到一个默认的初始值(0或null),而且初始化的顺序是按照定义的顺序,即使是变量定义在调用方法之后,任然会先初始化变量。、
静态数据会默认得到一个初始化(如果没有对它进行初始化),需要注意的是static
关键字不能用在局部变量。静态数据变量也能被修改,但只能是静态的修改。
1 class Spoon{
2 static int i;
3 static { i=47; }
4 //!i=47;
5 }
非静态实例初始化时可以有静态初始化一样的语法,只不过没有static关键字,而且实例化是在构造器之前执行。
可变参数列表
在参数个数类型未知的情况下创建一Object数组为参数的方法,这得益于java的单根继承性。
1 class VarArgs{
2 static void printArray(Object[] arg){
3 for(Object ar: arg)
4 out.print(ar+"\t");
5 out.println();
6 }
7 }
8
9 public class Test{
10 public static void main(String[] args){
11 VarArgs.printArray(new Object[]{1, 2, 'a', "spam", new Date()});
12 //!VarArgs.printArray();
13 }
14 }
这种方法也有不便之处,就是得遵循数组的语法,Java SE5以后加入了更好的语法,同样的功能,上边的代码可以这么写(而且对以上的语法是兼容的):
1 class VarArgsNew{
2 static void printArray(Object...arg){
3 for(Object obj: arg)
4 out.println(obj+":"+obj.getClass());
5 }
6 static void printArrayString(int req, String...arg){
7 //!out.println((req+1)+":"+req.getClass());
8 out.println((req+1)+":");
9 for(Object obj: arg)
10 out.println(obj+":"+obj.getClass());
11 }
12 }
13
14 public class Test{
15 public static void main(String[] args){
16 VarArgsNew.printArray(1, new Integer(2), 'a', "spam", new Date());
17 VarArgsNew.printArray(1, new Integer(2), 'a', "spam", new Date());
18 //还可以传递空值
19 VarArgsNew.printArray();
20 //可以限制传入类型
21 VarArgsNew.printArrayString(1, "spam", new Date().toString());
22 }
23 }
参照结果可以发现,在单一混合类型的参数列表中,自动包装机制有选择的的将基本类型提升为它的包装类,而在有明确类型要求的参数列表中,则不会。
枚举类型
枚举可以看做一个特殊的类,它也有自己的方法(ordinal(), static values()
)等,enum与switch语句可以很好的组合,扩展switch的一些功能:
1 enum Fruit{
2 APPLE, ORANGE, PEAR
3 }
4 class EatFruit{
5 Fruit fruit;
6 EatFruit(Fruit fruit){
7 this.fruit=fruit;
8 }
9 public void eat(){
10 out.print("eating ");
11 switch(fruit){
12 case APPLE: out.println(fruit.APPLE); break;
13 case ORANGE: out.println(fruit.ORANGE); break;
14 case PEAR: out.println(fruit.PEAR); break;
15 default: out.println("no eating");
16 }
17 }
18 }
19
20 public class Test{
21 public static void main(String[] args){
22 EatFruit
23 eta=new EatFruit(Fruit.APPLE),
24 eto=new EatFruit(Fruit.ORANGE),
25 etp=new EatFruit(Fruit.PEAR);
26 eta.eat();
27 eto.eat();
28 etp.eat();
29 }
30 }
第6章 访问权限控制
控制对成员的访问权有两个原因:
- 为了使用户不要去触碰那些他们不该接触的部分,这些部分对原类进行内部操作是必要的,但是它并不属于客户端程序所需接口的一部分。
- 是为了让类库设计者可以更改类的内部工作方式,而不必担心这样会对客户端程序产生重大的影响,这也是主要的原因。访问控制权限可以确保不会有任何客户端程序依赖于某个类的底层实现。
访问权限控制的等级从高到低以依次为:public、protected、包访问权限(没有关键字)和private。
同一个类 | 同一个包 | 不同包的子类 | 不同包的非子类 | |
private | √ | |||
default | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
java的包提供了一个命名空间的机制,不同类中相同的方法(参数也相同)不会冲突,因为有类名的限制,但是相同的类名只能通过包来加以区别。组织包用package关键字,如果不使用,就会有一个默认包(当前目录),即一个未命名包(按照惯例,包名的第一部分为类创建者的反顺序的域名,第二部分为类的文件组织目录)。一个java源代码文件称为一个编译单元,每个编译单元只能有一个(可以没有)public类,而且类名必须与文件名相同。如果编译单元中还有其他的类的话,那么在包外是不可见的,就是包访问权限级别,而且他们主要是用来为主public类提供支持的。
java解释器的运行过程:
- 找出环境变量CLASSPATH用作查找.class文件的根目录。
- 从根目录开始解释器获取包的名称,并把每个句点替换成反斜杠,以产从CLASSPATH根中产生一个路径名称。
- 得到的路径名称会与CLASSPATH中的不同项连接,解释器就在这些目录中查找相关的.class文件。
1 //这是一个使用private访问权的例子
2 class Sundae{
3 //单例模式
4 //不能被继承,始终只能创建它的一个对象
5 private Sundae(){}
6 private static Sundae sun=new Sundae();
7 public static Sundae makeSundae(){
8 return sun;
9 }
10 }
11
12 public class IceCream{
13 public static void main(String[] args){
14 //!Sundae sun=new Sundae();
15 Sundae sun=Sundae.makeSundae();
16 }
17 }
访问权限的控制常常被称作是具体实现的隐藏。把数据和方法包装进类中,以及具体实现的隐藏,一起被称为封装。
将接口和实现分开的好处是,客户端程序员除了向接口发送消息外什么也不能做,而随意的修改不是public的东西也不会破坏客户端的代码。
类的访问权限的一些限制:
- 每个编译单元只能有一个public类
- public类的名称必须完全与文件名相同,包括大小写
- 编译单元可以没有public类,这样也就没有了类名与文件名相同的限制,但是只有包访问权限
第7章 复用类
类复用的三种方式:组合、继承和代理。
类中的域(属性)都是以组合方式实现了复用,编译器默认将基本类型初始化为零,对象则初始化为null,如果需要自己初始化,可以在以下位置进行:
- 在类定义的地方,先于构造器初始化。
- 构造其中。
- 使用对象之前(惰性初始化)。
- 使用实例初始化。
创建一个类时,总是在继承,除非明确指明基类,否则隐式的继承java的单一根类Object。继承使用extends
关键字,继承时会自动得到父类的所有域和方法。
java会在子类的构造器中自动插入(靠前插入)默认构造器(无参)父类的构造器,父类的构造器优先调用,而且总会调用,带参数的构造器需要使用super
关键字显示调用。
1 import static com.mceiba.util.Print.*;
2 class Art{
3 Art(){
4 println("Art constructor");
5 }
6 Art(String name){
7 println("Art constructor : "+name);
8 }
9 }
10 class Drawing extends Art{
11 Drawing(){
12 println("Drawing constructor");
13 }
14 Drawing(String name){
15 super(name);
16 }
17 protected String name="Spam";
18 }
19 public class Cartoon extends Drawing{
20 Cartoon(){
21 println("Cartoon constructor");
22 }
23 Cartoon(String name){
24 super(name);
25 }
26 public static void main(String[] args){
27 Cartoon ct=new Cartoon();
28 Cartoon ctn=new Cartoon("Spam");
29 println(ctn.name);
30 }
31 }
32
33 //out:
34 //Art constructor
35 //Drawing constructor
36 //Cartoon constructor
37 //Art constructor : Spam
38 //Spam
java默认并没有对代理提供支持,这是继承和组合之间的中庸之道。
1 import static com.mceiba.util.Print.*;
2 class Controls{
3 void up(int velocity) { println("up: "+velocity); }
4 void down(int velocity) { println("down: "+velocity); }
5 void left(int velocity) { println("left: "+velocity); }
6 void right(int velocity) { println("right: "+velocity); }
7 }
8 class SpaceShip extends Controls{
9 private String name;
10 public SpaceShip(String name) { this.name=name; }
11 public String toString(){ return name; }
12 public static void main(String[] args){
13 SpaceShip protector=new SpaceShip("NSEA Protector");
14 protector.up(100);
15 }
16 }
17 public class Detergent{
18 private String name;
19 private Controls controls=new Controls();
20 public Detergent(String name) { this.name=name; }
21 public void up(int velocity) { controls.up(velocity); }
22 public void down(int velocity) { controls.down(velocity); }
23 public void left(int velocity) { controls.left(velocity); }
24 public void right(int velocity) { controls.right(velocity); }
25 public static void main(String[] args){
26 Detergent protector=new Detergent("NSEA Protector");
27 protector.up(100);
28 }
29 }
上例中用代理实现了与继承同样的接口,但是使用代理可以拥有更多的控制力,我们可以只提供对象成员方法的一个子集。一些情况下我们也会结合的使用继承和组合。继承中有时会遇到自己清理垃圾的情况,这时候需要注意调用的顺序,往往是先调用自己的清理方法,再调用父类的清理方法,就如同C++中的析构函数,经常需要放在finally{}
中,而不是finalize()
中。重载在继承中任然有效。使用@Override
注解可以防止在复写时以外的进行了重载的情况(不能通过编译)。
组合与继承之间的选择
组合与继承都允许在新的类中放置子对象,只不过一个是显示,一个是隐式。
- 组合常用于想在新类中使用现有类的功能,而非它的接口这种情况。嵌入一个现有类的private对象,新类中使用了现有类的功能,但是新类的用户看到的只是新类所定义的接口,而非嵌入类的接口。有时隐藏成员对象自身的具体实现,而将成员对象声明为public是安全的,而且有时具有特别的意义,比如:
1 class Engine{
2 public void start(){}
3 public void rev(){}
4 public void stop(){}
5 }
6 class Wheel{
7 public void inflate(int psi){}
8 }
9 class Window{
10 public void rollup(){}
11 public void rolldown(){}
12 }
13 class Door{
14 public Window window=new Window();
15 public void open(){}
16 public void close(){}
17 }
18 public class Car{
19 public Engine engine=new Engine();
20 public Window[] window=new Window[4];
21 public Door
22 left=new Door(),
23 right=new Door();
24 public Car(){
25 for(int i=0; i<4; i++)
26 wheel[i]=new Wheel();
27 }
28 public static void main(String[] args){
29 car car=new Car();
30 car.left.window.rollup();
31 car.wheel[0].inflate(72);
32 }
33 }
- 使用继承的时候通常意味着你是使用一个通用类,并为了某种需要而需要将其特殊化,是一个从一般到特殊化的过程。
使用组合还是转型最清晰的判断方法是是否需要从新类向基类进行向上转型。
向上转型
“为新类提供方法”并不是继承技术中最重要的方面,其最重要的方面是用来表现新类和基类之间的关系,即新类是现有类的一种类型。继承可以保证父类中的所有方法在子类中也有效,所以能够向基类发出的消息同样也可以向子类发送,由于向上转型是专用类向通用类转型,所以是安全的,向上转型是多态性的基础。
1 import static com.mceiba.util.Print.*;
2 class Language{
3 void lPrint(){}
4 }
5 class Java extends Language{
6 void lPrint(){
7 println("System.out.println(\"Hello World!\")");
8 }
9 }
10 class Python extends Language{
11 void lPrint(){
12 println("print(\"Hello World!\")");
13 }
14 }
15 public class Upcast{
16 public static void lPrint(Language lg){
17 lg.lPrint();
18 }
19 public static void main(String[] args){
20 Java java=new Java();
21 Python python=new Python();
22 lPrint(java);
23 lPrint(python);
24 }
25 }
26 //out:
27 //System.out.println("Hello World!")
28 //print("Hello World!")
final关键字
final
通常指的是无法改变,使用final的三种情况:数据、方法和类,通常是出于设计和效率的考虑。一个既是static有是final的域是一段不能改变的存储空间(一般用大写表示,即编译期常量),对于基本数据类型,final意味着数值恒定不变(定义时必须赋值),对于对象,意味着引用恒定不变(对象本身是可以修改的)。java允许空白final的存在,即声明为final但未给初值的域,但是编译器要确保空白final在使用前被初始化。这样空白final可以用在需要根据对象不同而有所不同,却又保持恒定不变的特征。
1 import static com.mceiba.util.Print.*;
2
3 public final class Empty{
4 private static final float PI=3.14f;
5 private final float r;
6 public Empty(){
7 r=1f;
8 }
9 public Empty(final float r){
10 this.r=r;
11 }
12 public void area(){
13 println("This area is "+PI*r*r);
14 }
15 public static void main(String[] args){
16 new Empty().area();
17 new Empty(3).area();
18 }
19 }
在参数列表中也可以将参数声明为final,这意味着无法在方法中修改参数的值或者参数所指向的引用。
使用final方法的两个原因是:
- 把方法锁定,以确保继承类中使方法行为保持不变,并且不会被覆盖。
- 早期的java版本中出于效率考虑,作为内联调用,现在由于JVM的自动优化,已经不需要了。
类中所有private方法都隐式的指定为final,但是好像没什么必要。final类意味着该类永远不需要修改,也不能有子类。
在带有继承的类中(实际上是所有的类),JVM总是试图首先访问main()
方法,加载器开始启动加载编译代码,如遇到基类,就首先加载基类(防止子类的初始化对基类的依赖),加载完成后就可以创建对象了。首先是对象中所有的基本类型设为默认值,对象引用设为null,然后基类的构造器会被调用,基类构造器完成之后实例变量按次序被初始化,最后执行构造器的其余部分。
第8章 多态
封装通过合并特征和行为来创建新的数据类型。实现隐藏则通过将细节私有化把接口和实现分离开来。多态(也称作动态绑定、后期绑定或运行时绑定)的作用是消除类型之间的耦合关系。
java中除了static方法和final方法(private方法属于final方法)外,其他方法都是后期绑定。方法声明为final可以防止被覆盖,但更重要的“关闭”了动态绑定。动态绑定是多态的基础,所以静态方法和final方法(私有方法)是不具有多态性的,而且所有访问域的操作也都是没有多态性的,因为域的操作时是由编译器来解析的,它在编译后就已经确定了。在编译之前对象是作为它的引用类型(转型过以后的类型)来处理的,所以不满足多态性的方法是作为转型后的类型的对象来发生行为的。
1 import static com.mceiba.util.Print.*;
2
3 class Circle extends Shape{
4 public String name="Circle";
5 public void draw(){
6 println("Drawing Circle");
7 }
8 public static void getType(){
9 println("Type: Circle");
10 }
11 }
12 class Square extends Shape{
13 public String name="Square";
14 public void say(){
15 println("I'm Shape");
16 }
17 public void draw(){
18 println("Drawing Square");
19 }
20 }
21
22 public class Shape{
23 public String name="Shape";
24 private void say(){
25 println("I'm Shape");
26 }
27 public void draw(){
28 println("Drawing Shape");
29 }
30 public static void getType(){
31 println("Type: Shape");
32 }
33 public static void main(String[] args){
34 Shape shape=new Shape();
35 Shape circle=new Circle();
36 Shape square=new Square();
37 println("Name: shape->"+shape.name+", circle->"+circle.name+", square->"+square.name);
38 println("*****draw()*****");
39 shape.draw();
40 circle.draw();
41 square.draw();
42 println("*****static: circle.getType()*****");
43 circle.getType();
44 println("*****Shape private: square. say()*****");
45 square. say();
46 }
47 }
48
49 //out:
50 //Name: shape->Shape, circle->Shape, square->Shape
51 //*****draw()*****
52 //Drawing Shape
53 //Drawing Circle
54 //Drawing Square
55 //*****static: circle.getType()*****
56 //Type: Shape
57 //*****Shape private: square. say()*****
58 //I'm Shape
在一个类中,构造器隐式的声明为static,private方法隐式声明为final,因此都是不具备多态性的。创建对象时总是优先调用父类的构造器,其次才参考当前类中域的声明顺序,即复杂对象调用构造器遵循下面的顺序(对象销毁的顺序与此相反):
- 在其他事情发生之前,将分配给对象的存储空间初始化为0(或者null)。
- 调用基类构造器,这个过程是不断递归调用。
- 按声明顺序调用成员的初始化方法。
- 调用导出类构造器的主体。
模拟引用计数的例子:
1 import static com.mceiba.util.Print.*;
2
3 class Shared{
4 private int refcount = 0;
5 private static long counter = 0;
6 private final long id = counter++;
7 public void shared(){
8 println("Creating "+this);
9 }
10 public void addRef() { refcount++; }
11 protected void dispose(){
12 if(--refcount == 0){
13 println("Disposing "+this);
14 }
15 }
16 public String toString() { return "Shared "+id;}
17 }
18 class Composing{
19 private Shared shared;
20 private static long counter = 0;
21 private final long id = counter++;
22 public Composing(Shared shared){
23 println("Creating "+this);
24 this.shared = shared;
25 this.shared.addRef();
26 }
27 protected void dispose(){
28 println("disposing "+this);
29 shared.dispose();
30 }
31 public String toString() { return "Composing "+id; }
32 }
33
34 public class RefCounting{
35 public static void main(String[] args){
36 Shared shared = new Shared();
37 Composing[] composing = {
38 new Composing(shared),
39 new Composing(shared),
40 new Composing(shared),
41 new Composing(shared),
42 new Composing(shared),
43 new Composing(shared),
44 new Composing(shared)
45 };
46 for(Composing c: composing) c.dispose();
47 }
48 }
49 //out:
50 //Creating Composing 0
51 //Creating Composing 1
52 //Creating Composing 2
53 //Creating Composing 3
54 //Creating Composing 4
55 //Creating Composing 5
56 //Creating Composing 6
57 //disposing Composing 0
58 //disposing Composing 1
59 //disposing Composing 2
60 //disposing Composing 3
61 //disposing Composing 4
62 //disposing Composing 5
63 //disposing Composing 6
64 //Disposing Shared 0
设计构造器的准则:用尽可能简单的方法使对象进入正常状态;如果可以的话,尽量避免调用其他方法。构造器内唯一能够安全调用的方法是基类的final(或private)方法,因为这些方法不能被覆盖,所以就不会出现转型的尴尬。
1 import static com.mceiba.util.Print.*;
2
3 class Glyph{
4 public void draw(){
5 println("Glyph.draw()");
6 }
7 public Glyph(){
8 println("Glyph() before draw()");
9 draw();
10 println("Glyph() after draw()");
11 }
12 }
13 class RoundGlyph extends Glyph{
14 private int radius = 1;
15 public RoundGlyph(int r){
16 radius = r;
17 println("RoundGlyph(), radius = "+radius);
18 }
19 public void draw(){
20 println("RoundGlyph.draw(), radius = "+radius);
21 }
22 }
23
24 public class PolyConstructors{
25 public static void main(String[] args){
26 new RoundGlyph(5);
27 }
28 }
29 //out:
30 //Glyph() before draw()
31 //RoundGlyph.draw(), radius = 0
32 //Glyph() after draw()
33 //RoundGlyph(), radius = 5
状态模式使我们能够在运行期间获得动态灵活性,既可以动态改变对象的状态。一条通用的准则为,用继承表达行为间的差异,并用字段表达状态上的变化。
1 import static com.mceiba.util.Print.*;
2
3 class Actor{
4 public void act() {}
5 }
6 class HappyActor extends Actor{
7 public void act() { println("HappyActor"); }
8 }
9 class SadActor extends Actor{
10 public void act() { println("SadActor"); }
11 }
12 class Stage{
13 private Actor actor = new HappyActor();
14 public void change() { actor = new SadActor(); }
15 public void performPlay() { actor.act(); }
16 }
17 public class Transmogrify{
18 public static void main(String[] args){
19 Stage stage = new Stage();
20 stage.performPlay();
21 stage.change();
22 stage.performPlay();
23 }
24 }
25 //out:
26 //HappyActor
27 //SadActor
向上转型是自然的,但却会造成信息丢失;先下转型需要强制转型,而且是不安全的,如果所转类型是正确的类型,则转型成功,否则会抛出一个ClassCastException
异常。
第9章 接口
接口和内部类为我们提供了一种将接口与实现分离的更加结构化的方法。
抽象类是对普通基类的一般抽象,它允许不完全的抽象,即抽象类中某些方法可以有具体实现。interface关键字产生一个完全抽象的类,不提供任何的实现。
- 接口中的域隐式声明为public、static和final,可以被被非常量表达式初始化,但是不能是空final。
- 接口中的方法默认是public,而且必须是(无论有没有显示声明)。
1 import static com.mceiba.util.Print.*;
2 interface Instrument{
3 int VALUE = 5;
4 void play();
5 void adjust();
6 String what();
7 }
8 abstract class Wind implements Instrument{
9 public abstract void play();
10 public abstract void adjust();
11 public String variety(){ return "Wind"; }
12 public String what(){ return variety()+": "+this.getClass().getSimpleName(); }
13 }
14 class Brass extends Wind{
15 public void play(){
16 println("Brass.play()");
17 }
18 public void adjust(){
19 println("Brass.adjust()");
20 }
21 }
22 class WoodWind extends Wind{
23 public void play(){
24 println("WoodWind.play()");
25 }
26 public void adjust(){
27 println("WoodWind.adjust()");
28 }
29 }
30 public class Music{
31 public void tune(Instrument it) { it.play(); }
32 public void describe(Instrument it) { println(it.what()); }
33 public void tuneAll(Instrument[] its){
34 for(Instrument it : its){
35 describe(it);
36 tune(it);
37 }
38
39 }
40 public static void main(String[] args){
41 Instrument[] its = {
42 new Brass(),
43 new WoodWind()
44 };
45 new Music().tuneAll(its);
46 }
47 }
48 //out:
49 //Wind: Brass
50 //Brass.play()
51 //Wind: WoodWind
52 //WoodWind.play()
策略模式:创建一个能够根据所传递的参数对象的不同而具有不同行为的方法。策略模式可以使处理问题的方法和所处理的问题之间完全解耦。
1 import static com.mceiba.util.Print.*;
2 import java.util.*;
3
4 interface Processor{
5 String name();
6 Object process(Object input);
7 }
8 class StringProcessor implements Processor{
9 public String name(){
10 return getClass().getSimpleName();
11 }
12 public Object process(Object input) { return input; }
13 }
14 class Upcase extends StringProcessor{
15 public String process(Object input){ //Covariant return
16 return ((String)input).toUpperCase();
17 }
18 }
19 class Downcase extends StringProcessor{
20 public String process(Object input){ //Covariant return
21 return ((String)input).toLowerCase();
22 }
23 }
24 class Splitter extends StringProcessor{
25 public String process(Object input){ //Covariant return
26 return Arrays.toString(((String)input).split(" "));
27 }
28 }
29 public class Strategy{
30 public static void process(Processor pro, Object obj){
31 println("Using Processor "+pro.name());
32 println(pro.process(obj));
33 }
34 public static String str = "Beautiful is better than ugly";
35 public static void main(String[] args){
36 process(new Upcase(), str);
37 process(new Downcase(), str);
38 process(new Splitter(), str);
39 }
40 }
41 //out:
42 //Using Processor Upcase
43 //BEAUTIFUL IS BETTER THAN UGLY
44 //Using Processor Downcase
45 //beautiful is better than ugly
46 //Using Processor Splitter
47 //[Beautiful, is, better, than, ugly]
但是,经常碰到的问题是想要使用的类是你无法修改的,因为大多数情况下使用的接口都不是自己创建的。这时,可以使用适配器模式,适配器中的代码将接受你所拥有的接口,并产生你所需要的接口。
1 import static com.mceiba.util.Print.*;
2 import java.util.*;
3
4 class Filter{
5 public String name(){
6 return getClass().getSimpleName();
7 }
8 public Wavaform process(Wavaform input) { return input; }
9 }
10 class FilterAdapter implements Processor{
11 Filter filter;
12 public FilterAdapter(Filter filter) { this.filter = filter; }
13 public String name(){ return filter.name(); }
14 public Wavaform process(Object input){
15 return filter.process((Wavaform)input);
16 }
17 }
18 public class Adapter{
19 public static void main(String[] args){
20 Strategy.process(new FilterAdapter(new LowPass(1.0)), new Wavaform());
21 Strategy.process(new FilterAdapter(new HighPass(2.0)), new Wavaform());
22 Strategy.process(new FilterAdapter(new BandPass(3.0, 4.0)), new Wavaform());
23 }
24 }
25
26 class Wavaform{
27 private static long counter;
28 private final long id = counter++;
29 public String toString() { return "Wavaform "+id; }
30 }
31 class LowPass extends Filter{
32 double cutoff;
33 public LowPass(double cutoff) { this.cutoff = cutoff; }
34 public Wavaform process(Wavaform input) { return input; }
35 }
36 class HighPass extends Filter{
37 double cutoff;
38 public HighPass(double cutoff) { this.cutoff = cutoff; }
39 public Wavaform process(Wavaform input) { return input; }
40 }
41 class BandPass extends Filter{
42 double lowCutoff, highCutoff;
43 public BandPass(double lowCutoff, double highCutoff){
44 this.lowCutoff = lowCutoff;
45 this.highCutoff = highCutoff;
46 }
47 public Wavaform process(Wavaform input) { return input; }
48 }
49 //out:
50 //Using Processor LowPass
51 //Wavaform 0
52 //Using Processor HighPass
53 //Wavaform 1
54 //Using Processor BandPass
55 //Wavaform 2
使用接口的原因是:
- 为了能够向上转型为多个基类型,以此来获得更大的灵活性(核心原因)。
- 防止客户端程序员以此类来创建对象,并确保这仅仅是建立一个接口。
接口可以支持多重继承,即组合多个类的接口,从而可以实现类的扩展,同时在需要的时候又可以向上转型为每一个接口。如果要同时使用extends
和implements
,那必须继承在前,而且继承的父类中与接口同签名的函数可以作为对该接口的实现。接口也可以跟普通类一样通过继承和组合得到扩展,但是应该避免在需要组合的不同接口中使用相同的方法名,由于在多重继承中,覆盖、实现和重载搅在一起,而且重载方法仅通过返回类型时区分不开的,这会造成代码可读性的混乱。
1 import static com.mceiba.util.Print.*;
2
3 interface CanFight { void fight(); }
4 interface CanSwim { void swim(); }
5 interface CanFly { void fly(); }
6 class ActionCharacter { public void fight() {}; }
7 class Hero extends ActionCharacter implements CanFight, CanFly, CanSwim {
8 public void swim() {};
9 public void fly() {};
10 }
11 public class Adventure{
12 public static void cft(CanFight x){ x.fight(); }
13 public static void csm(CanSwim x){ x.swim(); }
14 public static void cfy(CanFly x){ x.fly(); }
15 public static void acr(ActionCharacter x){ x.fight(); }
16 public static void main(String[] args){
17 Hero hero = new Hero();
18 cft(hero); //as a CanFight
19 csm(hero); //as a CanSwim
20 cfy(hero); //as a CanFly
21 acr(hero); //as an ActionCharacter
22 }
23 }
接口最吸引人的原因是它允许一个接口具有多个不同的具体实现,因此接口常用于策略模式,这样只要实现指定的接口,就可以用任何实现该接口的对象来调用该接口规定的方法。再结合适配器,我们可以在任何类之上添加新的接口,让方法接受接口类型,然后任何实现该接口的类都可以对接口的方法进行适配。
1 import static com.mceiba.util.Print.*;
2 import java.util.*;
3 import java.nio.*;
4
5 class RandomDoubles {
6 private static Random rand = new Random(47);
7 public double next() { return rand.nextDouble(); }
8 }
9 public class AdaptedRandomDoubles extends RandomDoubles implements Readable{
10 private int count;
11 public AdaptedRandomDoubles(int count) { this.count = count; }
12 public int read(CharBuffer cb){
13 if(count-- == 0) return -1;
14 String result = Double.toString(next())+" ";
15 cb.append(result);
16 return result.length();
17 }
18 public static void main(String[] args){
19 Scanner sn = new Scanner(new AdaptedRandomDoubles(7));
20 while(sn.hasNextDouble()) println(sn.nextDouble());
21 }
22 }
接口可以被嵌套在类或者其他接口中,当实现某个接口时不需要实现嵌套在其内部的任何接口,而且private接口不能在定义它的类之外被实现,private接口强制接口中的方法不允许添加任何类型信息,即不能向上转型,嵌套的public接口可以在外部被实现,实现格式为... implements ClassA.NestedB
。
接口是实现多重继承的途径,而生成遵循某个接口的对象的典型方式就是工厂方法模式。这与直接构造不同,我们在工厂对象上调用的是创建方法,工厂对象将生成接口的某个实现对象。理论上,通过这种方式,我们的代码将完全与接口的实现分离。
第10章 内部类
内部类就是将一个类的定义放在另一个类的内部,内部类有一些非常有用的特性,而且这与组合是完全不同的概念,它允许你将一些逻辑相关的类组织在一起,并且控制内部类的可视性。
在外部类中创建内部类的对象,必须明确指明对象的类型:OuterClassName.InnerClassName
。
1 import static com.mceiba.util.Print.*;
2 public class Parcel{
3 class Contents{
4 private int i = 11;
5 public int value() { return i; }
6 }
7 class Destination{
8 private String label;
9 Destination(String whereTo) { label = whereTo; }
10 String readLabel() { return label; }
11 }
12 public Destination to(String str){
13 return new Destination(str);
14 }
15 public Contents contents(){
16 return new Contents();
17 }
18 public void ship(String dest){
19 Contents con = contents();
20 Destination des = to(dest);
21 println(des.readLabel());
22 }
23 public static void main(String[] args){
24 Parcel p = new Parcel();
25 p.ship("Tasmania");
26 Parcel q = new Parcel();
27 Parcel.Contents pc = q.contents();
28 Parcel.Destination pd = q.to("Borneo");
29 }
30 }
迭代器模式
内部类不光只是一种名字隐藏和组织代码的模式,内部类还具有其外围类的所有元素的访问权,更重要的内部类的对象与制造它的外围对象之间就有了一种联系,使得它能访问其外围对象的所有成员。这里有一个迭代器模式内部类实现的例子。
1 import static com.mceiba.util.Print.*;
2 interface Selector{
3 boolean end();
4 Object current();
5 void next();
6 }
7 public class Iterator{
8 private Object[] items;
9 private int next = 0;
10 public Iterator(int size) { items = new Object[size]; }
11 public void add(Object obj){
12 if(next < items.length) items[next++] = obj;
13 }
14 private class IteratorSelector implements Selector{
15 private int i = 0;
16 public boolean end() { return i == items.length; }
17 public Object current() { return items[i]; }
18 public void next() { if(i < items.length) i++; }
19 }
20 public Selector selector() { return new IteratorSelector(); }
21 public static void main(String[] args){
22 Iterator iterator = new Iterator(10);
23 for(int i=0; i<10; i++)
24 iterator.add(Integer.toString(i));
25 Selector selector = iterator.selector();
26 while(!selector.end()){
27 print(selector.current()+" ");
28 selector.next();
29 }
30 println();
31 }
32 }
.this
&.new
语法
外部类的名字后紧跟圆点和this,就像OuterClassName.this
就可以在内部类中产生一个外部类的引用,当然单独的this还是内部类自己的引用。
1 import static com.mceiba.util.Print.*;
2 public class DotThis{
3 void funtion() { println("DotThis.funtion()"); }
4 public class Inner{
5 public DotThis outer() { return DotThis.this; }
6 }
7 public static class StaticInner{
8 public void say() { println("I'm static"); }
9 }
10 public Inner inner() { return new Inner(); }
11 public static void main(String[] args){
12 DotThis dt = new DotThis();
13 DotThis.Inner dti = dt.inner();
14 DotThis.Inner dni = dt.new Inner();
15 //StaticInner dsi = new StaticInner();
16 //两种方式都是可行的
17 DotThis.StaticInner dsi = new DotThis.StaticInner();
18 dti.outer().funtion();
19 dni.outer().funtion();
20 dsi.say();
21 }
22 }
上例展示了两种创建内部类对象的方法,第二种用到了.new
语法,就是你必须使用外部类的对象来创建内部类的对象。这是因为内部类的对象必须依附于外部类的对象,但是如果是嵌套类(即静态内部类),就不需要对外部类对象的引用。
内部类实现接口可以很方便的隐藏实现细节,private的内部类由于外界的访问是受限的,这样就能完全阻止任何依赖于类型的编码,因为客户端程序员只能看到原始的基类或者接口,看不到具体的实现,甚至导出类的具体类型。此外,由于从客户端程序员来看,由于不能访问新增的,不属于原接口的内容,所以扩展接口是没有意义的。
实际上可以在一个方法或者任意的作用域内定义内部类,这样做有两个好处:
- 实现某些类的接口,于是可以创建并返回对其的引用。
- 要解决一个复杂的问题,需要一个类来辅助你的解决方案,但是又不希望它是公共可用的,或者它在别的地方根本没用。
匿名内部类
匿名内部类允许创建一个继承自其它基类或者接口的匿名类的对象,即在new表达式中返回一个导出类的定义,而实际上返回一个自动向上转型为基类的引用。同时,还可以使用默认或者有参的构造器,只需要传递合适的参数给基类的构造器就可以了,如果内部类中需要使用外部的对象,那么作为参数传递时必须是final类型的,否则编译错误,但若只是传递给基类的构造器,而匿名类中没有直接 使用,则不要求变量一定是final型的。匿名类中不可能有命名的构造器,所有要想实现一些构造器的行为,则需要通过实例初始化。实例初始化的实际效果就是构造器,但是它受到了限制,不能重载实例化方法,因为你只有这一个构造器。
匿名内部类与正规的继承相比有些限制,匿名内部类可以扩展类,也可以实现接口,但是不能两者兼备,而且也只能实现一个接口。
工厂方法的内部类实现
工厂方法模式可以使用内部类更好的实现,服务实现的构造器可以使是private,并且也没有必要创建作为工厂的具名类(named class),而且一般只需要单一的工厂对象,因此声明为static域。
1 import static com.mceiba.util.Print.*;
2 import java.util.*;
3
4 interface Service{
5 void method1();
6 void method2();
7 }
8 interface ServiceFactory{
9 Service getService();
10 }
11 class Implementation1 implements Service{
12 private Implementation1() {}
13 public void method1() { println("Implementation1 method1()"); }
14 public void method2() { println("Implementation1 method2()"); }
15 public static ServiceFactory factory =
16 new ServiceFactory(){
17 public Service getService(){
18 return new Implementation1();
19 }
20 };
21 }
22 class Implementation2 implements Service{
23 private Implementation2() {}
24 public void method1() { println("Implementation2 method1()"); }
25 public void method2() { println("Implementation2 method2()"); }
26 public static ServiceFactory factory =
27 new ServiceFactory(){
28 public Service getService(){
29 return new Implementation2();
30 }
31 };
32 }
33 public class Factories{
34 public static void serviceConsumer(ServiceFactory fact){
35 Service sv = fact.getService();
36 sv.method1();
37 sv.method2();
38 }
39 public static void main(String[] args){
40 serviceConsumer(Implementation1.factory);
41 serviceConsumer(Implementation2.factory);
42 }
43 }
嵌套类
如果不需要内部类对象与其外部类之间有联系,可以声明为static,即嵌套类,他与普通嵌套类的区别是,普通的内部类对象隐式的保存了一个指向创建它的外部类对象的引用。而在嵌套类中这个引用就不存在了,这意味着:
- 要创建嵌套类的对象,并不需要其外部类的对象。
- 不能从嵌套类的对象中访问非静态的外围类对象。
- 普通的内部类不能有static方法和字段,也不能包含嵌套类,嵌套类则可以。
正常情况下,不能在接口内部放置任何代码,但嵌套类可以作为接口的一部分,接口中的类隐式的声明为public和static,这种方法可以用于测试,当然也不限于接口,嵌套类可以生成一个独立的类,运行这个类,只需要用$
将外部类和内部类隔开(一些环境下$
可能需要转义),执行起来就像这样java OuterClassName\$InnerStaticClassName
。
1 public interface ClassInInterface{
2 void funtion();
3 class InnerClass implements ClassInInterface{
4 public void funtion() { println("InnerClass.funtion()"); }
5 public static void main(String[] args){
6 new InnerClass().funtion();
7 }
8 }
9 }
内部类的多层嵌套
一个内部类可以嵌套多层,而且它能透明的访问它的所有外部类的所有成员。
1 import static com.mceiba.util.Print.*;
2 public class NestedClass{
3 private void className() { println(this.getClass().getSimpleName()); }
4 class A{
5 private void classA() { println(this.getClass().getSimpleName()); }
6 class B{
7 private void classB() { println(this.getClass().getSimpleName()); }
8 class C{
9 void classC(){
10 className();
11 classA();
12 classB();
13 println(this.getClass().getSimpleName());
14 //NestedClass.this.className();
15 //A.this.classA();
16 //B.this.classB();
17 //println(this.getClass().getSimpleName());
18 }
19 }
20 }
21 }
22 public static void main(String[] args){
23 NestedClass nc = new NestedClass();
24 NestedClass.A ca = nc.new A();
25 NestedClass.A.B cb = ca.new B();
26 NestedClass.A.B.C cc = cb.new C();
27 nc.className();
28 ca.classA();
29 cb.classB();
30 cc.classC();
31 NestedClass.A.B.C nabc = new NestedClass().new A().new B().new C();
32 //new NestedClass().new A().new B().new C().classC();
33 }
34 }
使用内部类的原因
使用内部类最重要的原因是:每个内部类都能独立地继承自一个(接口的)实现,所以无论其外部类是否已经继承了某个(接口的)实现,对于内部类来说都没有影响。
内部类使得多重继承的解决方案变得完整,接口只解决了部分问题,内部类允许继承多个非接口类型(类或者抽象类)。
1 import static com.mceiba.util.Print.*;
2
3 interface InterA {}
4 interface InterB {}
5 class ClassC {}
6 abstract class ClassD {}
7 class X implements InterA, InterB {}
8 class Y implements InterA {
9 InterB makeB(){
10 return new InterB() {};
11 }
12 }
13 class Z extends ClassD {
14 InterA makeA(){
15 return new InterA() {};
16 }
17 InterB makeB(){
18 return new InterB() {};
19 }
20 ClassC makeC(){
21 return new ClassC() {};
22 }
23 }
24 public class MultiImpl{
25 static void takeA(InterA a) {}
26 static void takeB(InterB b) {}
27 static void takeC(ClassC c) {}
28 static void takeD(ClassD d) {}
29 public static void main(String[] args){
30 //interface
31 X x = new X();
32 Y y = new Y();
33 takeA(x);
34 takeA(y);
35 takeB(x);
36 takeB(y.makeB());
37 //class
38 Z z = new Z();
39 takeD(z);
40 takeA(z.makeA());
41 takeB(z.makeB());
42 takeC(z.makeC());
43 }
44 }
内部类其他一些特性:
- 内部类可以有多个实例,每个实例都有自己的状态信息,并且与外围类对象的信息相互独立。
- 在单个外围类中,可以让多个内部类以不同方式实现同一个接口,或继承同一个类。
- 创建内部对象的时刻不依赖与外围类对象的创建。
- 内部类是一个独立的实体。
闭包是一个课调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。内部类是面向对象的闭包,它不仅包含外围类对象的信息,还自动拥有一个指向外围类对象的引用,在此作用域内,内部类有操作所有成员的权限,包括private。
java没有直接提供闭包和回调的功能,但是通过内部类可以提供闭包的功能,而且是一种更安全、更灵活的解决方案。回调的价值就在于它的灵活性,即可以在运行时动态的决定需要调用什么方法。
1 import static com.mceiba.util.Print.*;
2 interface Incrementable{
3 void increment();
4 }
5 class Callee1 implements Incrementable{
6 private int i = 0;
7 public void increment(){
8 i++;
9 println(i);
10 }
11 }
12 class MyIncrement{
13 public void increment() { println("Other operation"); }
14 static void fun(MyIncrement mi) { mi.increment(); }
15 }
16 class Callee2 extends MyIncrement{
17 private int i = 0;
18 public void increment(){
19 super.increment();
20 i++;
21 println(i);
22 }
23 private class Closure implements Incrementable{
24 public void increment(){
25 Callee2.this.increment();
26 }
27 }
28 Incrementable getCallbackReference(){
29 return new Closure();
30 }
31 }
32 class Caller{
33 private Incrementable callbackReference;
34 Caller(Incrementable cbh) { callbackReference = cbh;}
35 void go() { callbackReference.increment(); }
36 }
37 public class Callbacks{
38 public static void main(String[] args){
39 Callee1 c1 = new Callee1();
40 Callee2 c2 = new Callee2();
41 MyIncrement.fun(c2);
42 Caller caller1 = new Caller(c1);
43 Caller caller2 = new Caller(c2.getCallbackReference());
44 println("===============");
45 caller1.go();
46 caller1.go();
47 println("===============");
48 caller2.go();
49 caller2.go();
50 }
51 }
设计模式的关键是:使变化的事物与不变的事物分开。
应用程序框架是一个很好地使用内部类的例子,应用程序是被设计用来解决特定问题的一个类或一组类,具体的某个应用程序总是继承一个或多个类,并覆盖某些方法,以实现个性化的定制,这也是模板方法模式的一个例子。模板方法包含算法的基本结构,并且会调用一个或多个可覆盖的方法,以完成算法的动作。
1 import static com.mceiba.util.Print.*;
2 import java.util.*;
3 abstract class Event{
4 private long eventTime;
5 protected final long delayTime;
6 public Event(long delayTime){
7 this.delayTime = delayTime;
8 start();
9 }
10 public void start(){
11 eventTime = System.nanoTime()+delayTime;
12 }
13 public boolean ready(){
14 return System.nanoTime()>=eventTime;
15 }
16 public abstract void action();
17 }
18 class Controller{
19 private List<Event> eventList = new ArrayList<Event>();
20 public void addEvent(Event e) { eventList.add(e); }
21 public void run(){
22 while(eventList.size()>0)
23 for(Event e: new ArrayList<Event>(eventList))
24 if(e.ready()){
25 println(e);
26 e.action();
27 eventList.remove(e);
28 }
29 }
30 }
31 class GreenhouseControls extends Controller{
32 private boolean light = false;
33 private boolean water = false;
34 private String thermostat = "Day";
35 public class LightOn extends Event{
36 public LightOn(long delayTime) { super(delayTime); }
37 public void action(){
38 //hardware control code
39 //turn on the light
40 light = true;
41 }
42 public String toString() { return "Light is on"; }
43 }
44 public class LightOff extends Event{
45 public LightOff(long delayTime) { super(delayTime); }
46 public void action(){
47 //hardware control code
48 //turn off the light
49 light = false;
50 }
51 public String toString() { return "Light is off"; }
52 }
53 public class WaterOn extends Event{
54 public WaterOn(long delayTime) { super(delayTime); }
55 public void action(){
56 //hardware control code
57 //turn on the water
58 water = true;
59 }
60 public String toString() { return "Water is on"; }
61 }
62 public class WaterOff extends Event{
63 public WaterOff(long delayTime) { super(delayTime); }
64 public void action(){
65 //hardware control code
66 //turn off the water
67 water = false;
68 }
69 public String toString() { return "Water is off"; }
70 }
71 public class ThermostatNight extends Event{
72 public ThermostatNight(long delayTime) { super(delayTime); }
73 public void action(){
74 //hardware control code
75 thermostat = "Night";
76 }
77 public String toString() { return "Thermostat is on night setting"; }
78 }
79 public class ThermostatDay extends Event{
80 public ThermostatDay(long delayTime) { super(delayTime); }
81 public void action(){
82 //hardware control code
83 thermostat = "Day";
84 }
85 public String toString() { return "Thermostat is on day setting"; }
86 }
87 public class Bell extends Event{
88 public Bell(long delayTime) { super(delayTime); }
89 public void action(){
90 addEvent(new Bell(delayTime));
91 }
92 public String toString() { return "Bing!"; }
93 }
94 public class Restart extends Event{
95 private Event[] eventList;
96 public Restart(long delayTime, Event[] eventList) {
97 super(delayTime);
98 this.eventList = eventList;
99 for(Event e: eventList){
100 addEvent(e);
101 }
102 }
103 public void action(){
104 for(Event e: eventList){
105 e.start(); //Rerun each Event
106 addEvent(e);
107 }
108 start(); //Rerun this event
109 addEvent(this);
110 }
111 public String toString() { return "Restarting system"; }
112 }
113 public static class Terminate extends Event{
114 public Terminate(long delayTime) { super(delayTime); }
115 public void action(){
116 System.exit(0);
117 }
118 public String toString() { return "Terminating"; }
119 }
120 }
121 public class ControlFramework{
122 public static void main(String[] args){
123 GreenhouseControls gc = new GreenhouseControls();
124 //Configuration
125 gc.addEvent(gc.new Bell(900));
126 Event[] eventList = {
127 gc.new ThermostatNight(0),
128 gc.new LightOn(200),
129 gc.new LightOff(400),
130 gc.new WaterOn(600),
131 gc.new WaterOff(800),
132 gc.new ThermostatDay(1400)
133 };
134 gc.addEvent(gc.new Restart(200, eventList));
135 if(args.length == 1)
136 gc.addEvent(
137 new GreenhouseControls.Terminate(
138 new Integer(args[0])));
139 gc.run();
140 }
141 }
这个例子中还用到了命令设计模式,即“将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可取消的操作”。在evenList中每一个请求被封装成了对象。
内部类的继承
内部类的构造器必须连接到指向其外围类对象的引用,要继承,指向外围类对象的引用必须被初始化,而在导出类中不存在可连接的默认对象必须使用特殊的语法enclosingClassReference.super();
。
1 class WithInner{
2 class Inner {}
3 }
4 public class InheritInner extends WithInner.Inner{
5 InheritInner(WithInner wi) { wi.super(); }
6 public static void main(String[] args){
7 WithInner wi = new WithInner();
8 InheritInner ii = new InheritInner(wi);
9 }
10 }
继承自某个外围类时,内部类并没有发生变化,这说明内部类和它的外围类是相互独立的,各自在自己的命名空间。
1 import static com.mceiba.util.Print.*;
2 class Egg{
3 private Yolk y;
4 protected class Yolk{
5 public Yolk() { println("Egg.Yolk()"); }
6 }
7 public Egg(){
8 println("New Egg()");
9 y = new Yolk();
10 }
11 }
12 public class BigEgg extends Egg{
13 public class Yolk{
14 public Yolk() { println("BigEgg.Yolk()"); }
15 }
16 public static void main(String[] args){
17 new BigEgg();
18 }
19 }
局部内部类
可以在代码块内创建内部类,即局部内部类。局部内部类不能有访问说明符,因为它不是外围类的一部分,但是它可以访问当前代码块内的常量,以及外围类的所有成员。
1 import static com.mceiba.util.Print.*;
2 interface Couter{
3 int next();
4 }
5 public class LocalInnerClass{
6 private int count = 0;
7 Couter getCounterLocal(final String name){
8 class LocalCounter implements Couter{
9 public LocalCounter() { println("LocalCounter()"); }
10 public int next() {
11 print(name);
12 return count++;
13 }
14 }
15 return new LocalCounter();
16 }
17 Couter getCounterAnon(final String name){
18 return new Couter(){
19 { println("Counter()"); }
20 public int next(){
21 print(name);
22 return count++;
23 }
24 };
25 }
26 public static void main(String[] args){
27 LocalInnerClass lic = new LocalInnerClass();
28 Couter
29 c1 = lic.getCounterLocal("Local inner "),
30 c2 = lic.getCounterAnon("Anon inner ");
31 for(int i=0; i<5; i++)
32 println(c1.next());
33 for(int i=0; i<5; i++)
34 println(c2.next());
35 }
36 }
这里用局部内部类和匿名内部类实现了同样的功能,局部内部类在代码块以外是不可见的,这点和匿名内部类是类似的(匿名内部类本来就没有名字),但不同的是,局部内部类可以提供一个已命名的构造器,可以重载构造器,而匿名内部类只能用实例初始化。另外,局部内部类还可以提供不止一个的构造器。
每个类都会产生一个.class文件,其中包含了创建该类的全部信息(此信息产生一个“meta-class”,叫做class对象),内部类生成的.class文件是以其外围类名,加上$
,在加上内部类名而命名的,如果是匿名类名,则会以数字代替。
第11章 持有对象
复杂的程序中往往是在程序运行时才根据知道的某些条件去创建新对象,因此需要提供某种方法来保存对象,数组是最简单的保存对象的方式,但是数组有一些限制,使得它更适合保存一些基本数据类型,在更一般的情况下,还是使用容器类,或者叫集合类,其中基本的类型是List、Set、Queue和Map。
容器使用泛型可以防止错误的类型放入容器中,用尖括号括起来的就是类型参数(可以多个),同时在元素取出时类型转换也不再是必须的了。
容器的基本概念
java容器类的用途是“保存对象”:
- Collection,一个独立的序列,元素服从一条或多条规则。List必须按照插入的顺序保存元素;Set不能有重复元素;Queue按照排队规则来确定对象产生的顺序(一端插入,另一端移除)。
- Map,一组成对的“key-value”对象,允许使用键来查找值,可以称之为“字典”。ArrayList允许使用数字来查找对象,也被称为“关联数组”,因为它将某些对象与另外一些对象关联在一起。
创建容器很容易,如List<Apple> list = new ArrayList<Apple>()
,这里使用了向上转型,其目的在于如果你决定修改实现,只需要在创建处修改,就像这样List<Apple> list = new LinkedList<Apple>()
。当然如果使用了某些容器特殊的功能,就不能将他们转型为通用的接口。
1 import static com.mceiba.util.Print.*;
2 import java.util.*;
3 class Snow {}
4 class Power extends Snow {}
5 class Crusty extends Snow {}
6 class Slush extends Snow {}
7 class Light extends Power {}
8 class Heavy extends Power {}
9 public class SimpleCollection{
10 public static void main(String[] args){
11 Collection<Integer> collection = new ArrayList<Integer>(Arrays.asList(1,2,3,4,5));
12 Integer[] more = {6,7,8,9,10};
13 collection.addAll(Arrays.asList(more));
14 Collections.addAll(collection, 11,12,13,14,15);
15 Collections.addAll(collection, more);
16 List<Integer> list = Arrays.asList(0,1,2,3,4);
17 list.set(0,5);
18 //!list.add(5); //Runtime error
19 for(Integer in : collection)
20 print(in+" ");
21 println();
22
23 List<Snow> snow = Arrays.asList(new Power(), new Crusty(), new Slush());
24 //!List<Snow> light = Arrays.asList(new Light(), new Heavy());
25 List<Snow> empty = new ArrayList<Snow>();
26 Collections.addAll(empty, new Light(), new Heavy());
27 List<Snow> heavy = Arrays.<Snow>asList(new Light(), new Heavy());
28 }
29 }
Collection.addAll()
只能接受另一个Collection对象作为参数,不如Arrays.asList()
和Collection.addAll()
灵活;Arrays.asList()
返回的实际上是一个数组,所以在使用上会有一些限制,比如不能调整大小。Arrays.<Snow>asList()
是一个显式的类型参数说明。Map只能用另外一个Map实现自动初始化。
1 import static com.mceiba.util.Print.*;
2 import java.util.*;
3 public class PrintContainers{
4 public static Collection fill(Collection<String> collection){
5 collection.add("rat");
6 collection.add("cat");
7 collection.add("dog");
8 collection.add("dog");
9 return collection;
10 }
11 public static Map fill(Map<String,String> map){
12 map.put("rat", "Fuzzy");
13 map.put("cat", "Rags");
14 map.put("dog", "Bosco");
15 map.put("dog", "Spot");
16 return map;
17 }
18 public static void main(String[] args){
19 println(fill(new ArrayList<String>()));
20 println(fill(new LinkedList<String>()));
21 println(fill(new HashSet<String>()));
22 println(fill(new TreeSet<String>()));
23 println(fill(new LinkedHashSet<String>()));
24 println(fill(new HashMap<String, String>()));
25 println(fill(new TreeMap<String, String>()));
26 println(fill(new LinkedHashMap<String, String>()));
27 }
28 }
Set中,HashSet获取元素的速度最快;TreeSet按照比较结果的升序排列;LinkedHashSet按照添加的顺序保存对象。HashMap、TreeMap和LinkedHashMap与此类似。
List可以保证特定的序列顺序,同时可以在中间插入和移除元素:
- ArrayList,擅长随机访问,中间插入移除速度较慢。
- LinkedList,优化了顺序访问,能以较低的代价在List中间插入移除元素,但是随机访问较慢,LinkedList实现了Queue的接口,所以能进行一些队列的操作。
迭代器
迭代器(也是一种设计模式),是一个对象,它的工作是遍历并选择序列中的对象,而客户端程序员不用关心容器的具体信息。
Iterator的作用是:
- 使用
iterator()
方法返回一个Iterator,Iterator准备返回序列的第一个元素。 - 使用
next()
获得下一个元素。 - 使用
hasNext()
检查是否还有元素。 - 使用
remove()
移除新近返回的对象(在next()
之后调用)。
1 List<String> list = new ArrayList<String>(Arrays.asList("rat","cat","dog"));
2 Iterator<String> it = list.iterator();
3 while(it.hasNext()){
4 String str = it.next();
5 print(str+" ");
6 it.remove();
7 }
8 println(list.size());
java中有Stack,但是LinkedList同样可以实现栈的所有功能,遗憾的是java中并没有公共的Stack接口,这与其他容器比起来似乎缺少了一致性。
1 import static com.mceiba.util.Print.*;
2 import java.util.*;
3 public class Stack<T>{
4 private LinkedList<T> storage = new LinkedList<T>();
5 public void push(T v) { storage.addFirst(v); }
6 public T peek() { return storage.getFirst(); }
7 public T pop() { return storage.removeFirst(); }
8 public boolean empty() { return storage.isEmpty(); }
9 public String toString() { return storage.toString(); }
10 public static void main(String[] args){
11 Stack<String> stack = new Stack<String>();
12 stack.push("Rat");
13 println(stack.peek());
14 stack.pop();
15 println(stack.empty());
16 java.util.Stack<String> sk = new java.util.Stack<String>();
17 sk.push("Rat");
18 println(sk.peek());
19 sk.pop();
20 println(sk.empty());
21 }
22 }
Set具有和Collection完全一样的接口,只是行为不同而已(继承与多态思想的典型应用:表现的行为不同)。
1 import static com.mceiba.util.Print.*;
2 import java.util.*;
3 public class Statistics{
4 public static void main(String[] args){
5 Random rand = new Random();
6 Map<Integer, Integer> m = new TreeMap<Integer, Integer>();
7 for(int i=0; i<1000; i++){
8 int r = rand.nextInt(20);
9 Integer frq = m.get(r);
10 m.put(r, frq == null ? 1:frq+1);
11 }
12 println(m);
13 }
14 }
LinkedList具有Stack的功能,同时又实现了Queue(队列)接口。
1 import static com.mceiba.util.Print.*;
2 import java.util.*;
3 public class QueueDemo{
4 public static void main(String[] args){
5 Queue<Character> queue = new LinkedList<Character>();
6 for(char c: "qwertyuiop".toCharArray())
7 queue.offer(c);
8 while(queue.peek() != null)
9 print(queue.remove()+" ");
10 println();
11 PriorityQueue<String> pq = new PriorityQueue<String>(Arrays.asList("qwertyuiop".split("")));
12 while(pq.peek() != null)
13 print(pq.remove()+" ");
14 println();
15 }
16 }
任何实现了Iterable接口(包含iterator()
方法)的类都可以用于foreach语句,数组可以用佛reach,但是不代表数组也是一个Iterable。
1 import static com.mceiba.util.Print.*;
2 import java.util.*;
3
4 public class AdapterMethodIdiom implements Iterable<String>{
5 protected String[] words = "Beautiful is better than ugly".split(" ");
6 public AdapterMethodIdiom() {}
7 public AdapterMethodIdiom(String[] words) { this.words = words; }
8 public Iterator<String> iterator(){
9 return new Iterator<String>(){
10 private int index = 0;
11 public boolean hasNext(){
12 return index < words.length;
13 }
14 public String next() { return words[index++]; }
15 public void remove(){
16 throw new UnsupportedOperationException();
17 }
18 };
19 }
20 public Iterable<String> reversed(){
21 return new Iterable<String>(){
22 public Iterator<String> iterator(){
23 return new Iterator<String>(){
24 private int index = words.length-1;
25 public boolean hasNext(){
26 return index > -1;
27 }
28 public String next() { return words[index--]; }
29 public void remove(){
30 throw new UnsupportedOperationException();
31 }
32 };
33 }
34 };
35 }
36 public static void main(String[] args){
37 for(String s:new AdapterMethodIdiom())
38 print(s+" ");
39 println();
40 for(String s:new AdapterMethodIdiom().reversed())
41 print(s+" ");
42 //for(Map.Entry entry: System.getenv().entrySet()){
43 // println(entry.getKey()+": "+entry.getValue());
44 //}
45 }
46 }
适配器方法惯用法将Iterable接口用于其他接口。
第12章 用异常处理错误
程序的编译期并不能找出所有的错误,余下的错误必须在运行期间解决,这就需要错误源能通过某种方式把错误信息传递给某个接受者,该接受者知道如何正确处理这个问题,这就是异常处理机制,异常处理的初衷是为了方便程序员处理错误,一些无关紧要的错误可以忽略掉。
异常处理可以把异常情形和普通问题相区分,异常最重要的方面之一就是如果发生,它们将不允许程序沿正常的路径继续走下去。所有的异常都有两个构造器:一个默认构造器,一个接受字符串作为参数,异常的根类为Throwable。
try块是一个监控区域,可以捕获到异常,并由经跟其后的catch字句来处理(可以有多个),每个catch字句就像一个仅接受一个特殊参数的方法,只有匹配的catch字句才能执行(匹配不是绝对的匹配,只是找出“最近”的处理程序,子类的对象也可以匹配基类的处理程序,,Exception就可能屏蔽掉所有它之后的处理程序),而如果有finally语句,它总会被执行(在有break、continue以及return时也会执行)。
1 import static com.mceiba.util.Print.*;
2 import java.util.*;
3
4 public class MultiReturns{
5 public static void fun(int i){
6 println("Initialization");
7 try{
8 println("Point 1");if(i==1) return;
9 println("Point 2");if(i==2) return;
10 println("Point 3");if(i==3) return;
11 println("Point 4");if(i==4) return;
12 println("End"); return;
13 }finally{
14 println("Performing cleanup");
15 }
16 }
17 public static void main(String[] args){
18 for(int i=0; i<5; i++) fun(i);
19 }
20 }
异常处理理论上有两种基本模型:java(还有C++)支持终止模型,这种模型假设程序无法返回到异常发生的地方继续执行,一旦异常抛出,表明错误已无法挽回;另一种是恢复模型,异常处理程序的工作是修正错误,然后重新尝试出问题的方法。
创建自定义异常
自定义异常必须从已有的异常类继承,最好是选择意思相近的,还可以用java.util.logging
工具将异常输出到日志中。
1 import static com.mceiba.util.Print.*;
2 import java.util.logging.*;
3 import java.io.*;
4 class SimpleException extends Exception{
5 public SimpleException() {}
6 public SimpleException(String msg) { super(msg); }
7 }
8 class LoggingException extends Exception{
9 private static Logger logger = Logger.getLogger("LoggingException");
10 public LoggingException() {
11 StringWriter trace = new StringWriter();
12 printStackTrace(new PrintWriter(trace));
13 logger.severe(trace.toString());
14 }
15 }
16 public class MyException{
17 public void fun() throws SimpleException{
18 println("Throw SimpleException from fun()");
19 throw new SimpleException();
20 }
21 public void gun() throws SimpleException{
22 println("Throw SimpleException from gun()");
23 throw new SimpleException("Originated in gun()");
24 }
25 public void hun() throws LoggingException{
26 println("Throw LoggingException from hun()");
27 throw new LoggingException();
28 }
29 public static void main(String[] args){
30 MyException me = new MyException();
31 try{
32 me.fun();
33 }catch(SimpleException se){
34 println("Oops, Caught it!");
35 }
36 try{
37 me.gun();
38 }catch(SimpleException se){
39 se.printStackTrace(System.out);
40 //se.printStackTrace();
41 }
42 try{
43 me.hun();
44 }catch(LoggingException le){
45 System.err.println("Caught "+le);
46 }
47 }
48 }
使用Exception可以捕获所有异常,因为它是异常的基类(跟编程相关),printStackTrace()
提供的信息可以通过getStackTrace()
方法直接访问,返回一个由栈轨迹中的元素组成的数组。异常可以被重新抛出(throw e
),这样会把异常抛给上一级环境中的异常处理程序。常常需要在捕获一个异常以后抛出另外一个异常,并且希望把原始异常的信息保存下来,这可以使用异常链。现在所有的Throwable子类的构造器都可以接受一个cause对象(用来表示原始异常)作为参数,有三个基本的异常类提供带cause参数的构造器,即Error、Exception和RuntimeException,如果要把其他类型的异常链接起来,需要使用initCause()
方法。
Java标准异常
Throwable对象分为两种类型(Throwable的子类):Error是编译时和系统错误;Exception是可以被抛出的基本类型。运行时异常(RuntimeException)是个特列,他们自动被JVM抛出,不用自己捕获。
java中异常的限制也具有继承性,即子类覆盖方法只能抛出在基类方法的异常说明列出的那些异常,但是异常限制对构造器不起作用,也不能基于异常说明来重载方法,因为异常说明不属于方法类型的一部分。应该时刻注意“如果异常发生了,所有的东西都能被正确清理吗”,特别是在构造器中,需要注意,一般不要用finally来清理,因为它总会执行,比较安全的做法是使用嵌套的try字句。使用通用的清理惯用法基本规则是:在创建需要清理的对象之后,立即进入一个try-finally语句块。
异常处理的一个重要原则是只有在你知道如何处理的情况下才捕获异常,异常处理的一个重要目的就是把错误处理的代码和错误发生的地点相分离,错误代码的处理可以放在另一段代码中。
java中被检查的异常可能会使问题变得复杂,因为它强制你在可能还没有准备好处理异常的时候被迫加上catch子句,如果不处理则会引发“吞食则有害”的问题。简单的程序可以将异常打印到控制台,但这不是通用的做法,一个比较好的办法是把被检查的异常转化为不检查的异常,即把“检查的异常”包装进RuntimeException
,而且异常链保证你的异常不会丢失,你可以在合适的地方用getCause()
捕获并处理特定的异常。
1 import static com.mceiba.util.Print.*;
2 import java.util.logging.*;
3 import java.io.*;
4 class WrapCheckedException{
5 void throwRuntimeException(int type){
6 try{
7 switch(type){
8 case 0: throw new FileNotFoundException();
9 case 1: throw new IOException();
10 case 2: throw new RuntimeException("Where am I");
11 default: return;
12 }
13 }catch(Exception e){
14 throw new RuntimeException(e);
15 }
16 }
17 }
18 class SomeOtherException extends Exception {}
19 public class TurnoffChecking{
20 public static void main(String[] args){
21 WrapCheckedException wce = new WrapCheckedException();
22 wce.throwRuntimeException(3);
23 for(int i=0; i<4; i++)
24 try{
25 if(i<3) wce.throwRuntimeException(i);
26 else throw new SomeOtherException();
27 }catch(SomeOtherException se){
28 println("SomeOtherException: "+se);
29 }catch(RuntimeException re){
30 try{
31 throw re.getCause();
32 }catch(FileNotFoundException fe){
33 println("FileNotFoundException: "+fe);
34 }catch(IOException ie){
35 println("IOException: "+ie);
36 }catch(Throwable te){
37 println("Throwable: "+te);
38 }
39 }finally{
40 println("Handled all exception!");
41 }
42 }
43 }
异常处理指南:
- 在适当的级别处理问题(在知道该如何处理时才捕获)。
- 解决问题并且重新调用产生异常的方法。
- 进行少许修补,然后绕过异常经常发生的地方继续执行。
- 用别的数据进行计算,以代替方法预计会返回的值。
- 把当前环境下能做的事情尽量做完,然后把相同的异常重抛到更高层。
- 把当前环境下能做的事情尽量做完,然后把相同的异常抛到更高层。
- 终止程序。
- 进行简化。
- 让类库和程序更安全。
第13章 字符串
字符串基本操作
String对象是不可变的,每一个试图修改字符串的方法实际上都是生成个一个新的字符串,所以String对象是只读的。java不允许程序员重载任何的运算符,用于String的运算符只有重载过的“+”和“=+”。StringBuilder是一个更高效的处理字符串的类,在复杂的字符串操作中可以使用以提高效率。
Formatter类提供了格式化功能,格式化的格式为%[argument_index$][flags][width][.precision]conversion
。
1 import static com.mceiba.util.Print.*;
2 import java.util.*;
3 import java.io.*;
4 import java.math.*;
5 class AboutString{
6 public String toString(){
7 return "this: "+this+"\n";
8 }
9 public static void main(String[] args){
10 String s = "spam";
11 String ss = s.toUpperCase();
12 println("s: "+s+", ss: "+ss);
13 println("s+ss: "+(s+ss));
14 s+=ss;
15 println("s: "+s+", ss: "+ss);
16 StringBuilder sb = new StringBuilder("[");
17 for(int i=0; i<20; i++){
18 sb.append(new Random().nextInt(20));
19 sb.append(", ");
20 }
21 sb.delete(sb.length()-2, sb.length());
22 sb.append("]");
23 println(sb);
24 println("charAt(): "+s.charAt(1));
25 println("toCharArray(): "+s.toCharArray());
26 //println(new AboutString());
27 int x = 5;
28 double y = java.lang.Math.PI;
29 String z = "Format out: ";
30 System.out.println(z+"["+x+", "+y+"]");
31 System.out.printf("%s[%d, %f]\n", z, x, y);
32 System.out.format("%s[%d, %f]\n", z, x, y);
33 PrintStream outAlias = System.out;
34 Formatter ft = new Formatter(outAlias);
35 ft.format("%s[%d, %f]\n", z, x, y);
36 ft.format("%-10s %5s %-10s\n", "Item", "Qty", "Price");
37 ft.format("%-10s %5d %-10.2f\n", "Jack", 4, 10.87545);
38 ft.format("%-10s %5h %-10.4e\n", "Rose", 4231, 10875.45);
39 ft.format("%-10b %5x %-10d\n", "Calvin", 42, new BigInteger("32442424042412429487"));
40 try{
41 StringBuilder sbr = new StringBuilder();
42 int n = 0;
43 File file = new File("AboutString.class").getAbsoluteFile();
44 BufferedInputStream bf = new BufferedInputStream(new FileInputStream(file));
45 byte[] bt = new byte[bf.available()];
46 bf.read(bt);
47 bf.close();
48 for(byte b:bt){
49 if(n%16==0) sbr.append(String.format("%05X: ", n));
50 sbr.append(String.format("%02X ", b));
51 n++;
52 if(n%16==0) sbr.append("\n");
53 }
54 sbr.append("\n");
55 println(sbr);
56 }catch(Exception e){}
57 }
58 }
正则表达式
正则表达式是一种强大而灵活的文本处理工具,能够处理诸如:匹配、选择、编辑以及验证等字符串相关的问题。
java中正则表达式中的反斜杠有一些不同的处理,其他语言中“\”表示插入一个普通的反斜杠,而java中表示这就是一个正则表达式中表示特殊含义的反斜杠,要插入一个普通的反斜杠,应该是“\\”,但是正常的换行、制表符之类的东西还是只需要单个反斜杠\n\t
。
1 import static com.mceiba.util.Print.*;
2 import java.util.*;
3 public class StringRegular{
4 public static void main(String[] args){
5 println("-1234".matches("-?\\d+"));
6 println("1234".matches("-?\\d+"));
7 println("+1234".matches("-?\\d+"));
8 println("+1234".matches("(-|\\+)?\\d+"));
9 println(Arrays.toString("I'm CEO, bitch!".split("\\W+")));
10 println("I'm CEO, bitch!".replaceAll("b\\w+", "boy"));
11 }
12 }
创建正则表达式
量词描述了一个模式吸收输入文本的方式:
- 贪婪型,为所有可能的模式搜索尽可能多的匹配项。
- 勉强型,用问号指定,匹配满足模式所需的最少字符数。
- 占有型,java专有,以加号来指定,更高级,常用来防止正则表达式失控。
String类对正则表达式的支持有限,java更强大的正则表达式支持需要java.util.regex
包,产生的Pattern对象和Matcher对象可以实现很多功能。最简单的使用方法是先调用静态的Pattern.complie()
编译你的正则表达式,然后在使用Pattern对象的matcher()
方法来匹配你的输入,还可以使用split()
方法将输入字符串根据正则表达式断开成字符串数组。Pattern类的compile()
方法还可以接受一个标记参数,以调整匹配的行为,可以使用|
组合多个。
1 import static com.mceiba.util.Print.*;
2 import java.util.regex.*;
3 import java.util.*;
4 public class RegularExpression{
5 public static void main(String[] args){
6 String regex = "\\w+";
7 String str = "There should be one-- and preferably only one --obvious way to do it.";
8 //Pattern p = Pattern.compile(regex);
9 //Matcher m = p.matcher(str);
10 Matcher m = Pattern.compile(regex).matcher(str);
11 while(m.find()){
12 println("Match "+m.group()+" at "+m.start()+"-"+(m.end()-1));
13 }
14 println(Arrays.toString(Pattern.compile("\\-\\-").split(str,3)));
15 }
16 }
使用正则表达式进行扫扫描
可以使用Scanner类或者正则表达式进行文件,或者字符串扫描,以及分词等操作,StringTokenizer也能进行分词,但基本上被淘汰了。
1 import static com.mceiba.util.Print.*;
2 import java.util.*;
3 import java.util.regex.*;
4 import java.io.*;
5 public class SimpleRead{
6 public static BufferedReader input = new BufferedReader(
7 new StringReader("Sir Robin of Camelot\n22 1.61803"));
8 public static void main(String[] args){
9 try{
10 println("What's your name?");
11 String name = input.readLine();
12 println(name);
13 println("How old of you, and what's your favorite double?");
14 String numbers = input.readLine();
15 println(numbers);
16 }catch(IOException ie){
17 println("IOException");
18 }
19 String data =
20 "Calvin\n"+
21 "zhangsp@163.com\n"+
22 "24\n"+
23 "12619242230";
24 Scanner scanner = new Scanner(data);
25 String reg = "^[a-zA-Z][\\w\\.-]*[a-zA-Z0-9]@[a-zA-Z0-9][\\w\\.-]*[a-zA-Z0-9]\\.[a-zA-Z][a-zA-Z\\.]*[a-zA-Z]$";
26 Matcher m = Pattern.compile(reg).matcher("");
27 while(scanner.hasNext()){
28 String line = scanner.nextLine();
29 m.reset(line);
30 print(line+" ");
31 try{
32 print("e-mail: "+m.group(1)+" ");
33 }catch(IllegalStateException ie){
34 println(ie.getMessage());
35 }
36 }
37 println();
38 reg="\\d*";
39 String age=null;
40 String phone=null;
41 while(scanner.hasNext(reg)){
42 scanner.next(reg);
43 MatchResult match = scanner.match();
44 age = match.group(1);
45 phone = match.group(2);
46 }
47 println("age: "+age);
48 println("phone: "+phone);
49 //scanner = new Scanner("Calvin, 24, zhangsp@163.com, 13619242230");
50 scanner = new Scanner("13, 24, 163, 136");
51 println(scanner.delimiter());
52 scanner.useDelimiter("\\s*,\\s*");
53 while(scanner.hasNextInt()){
54 //println("age: "+scanner.nextInt());
55 //println("phone: "+scanner.nextInt());
56 println(scanner.nextInt());
57 }
58
59 }
60 }
第14章 类型信息
运行时类型信息(RTTI)使得你可以在程序运行时发现和使用类型信息,java中在运行时识别对象和类型信息有两种方式,即传统的RTTI(假定我们在编译时已经知道了所有的类型)和反射机制(允许我们在运行时发现和使用类的信息)。
面向对象编程的基本目的是:让代码只操纵对基类的引用,这样就很容易扩展新的类,而不会影响的原来的代码。
在java中,所有的类型转换都是在运行时进行类型正确性检查的。容器中的对象是以Object对象来持有的,在运行时由RTTI类型转换操作转换为声明的类型参数类型,在编译时由容器和java的泛型系统来确保这一点,而具体的类型转换由多态机制来实现。
Class对象包含了与类有关的信息,用来创建类的所有常规对象,每个类都有一个Class对象,java就是使用Class对象来执行RTTI的,而所有的Class对象都属于同一个Class类。所有的类都是在第一次使用时动态加载到JVM中的,使用new操作符创建的新对象,实际上是对类的静态成员的引用,这是因为它调用了静态的构造器。
1 package typeinfo.demo;
2 import static com.mceiba.util.Print.*;
3
4 interface HasBatteries {}
5 interface Waterproof {}
6 interface Shoots {}
7 class Toy{
8 Toy() {}
9 Toy(int i) {}
10 }
11 class FancyToy extends Toy implements HasBatteries, Waterproof, Shoots{
12 FancyToy() { super(1); }
13 }
14 public class ClassDemo{
15 static void printInfo(Class cc){
16 println("Class name: "+cc.getName()+" is interface ? ["+cc.isInterface()+"]");
17 println("Simple name: "+cc.getSimpleName());
18 println("Canonical name: "+cc.getCanonicalName());
19 }
20 public static void main(String[] args){
21 Class c = null;
22 try{
23 c = Class.forName("typeinfo.demo.FancyToy");
24 }catch(ClassNotFoundException e){
25 println("Can't find FancyToy");
26 System.exit(1);
27 }
28 printInfo(c);
29 for(Class face: c.getInterfaces()) printInfo(face);
30 Class up = c.getSuperclass();
31 Object obj = null;
32 try{
33 obj = up.newInstance();
34 }catch(InstantiationException e){
35 println("Cannot instantiate");
36 System.exit(1);
37 }catch(IllegalAccessException e){
38 println("Cannot access");
39 System.exit(1);
40 }
41 printInfo(obj.getClass());
42 }
43 }
除了使用Class.forName()
,getClass()
,还可以使用类字面常量来生成对Class对象的引用,调用格式为[className].class
,适用于普通类、接口、数组以及基本数据类型(字面量是在源代码级别表示一个对象或者数值的字符串)。对于基本类型的包装类还有一个TYPE字段,也是一个引用,指向对应的基本类型的Class对象,如Boolean.TYPE
等价于boolean.class
。它更简单、更安全,因为它在编译时就检查错误(不用放在try字句中),而且根除了forName()
方法的调用,所以更高效。
java中为了使用类而进行的准备工作包括三个步骤:
- 加载,由类加载器执行,查找字节码(通常在classpath指定的路径中查找),并从字节码中创建一个Class对象。
- 链接,验证类中的字节码,为静态域分配存储空间,必要时解析这个类创建的对其他类的所有引用。
- 初始化,如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块。
初始化被延迟到了对静态方法或者非常数静态域进行首次引用时才执行,而使用.class
创建对Class对象的引用时不会自动初始化该Class对象。
1 import static com.mceiba.util.Print.*;
2 import java.util.*;
3 class Initable1{
4 static final int staticFinal1 = 147;
5 static final int staticFinal2 = ClassInitialization.rand.nextInt(1000);
6 static { println("Initializing Initable1"); }
7 }
8 class Initable2{
9 static int staticNonFinal = 247;
10 static { println("Initializing Initable2"); }
11 }
12 class Initable3{
13 static int staticNonFinal = 347;
14 static { println("Initializing Initable3"); }
15 }
16 public class ClassInitialization{
17 public static Random rand =new Random();
18 public static void main(String[] args) throws Exception{
19 Class initable1 = Initable1.class;
20 println("After creating Initable1 ref");
21 println(Initable1.staticFinal1);
22 println(Initable1.staticFinal2);
23 println(Initable2.staticNonFinal);
24 Class initable3 = Class.forName("Initable3");
25 println("After creating Initable3 ref");
26 println(Initable3.staticNonFinal);
27 }
28 }
仅使用.class
来获取对象的引用不会发生初始化,而forName()
就会立即进入初始化。static final的“编译期常量”不需要对类进行初始化就可以读取,但是对static final的非编译期常量的调用就会引发强制的初始化。只是static而不是final型的域在读取之前要进行链接和初始化。
Class引用可以指向某个Class对象,普通的类指向可以被重新指向任何其他的Class对象,因为它就如同一个基类的对象,可以将任何的子类对象转型,这样有时是有害的,因为它不会产生编译器的警告信息(本来就是合法的),但是可以使用泛型的语法来避免这一点,而且可以使用?
通配符,以及通配符跟extends的组合来限定自己或者子类的类型。
1 import static com.mceiba.util.Print.*;
2 import java.util.*;
3 class CountedInteger{
4 private static long counter;
5 private final long id = counter++;
6 //public CountedInteger(int id) {};
7 public String toString() { return Long.toString(id); }
8 }
9 class FilledList<T>{
10 private Class<T> type;
11 public FilledList(Class<T> type) { this.type = type; }
12 public List<T> create(int nElements){
13 List<T> result = new ArrayList<T>();
14 try{
15 for(int i=0; i<nElements; i++)
16 result.add(type.newInstance());
17 }catch(Exception e){
18 throw new RuntimeException(e);
19 }
20 return result;
21 }
22 }
23 public class GenericClassRef{
24 public static void main(String[] args) throws Exception{
25 Class intClass = int.class;
26 Class<Integer> genericIntClass = int.class;
27 genericIntClass = Integer.class;
28 intClass = double.class;
29 //!genericIntClass = double.class;
30 Class<?> comIntClass = int.class;
31 comIntClass = double.class;
32 //!Class<Number> numClass = int.class;
33 Class<? extends Number> subNumClass = int.class;
34 subNumClass = int.class;
35 subNumClass = double.class;
36 subNumClass = Number.class;
37 Class<? super Number> superIntegerClass = Number.class;
38 println(superIntegerClass.getName());
39 CountedInteger ins = CountedInteger.class.newInstance();
40 //Integer inte = Integer.class.newInstance();
41
42 FilledList<CountedInteger> fl = new FilledList<CountedInteger>(CountedInteger.class);
43 println(fl.create(15));
44 }
45 }
newInstance()
可以产生一个对象实例,效果如同不带参数列表的new表达式创建一个实例,但前提是必须有一个默认的无参构造函数,因为
newInstance()
不接收任何参数,此外newInstance()
在下列情况会抛出异常:
- IllegalAccessException - 如果此类或其无参构造方法是不可访问的。
- InstantiationException - 如果此 Class 表示一个抽象类、接口、数组类、基本类型或 void; 或者该类没有无参构造方法; 或者由于某种其他原因导致实例化过程失败。
- ExceptionInInitializerError - 如果该方法引发的初始化失败。
- SecurityException - 如果存在安全管理器 s,并满足下列任一条件:
- 调用 s.checkMemberAccess(this, Member.PUBLIC) 拒绝创建该类的新实例 。
- 调用方的类加载器不同于也不是该类的类加载器的一个祖先,并且对 s.checkPackageAccess() 的调用拒绝访问该类的包 。
cast()
方法接受参数对象,并将其转型为Class引用的类型,效果类似于向下转型。子类声明为父类的引用时,在编译期间,编译器只把它当做父类来处理,如果需要子类额外的功能,则需要显示的向下转型,使用instanceof()
可以判断对象是不是某个特定类型的实例,验证成功,则说明可以进行转型,与它等价的是Class.isInstance()
,它,它们都能判断是否是本类或者派生类,而如果用==
或者equals()
判断类对象,只能判断是不是本类。
1 import static com.mceiba.util.Print.*;
2 import java.util.*;
3 class Building {}
4 class House extends Building {}
5 public class ClassCasts{
6 public static void main(String[] args) throws Exception{
7 Building b = new House();
8 Class<House> house = House.class;
9 House h = house.cast(b);
10 //h = (House)b; //the same result
11 println(h instanceof Building);
12 println(house.isInstance(h));
13 //!println(h instanceof House.class);
14 }
15 }
反射机制
反射提供了一种机制,用来检查可用的方法,并返回方法名。反射与RTTI的区别在于:对RTTI来说,编译器在编译时打开和检查.class
文件,而对反射机制来说,.class
文件在编译时是不可取的,所以在运行时打开和检查.class
文件。
RTTI可以提供在编译时已知的类型信息,但是人们要在运行时获得类信息的另一个动机是,希望能够提供在跨网络的远程平台上创建和运行对象的能力,即远程方法调用(RMI),它允许一个java程序将对象分布在多台电脑上,即分布式的能力,这就需要反射机制的协助。
通常不需要直接使用反射机制,但是需要创建更动态的代码时反射就会很有用,所以大多数情况下用来支持其他特性,如对象序列化和JavaBean,下面是一个类方法提取器的例子。
1 import static com.mceiba.util.Print.*;
2 import java.util.regex.*;
3 import java.lang.reflect.*;
4 public class ShowMethods{
5 private static String usage =
6 "usage:\n"+
7 "ShowMethods qualified.class.name\n"+
8 "To show all methods in class or:\n"+
9 "ShowMethods qualified.class.name word\n"+
10 "To search for methods involving 'word'";
11 private static Pattern p =Pattern.compile("\\w+\\.");
12 public static void main(String[] args) throws Exception{
13 if(args.length<1){
14 println(usage);
15 System.exit(0);
16 }
17 int lines = 0;
18 try{
19 Class<?> c = Class.forName(args[0]);
20 Method[] methods = c.getMethods();
21 Constructor[] ctors = c.getConstructors();
22 if(args.length==1){
23 for(Method method : methods)
24 println(p.matcher(method.toString()).replaceAll(""));
25 for(Constructor ctor : ctors)
26 println(p.matcher(ctor.toString()).replaceAll(""));
27 lines = methods.length+ctors.length;
28 }else{
29 for(Method method : methods)
30 if(method.toString().indexOf(args[1])!=-1){
31 println(p.matcher(method.toString()).replaceAll(""));
32 lines++;
33 }
34 for(Constructor ctor : ctors)
35 if(ctor.toString().indexOf(args[1])!=-1){
36 println(p.matcher(ctor.toString()).replaceAll(""));
37 lines++;
38 }
39 }
40 }catch(ClassNotFoundException e){
41 println(e.getMessage()+": "+e);
42 }
43 }
44 }
动态代理
代理模式是为了提供额外的或不同的操作,而插入的用来代替“实际”对象的对象,这些操作设计和“实际”对象之间的通信,所以代理充当的是一个中间人的角色。设计模式的关键是封装修改,代理模式也是为了把额外的操作分离出来并封装起来,以易于修改。
1 import static com.mceiba.util.Print.*;
2
3 interface Interface{
4 void doSomething();
5 void somethingElse(String arg);
6 }
7 class RealObject implements Interface{
8 public void doSomething() { println("doSomething"); }
9 public void somethingElse(String arg) { println("somethingElse "+arg); }
10 }
11 class SimpleProxy implements Interface{
12 private Interface proxied;
13 public SimpleProxy(Interface proxied) { this.proxied = proxied; }
14 public void doSomething(){
15 println("SimpleProxy doSomething");
16 proxied.doSomething();
17 }
18 public void somethingElse(String arg) {
19 println("SimpleProxy somethingElse "+arg);
20 proxied.somethingElse(arg);
21 }
22 }
23 public class Proxy{
24 public static void consumer(Interface iface){
25 iface.doSomething();
26 iface.somethingElse("bonn");
27 }
28 public static void main(String[] args){
29 consumer(new RealObject());
30 consumer(new SimpleProxy(new RealObject()));
31 }
32 }
动态代理可以更好的解决问题,它可以动态的创建代理并动态的处理对所代理方法的调用。在动态代理上所做的所有调用都会被重定向到单一的调用处理器上,它的工作是揭示调用的类型并确定相应的对策。
1 import static com.mceiba.util.Print.*;
2 import java.lang.reflect.*;
3 import java.lang.reflect.Proxy;
4
5 interface Something{
6 void doSomething();
7 void somethingElse(String arg);
8 }
9 class RealObjects implements Something{
10 public void doSomething() { println("doSomething"); }
11 public void somethingElse(String arg) { println("somethingElse "+arg); }
12 }
13 class DynamicProxyHandler implements InvocationHandler{
14 private Object proxied;
15 public DynamicProxyHandler(Object proxied) { this.proxied = proxied; }
16 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
17 println("**** proxy: "+proxy.getClass()+", method: "+method+", args: "+args);
18 if(args != null)
19 for(Object arg : args) println(" "+arg);
20 return method.invoke(proxied, args);
21 }
22 }
23 public class DynamicProxy{
24 public static void consumer(Something iface){
25 iface.doSomething();
26 iface.somethingElse("bonn");
27 }
28 public static void main(String[] args){
29 RealObjects real = new RealObjects();
30 consumer(real);
31 Something proxy = (Something)Proxy.newProxyInstance(
32 Something.class.getClassLoader(),
33 new Class[]{ Something.class },
34 new DynamicProxyHandler(real));
35 consumer(proxy);
36 }
37 }
使用静态方法Proxy.newProxyInstance()
可以创建动态代理,这个方法需要一个类加载器(通常可以从已加载的对象中获取其类加载器),一个需要代理实现的接口列表(不是类或抽象类),以及InvocationHandler
接口的一个实现。动态代理可以将所有调用重定向到调用处理器,因此通常会向调用处理器的构造器传递一个实际对象的引用,从而使得调用处理器在执行某个中介任务时,可以将请求转发。invoke()
方法中传递进来了代理对象,以防止你需要区分请求的来源,要注意在代理上方法的调用,因为对接口的调用将被重定向为对代理的调用。通常,先执行被代理的操作,然后使用Method.invoke()
将请求转发给被代理对象,并传入必需的参数,这个地方可以传递其他的参数来过滤掉某些方法的调用。
1 import static com.mceiba.util.Print.*;
2 import java.lang.reflect.*;
3 import java.lang.reflect.Proxy;
4
5 interface SomeMethods{
6 void boring1();
7 void boring2();
8 void boring3();
9 void interesting(String arg);
10 }
11 class Implementation implements SomeMethods{
12 public void boring1() { println("boring1"); }
13 public void boring2() { println("boring2"); }
14 public void boring3() { println("boring3"); }
15 public void interesting(String arg) { println("interesting "+arg); }
16 }
17 class MethodSelector implements InvocationHandler{
18 private Object proxied;
19 public MethodSelector(Object proxied) { this.proxied = proxied; }
20 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
21 if(method.getName().equals("interesting"))
22 println("Proxy detected the interesting method");
23 return method.invoke(proxied, args);
24 }
25 }
26 public class SelectingMethods{
27 public static void main(String[] args){
28 SomeMethods proxy = (SomeMethods)Proxy.newProxyInstance(
29 SomeMethods.class.getClassLoader(),
30 new Class[]{ SomeMethods.class },
31 new MethodSelector(new Implementation()));
32 proxy.boring1();
33 proxy.boring2();
34 proxy.boring3();
35 proxy.interesting("bonn");
36 }
37 }
对空对象方法的调用会产生烦人的NullPointerException
异常,但是它并非都是有害的,空对象最有用之处在于它更靠近数据,因为对象表示的是问题空间内的实体。空对象都是单例,可以在合适的时候被其他非空对象替代,或者传递类型信息。
接口与类型信息
interface关键字的一种重要目标就是允许程序员隔离构件,进而降低耦合性。但是通过类型信息,这种耦合性还是会传播出去,我们不想让客户端程序员访问的方法或域,通过反射还是可以访问,甚至修改,private、私有内部类、匿名类都是可以访问的。
1 import static com.mceiba.util.Print.*;
2 import java.lang.reflect.*;
3
4 interface A{
5 void fun();
6 }
7 class B implements A{
8 public void fun() { println("public B.fun()"); }
9 public void gun() { println("public B.gun()"); }
10 }
11 class C implements A{
12 private int i = 3;
13 private final String str = "private final C.str";
14 private static final String sfs = "private static final C.str";
15 public void fun() { println("public C.fun()"); }
16 public void gun() { println("public C.gun()"); }
17 void hun() { println("package C.hun()"); };
18 protected void jun() { println("protected C.jun()"); }
19 private void kun() { println("private C.kun()"); }
20 private static class InnerA implements A{
21 public void fun() { println("public InnerA.fun()"); }
22 public void gun() { println("public InnerA.gun()"); }
23 void hun() { println("package InnerA.hun()"); };
24 protected void jun() { println("protected InnerA.jun()"); }
25 private void kun() { println("private InnerA.kun()"); }
26 }
27 public static A makeInnerA() { return new InnerA(); }
28 public static A makeAnonA(){
29 return new A(){
30 public void fun() { println("public AnonA.fun()"); }
31 public void gun() { println("public AnonA.gun()"); }
32 void hun() { println("package AnonA.hun()"); }
33 protected void jun() { println("protected AnonA.jun()"); }
34 private void kun() { println("private AnonA.kun()"); }
35 };
36 }
37 }
38 public class HiddenImplementation{
39 static void callHiddenMethod(Object a, String methodName) throws Exception{
40 Method method = a.getClass().getDeclaredMethod(methodName);
41 method.setAccessible(true);
42 method.invoke(a);
43 }
44 static Object setHiddenField(Object a, Object value, String fieldName) throws Exception{
45 Field f = a.getClass().getDeclaredField(fieldName);
46 f.setAccessible(true);
47 f.set(a, value);
48 return f.get(a);
49 }
50 static Object getHiddenField(Object a, String fieldName) throws Exception{
51 Field f = a.getClass().getDeclaredField(fieldName);
52 f.setAccessible(true);
53 return f.get(a);
54 }
55 public static void main(String[] args) throws Exception{
56 A a = new C();
57 a.fun();
58 //!a.gun();
59 println(a instanceof A);
60 println(a instanceof B);
61 println(a instanceof C);
62 println(a.getClass().getName());
63 C c = (C)a;
64 c.gun();
65 //!c.str;
66 println(getHiddenField(c, "i"));
67 setHiddenField(c, 33, "i");
68 println(getHiddenField(c, "i"));
69 println(getHiddenField(c, "str"));
70 setHiddenField(c, "final can be changed", "str");
71 println(getHiddenField(c, "str"));
72 println(getHiddenField(c, "sfs"));
73 //!setHiddenField(c, "static final can be changed", "sfs");
74 //println(getHiddenField(c, "sfs"));
75
76 C cc = new C();
77 println(getHiddenField(cc, "i"));
78 println(getHiddenField(cc, "str"));
79 println(getHiddenField(cc, "sfs"));
80
81
82 c.hun();
83 c.jun();
84 //!c.kun();
85 callHiddenMethod(c, "kun");
86 A in = C.makeInnerA();
87 callHiddenMethod(in, "fun");
88 callHiddenMethod(in, "gun");
89 callHiddenMethod(in, "hun");
90 callHiddenMethod(in, "kun");
91
92 A an = C.makeInnerA();
93 callHiddenMethod(an, "fun");
94 callHiddenMethod(an, "gun");
95 callHiddenMethod(an, "hun");
96 callHiddenMethod(an, "kun");
97 }
98 }
其中的修改都只是针对于对象的,static的域是不能被修改的,因为它是和类相关联的。通常这些违反访问权限的操作并非都是最糟糕的事情,这种类中留下的后门实际上是可以解决某些特定类型的问题,当然访问权限最主要的作用还是在于封装性,让合适的人看到合适的域或者方法,所以违反访问权限的做法不见得是有意义的,相反却可以解决一些特定的问题。
第15章 泛型
泛型类
泛型实现了参数化类型的概念,使代码可以应用于多种类型,这是通过解耦类或者方法使用类型之间的约束,编译器会为你转换类型。
泛型主要目的之一就是用来指定容器要持有什么样类型的对象,而且由编译器来保证类型的正确性。类型参数不能使用基本类型,但是自动包装功能依然能使我们在内部使用基本类型。
生成器(generator)是一种专门负责生成创建对象的类,是工厂模式的一种应用,只不过它不需要参数,只有一个产生新对象的方法next()
。
1 import static com.mceiba.util.Print.*;
2 import java.util.*;
3 interface Generator<T> { T next(); }
4 class Coffee{
5 private static long counter;
6 private final long id = counter++;
7 public String toString() { return getClass().getSimpleName()+" "+id; }
8 }
9 class Latte extends Coffee {}
10 class Mocha extends Coffee {}
11 class Cappuccino extends Coffee {}
12 class Americano extends Coffee {}
13 class Breve extends Coffee {}
14 public class CoffeeGenerator implements Generator<Coffee>, Iterable<Coffee>{
15 private Class[] types = { Latte.class, Mocha.class, Cappuccino.class, Americano.class, Breve.class};
16 private static Random rand = new Random(47);
17 private int size = 0;
18 public CoffeeGenerator() {};
19 public CoffeeGenerator(int sz) { size = sz; };
20 public Coffee next(){
21 try{
22 return (Coffee)types[rand.nextInt(types.length)].newInstance();
23 }catch(Exception e){
24 throw new RuntimeException(e);
25 }
26 }
27 class CoffeeIterator implements Iterator<Coffee>{
28 int count = size;
29 public boolean hasNext() { return count>0; }
30 public Coffee next() {
31 count--;
32 return CoffeeGenerator.this.next();
33 }
34 public void remove() {
35 throw new UnsupportedOperationException();
36 }
37 }
38 public Iterator<Coffee> iterator() { return new CoffeeIterator(); }
39 public static void main(String[] args){
40 CoffeeGenerator gen = new CoffeeGenerator();
41 for(int i=0; i<5; i++) {
42 println(gen.next());
43 }
44 for(Coffee c: new CoffeeGenerator(5)) {
45 println(c);
46 }
47 }
48 }
泛型方法
泛型方法能够独立于类而产生变化,基本的原则是尽量的使用泛型方法。static方法无法使用泛型类的类型参数,所以只能使用泛型方法,泛型方法的参数列表置于返回值之前。泛型类使用前需指明具体的参数,而泛型方法则无需指明,编译器会自动执行来类型参数推断,所以可以直接使用,就如同被无限次的重载过,但是类型推断只对赋值操作有效,其他时候并不起作用,不过可以使用显示的类型说明来解决,即在点操作符与方法名之间插入带有类型参数的尖括号,在内部类中可能需要在点操作符前加this,而静态方法点操作符前需要类名。
1 import static com.mceiba.util.Print.*;
2 import static com.mceiba.util.Container.*;
3 import java.util.*;
4
5 public class GenericMethods{
6 public <T> void fun(T t){
7 println(t.getClass().getName()+", "+t);
8 }
9 public static <T> void gun(T t){
10 println("static, "+t.getClass().getName()+", "+t);
11 }
12 public static void main(String[] args) throws Exception{
13 gun(1);
14 gun(3.14);
15 gun(3.14f);
16 gun('m');
17 gun("mceiba");
18 GenericMethods gm = new GenericMethods();
19 gm.fun(1);
20 gm.fun(3.14);
21 gm.fun(3.14f);
22 gm.fun('m');
23 gm.fun("mceiba");
24 }
25 }
为了避免创建容器类时繁琐的泛型类型参数声明,可以使用泛型方法创建一个产生容器的工具包。
1 // Easy create all kinds of container
2 package com.mceiba.util;
3 import java.util.*;
4 public class Container {
5 // create a HashMap:
6 public static <K, V> Map<K, V> map(){
7 return new HashMap<K, V>();
8 }
9 // create an ArrayList:
10 public static <T> List<T> list(){
11 return new ArrayList<T>();
12 }
13 // create a LinkedList:
14 public static <T> List<T> lklist(){
15 return new LinkedList<T>();
16 }
17 // create a Set:
18 public static <T> Set<T> set(){
19 return new HashSet<T>();
20 }
21 // create a Queue:
22 public static <T> Queue<T> queue(){
23 return new LinkedList<T>();
24 }
25 // create a Stack:
26 public static <T> Stack<T> stack(){
27 return new Stack<T>();
28 }
29 }
泛型方法与可变参数列表也能很好的共存。
1 import static com.mceiba.util.Print.*;
2 import java.util.*;
3
4 public class GenericVarargs{
5 @SafeVarargs
6 public static <T> List<T> makeList(T... args){
7 List<T> list = new ArrayList<T>();
8 for(T t : args) list.add(t);
9 return list;
10 }
11 public static void main(String[] args){
12 List<String> list = makeList("QWERTYUIOP".split(""));
13 println(list);
14 }
15 }
存在这种可能性:参数化类型的变量引用不是该类型的对象,这种情况叫做堆污染。这种情况的发生在程序执行了一些在编译期会引起未检查警告的操作。
泛型可以用于生成器,这样就可以构造较为通用的构造器。
1 interface Generator<T> { T next(); }
2 class BasicGenerator<T> implements Generator<T>{
3 private Class<T> type;
4 public BasicGenerator(Class<T> type) { this.type = type; }
5 public T next(){
6 try{
7 return type.newInstance();
8 }catch(Exception e){
9 throw new RuntimeException(e);
10 }
11 }
12 public static <T> Generator<T> create(Class<T> type){
13 return new BasicGenerator<T>(type);
14 }
15 }
擦除
在泛型代码内部,无法获得任何有关泛型参数类型的信息。java泛型是通过擦除来实现的,这意味着使用泛型时,任何具体的类型信息都被擦除掉了。List<String>
和List<Integer>
在运行时实际上是相同的类型,都被擦除为原生类型List。要在泛型内部使用具体的类型信息,需要给定泛型类的边界,以告知编译器只能接受遵循这个边界的类型,这里重用了extends。大多数情况下它不如直接使用基类代替导出类这种较低层次的泛化来的简单,但是如果需要使用跟具体类型相关的功能,而又要返回具体的类型,则只能使用这种给定了边界的泛型。
1 import static com.mceiba.util.Print.*;
2 import java.util.*;
3 public class GenericType<T extends Number>{
4 private T t;
5 public GenericType(T t) { this.t = t; }
6 public T get() { return t; }
7 public int getInt() { return t.intValue(); }
8 public static void main(String[] args){
9 GenericType<Integer> gti = new GenericType<Integer>(1);
10 GenericType<Float> gtf = new GenericType<Float>(0.618f);
11 GenericType<Double> gtd = new GenericType<Double>(3.14);
12 println(gti.get()+", "+gtf.get()+", "+gtd.get());
13 println(gti.getInt()+", "+gtf.getInt()+", "+gtd.getInt());
14 }
15 }
擦除减少了泛型的泛化性,是一种折中的方式,在基于擦除的实现中,泛型类型被当做第二类类型处理,即不能在重要的上下文环境中使用类型,只在静态检查时才出现泛型类型,在此之后所有的泛型类型都将被擦除,替换为它的非泛型上界,容器类被擦除为其原生类,普通类型变量在未指定边界的情况下被擦除为Object,所以不能进行基于类型的语言操作以及反射操作等。
擦除的核心动机是使得泛化的客户端可以使用非泛化的类库,反之亦然,即迁移兼容性。虽然移除了有关实际类型的信息,但是编译器可以保证在方法或者类中使用的类型的内部一直性。
泛型中的所有动作都发生在边界处——对传递进来的值进行额外的编译期检查,并插入对传递出去的值的转型,即边界就是发生动作的地方。
偶尔也可以绕过这些问题来编程,比如引入类型标签来对擦除进行补偿,即通过构造器传入一个Class对象,内部还可以使用isIntance()
,可以使用简单工厂或者模板方法通过泛型来产生需要的对象。
1 import static com.mceiba.util.Print.*;
2 import java.util.*;
3 import java.lang.reflect.*;
4 interface SimpleFactory<T>{
5 T create();
6 }
7 abstract class GenericWithCreate<T>{
8 final T element;
9 GenericWithCreate() { element = create(); }
10 abstract T create();
11 }
12 class Foo<T>{
13 private T x;
14 public <F extends SimpleFactory<T>> Foo(F factory){
15 x = factory.create();
16 }
17 }
18 class IntFactory implements SimpleFactory<Integer>{
19 public Integer create() { return new Integer(0); }
20 }
21 class Widget{
22 public static class Factory implements SimpleFactory<Widget>{
23 public Widget create(){
24 return new Widget();
25 }
26 }
27 }
28 class X {}
29 class Creator extends GenericWithCreate<X>{
30 X create() { return new X(); }
31 void fun() { println(element.getClass().getSimpleName()); }
32 }
33 class GenericArray<T>{
34 private T[] array;
35 @SuppressWarnings("unchecked")
36 public GenericArray(Class<T> type, int size){
37 array = (T[])Array.newInstance(type, size);
38 }
39 public void put(int index, T item) { array[index] = item; }
40 public T get(int index){ return array[index]; }
41 public T[] rep(){ return array; }
42 }
43 public class CreatorGeneric{
44 public static void main(String[] args){
45 new Foo<Integer>(new IntFactory());
46 new Foo<Widget>(new Widget.Factory());
47 Creator c = new Creator();
48 c.fun();
49 GenericArray<String> array = new GenericArray<String>(String.class, 5);
50 array.put(0, "spam");
51 array.put(1, "Hum");
52 array.put(2, "Bat");
53 array.put(3, "Monty");
54 array.put(4, "Opps");
55 StringBuilder sd = new StringBuilder("[");
56 for(int i=0; i<5; i++){
57 sd.append(array.get(i));
58 sd.append(", ");
59 }
60 String str = sd.substring(0, sd.length()-3);
61 sd.append("]");
62 str+="]";
63 println(str);
64 }
65 }
不能直接使用泛型类型变量来创建数组,一般的解决方案是在需要数组的地方使用ArrayList,也可以将Object数组转型为需要类型,而且需要传入类型标记,以便可以从擦除中恢复类型,一般会产生警告,可以使用@SuppressWarnings("unchecked")
忽略掉。
设置边界,可以强制规定泛型可以运用的类型,似乎弱化了泛型,但同时一个重要的好处是我们可以按照自己的边界类型来调用方法,这是一个折中的方案。
1 import static com.mceiba.util.Print.*;
2 import java.util.*;
3
4 interface HasColor { java.awt.Color getColor(); }
5 class HoldItem<T>{
6 T item;
7 HoldItem(T item) { this.item = item; }
8 T getItem() { return item; }
9 }
10 class Colored<T extends HasColor> extends HoldItem<T>{
11 Colored(T item) { super(item); }
12 java.awt.Color color() { return item.getColor(); }
13 }
14 class Dimension { public int x, y, z; }
15 //!class ColoredDimension<T extends HasColor & Dimension> //class first, then interface
16 class ColoredDimension<T extends Dimension & HasColor> extends Colored<T>{
17 ColoredDimension(T item) { super(item); }
18 int getX() { return item.x; }
19 int getY() { return item.y; }
20 int getZ() { return item.z; }
21 }
22 interface Weight { int weight(); }
23 class Solid<T extends Dimension & HasColor & Weight> extends ColoredDimension<T>{
24 Solid(T item) { super(item); }
25 int weight() { return item.weight(); }
26 }
27 class Bounded extends Dimension implements HasColor, Weight{
28 public java.awt.Color getColor() { return null; }
29 public int weight() { return 0; }
30 }
31 public class BasicBounds{
32 public static void main(String[] args){
33 Solid<Bounded> solid = new Solid<Bounded>(new Bounded());
34 solid.color();
35 solid.getY();
36 solid.weight();
37 }
38 }
通配符
数组有一种特殊的行为:可以向导出类型的数组赋予基类型的数组引用(只是在编译期允许),这并不适用于容器类。
通配符(?
)经常和extends和super一起工作,来确定上界或者下界,甚至还可以与泛型参数变量一起工作,一般的形式有<? extends MyClass>
、<? super MyClass>
、<? extends T>
,还有一种无解通配符,即<?>
。在一般情况下,List(持有任何Object类型的原生List)、List<?>(持有某种特定类型的非原生List,只是现在还不知道)和List
1 Number[] nb = new Integer[5];
2 try{
3 nb[0] = new Integer(3);
4 nb[1] = new Float(3.14f);
5 nb[2] = new Double(3.14);
6 for(Number num : nb) print(num+" ");
7 }catch(Exception e){}
8
9 //!List<Number> lt = new ArrayList<Integer>();
10 List<? extends Number> lts = Arrays.asList(new Integer(0), 3.14f, 3.14);
11 List<? extends Number> list = new ArrayList<Integer>();
12 //list.add(3);
编译器只能验证确切的类型,只关注传递进来和要返回的对象类型,set()
方法不能工作与Apple和Fruit,因为由于泛型set()
方法的参数也变成<? extends Fruit>
,这意味着它是继承与Fruit的任何类型,而编译器无法验证任何类型。
1 class GenericWritting{
2 static <T> void write(List<T> list, T item){
3 list.add(item);
4 }
5 static <T> void writeWithWildcard(List<? super T> list, T item){
6 list.add(item);
7 }
8 static List<Apple> apples = new ArrayList<Apple>();
9 static List<Fruit> fruits = new ArrayList<Fruit>();
10 public static void testWritting(){
11 write(apples, new Apple());
12 write(fruits, new Apple());
13 writeWithWildcard(apples, new Apple());
14 writeWithWildcard(fruits, new Apple());
15 }
16 }
17 class GenericReading{
18 static <T> T read(List<T> list){
19 return list.get(0);
20 }
21 static List<Apple> apples = Arrays.asList(new Apple());
22 static List<Fruit> fruits = Arrays.asList(new Fruit());
23 static class Reader<T>{
24 T read(List<T> list) { return list.get(0); }
25 }
26 static class CovariantReader<T>{
27 T read(List<? extends T> list) { return list.get(0); }
28 }
29 static void testReading(){
30 Apple a =read(apples);
31 Fruit f =read(fruits);
32 f = read(apples);
33
34 Reader<Fruit> fruitReader = new Reader<Fruit>();
35 Fruit ft = fruitReader.read(fruits);
36 //!Fruit ft = fruitReader.read(apples);
37
38 CovariantReader<Fruit> coReader = new CovariantReader<Fruit>();
39 Fruit cf = coReader.read(fruits);
40 Fruit ca = coReader.read(apples);
41 }
42 }
原生类型、具体参数类型、有界参数类型以及无界通配符参数类型作为参数的接受类型,具有不同的表现形式。
1 class Hold<T>{
2 private T value;
3 public Hold() {}
4 public Hold(T t) { value = t; }
5 public void set(T t) { value = t; }
6 public T get() { return value; }
7 public boolean equals(Object obj) { return value.equals(obj); }
8 public static void testHold(){
9 Hold<Apple> apple = new Hold<Apple>(new Apple());
10 Apple a = apple.get();
11 apple.set(a);
12 //!Hold<Fruit> fruit = apple;
13 Hold<? extends Fruit> fruit =apple;
14 //Fruit f = (Apple)fruit.get();
15 //!fruit.set(f);
16 try{
17 Fruit f = (Orange)fruit.get();
18 println(f.getClass().getName());
19 }catch(Exception e){ println(e); }
20 println(fruit.equals(a));
21 }
22 static void rawArgs( Hold hold, Object arg){
23 //!hold.set(arg);
24 Object obj = hold.get();
25 }
26 static void unboundArgs( Hold<?> hold, Object arg){
27 //!!hold.set(arg);
28 Object obj = hold.get();
29 }
30 static <T> T exact1(Hold<T> hold){
31 T t = hold.get();
32 return t;
33 }
34 static <T> T exact2(Hold<T> hold, T arg){
35 hold.set(arg);
36 T t = hold.get();
37 return t;
38 }
39 static <T> T wildSubtype(Hold<? extends T> hold, T arg){
40 //!!hold.set(arg);
41 T t = hold.get();
42 return t;
43 }
44 static <T> void wildSuptype(Hold<? super T> hold, T arg){
45 hold.set(arg);
46 //!!T t = hold.get();
47 Object obj = hold.get();
48 }
49 public static void testBound(){
50 Hold raw = new Hold();
51 Hold<Long> qualified = new Hold<Long>();
52 Hold<?> unbound = new Hold<Long>();
53 Hold<? extends Long> bounded = new Hold<Long>();
54 Long lng = 1L;
55
56 rawArgs(raw , lng);
57 rawArgs(qualified , lng);
58 rawArgs(unbound , lng);
59 rawArgs(bounded , lng);
60
61 unboundArgs(raw , lng);
62 unboundArgs(qualified , lng);
63 unboundArgs(unbound , lng);
64 unboundArgs(bounded , lng);
65
66 //!exact1(raw);
67 exact1(qualified);
68 exact1(unbound);
69 exact1(bounded);
70
71 //!exact2(raw , lng);
72 exact2(qualified , lng);
73 //!!exact2(unbound , lng);
74 //!!exact2(bounded , lng);
75
76 //!wildSubtype(raw , lng);
77 wildSubtype(qualified , lng);
78 wildSubtype(unbound , lng);
79 wildSubtype(bounded , lng);
80
81 //!wildSuptype(raw , lng);
82 wildSuptype(qualified , lng);
83 //!!wildSuptype(unbound , lng);
84 //!!wildSuptype(bounded , lng);
85
86 }
87 }
在rawArgs()
中,编译器知道Hold是一个泛型类型,在这里表示为一个原生类型,所以向set()
传递任何类型的参数,都被向上转型为Object,无论何时只要使用了原生类型,编译器都会放弃编译期检查,而返回一个警告。
unbound()
显示Hold
和Hold<?>
是不同的,这与rawArgs()
中的情况是类似的,但给出的是错误信息,因为原生Hold
可以持有任何类型对象的组合,而Hold<?>
将持有某种同构类型的集合,不能只向其传递Object。
exact*()
中要求具体的类型,所以不确定的参数类型传入就会引发错误。
因此,使用确切类型来代替通配符类型的好处是,可以用泛型参数来做更多的事,但是使用通配符使得你必须接受范围更宽的参数化类型作为参数。
捕捉转换中需要使用<?>
而不是原生类型,即如果向一个使用<?>
的方法传递原生类型,那么编译器可能会推断出实际的类型参数,使用这个方法可以回转并调用另一个使用这个确切类型的方法。
1 class CaptureConversion{
2 static <T> void fun(Hold<T> hold){
3 T t = hold.get();
4 println(t.getClass().getSimpleName());
5 }
6 static void hun(Hold<?> hold){
7 fun(hold);
8 }
9 @SuppressWarnings("unchecked")
10 static void testCapture(){
11 Hold raw = new Hold<Integer>(1);
12 fun(raw); //!
13 hun(raw);
14 Hold baseRaw = new Hold();
15 baseRaw.set(new Object()); //!
16 hun(baseRaw);
17 Hold<?> wc = new Hold<Double>(3.14);
18 hun(wc);
19 }
20 }
fun()
中的参数类型时确切的,而在hun()
中,Hold的参数是一个无界通配符,看起来是未知的,但是在内部被调用的时候发生的是:参数类型在调用hun()
的过程中被捕获,因此它可以在对fun()
的调用中被使用。
使用java泛型时会出现的问题:
- 任何基本类型都不能作为类型参数,但是可以使用其包装类或者自动包装机制,需要注意的是自动包装机制并不适用于数组。
- 一个类不能实现同一个泛型接口的两种变体,由于擦除原因,这两个变体会成为相同的接口。
- 使用带有泛型类型参数的转型或instanceof不会有任何效果,如果没有
@SuppressWarnings
注解,编译器会产生警告,这是由于擦除原因,编译器不知道转型是否安全。但是可以使用泛型类来转型,即用新的转型方式(Class.cast()
)。 - 重载时泛型方法使用不同的类型参数不足以作为不同的方法签名。
- 基类实现了某个泛型接口,导出类就不能再实现该接口的不同变体。
自限定类型
“古怪的循环(CRG)”是指类相当古怪地出现在自己的基类中这一事实。即类似于这样class CRG extends GenericType<CRG> {}
,它能够产生使用导出类作为其参数和返回类型的基类,还能将导出类型用作其域类型,甚至将被擦除为Object的类型。
1 import static com.mceiba.util.Print.*;
2 import java.util.*;
3 class BasicHolder<T>{
4 T element;
5 void set(T arg) { element = arg; }
6 T get() { return element; }
7 void fun() { println(element.getClass().getSimpleName()); }
8 }
9 class Subtype extends BasicHolder<Subtype> {}
10 public class RecurringGeneric{
11 public static void main(String[] args){
12 Subtype sb = new Subtype(), st = new Subtype();
13 sb.set(st);
14 Subtype sp = sb.get();
15 sb.fun();
16 }
17 }
在这里,新类Subtype接受的参数和返回值具有Subtype类型而不仅仅是基类BasicHolder类型。这就是CRG的本质:基类用导出类替代其参数。这意味着泛型基类变成了一种其所有导出类的公共功能的模板,但是这些功能对于其所有参数和返回值,将使用导出类型,也就是说所产生的类中将使用确切类型而不是基类型。
自限定采用额外的步骤,强制泛型当做其自己的边界参数来使用。即类似于class A extends SelfBounded<A> {}
,强制要求将正在定义的类当做参数传递给基类。自限定的意义在于它保证类型参数必须与正在定义的类相同。可以使用不同的自限定类型,但是不能使用不是自限定类型的类型参数。自限定只强作用于继承关系,还可以用作于泛型方法,这可以防止这个方法被用于规定的自限定参数之外的任何事物上。
1 import static com.mceiba.util.Print.*;
2 import java.util.*;
3 class SelfBounded<T extends SelfBounded<T>>{
4 T element;
5 SelfBounded<T> set(T arg){
6 element = arg;
7 return this;
8 }
9 T get() { return element; }
10 void fun() { println(element.getClass().getSimpleName()); }
11 }
12 class A extends SelfBounded<A> {}
13 class B extends SelfBounded<A> {}
14 class C extends SelfBounded<C>{
15 C setAndGet(C c) { set(c); return get(); }
16 }
17 class SelfBoundingMethod{
18 static <T extends SelfBounded<T>> T fun(T arg){
19 return arg.set(arg).get();
20 }
21 }
22 interface GenericGetter<T extends GenericGetter<T>> { T get(); }
23 interface Getter extends GenericGetter<Getter> {}
24 public class SelfBound{
25 static void test(Getter g){
26 Getter gt = g.get();
27 GenericGetter gg = g.get();
28 }
29 public static void main(String[] args){
30 A a = new A();
31 a.set(new A());
32 a = a.set(new A()).get();
33 a = a.get();
34 C c = new C();
35 c = c.setAndGet(new C());
36 A s = SelfBoundingMethod.fun(new A());
37 }
38 }
自限定类型的价值在于它们可以产生协变参数类型,即方法参数类型会随子类而变化。协变参数类型允许返回更具体的类型,即子类中与父类方法签名一致,但是返回类型是父类返回类型的某个子类时就可以覆盖。在使用自限定类型时,在导出类中只有一个方法,并且这个方法接受导出类型而不是基类型为参数。
由于擦除的原因,将泛型应用于异常是非常受限的。catch语句不能捕获泛型类型的异常,因为在编译期和运行期都必须知道异常的确切类型。泛型类也不能直接或间接继承自Thowable,但是类型参数可能会在一个方法的throws子句中用到,这样throw语句抛出的异常可以被捕获到。
混型
混型最基本的含义是混合多个类的能力,以产生一个可以表示混型中所有类型的类。混型的价值之一是它可以将特性和行为一致地应用于多个类之上,如果想在混型类中修改某些东西,这些修改将会应用于混型所应用的所有类型之上。
java常见的解决方案是使用接口来产生混型效果,基本上是使用代理,每个混入对象都要求在混型类中有一个相应的域,并编写所需的方法,将调用转发给适当的对象。
1 import static com.mceiba.util.Print.*;
2 import java.util.*;
3 interface TimeStamp { long getStamp(); }
4 class TimeStampImpl implements TimeStamp{
5 private final long timeStamp;
6 public TimeStampImpl() { timeStamp = new Date().getTime(); }
7 public long getStamp() { return timeStamp; }
8 }
9 interface SerialNumber { long getSerialNumber(); }
10 class SerialNumberImpl implements SerialNumber{
11 private static long counter = 1;
12 private final long serialNumber = counter++;
13 public long getSerialNumber() { return serialNumber; }
14 }
15 interface Basic{
16 void set(String val);
17 String get();
18 }
19 class BasicImpl implements Basic{
20 private String value;
21 public void set(String val) { value = val; }
22 public String get() { return value; }
23 }
24 class Mixin extends BasicImpl implements TimeStamp, SerialNumber{
25 private TimeStamp timeStamp = new TimeStampImpl();
26 private SerialNumber serialNumber = new SerialNumberImpl();
27 public long getStamp() { return timeStamp.getStamp(); }
28 public long getSerialNumber() { return serialNumber.getSerialNumber(); }
29 }
30 public class Mixins{
31 public static void main(String[] args){
32 Mixin mixin1 = new Mixin(), mixin2 = new Mixin();
33 mixin1.set("test string 1");
34 mixin2.set("test string 2");
35 println(mixin1.get()+" "+mixin1.getStamp()+" "+mixin1.getSerialNumber());
36 println(mixin2.get()+" "+mixin2.getStamp()+" "+mixin2.getSerialNumber());
37 }
38 }
装饰器模式使用分层对象来动态透明的地向单个对象中添加责任。装饰器指定包装在最初的对象周围的所有对象都具有相同的基本接口。某些事物是可装饰的,可以通过将其他类包装在这个可装饰对象的四周,来将功能分层。装饰器是通过使用组合和形式化结构(可装饰物/装饰器层次结构)来实现的,而混型是基于继承的。基于参数化类型的混型可以当做是一种泛型装饰器机制,这种机制不需要装饰器模式的继承结构。
1 import static com.mceiba.util.Print.*;
2 import java.util.*;
3 class Basic{
4 private String value;
5 public void set(String val) { value = val; }
6 public String get() { return value; }
7 }
8 class Decorator extends Basic{
9 protected Basic basic;
10 public Decorator(Basic basic) { this.basic = basic; }
11 public void set(String val) { basic.set(val); }
12 public String get() { return basic.get(); }
13 }
14 class TimeStamp extends Decorator{
15 private final long timeStamp;
16 public TimeStamp(Basic basic){
17 super(basic);
18 timeStamp = new Date().getTime();
19 }
20 public long getStamp() { return timeStamp; }
21 }
22 class SerialNumber extends Decorator{
23 private static long counter = 1;
24 private final long serialNumber = counter++;
25 public SerialNumber(Basic basic) { super(basic); }
26 public long getSerialNumber() { return serialNumber; }
27 }
28 public class Decoration{
29 public static void main(String[] args){
30 TimeStamp ts = new TimeStamp(new Basic());
31 SerialNumber sn = new SerialNumber(new Basic());
32 }
33 }
装饰器只是对混型提出的问题的一种局限的解决方案,因为它只能有效地工作于装饰中的最后一层,使用动态代理可以创建一种比装饰器更贴近混型模型的机制。
我们总是在设法编写能够尽可能广泛的应用的代码,为实现这一点,我们需要放松对代码将要作用的类型的限制,同时又不丢失静态类型检查的好处。泛型像这方面迈进了一步,至少在持有对象时,但是要想在泛型类型上执行操作就会产生问题,由于擦除的原因使得泛化受到限制。一些编程语言提供的解决方案称为潜在类型机制或者结构化类型机制,又或者鸭子类型机制,即“如果它走起来像鸭子,并且叫起来也像鸭子,那么你就可以把它当鸭子对待”。
潜在类型机制
潜在类型机制是一种代码组织和复用机制,使用它的代码可以更容易的复用,C++、Python、Ruby等都支持潜在类型机制。
1 class Duck:
2 def move(self):
3 print("Duck moving")
4 def speak(self):
5 print("Duck speaking ...")
6 def fun(self):
7 pass
8 class Robot:
9 def move(self):
10 print("Robot moving")
11 def speak(self):
12 print("Robot speaking ...")
13 def fun(self):
14 pass
15 def perform(anything):
16 anything.move()
17 anything.speak()
18
19 if __name__ == '__main__':
20 duck = Duck()
21 robot = Robot()
22 perform(duck)
23 perform(robot)
24 #Duck moving
25 #Duck speaking ...
26 #Robot moving
27 #Robot speaking ...
而在java中由于泛型机制是后期加入的,所以并未提供潜在类型机制的支持,类似的效果必须强制使用实现自相同的接口或有共同的父类。但是这并不意味着有界泛型代码不能在不同的类型层次结构之间应用,我们可以使用其他方式创建真正的泛型代码。
可以使用的一种方式是反射。
1 import static com.mceiba.util.Print.*;
2 import java.lang.reflect.*;
3 class Mime{
4 public void walk() {}
5 public void sit() { println("Pretending to sit"); }
6 public void push() {}
7 public String toString() { return "Mime"; }
8 }
9 class Dog{
10 public void speak() { println("Woof!"); }
11 public void sit() { println("Sitting"); }
12 public void reproduce() {}
13 }
14 class Robot{
15 public void speak() { println("Click!"); }
16 public void sit() { println("Clank"); }
17 public void oilChange() {}
18 }
19 class Communicate{
20 public static void perform(Object speaker){
21 Class<?> spk = speaker.getClass();
22 try{
23 try{
24 Method speak = spk.getMethod("speak");
25 speak.invoke(speaker);
26 }catch(NoSuchMethodException e){
27 println(speaker+" cannot speak");
28 }
29 try{
30 Method sit = spk.getMethod("sit");
31 sit.invoke(speaker);
32 }catch(NoSuchMethodException e){
33 println(speaker+" cannot sit");
34 }
35 }catch(Exception e){
36 throw new RuntimeException(speaker.toString(), e);
37 }
38 }
39 }
40 public class LatentReflection{
41 public static void main(String[] args){
42 Communicate.perform(new Dog());
43 Communicate.perform(new Robot());
44 Communicate.perform(new Mime());
45 }
46 }
反射实现了一些有趣的可能性,但是它将所有类型检查都转移到了运行时,如果能够实现编译期的类型检查,这通常更符合要求。
这里有一个将一个方法应用于序列的例子。
1 import static com.mceiba.util.Print.*;
2 import java.lang.reflect.*;
3 import java.util.*;
4 class Apply{
5 public static <T, S extends Iterable<? extends T>>
6 void apply(S seq, Method f, Object... args){
7 try{
8 for(T t: seq) f.invoke(t, args);
9 }catch(Exception e){
10 throw new RuntimeException(e);
11 }
12 }
13 }
14 class Shape{
15 public void rotate() { println(this+" rotate"); }
16 public void resize(int size) { println(this+" resize "+size); }
17 }
18 class Square extends Shape {}
19 class FilledList<T> extends ArrayList<T>{
20 public FilledList(Class<? extends T> type, int size){
21 try{
22 for(int i=0; i<size; i++)
23 add(type.newInstance());
24 }catch(Exception e){
25 throw new RuntimeException(e);
26 }
27 }
28 }
29 class SimpleQueue<T> implements Iterable<T>{
30 private LinkedList<T> storage = new LinkedList<T>();
31 public void add(T t) { storage.offer(t); }
32 public T get() { return storage.poll(); }
33 public Iterator<T> iterator() { return storage.iterator(); }
34 }
35 public class TypeMark{
36 public static void main(String[] args) throws Exception{
37 List<Shape> shapes = new ArrayList<Shape>();
38 for(int i=0; i<10; i++)
39 shapes.add(new Shape());
40 Apply.apply(shapes, Shape.class.getMethod("rotate"));
41 Apply.apply(shapes, Shape.class.getMethod("resize", int.class), 5);
42 List<Square> squares = new ArrayList<Square>();
43 for(int i=0; i<10; i++)
44 squares.add(new Square());
45 Apply.apply(squares, Shape.class.getMethod("rotate"));
46 Apply.apply(squares, Shape.class.getMethod("resize", int.class), 5);
47
48 Apply.apply(new FilledList<Shape>(Shape.class, 10),
49 Shape.class.getMethod("rotate"));
50 Apply.apply(new FilledList<Shape>(Shape.class, 10),
51 Shape.class.getMethod("resize", int.class), 5);
52
53 SimpleQueue<Shape> shapeQ = new SimpleQueue<Shape>();
54 for(int i=0; i<5; i++){
55 shapeQ.add(new Shape());
56 shapeQ.add(new Square());
57 }
58 Apply.apply(shapeQ, Shape.class.getMethod("rotate"));
59 }
60 }
上面的例子确实很优雅,但是由于只能作用于实现了Iterable接口的对象,因此还是受限的,当我们没有合适的接口时,自然想到了适配器模式,用我们使用适配器来适配已有的接口,以产生想要的接口。使用适配器能编写出真正的泛型代码,作为java中比较好的潜在类型机制替代方案。
1 import java.util.*;
2 import static com.mceiba.util.Print.*;
3 interface Addable<T> { void add(T t); }
4 class Fill {
5 // Classtoken version:
6 public static <T> void fill(Addable<T> addable,
7 Class<? extends T> classToken, int size) {
8 for(int i = 0; i < size; i++)
9 try {
10 addable.add(classToken.newInstance());
11 } catch(Exception e) {
12 throw new RuntimeException(e);
13 }
14 }
15 // Generator version:
16 public static <T> void fill(Addable<T> addable,
17 Generator<T> generator, int size) {
18 for(int i = 0; i < size; i++)
19 addable.add(generator.next());
20 }
21 }
22
23 // To adapt a base type, you must use composition.
24 // Make any Collection Addable using composition:
25 class AddableCollectionAdapter<T> implements Addable<T> {
26 private Collection<T> c;
27 public AddableCollectionAdapter(Collection<T> c) {
28 this.c = c;
29 }
30 public void add(T item) { c.add(item); }
31 }
32
33 // A Helper to capture the type automatically:
34 class Adapter {
35 public static <T> Addable<T> collectionAdapter(Collection<T> c) {
36 return new AddableCollectionAdapter<T>(c);
37 }
38 }
39
40 // To adapt a specific type, you can use inheritance.
41 // Make a SimpleQueue Addable using inheritance:
42 class AddableSimpleQueue<T>
43 extends SimpleQueue<T> implements Addable<T> {
44 public void add(T item) { super.add(item); }
45 }
46
47 public class FillTest {
48 public static void main(String[] args) {
49 // Adapt a Collection:
50 List<Coffee> carrier = new ArrayList<Coffee>();
51 Fill.fill(
52 new AddableCollectionAdapter<Coffee>(carrier),
53 Coffee.class, 3);
54 // Helper method captures the type:
55 Fill.fill(Adapter.collectionAdapter(carrier),
56 Latte.class, 2);
57 for(Coffee c: carrier)
58 println(c);
59 println("----------------------");
60 // Use an adapted class:
61 AddableSimpleQueue<Coffee> coffeeQueue =
62 new AddableSimpleQueue<Coffee>();
63 Fill.fill(coffeeQueue, Mocha.class, 4);
64 Fill.fill(coffeeQueue, Latte.class, 1);
65 for(Coffee c: coffeeQueue)
66 println(c);
67 }
68 } /* Output:
69 Coffee 0
70 Coffee 1
71 Coffee 2
72 Latte 3
73 Latte 4
74 ----------------------
75 Mocha 5
76 Mocha 6
77 Mocha 7
78 Mocha 8
79 Latte 9
80 *///:~
第16章 数组
数组的基本操作
数组与其他容器的区别有三方面:效率、类型和保存基本类型的能力。在java中数组是一种效率最高的存储和随机访问对象引用序列的方式。数组是一个简单的线性序列,元素访问非常快,代价是大小被固定,在其生命周期中不可变。数组和容器也都不能越界,否则会产生异常。泛型之前的容器不能持有基本类型,只有数组可以。数组与现在的容器相比仅存的优势效率。
数组的[]
语法是访问数组对象唯一的方式,只读成员length
(返回数组的大小而不是元素的个数)是数组对象的一部分,也是数组唯一可以访问的字段或方法。对象数组和基本类型数组唯一的区别是对象数组保存的是引用,基本类型数组直接保存的是基本类型的值。
数组标示符其实只是一个引用,指向在堆中创建的一个真实的数组对象,这个对象用以保存指向其他对象的引用。
1 import static com.mceiba.util.Print.*;
2 import java.util.*;
3 class Sphere{
4 private static long counter;
5 private final long id = counter++;
6 public String toString() { return " Sphere "+id; }
7 }
8 class ArrayOptions{
9 public static void main(String[] args){
10 Sphere[] a;
11 Sphere[] b = new Sphere[5];
12 println("b: "+Arrays.toString(b));
13 Sphere[] c = new Sphere[4];
14 for(int i=0; i<c.length; i++)
15 if(c[i]==null)
16 c[i]=new Sphere();
17 Sphere[] d = {new Sphere(), new Sphere(), new Sphere()};
18 a = new Sphere[]{new Sphere(), new Sphere()};
19 println("a.length = "+a.length);
20 println("b.length = "+b.length);
21 println("c.length = "+c.length);
22 println("d.length = "+d.length);
23 a = d;
24 println("a.length = "+a.length);
25
26 int[] e;
27 int[] f = new int[5];
28 println("f: "+Arrays.toString(f));
29 int[] g = new int[4];
30 for(int i=0; i<g.length; i++)
31 g[i]=i*i;
32 int[] h = {11, 22, 33};
33 //1println("e.length = "+e.length);
34 println("f.length = "+f.length);
35 println("g.length = "+g.length);
36 println("h.length = "+h.length);
37 e = h;
38 println("e.length = "+e.length);
39 e = new int[]{1, 2};
40 println("e.length = "+e.length);
41 }
42 }
粗糙数组中构成矩阵的每个向量都可以具有任意的长度,使用Arrays.deepToString()
可以深层次的显示数组,对基本类型和对象都适用。C++中只能返回数组的引用,而java可以返回数组本身,就像其他的对象一样。
1 static char[] getChar(int num){
2 num = (num>26)? 26:num;
3 char[] chr = new char[num];
4 Random rand = new Random();
5 for(int i=0; i<num; i++)
6 chr[i] = ch[rand.nextInt(num-1)];
7 return chr;
8 }
9 char[] ch = new char[3];
10 boolean[] bl = new boolean[3];
11 println("ch: "+Arrays.toString(ch));
12 println("bl: "+Arrays.toString(bl));
13 char[] chr = getChar(10);
14 println("chr: "+Arrays.toString(chr));
15 int[][][] k = new int[2][3][4];
16 int[][] m = {
17 {1, 2, 3},
18 {4, 5, 6},
19 {7, 8, 9},
20 } ;
21 println("m: "+Arrays.toString(m));
22 println("m: "+Arrays.deepToString(m));
23 Random rand = new Random();
24 int[][][] kk = new int[5][][];
25 for(int i=0; i<kk.length; i++){
26 kk[i]=new int[rand.nextInt(5)][];
27 for(int j=0; j<kk[i].length; j++)
28 kk[i][j] = new int[rand.nextInt(5)];
29 }
30 println("kk: "+Arrays.deepToString(kk));
数组与泛型
通常数组与泛型不能很好的结合,不能实例化具有参数类型的数组,擦除会移除参数类型信息,而数组必须知道他们所持有的确切类型,但是可以参数化数组本身的类型。
1 import static com.mceiba.util.Print.*;
2 import java.util.*;
3 class ClassParam<T>{
4 public T[] fun(T[] arg) { return arg; }
5 }
6 class MethodParam{
7 public static <T> T[] fun(T[] arg) { return arg; }
8 }
9 class ArrayType{
10 @SuppressWarnings("unchecked")
11 public static void main(String[] args){
12 Integer[] in = {1, 2, 3, 4, 5};
13 Double[] dou = {1.1, 2.2, 3.3, 4.4, 5.5};
14
15 Integer[] inte = new ClassParam<Integer>().fun(in);
16 Double[] doub = MethodParam.fun(dou);
17
18 List<String>[] ls;
19 List[] la = new List[5];
20 ls = (List<String>[])la;
21 ls[0] = new ArrayList<String>();
22 //!ls[1] = new ArrayList<Integer>();
23 Object[] obj = ls;
24 obj[1] = new ArrayList<Integer>();
25 List<Sphere>[] sphere = (List<Sphere>[])new List[5];
26 for(int i=0; i<sphere.length; i++){
27 sphere[i] = new ArrayList<Sphere>();
28 sphere[i].add(new Sphere());
29 sphere[i].add(new Sphere());
30 }
31 println("sphere: "+Arrays.toString(sphere));
32 }
33 }
不能直接创建泛型数组,但是可以创建非泛型的数组,然后将其转型。由于数组是协变类型的,可以将不同参数类型的容器赋值给数组,因为他们都可以看做是Object。
Arrays的实用功能
Arrays.fill()
:可以用单一的数值来填充整个数组或者数组的某个区域。System.arraycopy()
:复制数组比for循环要快,但是只是浅复制,对于对象只是复制了引用。Arrays.equals()
:重载后可以比较整个数组,数组相等的条件是元素个数相等,且对应位置元素也相等。也可以对每个元素使用equals()
(基本类型使用包装类的equals()
方法)。Arrays.sort()
:实现comparable接口(只有一个compareTo()
方法)或者具有相关联的Comparator,就可以进行比较和排序。Arrays.binarySearch()
:对已排序的的数组进行快速查找
1 import static com.mceiba.util.Print.*;
2 import java.util.*;
3 class CompType implements Comparable<CompType>{
4 int i, j;
5 private static int count = 1;
6 public CompType(int i, int j) { this.i = i; this.j = j; }
7 public String toString(){
8 String result = "[ i = "+i+", j = "+j+" ]";
9 if(count++%3 == 0) result+="\n";
10 return result;
11 }
12 public int compareTo(CompType rv){
13 return (i<rv.i ? -1 : (i==rv.i ? 0 : 1));
14 }
15 }
16 class ArraysFunc{
17 public static void main(String[] args){
18 int[] in = {1, 2, 3, 4, 5};
19 Arrays.fill(in, 33);
20 println("fill in: "+Arrays.toString(in));
21 Arrays.fill(in, 1, 4, 99);
22 println("fill in: "+Arrays.toString(in));
23
24 int[] out = new int[10];
25 System.arraycopy(in, 0, out, 0, in.length);
26 println("copy out: "+Arrays.toString(out));
27 println(Arrays.equals(in, out));
28
29 Random rand = new Random();
30 CompType[] com =new CompType[9];
31 for(int i=0; i<com.length; i++)
32 com[i]=new CompType(rand.nextInt(100), rand.nextInt(100));
33 println("before sort:");
34 println(Arrays.toString(com));
35 Arrays.sort(com);
36 println("after sort:");
37 println(Arrays.toString(com));
38
39 int[] bs = new int[10];
40 for(int i=0; i<bs.length; i++)
41 bs[i] = rand.nextInt(50);
42 Arrays.sort(bs);
43 int sb = Arrays.binarySearch(bs, bs[8]);
44 println("binarySearch bs: "+Arrays.toString(bs)+", "+bs[8]+": "+sb);
45 }
46 }
第17章 容器深入研究
第18章 Java I/O系统
第19章 枚举类型
基本enum特性
values()
可以返回enum实例的数组,元素顺序严格保持在enum中声明的顺序。创建enum时编译器会生成一个相关的类(final型),这个类继承自java.lang.Enum。enum除了不能继承外,基本上可以看做一个类,即可以添加方法(实例在最开始),甚至可以有main方法。enum可以有自己的构造器,但是只能在内部使用创建enum实例,即在enum定义结束后,编译器不允许再使用构造器。enum还可以覆盖方法,比如toString()
。
在switch语句的case中使用enum实例可以不用enum类型来修饰。values()
方法是由编译器插入到enum中的static方法,如果将enum向上转型为Enum,values()
方法将不可用,不过可以用Class.getEnumConstants()
方法获得所有的enum实例。
enum都继承自Enum,所以就不能继承其他的类,但是可以实现接口。
1 import static com.mceiba.util.Print.*;
2 import com.mceiba.util.OSExecute;
3 import java.util.*;
4 import java.lang.Enum;
5 enum Shrubbery { GROUND, CRAWLING, HANGING }
6 enum OzWitch{
7 WEST("Miss Gulch, aka the Wiched Witch of the West"),
8 NORTH("Glinda, the Good Witch of the North"),
9 EAST("Wicked Witch of the East, wearer of the Ruby"),
10 SOUTH("Good by inference, but missing");
11 private String description;
12 private OzWitch(String description) { this.description = description; }
13 public String getDescription() { return description; }
14 public String toString(){
15 String id = name();
16 String lower = id.substring(1).toLowerCase();
17 return id.charAt(0)+lower;
18 }
19 public static void test(){
20 for(OzWitch ow : OzWitch.values()){
21 println(ow+": "+ow.getDescription());
22 }
23 }
24 }
25 enum Signal { GREEN, YELLOW, RED }
26 public class EnumClass{
27 Signal color = Signal.RED;
28 public void change(){
29 switch(color){
30 case RED : color = Signal.YELLOW; break;
31 case YELLOW : color = Signal.GREEN; break;
32 case GREEN : color = Signal.RED; break;
33 }
34 }
35 public String toString(){
36 return "This tranffic light is "+color;
37 }
38 public static void main(String[] args){
39 for(Shrubbery sb : Shrubbery.values()){
40 println(sb+" ordinal: "+sb.ordinal()+", DeclaringCalss: "+
41 sb.getDeclaringClass()+", name: "+sb.name());
42 }
43 Enum eu = Signal.GREEN;
44 //!eu.values();
45 for(Enum en : eu.getClass().getEnumConstants())
46 print(en+" ");
47 println();
48 for(String st : "GROUND CRAWLING HANGING".split(" ")){
49 Shrubbery sby = Enum.valueOf(Shrubbery.class, st);
50 println(sby);
51 }
52 OzWitch.test();
53 EnumClass tranffic = new EnumClass();
54 for(int i=0; i<5; i++){
55 println(tranffic);
56 tranffic.change();
57 }
58 OSExecute.command("javap Signal");
59 }
60 }
组织枚举
在一个接口的内部,创建实现该接口的枚举,可以达到将枚举元素分类组织的目的。还可以使用嵌套的方式来组织枚举。
1 import static com.mceiba.util.Print.*;
2 import java.util.*;
3 import java.lang.Enum;
4 interface Food{
5 enum Appetizer implements Food{
6 SALAD, SOUP, SPARING_ROLLS;
7 }
8 enum MainCourse implements Food{
9 LASAGNE, BURRITO, PAD_THAI,
10 LENTILS, HUMMOUS, VINDALOO;
11 }
12 enum Dessert implements Food{
13 TIRAMISU, GELATO, BLACK_FORSET,
14 FRUIT, CREME_CARAMEL;
15 }
16 enum Coffee implements Food{
17 BLACK_COFFEE, DECAF_COFFEE, ESPRESSO,
18 LATTE, CAPPUCCINO, TEA, HERB_TEA;
19 }
20 }
21 enum Course{
22 APPETIZER(Food.Appetizer.class),
23 MAINCOURSE(Food.MainCourse.class),
24 DESSERT(Food.Dessert.class),
25 COFFEE(Food.Coffee.class);
26 private Food[] values;
27 private Course(Class<? extends Food> kind){
28 values = kind.getEnumConstants();
29 }
30 Random rand = new Random();
31 public Food getValue() { return values[rand.nextInt(values.length)]; }
32 }
33 enum Meal{
34 APPETIZER(Food.Appetizer.class),
35 MAINCOURSE(Food.MainCourse.class),
36 DESSERT(Food.Dessert.class),
37 COFFEE(Food.Coffee.class);
38 private Food[] values;
39 public interface Food{
40 enum Appetizer implements Food{
41 SALAD, SOUP, SPARING_ROLLS;
42 }
43 enum MainCourse implements Food{
44 LASAGNE, BURRITO, PAD_THAI,
45 LENTILS, HUMMOUS, VINDALOO;
46 }
47 enum Dessert implements Food{
48 TIRAMISU, GELATO, BLACK_FORSET,
49 FRUIT, CREME_CARAMEL;
50 }
51 enum Coffee implements Food{
52 BLACK_COFFEE, DECAF_COFFEE, ESPRESSO,
53 LATTE, CAPPUCCINO, TEA, HERB_TEA;
54 }
55 }
56 private Meal(Class<? extends Food> kind){
57 values = kind.getEnumConstants();
58 }
59 Random rand = new Random();
60 public Food getValue() { return values[rand.nextInt(values.length)]; }
61 }
62 public class TypeOfFood{
63 public static void main(String[] args){
64 for(int i=0; i<2; i++){
65 for(Course course : Course.values()){
66 Food food = course.getValue();
67 println(food);
68 }
69 println("-----");
70 }
71 for(int i=0; i<2; i++){
72 for(Meal meal : Meal.values()){
73 Meal.Food food = meal.getValue();
74 println(food);
75 }
76 println("-----");
77 }
78 }
79 }
EnumSet可以替代传统的基于int的位标识,这种标志同样表示“开/关”的信息,取代bit,它的内部是将一个long值作为比特向量,同时保证了速度而又更容易让人理解。
1 EnumSet<Signal> num = EnumSet.noneOf(Signal.class);
2 num.add(Signal.RED);
3 println(num);
4 num.addAll(EnumSet.of(Signal.YELLOW, Signal.GREEN));
5 println(num);
6 num.removeAll(EnumSet.of(Signal.YELLOW, Signal.GREEN));
7 println(num);
8 num = EnumSet.allOf(Signal.class);
9 println(num);
EnumMap是一种特殊的Map,其中的key必须来自于一个enum,而且enum作为一个实例的键总是存在的,如果没有调用put()
方法存入值,对应的值就是null。
1 interface Command { void action(); }
2 enum Signal { RED, YELLOW, GREEN }
3 public class Commands{
4 public static void main(String[] args){
5 EnumMap<Signal, Command> em = new EnumMap<Signal, Command>(Signal.class);
6 em.put(Signal.RED, new Command(){
7 public void action() { println("stop"); }
8 });
9 em.put(Signal.YELLOW, new Command(){
10 public void action() { println("wait"); }
11 });
12 em.put(Signal.GREEN, new Command(){
13 public void action() { println("go"); }
14 });
15 for(Map.Entry<Signal, Command> e : em.entrySet()){
16 print(e.getKey()+": ");
17 e.getValue().action();
18 }
19 }
20 }
这里使用了命令模式,即首先需要一个只有单一方法的接口,然后从该接口实现具有不同行为的多了类,接下来构建命令对象,并且根据对象实现不同的行为。
常量相关的方法
enum允许为enum实例编写方法,从而可以为每个enum实例赋予不同的行为。要实现常量相关的方法,需要为enum定义一个或多个abstract方法,然后为每个enum实例实现该抽象方法,也可以定义一般的方法,并且在enum实例中可以覆盖。这通常称为表驱动的代码(table-driven code),与命令模式具有某种相似之处。
enum的实例与类也有一些相似之处,在调用常量相关的方法时表现出了“多态”的行为,但是相似之处也仅限于此,并不能将enum实例作为一个类来使用。每个enum元素都是enum类型放入static final实例。
1 public enum ConstantMethod{
2 DATETIME{
3 void fun() { println("Current date"); }
4 String getInfo(){
5 return DateFormat.getDateInstance().format(new Date());
6 }
7 },
8 CLASSPATH{
9 String getInfo(){
10 return System.getenv("CLASSPATH");
11 }
12 },
13 VERSION{
14 void fun() { println("System JDK version"); }
15 String getInfo(){
16 return System.getProperty("java.version");
17 }
18 };
19 abstract String getInfo();
20 void fun() { println("default method"); }
21 public static void main(String[] args){
22 for(ConstantMethod cm : values()){
23 cm.fun();
24 println(cm.getInfo());
25 }
26 }
27 }
通过常量相关的方法还可以实现简单的责任链模式,即以多种不同的方法来解决一个问题,然后将这些方法链接在一起,当一个请求到来时,遍历整个链,直到链中的某个解决方案(每一个解决方案可以当做一种策略)可以处理该请求。
1 import java.util.*;
2 import static com.mceiba.util.Print.*;
3 class Enums{
4 public static <T extends Enum<T>> T random(Class<T> type){
5 Random rand = new Random();
6 return type.getEnumConstants()[rand.nextInt(type.getEnumConstants().length)];
7 }
8 }
9 class Mail {
10 // The NO's lower the probability of random selection:
11 enum GeneralDelivery {YES,NO1,NO2,NO3,NO4,NO5}
12 enum Scannability {UNSCANNABLE,YES1,YES2,YES3,YES4}
13 enum Readability {ILLEGIBLE,YES1,YES2,YES3,YES4}
14 enum Address {INCORRECT,OK1,OK2,OK3,OK4,OK5,OK6}
15 enum ReturnAddress {MISSING,OK1,OK2,OK3,OK4,OK5}
16 GeneralDelivery generalDelivery;
17 Scannability scannability;
18 Readability readability;
19 Address address;
20 ReturnAddress returnAddress;
21 static long counter = 0;
22 long id = counter++;
23 public String toString() { return "Mail " + id; }
24 public String details() {
25 return toString() +
26 ", General Delivery: " + generalDelivery +
27 ", Address Scanability: " + scannability +
28 ", Address Readability: " + readability +
29 ", Address Address: " + address +
30 ", Return address: " + returnAddress;
31 }
32 // Generate test Mail:
33 public static Mail randomMail() {
34 Mail m = new Mail();
35 m.generalDelivery= Enums.random(GeneralDelivery.class);
36 m.scannability = Enums.random(Scannability.class);
37 m.readability = Enums.random(Readability.class);
38 m.address = Enums.random(Address.class);
39 m.returnAddress = Enums.random(ReturnAddress.class);
40 return m;
41 }
42 public static Iterable<Mail> generator(final int count) {
43 return new Iterable<Mail>() {
44 int n = count;
45 public java.util.Iterator<Mail> iterator() {
46 return new java.util.Iterator<Mail>() {
47 public boolean hasNext() { return n-- > 0; }
48 public Mail next() { return randomMail(); }
49 public void remove() { // Not implemented
50 throw new UnsupportedOperationException();
51 }
52 };
53 }
54 };
55 }
56 }
57
58 public class Chain {
59 enum MailHandler {
60 GENERAL_DELIVERY {
61 boolean handle(Mail m) {
62 switch(m.generalDelivery) {
63 case YES:
64 println("Using general delivery for " + m);
65 return true;
66 default: return false;
67 }
68 }
69 },
70 MACHINE_SCAN {
71 boolean handle(Mail m) {
72 switch(m.scannability) {
73 case UNSCANNABLE: return false;
74 default:
75 switch(m.address) {
76 case INCORRECT: return false;
77 default:
78 println("Delivering "+ m + " automatically");
79 return true;
80 }
81 }
82 }
83 },
84 VISUAL_INSPECTION {
85 boolean handle(Mail m) {
86 switch(m.readability) {
87 case ILLEGIBLE: return false;
88 default:
89 switch(m.address) {
90 case INCORRECT: return false;
91 default:
92 println("Delivering " + m + " normally");
93 return true;
94 }
95 }
96 }
97 },
98 RETURN_TO_SENDER {
99 boolean handle(Mail m) {
100 switch(m.returnAddress) {
101 case MISSING: return false;
102 default:
103 println("Returning " + m + " to sender");
104 return true;
105 }
106 }
107 };
108 abstract boolean handle(Mail m);
109 }
110 static void handle(Mail m) {
111 for(MailHandler handler : MailHandler.values())
112 if(handler.handle(m))
113 return;
114 println(m + " is a dead letter");
115 }
116 public static void main(String[] args) {
117 for(Mail mail : Mail.generator(10)) {
118 println(mail.details());
119 handle(mail);
120 println("*****");
121 }
122 }
123 } /* Output:
124 Mail 0, General Delivery: NO2, Address Scanability: UNSCANNABLE, Address Readability: YES3, Address Address: OK1, Return address: OK1
125 Delivering Mail 0 normally
126 *****
127 Mail 1, General Delivery: NO5, Address Scanability: YES3, Address Readability: ILLEGIBLE, Address Address: OK5, Return address: OK1
128 Delivering Mail 1 automatically
129 *****
130 Mail 2, General Delivery: YES, Address Scanability: YES3, Address Readability: YES1, Address Address: OK1, Return address: OK5
131 Using general delivery for Mail 2
132 *****
133 Mail 3, General Delivery: NO4, Address Scanability: YES3, Address Readability: YES1, Address Address: INCORRECT, Return address: OK4
134 Returning Mail 3 to sender
135 *****
136 Mail 4, General Delivery: NO4, Address Scanability: UNSCANNABLE, Address Readability: YES1, Address Address: INCORRECT, Return address: OK2
137 Returning Mail 4 to sender
138 *****
139 Mail 5, General Delivery: NO3, Address Scanability: YES1, Address Readability: ILLEGIBLE, Address Address: OK4, Return address: OK2
140 Delivering Mail 5 automatically
141 *****
142 Mail 6, General Delivery: YES, Address Scanability: YES4, Address Readability: ILLEGIBLE, Address Address: OK4, Return address: OK4
143 Using general delivery for Mail 6
144 *****
145 Mail 7, General Delivery: YES, Address Scanability: YES3, Address Readability: YES4, Address Address: OK2, Return address: MISSING
146 Using general delivery for Mail 7
147 *****
148 Mail 8, General Delivery: NO3, Address Scanability: YES1, Address Readability: YES3, Address Address: INCORRECT, Return address: MISSING
149 Mail 8 is a dead letter
150 *****
151 Mail 9, General Delivery: NO1, Address Scanability: UNSCANNABLE, Address Readability: YES2, Address Address: OK1, Return address: OK4
152 Delivering Mail 9 normally
153 *****
154 *///:~
枚举类型非常适合用来创建状态机,状态机一般具有多个特定的状态,根据输入从一个状态转移到下了一个状态,但是也可能存在瞬时状态。
1 import static com.mceiba.util.Print.*;
2 import com.mceiba.util.TextFile;
3 import java.util.*;
4
5 interface Generator<T> { T next(); }
6 enum Input{
7 NICKEL(5), DIME(10), QUARTER(25), DOLLAR(100),
8 TOOTHPASTE(200), CHIPS(75), SODA(100), SOAP(50),
9 ABORT_TRANSACTION{
10 public int amount(){
11 throw new RuntimeException("ABORT.amount()");
12 }
13 },
14 STOP{
15 public int amount(){
16 throw new RuntimeException("SHUT_DOWN.amount()");
17 }
18 };
19 int value;
20 Input(int value) { this.value = value; }
21 Input() {}
22 int amount() { return value; }
23 static Random rand = new Random();
24 public static Input randomSelection(){
25 return values()[rand.nextInt(values().length-1)];
26 }
27 }
28 enum Category{
29 MONEY(Input.NICKEL, Input.DIME, Input.QUARTER, Input.DOLLAR),
30 ITEM_SELECTION(Input.TOOTHPASTE, Input.CHIPS, Input.SODA, Input.SOAP),
31 QUIT_TANSACTION(Input.ABORT_TRANSACTION),
32 SHUT_DOWN(Input.STOP);
33 private Input[] values;
34 Category(Input... types) { values = types; }
35 private static EnumMap<Input, Category> categories =
36 new EnumMap<Input, Category>(Input.class);
37 static{
38 for(Category c : Category.class.getEnumConstants())
39 for(Input type : c.values)
40 categories.put(type, c);
41 }
42 public static Category categorize(Input input){
43 return categories.get(input);
44 }
45 }
46 public class VendingMachine{
47 private static State state = State.RESTING;
48 private static int amount = 0;
49 private static Input selection = null;
50 enum StateDuration { TRANSIENT }
51 enum State{
52 RESTING{
53 void next(Input input){
54 switch(Category.categorize(input)){
55 case MONEY:
56 amount += input.amount();
57 state = ADDING_MONEY;
58 break;
59 case SHUT_DOWN:
60 state = TERMINAL;
61 default:
62 }
63 }
64 },
65 ADDING_MONEY{
66 void next(Input input){
67 switch(Category.categorize(input)){
68 case MONEY:
69 amount += input.amount();
70 break;
71 case ITEM_SELECTION:
72 selection = input;
73 if(amount<selection.amount())
74 println("Insufficient money for "+selection);
75 else state = DISPENSING;
76 break;
77 case QUIT_TANSACTION:
78 state = GIVING_CHANGE;
79 break;
80 case SHUT_DOWN:
81 state = TERMINAL;
82 default:
83 }
84 }
85 },
86 DISPENSING(StateDuration.TRANSIENT){
87 void next(){
88 println("Here is your "+amount);
89 amount -= selection.amount();
90 state = GIVING_CHANGE;
91 }
92 },
93 GIVING_CHANGE(StateDuration.TRANSIENT){
94 void next(){
95 if(amount>0){
96 println("Your change: "+amount);
97 amount = 0;
98 }
99 state = RESTING;
100 }
101 },
102 TERMINAL{ void output() { println("Halted"); }};
103 private boolean isTransient = false;
104 State() {}
105 State(StateDuration trans) { isTransient = true; }
106 void next(Input input){
107 throw new RuntimeException("Only call next() for "+
108 "StateDuration.TRANSIENT states");
109 }
110 void next(){
111 throw new RuntimeException("Only call "+
112 "next(Input input) for non-transient states");
113 }
114 void output() { println(amount); }
115 }
116 static void run(Generator<Input> gen){
117 while(state != State.TERMINAL){
118 state.next(gen.next());
119 while(state.isTransient)
120 state.next();
121 state.output();
122 }
123 }
124 public static void main(String[] args){
125 Generator<Input> gen = new RandomInputGenerator();
126 if(args.length ==1)
127 gen = new FileInputGenerator(args[0]);
128 run(gen);
129 }
130 }
131
132 class RandomInputGenerator implements Generator<Input>{
133 public Input next() { return Input.randomSelection(); }
134 }
135 class FileInputGenerator implements Generator<Input>{
136 private Iterator<String> input;
137 public FileInputGenerator(String fileName){
138 input = new TextFile(fileName, ";").iterator();
139 }
140 public Input next() {
141 if(!input.hasNext())
142 return null;
143 return Enum.valueOf(Input.class, input.next().trim());
144 }
145 }
多路分发
Java只支持单路分发,即如果要执行的操作包含了不止一个类型未知的对象时,那么java的动态绑定机制只能处理其中一个的类型。要实现多路分发需要一些额外的工作,由于多态只能发生在方法调用时,所以要使用多路分发,那么就必须有多个方法调用,每个方法确定一个位置类型。但是一般可以设定一些配置,使一个方法的调用可以引出多个方法的调用。可以使用多种方法实现多路分发。
1 import static com.mceiba.util.Print.*;
2 import com.mceiba.util.Enums;
3 import java.util.*;
4 enum Outcome { WIN, LOSE, DRAW }
5 interface Item{
6 Outcome compete(Item it);
7 Outcome eval(Paper p);
8 Outcome eval(Scissors s);
9 Outcome eval(Rock r);
10 }
11 class Paper implements Item{
12 public Outcome compete(Item it) { return it.eval(this); }
13 public Outcome eval(Paper p) { return Outcome.DRAW; }
14 public Outcome eval(Scissors s) { return Outcome.WIN; }
15 public Outcome eval(Rock r) { return Outcome.LOSE; }
16 public String toString() { return "Paper"; }
17 }
18 class Scissors implements Item{
19 public Outcome compete(Item it) { return it.eval(this); }
20 public Outcome eval(Paper p) { return Outcome.LOSE; }
21 public Outcome eval(Scissors s) { return Outcome.DRAW; }
22 public Outcome eval(Rock r) { return Outcome.WIN; }
23 public String toString() { return "Scissors"; }
24 }
25 class Rock implements Item{
26 public Outcome compete(Item it) { return it.eval(this); }
27 public Outcome eval(Paper p) { return Outcome.WIN; }
28 public Outcome eval(Scissors s) { return Outcome.LOSE; }
29 public Outcome eval(Rock r) { return Outcome.DRAW; }
30 public String toString() { return "Rock"; }
31 }
32 class RoShamBo1{
33 static final int SIZE = 10;
34 private static Random rand = new Random();
35 public static Item newItem() {
36 switch(rand.nextInt(3)){
37 default:
38 case 0: return new Scissors();
39 case 1: return new Paper();
40 case 2: return new Rock();
41 }
42 }
43 public static void match(Item a, Item b){
44 println(a+" vs "+b+": "+a.compete(b));
45 }
46 public static void test(){
47 println("***(1) use same interface ***");
48 for(int i=0; i<SIZE; i++)
49 match(newItem(), newItem());
50 }
51 }
52 interface Competitor<T extends Competitor<T>>{
53 Outcome compete(T competitor);
54 }
55 enum RoShamBo2 implements Competitor<RoShamBo2>{
56 PAPER(Outcome.DRAW, Outcome.LOSE, Outcome.WIN),
57 SCISSORS(Outcome.DRAW, Outcome.LOSE, Outcome.WIN),
58 ROCK(Outcome.DRAW, Outcome.LOSE, Outcome.WIN);
59 private Outcome vPAPER, vSCISSORS, vROCK;
60 RoShamBo2(Outcome paper, Outcome scissors, Outcome rock){
61 this.vPAPER = paper;
62 this.vSCISSORS = scissors;
63 this.vROCK = rock;
64 }
65 public Outcome compete(RoShamBo2 it){
66 switch(it){
67 default:
68 case PAPER: return vPAPER;
69 case SCISSORS: return vSCISSORS;
70 case ROCK: return vROCK;
71 }
72 }
73 }
74 enum RoShamBo3 implements Competitor<RoShamBo3>{
75 ROCK{
76 public Outcome compete(RoShamBo3 opponent){
77 return compete(SCISSORS, opponent);
78 }
79 },
80 SCISSORS{
81 public Outcome compete(RoShamBo3 opponent){
82 return compete(ROCK, opponent);
83 }
84 },
85 PAPER{
86 public Outcome compete(RoShamBo3 opponent){
87 return compete(SCISSORS, opponent);
88 }
89 };
90 Outcome compete(RoShamBo3 loser, RoShamBo3 opponent){
91 return ((opponent == this) ? Outcome.DRAW :
92 ((opponent == loser) ? Outcome.WIN : Outcome.LOSE));
93 }
94 }
95 enum RoShamBo4 implements Competitor<RoShamBo4>{
96 PAPER, SCISSORS, ROCK;
97 static EnumMap<RoShamBo4, EnumMap<RoShamBo4, Outcome>> table =
98 new EnumMap <RoShamBo4, EnumMap<RoShamBo4, Outcome>>(RoShamBo4.class);
99 static{
100 for(RoShamBo4 it : RoShamBo4.values())
101 table.put(it, new EnumMap<RoShamBo4, Outcome>(RoShamBo4.class));
102 initRow(PAPER, Outcome.DRAW, Outcome.LOSE, Outcome.WIN);
103 initRow(SCISSORS, Outcome.WIN, Outcome.DRAW, Outcome.LOSE);
104 initRow(ROCK, Outcome.LOSE, Outcome.WIN, Outcome.DRAW);
105 }
106 static void initRow(RoShamBo4 it, Outcome vPAPER, Outcome vSCISSORS, Outcome vROCK){
107 EnumMap<RoShamBo4, Outcome> row = RoShamBo4.table.get(it);
108 row.put(RoShamBo4.PAPER, vPAPER);
109 row.put(RoShamBo4.SCISSORS, vSCISSORS);
110 row.put(RoShamBo4.ROCK, vROCK);
111 }
112 public Outcome compete(RoShamBo4 it){
113 return table.get(this).get(it);
114 }
115 }
116 enum RoShamBo5 implements Competitor<RoShamBo5>{
117 PAPER, SCISSORS, ROCK;
118 private static Outcome[][] table = {
119 { Outcome.DRAW, Outcome.LOSE, Outcome.WIN },
120 { Outcome.WIN, Outcome.DRAW, Outcome.LOSE },
121 { Outcome.LOSE, Outcome.WIN, Outcome.DRAW }
122 };
123 public Outcome compete(RoShamBo5 other){
124 return table[this.ordinal()][other.ordinal()];
125 }
126 }
127 public class RoShamBo{
128 public static <T extends Competitor<T>> void match(T a ,T b){
129 println(a+" vs "+b+": "+a.compete(b));
130 }
131 public static <T extends Enum<T> & Competitor<T>> void test(Class<T> rsb, int size){
132 for(int i=0; i<size; i++)
133 match(Enums.random(rsb), Enums.random(rsb));
134 }
135 public static void main(String[] args){
136 RoShamBo1.test();
137 println("\n*** (2) use constructorv init instances ***");
138 test(RoShamBo2.class, 10);
139 println("\n*** (3) use constants method ***");
140 test(RoShamBo3.class, 10);
141 println("\n*** (4) use EnumMap ***");
142 test(RoShamBo4.class, 10);
143 println("\n*** (5) use 2D Array ***");
144 test(RoShamBo5.class, 10);
145 }
146 }
第20章 注解
第21章 并发
并发编程可以使程序执行速度得到极大的提高,或者设计某些类型的程序提供更易用的模型,用并发解决的问题大体上可以分为“速度”和“设计可管理性”两种。
并发是用于多处理器编程额基本工具,但是并发通常是提高运行在单处理器上程序的性能。从性能上说,如果没有阻塞,那么在单核处理器上使用并发就没有任何意义,在单核处理系统中性能提高的常见示例是事件驱动编程。
基本的线程机制
实现并发最直接的方式是在操作系统级别使用进程。进程是运行在它自己的地址空间内的自包容程序。java提供了线程的支持,与多任务操作系统中分叉外部进程不同,线程机制是由执行程序表示的单一进程中创建任务。java的线程机制是抢占式的,调度机制会周期性的中断线程,将上下文切换到另一个线程,从而为每个线程都分配数量合理的时间片去驱动它的任务。单个进程可以有多个并发执行的任务,线程的一大好处是代码不必知道他是运行在一个还是多个CPU的机器上。
多任务和多线程是使用多处理器系统最合理的方式。线程可以驱动任务,Runnable接口的run()
方法提供了对任务的一种描述方式,将Runnable对象转变为工作任务的传统方式是把它交给一个Thread构造器,start()
方法进程执行必须的初始化操作,然后调用Runnable对象的run()
方法,并迅速返回,start()
方法调用完成之后,执行线程会继续存在,而Thread对象在任务退出run()
方法死亡之前,GC都不能清理它。调用Thread.yield()
是对线程调度器的一种建议,声明现在可以切换到其他任务。
blog comments powered by Disqus