Thinking in Java 第四版

相关下载:

JDK: http://www.oracle.com/technetwork/java/javase/downloads/index.html

Eclips: http://www.eclipse.org/downloads/

废话少说,仅讨论Java与C++的区别。


基本类型

Java中的所有对象均是Object的子类且内存都是在堆中分配的,但基本类型(boolean、char、byte、short、int、long、float、double)是在栈中分配的。

boolean:与C++的bool类似。不同的是C++中bool实际上是一个整型,但Java中写不出这样的代码“boolean b = !0”。

每个基本类型都有对应的一个包装器类型:Boolean、Character、Byte、Short、Integer、Long、Float、Double、Void。这些包装类提供更丰富的方法,比如Integer.ParseInt。



操作符

Java是不支持操作符重载的。

扫描二维码关注公众号,回复: 5701609 查看本文章

Java中没有sizeof操作符,因为Java中所有的数据类型在所有机器中的大小都是相同的。

"=="仅比较对象的引用,但比较基本类型时是进行值的比较。看下面代码:

[java]  view plain copy
  1. Integer x = new Integer(7);  
  2. Integer y = new Integer(7);  
  3. boolean b = x == y; // false  
  4.   
  5. int i = 7;  
  6. int j = 7;  
  7. b = i == j; // true;  

如果要自定义比较,就需要覆盖Object类中的equal方法。

 


控制执行流程

Foreach 语法:

[cpp]  view plain copy
  1. int a[] = {1, 2, 3, 4, 5};  
  2. for (int x: a)  
  3.     System.out.print(x);  
break/continue label 语法(适用于嵌套循环中):
[java]  view plain copy
  1. int x = 10;  
  2. int y = 5;  
  3. outer: // this is the label  
  4. while (x-- > 0)  
  5. {  
  6.     while (y++ < 10)  
  7.     {  
  8.         if (x + y == 9)  
  9.             break outer;  
  10.     }  
  11. }  


初始化与清理

所有局部变量都必须显式给一个初始值,使用未初始化的局部变量会得到编译错误。(C++中未初始的局部变量会是一个未定义的值。)

[java]  view plain copy
  1. void f()  
  2. {  
  3.     int i;  
  4.     i++; // error - i is not initialized  
  5. }  
类的成员变量在未显式初始化时会得到默认的初始值,如int会初始化为0,而boolean初始化为false。

同样你也可以显式地初始化成员变量:

[java]  view plain copy
  1. public class MyClass  
  2. {  
  3.     int a = 5;  
  4.     bool b = true;  
  5.     char c = 'c';  
  6.     SomeClass sc = new SomeClass();  
  7. }  
更有甚者你可以通过把一个方法的返回值作为一个成员变量的初始值:
[java]  view plain copy
  1. public class MyClass  
  2. {  
  3.     int a = f();  
  4.     int b = g(a);  
  5.     int f() { return 10; }  
  6.     int g(int n) { return n * 3; }  
  7. }  
成员变量初始化的顺序取决于它们定义的顺序。上述代码中,a会先于b初始化。如果改变它们的顺序会出现编译错误:
[cpp]  view plain copy
  1. public class MyClass  
  2. {  
  3.     int b = g(a); // error - a is not defined yet  
  4.     int a = f();  
  5. }  
当然成员变量也可以在构造器中初始化。
static成员变量的初始方法与非静态成员变量的初始化相同。
[java]  view plain copy
  1. public class MyClass  
  2. {  
  3.     int a = 5;  
  4.     bool b = true;  
  5.     static char c = 'c';  
  6. }  
以上述代码为例,一个Java类的初始化顺序为:

1. 当MyClass对象被创建(new MyClass)或MyClass的静态方法被调用时,Java解释器会定位MyClass.class文件;

2.载入MyClass.class,初始化所有静态成员变量;

3.在new MyClass调用时,在堆上分配足够存储空间;

4.分配出的存储空间全部清零,这也是为什么成员变量在未显式初始化时会有默认的初始值;

5.执行所有成员变量的显式初始化;

6.执行构造器。

多个静态初始化可以组织成“静态子句

[java]  view plain copy
  1. public class MyClass  
  2. {  
  3.     int a = 5;  
  4.     bool b = true;  
  5.     static char c = 'c';  
  6.     static {  
  7.         double d = 4.0;  
  8.         int e = 7;  
  9.     }  
  10. }  

实例初始化 和静态子句相似,用来初始化非静态成员,并且可以保证在构造器之前调用,这在有多个构造器的时候是非常方便的:
[java]  view plain copy
  1. public class MyClass  
  2. {  
  3.     int a;  
  4.     boolean b;  
  5.     {  
  6.         a = 5;  
  7.         b = true;  
  8.         System.out.println("look!");  
  9.     }  
  10. }  

数组 的定义可以是"int a[]"也可以是"int[] a"。你可以用“int a[] = new int[6];”的方式来初始化一个数组。
[cpp]  view plain copy
  1. Integer[] i = {  
  2.     new Integer(1),  
  3.     new Integer(2),  
  4.     3 // Autoboxing  
  5. };  
等价于:
[java]  view plain copy
  1. Integer[] i = new Integer{  
  2.     new Integer(1),  
  3.     new Integer(2),   
  4.     3// the last comma is also valid!  
  5. };  
所以花括号的定义仍然是在堆中分配内存。

可变参数列表 是非常有趣的特性:
[java]  view plain copy
  1. void f(Object... args)  
  2. {  
  3.     for (Object o: args)      
  4.         System.out.println(o);  
  5. }  
  6.   
  7. // caller  
  8. f(1'c'false);  


枚举不是一些简单的常数,它是可以用打印的。

[java]  view plain copy
  1. public enum E { A, B, C };  
  2. //caller  
  3. E e = E.C;  
  4. System.out.println(e); // output: C  

Java中的对象是不需要手动清理的,垃圾回收器会(在适当的时候)清理所有没被引用的对象。当然,你也可以强制让垃圾回收器进行清理:

[java]  view plain copy
  1. void f()  
  2. {  
  3.     MyClass mc = new MyClass();  
  4.     mc = null;  
  5.     System.gc();  
  6. }  

 

垃圾回收器的机制:

有一种所谓的“引用计数机制”,当对象被引用时计数器加一,当取消引用时计数器减一。当计数器为0时则释放该对象。这种模式非常慢,JVM实际上用一些更快的模式。

根据当前所有的引用(指针),肯定可以能找到它在堆中所指的对象。通过这种方法可以在堆中找到所有存活的对象。在清理时把这些“活”的对象复制到新堆中并紧密排列,并把所有的引用更新到新堆的地址,再把旧堆弃置。在处理过程中,当前的程序会停止,所以这种操作称为“停止-复制”(Stop-and-copy)。由于分配新内存时,堆里的对象是紧密排列的、连续的,所以只需要移动堆顶指针就可以得到一块新的内存,这样和在栈中分配内存的相似,所在java中的new操作非常高效。

由于stop-and-copy需要额外一个堆,所以要维护比实际需要大一倍的内存空间,此外复制操作也是很耗时的,当程序进入稳定状态时,只有少量的垃圾产生,但仍然复制所有的对象无疑是种浪费。所以有另一种模式“标记-清扫”(mark-and-sweep)。在这种模式下,仍然通过引用找到所有存活的对象,然后把所有死掉的对象释放掉,再把剩下的堆空间整理成连续的空间。这种方式仍然需要中止当前程序。

JVM综合上述两种模式,按需在堆中分配几块较大的内存,复制操作就发生在这些内存块之间。每个块都有表示它存活时间的“代数(generation count)”,当一个块被清理过一遍后它的代数会增加,一般清理会从代数最低的块开始,因为里面可能包含大量的临时变量。当“停止-复制”变得低效时,JVM会切换到“标记-清扫”模式。当后一个模式产生大量内存碎片,就切换回前一个模式。这就是“自适应技术”

 

Java的对象可以覆写一个叫finalize的方法。这个方法会在GC释放一个对象之前被调用,且最多只能调一次。但是,“但是”,这个方法并不保证何时被调用,也不保证一定会调用。不能把它当成C++里的析构函数使用。如果需要做一些析构操作,需要定义其它方法,并且进行显式调用。finalize这种东西基本靠不住,不靠谱的,最好别用。



访问权限控制

Java中的“包(Package)”是一个类的集合。它是Java中的类库。包里的所有类都属于同一个命名空间。与C++的namespace类似,在java文件除注释外的首行使用语句“package name”定义一个命名空间name。使用语句“import name.className”来导入某类或“import name.*”为导入该空间中的所有类。静态导入“import static name.className.*"用来导入一个类中的所有静态方法与静态成员变量。如果没有用package语句声明的话,类会放入默认包(default package)中。

Java代码写在一个.java文件里,编译后每个类会生成一个.class文件。JVM可以解释运行这个.class文件。多个.class文件也可以打包成.jar文件,JVM同样这以解释运行这个.jar文件。在一个Java文件中,只能有一个public类,且该类名必须与文件名相同。其余定义在该文件中的类必须是只能是包可见,否则编译器会报错。

类成员的访问权限有四种:public、protected、包访问权限(没有关键字)和private。类本身的访问权限有两种:public和包访问权限(没有关键字)。



复用类

Java的继承语法是"class Derived extends Base {}"。未显式声明父类的类都会隐式继承Object类。在构造器中通过调用super()来初始化父类:“Derived() { super(); }”。如果不显式初始化父类,编译器会默认调用无参数的父类构造器。

C++中重载父类方法会隐藏重载的方法,而Java中不会。为了防止此类事件发生,可以在方法定义前加入“@Override“来进行修饰。
final修饰成员变量或局部变量时类似于C++中的const,表示只读。final成员变量可以在声明时初始化,也可以在构造器中初始化。static final表示全局只读变量。Java中所有的非静态方法都是虚函数,用final方法不能再被覆写。final类不能被继承。



接口

abstract修饰一个类说明它是一个抽象类。只有在抽象类中才可以定义抽象方法(纯虚虚数),用“abstract void f();”表示。

interface定义一个接口,接口里所有的方法都是public的抽象方法,但不需要abstract或public修饰。类实现一个接口的以这种形式:“class Implementorimplements Interface {}”。接口可以继承自另一个接口:“interface DerivedInterfaceextends Interface”。

接口里是可以定义数据的,这些数据隐式声明为public static final:

[java]  view plain copy
  1. public interface Interface {   
  2.     int ONE = 1, TWO = 2, Three = 3;  
  3. }  

接口里可以定义嵌套接口

[java]  view plain copy
  1. public interface Outer {  
  2.     public interface Inner {  
  3.         void f();  
  4.     }  
  5. }  
  6.   
  7. class Implementor implements Outer.Inner {  
  8.     pubilc void f() {}  
  9. }  



内部类

Java的内部类很有趣。你可以这样定义一个内部类:

[java]  view plain copy
  1. class Outer {  
  2.     class Inner {}  
  3. }  

Inner是必须与某一个Outer对象绑定的,通过外部创建内部类的方法就可以得知:

[java]  view plain copy
  1. Outer oo = new Outer();  
  2. Outer::Inner ii = oo.new Inner();  

所以每一个Inner都有对应的一个外部类对象,而且内部类可以访问外部类对象中的所有属性和方法,包括private权限的。内部类可以用Outer.this来得到外部类对象的引用:

[java]  view plain copy
  1. class Outer {  
  2.     class Inner {  
  3.         Outer GetOuter()   
  4.         {  
  5.             return Outer.this;  
  6.         }  
  7.     }  
  8. }  

在方法中或一个作用域“{}”中也可以定义内部类,用一段比较变态的代码来说明问题:

[java]  view plain copy
  1. class Outer {  
  2.     int a = 6  
  3.     class Inner {  
  4.         int b = 3;  
  5.         void f()  
  6.         {  
  7.             class FunInner { // class defined in a funciton!!  
  8.                 int sum()  
  9.                 {  
  10.                     return a + b; // Outer.a + Inner.b  
  11.                 }  
  12.             }  
  13.             FunInner fi = new FunInner();  
  14.             System.out.print(fi.sum()); // 9!!  
  15.         }  
  16.     }  
  17. }  

还有一种东西叫“匿名内部类”:

[java]  view plain copy
  1. interface I {  
  2.     void f();  
  3. }  
  4.   
  5. class Outer {  
  6.     I getI()  
  7.     {  
  8.         return new I() { // anonymous inner class !!  
  9.             public void f() {}  
  10.         };  
  11.     }  
  12. }  

如果不需要和外部类绑定,可以定义一个嵌套类

[java]  view plain copy
  1. class Outer {  
  2.     static class Nested {} // static  
  3. }  

嵌套类和C++中的嵌套类基本相同,只能访问外部类的静态数据。它的创建方法为:

[java]  view plain copy
  1. Outer.Nested nested = new Outer.Nested();  

接口里面也可以定义嵌套类,但不需要static修饰,因为接口里定义的变量都是默认为public static的:

[java]  view plain copy
  1. interface I {  
  2.     void f();  
  3.     class MyI implements I {  
  4.         void void f() {}  
  5.     }  
  6. }  

这样做的好处是接口的定义同时还能提供默认的实现。

内部类的作用:
内部类可以继承别的类,同时又可以完全访问外部类的成员,这就等同于多重继承的效果。
Java里没有指针,所以不能像C++一样把函数指针传递给出去作为回调函数(callback)。通过内部类可以实现:

[java]  view plain copy
  1. interface Callback { void call(); }  
  2.   
  3. class MyClass {  
  4.     void f() {} // want to be a callback  
  5.     Callback getCallback()  
  6.     {  
  7.         return new Callback() {  
  8.             void call() { f(); }  
  9.         };  
  10.     }  
  11. }  
  12.   
  13. class Caller {  
  14.     void operation(Callback callback)  
  15.     {  
  16.         callback.call();  
  17.     }  
  18.   
  19.     static void main(String[] args)  
  20.     {  
  21.         Caller caller = new Caller();  
  22.         MyClass mc = new MyClass();  
  23.         caller.operation(mc.getCallback());  
  24.     }  
  25. }  
Java中的这样的回调机制叫做 闭包(closure)


内部类也可以被继承。因为外部类和内部类有着千丝万缕的联系,所以继承的类同样需要某种机制建立起这种联系: 

[java]  view plain copy
  1. class Outer { class Inner{} }  
  2. public class InheritInner extends Outer.Inner {  
  3.     // InheritInner() {} // compile error!  
  4.     InheritInner(Outer oo) {  
  5.         oo.super(); // It's a must to connect to the outer class  
  6.     }  
  7.     static void main(String[] args)  
  8.     {  
  9.         Outer oo = new Outer();  
  10.         InheritInner ii = new InheritInner(oo);  
  11.     }  
  12. }  

内部类同样会生成.class文件,它的文件名会是这样的形式:Outer$Inner.class。如果是匿名内部类,在$后会简单加入一个数字作为标识符。



持有对象

这里仅概述Java中的容器。Java也是支持范型的,比如“ArrayList<Integer> a = new ArrayList<Integer>”。不带范型参数的容器默认为Object对象的集合。

容器包括两大类:CollectionMap,它们包括:
Collection:List(ArrayList、LinkedList)、Set(HashSet、TreeSet、LinkedHashSet)、Queue(PriorityQueue)。
Map:HashMap、TreeMap、LinkedHashMap。

Collection提供一个iterator()方法来返回一个迭代器(Iterator),一个Iterator要包含以下方法:hasNext()、next()和remove()(可选)。

为了支持Foreach的语法,一个类必须实现Iterable接口。这个接口有一个方法iterator()来得到一个迭代器。Collection实现了这个接口。下面的代码定义了支持Foreach的类:

[java]  view plain copy
  1. class MyCollection implements Iterable<Integer> {  
  2.     public Iterator<Integer> iterator()  
  3.     {  
  4.         return new Iterator<Integer>() {  
  5.             int i = 0;  
  6.             public boolean hasNext() { return i < 10; }  
  7.             public Integer next() { return i++; }  
  8.             public void remove() { throw new UnsupportedOperationException(); }  
  9.         };  
  10.     }  
  11.     public static void main(String[] args)  
  12.     {  
  13.         MyCollection mc = new MyCollection();  
  14.         for (int i: mc)  
  15.             System.out.print(i);  
  16.     }  
  17. }  


通过异常处理错误

Java中可以通过throw抛出所有继承Throwable的类。任何抛出的异常(RuntimeException除外)被编译器强制需要catch,如果不catch会出现编译错误。

[java]  view plain copy
  1. void makeExcetion()  
  2. {  
  3.     // throw new MyException(); // error: this exception must be handled.  
  4. }  
如果需要把异常抛到方法外,则 需要在方法定义中声明该异常
[java]  view plain copy
  1. void makeException() throws MyException  
  2. {  
  3.     throw new MyException();  
  4. }  
这样编译器才会让你通过,但是 所有调用了这个方法的地方必须catch这个被声明的异常 ,否则编译不通过,(当然也可以通过上述声明异常的方式把异常抛到更外层)。相比之下,C++的异常声明更像一个提示,不带有强制性质。


Throwable分为两种类型:ErrorException。Error用来表示编译时和系统错误,所以我们一般不用关心。Exception类有一个方法printStackTrace可以打印出异常抛出点的调用栈,另一个函数fillStackTrace可以在catch后更新调用栈信息再抛出。

RuntimeException是Exeption的子类,用来表示运行时发现的编程错误,比如访问空指针(NullPointerException)和数组越界(ArrayIndexOfBoundsException)。这种类型的异常是唯一不被编译器强制需要捕获的。如果一个RuntimeException抛出后没有被catch,程序会退出并调用printStackTrace方法。

使用finally块可以保证不管是否有异常发生,该块的代码都会被运行:

[java]  view plain copy
  1. try   
  2. {  
  3.     // dosomething  
  4. }   
  5. catch (Exception e)   
  6. {  
  7. }   
  8. finally  
  9. {  
  10.     // always be executed. Usually dispose.  
  11. }  
finally也可以用来保证有多个return点的函数可以执行必要的清理工作,这也弥补了没有析构的不足。


有时候你可能需要把一些异常串成一个异常链。Exception.initCause可以支持这个需求:

[java]  view plain copy
  1.    
  2. class LevelOneException extends Exception {}  
  3. class LevelTwoException extends Exception {}  
  4.   
  5. class ExceptionMaker {  
  6.     void makeLevelOne() throws LevelOneException   
  7.     {  
  8.         throw new LevelOneException();  
  9.     }  
  10.     void makeLevelTwo() throws LevelTwoException   
  11.     {  
  12.         try {  
  13.             makeLevelOne();  
  14.         }  
  15.         catch (LevelOneException loe)  
  16.         {  
  17.             LevelTwoException lte = new LevelTwoException();  
  18.             lte.initCause(loe); // combine the exceptions  
  19.             throw lte;  
  20.         }  
  21.     }  
  22.     void Test()   
  23.     {  
  24.         try {  
  25.             makeLevelTwo();  
  26.         }  
  27.         catch (LevelTwoException lte)  
  28.         {  
  29.             Throwable t = lte.getCause(); // get the previous exception in the link  
  30.         }  
  31.     }  
  32. }  

在C++里,当一个异常没有被Catch的情况下,再抛出一个新的异常,比如析构函数里的异常,这个程序基本就over了。但在Java中,这种情况会导致旧的异常丢失,新的异常被传递出去:

[java]  view plain copy
  1. void loseException() {  
  2.     try {  
  3.         try {  
  4.             throw new LevelOneException();  
  5.         } finally {  
  6.             throw new LevelTwoException();  
  7.         }  
  8.     } catch (LevelTwoException lte) {  
  9.         lte.printStackTrace();  
  10.     }  
  11. }  

这应该算是Java的一个缺陷吧。


在覆写父类方法时,子类方法中的异常声明必须是父类声明的异常的子集。就是说子类的方法只能抛出比父类更少的异常,而不能抛出父类不会抛出的异常。



字符串

Java中的String类方便的操作字符串。它是唯一重载操作符(+、+=)的类。可以这样拼接字符串:String s = new String("ADD"); s += "More"; s += "More" + "And" + "More";可以注意到"More"+"And"+"More"看起来可能会产生很多临时变量,但Java对它进行了优化。上面的加号操等价于"StringBuilder sb = new StringBuilder(); sb.append("More"); sb.append("And"); sb.append("More"); s = sb.toString();

Object类定义了一个toString方法,它的实现是返回对象类名以及内存地址。所以所有的类都可以转换成String而且也可以实现自己的toString。

有一个类Fomatter(java.util.Formatter),它是专门用来格式化字符串的,和C里面的printf相似:

[java]  view plain copy
  1. void format()  
  2. {  
  3.     PrintStream o = System.out;  
  4.     Formatter f  = new Formatter(o);  
  5.     f.format("%d + %d = %d"112);  
  6. }  
PrintStream 类(如System.out)里也定义了一个 format 方法和一个 printf 方法。它们是等价的。它们的内部实现也是借助了Formatter类。 String format 方法也同样利用了Formatter类。

String支持正则表达式。String的matchessplitreplaceFirstreplaceAll等方法都和正则表达式有很好的交互。java.lang.regex包中的Pattern类更好地支持正则表达式。这里不多做研究,或许将来单独开一篇博客来探讨正则表达式。



类型信息

Java中的每一个类都对应着一个Class对象(java.lang.Class)。通过这个Class对象你可以在运行时得到很多类中的有用的信息。用Class.forName来得到一个Class对象。

[java]  view plain copy
  1. try {  
  2.     Class c = Class.forName("MyClass");  
  3.     String name = c.getName(); // "MyPackge.MyClass"  
  4.     name = c.getSimpleName(); // "MyClass"  
  5.     name = c.getCanonicalName(); // "MyPackge.MyClass"  
  6.     Class superClass = c.getSuper();  
  7.     if (superClass != null// Object's super class is null  
  8.         superClass.newInstance();  
  9.     boolean b = c.isInterface();  
  10.     for(java.lang.Class face: c.getInterfaces())  
  11.         name = face.getName();  
  12. catch (ClassNotFoundException) {}  

除了Class.forName,还可以直接用MyClass.class来得到一个Class对象。这种方式不会抛出异常。所有的类,包括基本类型,都可以使用“.class”。比如int.class。基本类型的包装类有TYPE成员,比如Integer.TYPE与int.class是一样的。

不像C++在程序启动时就把所有的静态数据与执行代码载入到内存中,java根据需要在运行时把字节码载入到内存,它分三个步骤:1、加载:类加载器查找到字节码(.class文件)并根据这些字节码创建一个Class对象;2、链接:验证类中的字节码,为静态域分配存储空间,需要的话同时解析这个类其它类的所有引用;3、初始化:当类的静态方法(构造器是特殊的静态方法)或者非常数静态域(即不是编译器常量)被首次引用时,执行静态初始化块和初始化静态数据。

Class类是支持范型的:

[java]  view plain copy
  1. Class<Integer> c = int.class;  
Class范型的作用是可以得到编译器的支持,比如类型检查:
[java]  view plain copy
  1. Class<Interger> c = int.class;  
  2. // c = double.class; // error  
除了类型检查,Class范型的new Instance会返回相应类型的对象,不仅仅是个简单的Object:
[java]  view plain copy
  1. Class c = int.class;  
  2. Class<Integer> cc = int.class;  
  3. Object o = c.newInstance();  
  4. Integer i = cc.newInstance(); // not a simple Object  
这种范型强制性太大,如果希望一个Class范型的引用接受别的Class类型的对象,可以使用 通配符
[java]  view plain copy
  1. Class<?> c = int.class;  
  2. c = double.class// compiling passes  

Class<?>其实与普通的Class类是一样的,只不过显式声明你确实需要一个接受任何类型的Class对象,而不是忘了加范型参数。

如果你想让一个Class范型类既能接受int.class,又能接受double.class,但是不想接受其它非数值的类型,可以这样:

[java]  view plain copy
  1. Class<? extends Number> c = int.class;  
  2. c = double.class;  
  3. Number n = c.newInstance(); // not a simple Number  
?extends 通配符可以让编译器确保接受的类型是某个类型的子类。另一个通配符 ?super 可以得保证接受的类型是某个类的超类:
[java]  view plain copy
  1. class Base {}  
  2. class Derived extends Base {}  
  3. // Class<Base> c = Derived.class.getSuperclass(); // won't compile  
  4. Class<? super Derived> c = Derived.class.getSuperclass();  
  5. Object o = c.newInstance(); // a simple Object  

看到上例的Class<Base>不能接受子类的getSuperClass的返回值,还是挺奇怪的,毕竟Base是Derived的基类是在编译时就确定的。不过既然编译器规定Class<? super Derived>到Class<Base>的转换,那也只能遵从了。

Class范型提供一个cast方法:
[java]  view plain copy
  1. Base b = new Derived;  
  2. Class<Derived> c = Derived.class;  
  3. Derived d = c.cast(b);  
这样的cast其实与Derived d = (Derived)b;是完全等价的,所以这样的cast基本不怎么用。如果被转换的类不能被cast到目标类型的话,会抛出一个ClassCastException异常。(C++里cast是不会使用RTTI的。)另一个基本没用的方法是Class.asSubClass:
[java]  view plain copy
  1. Class<? extends Base> c = Derived.class.asSubclass(Base.class);  
  2. Derived d = (Derived)c.newInstance();  
另一个在运行时得到类型信息的方法是关键字 instanceof 它与 Class.isInstance 是等价的:
[java]  view plain copy
  1. Base o = new Derived();  
  2. boolean b = o instanceof Derived; // true  
  3. b = Derived.class.isInstance(o); // true  
以上介绍的都是java的 RTTI机制 。Java还有一套 反射机制 。RTTI能够维护的类型都是编译时已知的类型,而反射可以使用一些在编译时完全不可知的类型。比如在进行一个远程调用时,类信息是通过网络传输过来的,编译器在编译时并不知道这个类的存在。下面演示如何使用反射:
[java]  view plain copy
  1. import java.lang.reflect.*;  
  2.   
  3. class SomeClass {  
  4. public SomeClass() {}  
  5. public int SomeMethod(double d, char c) { return 2; }  
  6.     public int a;  
  7. }  
  8.   
  9. public class ReflectTest {  
  10.     public static void main(String[] args) {  
  11.         Class c = Class.forName("SomeClass");  
  12.         for (Constructor<?> constructor: c.getConstructors())  
  13.             System.out.println(constructor);  
  14.         for (Method method: c.getMethods())  
  15.             System.out.println(method);  
  16.         for (Field field: c.getFields())  
  17.             System.out.println(field);  
  18.                       
  19.         SomeClass sc = new SomeClass();  
  20.         Method method = c.getMethod("SomeMethod"double.classchar.class);  
  21.         Integer returnedValue = (Integer)method.invoke(sc, 3'4');  
  22.         Field field = c.getField("a");  
  23.         int value = field.getInt(sc);  
  24.         System.out.println(value);  
  25.     }  
  26. }  
其实反射和RTTI并没有什么本质的区别,因为java的类都是在运行是加载并解析的,而且两者通过Class对象来获取类型信息。不同的地方就是RTTI可以直接使用方法名来调用一个方法,而不必用字符串去执行一个方法。

设计模式里有个"代理模式"(点击链接进入博客:设计模式 —— 《Head First》)。代理模式里会定义一个接口,真正工作的类和代理类都会实现这个接口,但用户只会看到代理类,而不知道真正工作的类。这个模式的好处就是可以隐藏实现细节,经常改动的地方对用户是不可见的。Java里提供了一个自动生成代理类的机制,主要使用java.lang.reflect包里的Proxy类和InvocationHandler接口:

[java]  view plain copy
  1. import java.lang.reflect.*;  
  2.   
  3. interface MyInterface {  
  4.     void doSomething();  
  5. }  
  6.   
  7. class RealWorker implements MyInterface {  
  8.     public void doSomething() { System.out.println("RealWorker"); }  
  9. }  
  10.   
  11. class MyHandler implements InvocationHandler {  
  12.     public Object invoke(Object proxy, Method method, Object[] args)  
  13.             throws Throwable {  
  14.         return method.invoke(worker, args);  
  15.     }  
  16.     private MyInterface worker = new RealWorker();  
  17. }  
  18.   
  19. public class ProxyTest {  
  20.     public static void main(String[] args) {  
  21.         MyInterface myProxy = (MyInterface)Proxy.newProxyInstance(  
  22.                 MyInterface.class.getClassLoader(),  
  23.                 new Class[] {MyInterface.class}, new MyHandler());  
  24.         // call proxy's methods  
  25.         myProxy.doSomething();  
  26.     }  
  27. }  
可以看到我们只定义了一个接口和实现这个接口的类,但没有直接定义一个代理类,而是通过实现 InvocationHandler 和使用 Proxy.newProxyInstance 让编译器自动生成一个 Proxy 类。


通过反射机制可以做一些很违反规定的事情。 你可以使用一个某个包里不对外开放的类,以及它的私有方法
[java]  view plain copy
  1. ///////////// HiddenClass.java ///////////////  
  2. package hidden;  
  3.   
  4. class HiddenClass {  
  5.     private void invisible() { System.out.println("invisible"); }  
  6. }  
  7.   
  8. ///////// HiddenClassTest.java ///////////////////  
  9. import java.lang.reflect.*;  
  10. import hidden.*;  
  11.   
  12. public class HiddenClassTest {  
  13.     public static void main(String[] args) {   
  14.         Class c = Class.forName("hidden.HiddenClass");  
  15.               
  16.         // Object obj = c.newInstance(); // IllegalAccessExcetion  
  17.         Constructor constructor = c.getDeclaredConstructor();  
  18.         constructor.setAccessible(true);  
  19.         Object obj = constructor.newInstance(); // new HiddenClass()  
  20.           
  21.         Method method = c.getDeclaredMethod("invisible");  
  22.         method.setAccessible(true);  
  23.         method.invoke(obj); // call HiddenClass.invisible()  
  24.     }  
  25. }  
当然这样的做法是很不值得提倡的。除了普通的类,同样可以用 Class.forName("OuterClass$InnerClass") 的方式来访问内部类。访问匿名类则用 Class.forName("OuterClass$1") 的方式。有一点值得注意的是,内部类和匿名类的构造函数的第一个参数是外部类的引用,所以getDeclaredConstructor方法的以外部类的类型作为第一个参数。这里就不再列出代码了。



范型

java里也有范型的概念,可以自定义范型类、范型接口、范型内部类和范型方法等:

[java]  view plain copy
  1. class MyGeneric<T> { public T value; }  
  2. class MyOtherGeneric<A, B> { void f(A a, B b) {} }  
  3. interface MyGenericInterface<T> { T getT(); }  
  4. class NormalOuter {  
  5.     class GenericInner<T> {}  
  6.     public <T> void GenericMethod(T value) {}  
  7. }  
注意范型不能接受基本类型作为参数,但是可以使用包装类。比如不能使用MyGeneric<int>但可以使用MyGeneric<Integer>。
java中的范型使用了一种“擦除”的机制,即范型只在编译期存在,编译器去除了(不是替代)范型参数,而java虚拟机根本不知道范型的存在。带有不同的范型参数的范型类在运行时都是同一个类:
[java]  view plain copy
  1. MyGeneric<Integer> mg1 = new MyGeneric<Integer>();  
  2. MyGeneric<Double> mg2 = new MyGeneric<Double>();  
  3. boolean b = mg1.getClass() == mg2.getClass(); // true;  
因为范型类在运行时最终都会被擦除成普通的类,所以不能定义两个类名相同,而范型参数不同的类:
[java]  view plain copy
  1. class MyGeneric<T> { public T value; }  
  2. // class MyGeneric<A, B> { public T value; } // The type MyGeneric is already defined  
更悲惨的是,在运行时是无法确切知道范型参数的实际类型。接着上面的代码:
[java]  view plain copy
  1. MyOtherGeneric<Integer, Double> mog = new MyOtherGeneric<Integer, Double>();   
  2. TypeVariable<?>[] types = mog.getClass().getTypeParameters();  
  3. System.out.println(Arrays.toString(types)); // [A, B]  
getTypeParameters这个方法只是返回范型参数的占位符,不是具体的某个类型。

java的范型在定义时不是假定范型参数支持哪些方法,尽管C++是可以做到的。看下面的代码:

[java]  view plain copy
  1. class MyGeneric<T> {   
  2.     // public void GuessT(T t) { t.f(); } // error: The method f() is undefined for the type T  
  3. }  
如果要让上面的代码编译通过,则需要使用到边界:
[java]  view plain copy
  1. class SupportT { void f() {} }  
  2. class MyGeneric<T extends SupportT> {   
  3.     public void GuessT(T t) { t.f(); }  
  4. }  

java中之所以用擦除的方式来实现范型,而不是像C++一样根据不同的范型参数具现化不同的类,是由于java语言在发布初期并不支持范型,在发布很长时间后才开始引入范型,为了很原来的类库兼容才使用了擦除。擦除会把参数T换成Object。

因为擦除,在范型类里是不能创建范型参数类型的对象,因为编译器不能保证T拥有默认构造器:

[java]  view plain copy
  1. class ClassMaker<T> {  
  2.     T create() {  
  3.         // return new T(); // error: Cannot instantiate the type T  
  4.     }  
  5. }  
要创建一个范型对象,可以利用Class类型:
[java]  view plain copy
  1. class ClassMaker<T> {  
  2.     ClassMaker(Class<T> c) { type = c; }  
  3.     T create() {  
  4.         T t = null;  
  5.         try { t = type.newInstance(); }   
  6.         catch (InstantiationException e) { e.printStackTrace(); }  
  7.         catch (IllegalAccessException e) { e.printStackTrace(); }  
  8.         return t;  
  9.     }  
  10.     private Class<T> type;  
  11. }  
同样,如果要创建范型数组的话:
[java]  view plain copy
  1. class ClassMaker<T> {  
  2.     ClassMaker(Class<T> c) { type = c; }  
  3.     T[] createArray(int size) {  
  4.         // return new T[size]; // error: Cannot create a generic array of T  
  5.         // return (T[]) new Object[size]; // success!  
  6.         return (T[])Array.newInstance(type, size); // success!  
  7.     }  
  8.     private Class<T> type;  
  9. }  

范型的边界(extends)可以加入多个限定,但只能是一个类和多个接口:

[java]  view plain copy
  1. interface face1 {}  
  2. interface face2 {}  
  3. class class1 {}  
  4. class Bound<T extends class1 & face1 & face2> {}  
在子类还能加入更多的限定:
[java]  view plain copy
  1. interface face3 {}  
  2. class BoundDerived<T extends class1 & face1 & face2 & face3> extends Bound<T> {}  

试试把一个子类的数组赋给父类数组的引用:

[java]  view plain copy
  1. class Base1 {}  
  2. class Derived1 extends Base1 {}  
  3. Base1[] base = new Derived1[3];  
  4. base[0] = new Base1(); // java.lang.ArrayStoreException  
父类数组的引用实际指向一个子类的数组,当给元素赋值时,传入一个父类对象对得到一个异常,因为它期望的是一个子类的类型。
如果你希望在编译期时就发现这样的错误,而不是等到异常发生时才发现,可以使用范型:
[java]  view plain copy
  1. // ArrayList<Base1> alb = new ArrayList<Derived1>(); // Type mismatch: cannot convert from ArrayList<Derived1> to ArrayList<Base1>  
这样你在编译时就会发现这个错误。

有时候你可能就是希望一个合法的向上转型,这时可以使用通配符

[java]  view plain copy
  1. ArrayList<? extends Base1> aleb = new ArrayList<Derived1>(); // success!  
  2. // aleb.add(new Base1()); // error: The method add(capture#3-of ? extends Base1) in the type ArrayList<capture#3-of ? extends Base1> is not applicable for the arguments (Object)  
  3. // aleb.add(new Derived1()); error  
  4. // aleb.add(new Object()); // error  
  5. aleb.add(null); // success  
  6. Base1 b = aleb.get(0); // success  
可以发现带有通配符参数的范型类,它的所有带范型参数的方法调用都不能通过编译,比如上例中的ArrayList.add(T)。甚至连Object类型都不能接受,只能接受null。不带范型参数的方法可以调用,如ArrayList.get。这是因为编译器不知道应该接受什么类型,所以干脆就什么类型都不接受。如果你希望你的范型类在参数是通配符的时候,它的某些方法仍然能被调用,则定义方法的参数类型为Object,而非范型类型T。

与通配符相对的是 超类型通配符 ,即 <? super T>
[java]  view plain copy
  1. ArrayList<? super Derived1> alsb = new ArrayList<Base1>();  
  2. alsb.add(new Derived1()); //success  
  3. // alsb.add(new Base1()); // error: The method add(capture#4-of ? super Derived1) in the type ArrayList<capture#4-of ? super Derived1> is not applicable for the arguments (Base1)  
  4. Object d = alsb.get(0); // return an Object  
可以看到在接受参数时限制放宽了,因为编译器知道范型的下界,只要是Derived类或它的子类都是合法的。但是在返回时,它只能返回Object类型,因为它不能确定它的上界。

无界通配符 ,即 <?> ,与 原生类型 (非范型类)大体相似,但仍有少许不同:
[java]  view plain copy
  1. ArrayList<?> al_any = new ArrayList<Base1>();  
  2. // al_any.add(new Object()); // error: The method add(capture#5-of ?) in the type ArrayList<capture#5-of ?> is not applicable for the arguments (Object)  
  3. Object obj = al_any.get(0); // return an Object  
  4.   
  5. ArrayList al_raw = new ArrayList<Base1>();  
  6. al_raw.add(new Object()); // warning: Type safety: The method add(Object) belongs to the raw type ArrayList. References to generic type ArrayList<E> should be parameterized  
  7. Object obj2 = al_raw.get(0);  
调用add时,无界通配符范型不接受任何类型的参数,而原生类型可以接受Object类型但会发出一个警告。


有一种看起来很诡异的范型参数定义,即自限定的类型。它是这样的形式:

[java]  view plain copy
  1. class SelfBounded<T extends SelfBounded<T>> {  
  2.     void f(T t) {}  
  3. }  
看起来很复杂,先把范型参数提出来研究下:T extends SelfBounded<T>,它表明了一种类型,这种类型是SelfBounded<T>的子类,而SelfBounded的范型参数恰恰就是这个类型本身。举个例子:
[java]  view plain copy
  1. class Derived2 extends SelfBounded<Derived2> {}  
Derived2这个类就是以Derived2为参数的SelfBounded范型的子类,而SelfBounded类只能接受如Derived2这样特殊的类型作为范型参数。T extends SelfBounded<T>其实是限定了一个继承关系,下面的定义是不合法的:
[java]  view plain copy
  1. class OtherType {}  
  2. class Derived3 extends SelfBounded<OtherType> {} // error: Bound mismatch: The type OtherType is not a valid substitute for the bounded parameter <T extends SelfBounded<T>> of the type SelfBounded<T>  
因为OtherType并不是SelfBounded<OtherType>的子类,所以不能作为SelfBounded的合法范型参数。

自限定类型一般用于参数协变。Java是支持返回值协变的:

[java]  view plain copy
  1. class BaseFace { Base1 getObject() { System.out.println("BaseFace"); return new Base1(); } }  
  2. class DerivedFace extends BaseFace { Derived1 getObject() { System.out.println("DerivedFace"); return new Derive1(); } }  
子类的同名方法覆盖了父类的方法,不是重载。下面的代码可以证明:
[java]  view plain copy
  1. void test(DerivedFace df) {  
  2.     Derived1 d1 = df.getObject(); // output: DerivedFace  
  3.     BaseFace bf = df;  
  4.     Base1 b1 = bf.getObject(); // output: DerivedFace  
  5. }  
有趣的是,你可以调用DerivedFace.class.getMethods()看看,Derived类一共定义了两个方法,和普通的覆盖又有不同。

尽管支持返回值协变,Java并不支持参数协变。改写父类和子类的定义:

[java]  view plain copy
  1. class BaseFace { setObject(Base1 b) { System.out.println("BaseFace"); } }  
  2. class DerivedFace extends BaseFace { void setObject(Derived1 d) { System.out.println("DerivedFace"); } }  
  3. void test(DerivedFace df) {  
  4.     Derived1 d1 = new Derived1();      
  5.     df.setObject(d1); // output: DerivedFace  
  6.     df.setObject((Base1)d1); // output: BaseFace set  
  7.     BaseFace bf = df;  
  8.     bf.setObject(d1); // output: BaseFace  
  9. }  
自限定类型很好的支持了参数协变。
[java]  view plain copy
  1. class BaseFace2<T extends BaseFace2<T>> { void setObject(T t) {} }  
  2. class DerivedFace2 extends BaseFace2<DerivedFace2> {}  
  3. void test(DerivedFace2 df, BaseFace2 bf) {  
  4.     df.setObject(df); // Output: DerivedFace  
  5.     // df.setObject(bf); // error: The method setObject(DerivedFace2) in the type BaseFace2<DerivedFace2> is not applicable for the arguments (BaseFace2)  
  6.     bf.setObject(bf); // Output: BaseFace  
  7.     bf.setObject(df); // Output: BaseFace<span style="white-space:pre">      </span>  
  8. }  
可以看到自限定类型的子类只能接受子类作为参数,而且方法是被覆盖的,而不是重载。


Java容器的插入方法并不检查类型,所以很可能会插入错误的类型:

[java]  view plain copy
  1. List l_raw = new ArrayList<Integer>();  
  2. l_raw.add(new Double(3)); // success  
Collections提供了检查类型的方法,比如checkedList返回一个在插入时检查类型的List:
[java]  view plain copy
  1. List<Integer> l = Collections.checkedList(new ArrayList<Integer>(), Integer.class);  
  2. List l_raw = l;  
  3. l_raw.add(new Double(3)); // throws exception!  
在checked容器里试图插入不合法的类型会抛出异常。



数组

Java中的数组是在堆中分配的连续内存,它可以用来存放基本类型的值或对象类型的引用。除了操作符“[]”外,数组同样还有一个length属性,这和C++里的数组不同。
我们还可以使用多维数组

[java]  view plain copy
  1. int a[][][] = new int[2][3][2];  
  2. a[1][2][1] = 5;  
数组内所有元素都会被赋一个默认初值(null、0、false等)。因为数组是在堆中初始化的,所以可变长度的维度是可行的。这样的数组被称为 粗糙数组
[java]  view plain copy
  1. int a[][][] = new int[2][][];  
  2. a[0] = new int[3][];  
  3. a[1] = new int[5][];  
  4. a[0][1] = new int[6];  
  5. a[1][2] = new int[8];  
  6. System.out.println(Arrays.deepToString(a)); // Output: [[null, [0, 0, 0, 0, 0, 0], null], [null, null, [0, 0, 0, 0, 0, 0, 0, 0], null, null]]  
Arrays.deepToString 方法可以将多维数组转成字符串。同样你也可以用数组初始化的方式来定义粗糙数组:
[java]  view plain copy
  1. int a[][][] = new int[][][] {   
  2.         { {1,2,3,4}, {5,6} },  
  3.         { {7}, {8}, {9,10,11}, {12} },  
  4.         { {13,14}, {15,16}, {17,18} }  
  5. };  

Java提供了一些对数组的操作,比如fill、equal、copy等。下面演示下:
Arrays.fill

[java]  view plain copy
  1. void fillArray() {  
  2.     int a[] = new int[5];  
  3.     Arrays.fill(a, 3);  
  4.     System.out.println(Arrays.toString(a)); // [3, 3, 3, 3, 3]  
  5.     int b[] = new int[8];  
  6.     Arrays.fill(b, 263);  
  7.     System.out.println(Arrays.toString(b)); // [0, 0, 3, 3, 3, 3, 0, 0]  
  8. }  
System.arraycopy
[java]  view plain copy
  1. void copyArray() {  
  2.     int a[] = {12345};  
  3.     int b[] = new int[a.length+1];  
  4.     System.arraycopy(a, 0, b, 1, a.length);  
  5.     System.out.println(Arrays.toString(b)); // [0, 1, 2, 3, 4, 5]  
  6. }  
Arrays.equals
[java]  view plain copy
  1. void equalsArray() {  
  2.     int a[] = {12345};  
  3.     int b[] = {12345};  
  4.     Arrays.equals(a, b); // true  
  5. }  
还有数组的排序,对象类型并须实现 Comparable 接口才可以用 Arrays.sort 排序,否则在会得到异常。示例代码:
[java]  view plain copy
  1. class MyElement implements Comparable<MyElement> {  
  2.     public int compareTo(MyElement other) {  
  3.         return this.value - other.value;  
  4.     }  
  5.     static MyElement createNext() {  
  6.         MyElement me = new MyElement();  
  7.         me.value = r.nextInt(20);  
  8.         return me;  
  9.     }  
  10.     public String toString() {  
  11.         return String.valueOf(value);  
  12.     }  
  13.     private int value;  
  14.     private static Random r = new Random();  
  15. }  
  16.   
  17. static void sortArray() {  
  18.     MyElement meArray[] = new MyElement[8];  
  19.     for (int i = 0; i < meArray.length; ++i) {  
  20.         meArray[i] = MyElement.createNext();  
  21.     }  
  22.     System.out.println(Arrays.toString(meArray)); // [8, 18, 4, 2, 16, 0, 13, 3]  
  23.     Arrays.sort(meArray);  
  24.     System.out.println(Arrays.toString(meArray)); // [0, 2, 3, 4, 8, 13, 16, 18]  
  25. }  
如果你对Comparable的排序不满意,你可以自己定义一个 Comparator
[java]  view plain copy
  1. class MyElementComparator implements Comparator<MyElement> {  
  2.     @Override  
  3.     public int compare(MyElement left, MyElement right) {  
  4.         return right.compareTo(left); // reverse  
  5.     }  
  6. }  
  7. void reverseSortArray() {  
  8.     MyElement meArray[] = new MyElement[8];  
  9.     for (int i = 0; i < meArray.length; ++i) {  
  10.         meArray[i] = MyElement.createNext();  
  11.     }  
  12.     System.out.println(Arrays.toString(meArray)); // [17, 14, 12, 9, 4, 7, 19, 11]  
  13.     Arrays.sort(meArray, new MyElementComparator()); // add a comparator  
  14.     System.out.println(Arrays.toString(meArray)); // [19, 17, 14, 12, 11, 9, 7, 4]  
  15. }  
对于排好序的数组,可以使用二分法来查找元素。 Arrays.binarySearch
[java]  view plain copy
  1. void binarySearchArray() {  
  2.     int a[] = { 3524971 };  
  3.     Arrays.sort(a);  
  4.     int index = Arrays.binarySearch(a, 7); // a[5]=7  
  5. }  

编译器不允许你创建一个范型数组,但是允许你使用范型数组的引用:
[java]  view plain copy
  1. // List<Integer> liArray[] = new List<Integer>[8]; // Cannot create a generic array of List<Integer>  
  2. List<Integer> liArray[] = new List[8]; // success!  
  3. // liArray[0] = new ArrayList<Double>(3); // Type mismatch: cannot convert from ArrayList<Double> to List<Integer>  



容器深入研究

Java容器分为两大类CollectionMap,它们分别对应着两个接口。我们先来研究Collection这一大类。

Collection又分为三类ListSetQueue,它们三个同样对应着三个接口。看一下Collection接口的定义:

[java]  view plain copy
  1. interface Collection<T> {  
  2.     boolean add(T e);  
  3.     boolean addAll(Collection<? extends T> c);  
  4.     void clear();  
  5.     boolean contains(Object o);  
  6.     boolean containsAll(Collection<?> c);  
  7.     boolean isEmpty();  
  8.     Iterator<T> iterator();  
  9.     boolean remove(Object o);  
  10.     boolean removeAll(Collection<?> c);  
  11.     boolean retainAll(Collection<?> c);  
  12.     int size();  
  13.     Object[] toArray();  
  14.     <T> T[] toArray(T[] a);  
  15. }  
这些方法从字面上看都很理解。其中retainAll与removeAll相对,只保留参数中的元素。同时它还可以返回一个迭代器 Iterator<T>

再看看List接口的定义:

[java]  view plain copy
  1. interface List<T> extends Collection<T> {  
  2.     void add(int index, T element);  
  3.     boolean addAll(int index, Collection<? extends T> c);  
  4.     T get(int index);  
  5.     int indexOf(Object o);  
  6.     int lastIndexOf(Object o);  
  7.     ListIterator<T> listIterator();   
  8.     ListIterator<T> listIterator(int index);  
  9.     T remove(int index);  
  10.     T set(int index, T element);  
  11.     List<T> subList(int fromIndex, int toIndex);  
  12. }     
List加入了对index的支持。同时它还可以另一个迭代器类型 ListIterator<T>

接下来是Set接口的定义:

[java]  view plain copy
  1. interface Set<T> extends Collection<T> {  
  2. }  
它没有加入任何新的方法。

还有Queue接口的定义:

[java]  view plain copy
  1. interface Queue<T> extends Collection<T> {  
  2.     T element();  
  3.     boolean offer(T e);  
  4.     T peek();  
  5.     T poll();  
  6.     T remove();  
  7. }  
element 方法和 peek 方法都是获取队首元素,不同的是当队列为空是element抛出异常(NoSuchElementException)而peek返回null。 poll remove 都是返回并删除队首元素,当队列为空时remove抛出异常而poll返回null。offer方法类似于add。它在不违反队列容量限制的情况下试图插入一个元素,不成功的话返回false而不抛出异常(add方法会抛出异常IllegalStateException)。


虽然这些接口定义了各种方法,但实现它们的类并不必支持这些方法。实现接口的类在方法的实现中抛出UnSupportedOperationException来表示它不支持该方法。比如一个ArrayList的长度是可以变化的,但是如果由Arrays.asList()得到的话,那它就不再支持改变长度的方法了:

[java]  view plain copy
  1. List<Integer> arrayAsList = Arrays.asList(135);   
  2. List<Integer> modifiableList = new ArrayList<Integer>(arrayAsList);    
  3. modifiableList.clear(); // success!  
  4. arrayAsList.set(23); // success! Able to change an element  
  5. arrayAsList.clear(); // UnsupportedOperationException: cannot change the size  
UnSupportedOperationException是一个RuntimeException,它表示一个编程错误,其实和C++里的Assert一样。 Collections.unmodifiableList 方法会得到一个只读的List:
[java]  view plain copy
  1. List<Integer> unmodifiableList = Collections.unmodifiableList(modifiableList);  
  2. unmodifiableList.set(23); // UnsupportedOperationException: cannot modify the list  

Collections.unmodifiableList返回的其实是一个内部类Collections$UnmodifiableRandomAccessList的一个实例。

相应的,Collections.synchronizedCollectionCollections.synchronizedListCollections.synchronizedMapCollections.synchronizedSetCollections.synchronizedSortedMapCollections.synchronizedSortedSet会返回支持线程同步的容器。它们也都是Collections的内部类。


每种类型的接口都有一个Abstract类的实现,比如Collection接口被AbstractCollection实现。AbstractCollection实现了Collection里绝大多数的方法,除了iterator()方法和size()方法。那些被实现的方法仅仅是抛出一个UnSupportedOperationException。Abstract容器类存在的价值在于,当你需要写一个容器类,可以继承自这个Abstract容器类并覆盖你仅需要支持的方法,其它的不需要支持的方法可以继续抛出异常。这样你就不必耗费大量精力去实现容器接口的所有方法。相应的,你还可以发现AbstractListAbstractSetAbstractMap等Abstract容器类。Java类库里所有的具体容器类都继承自某个Abstract容器类。


现在来看看Iterator类。一个Collection都会返回一个Iterator用于遍历这个容器。看看Iterator的定义:

[java]  view plain copy
  1. interface Iterator<T> {  
  2.     boolean hasNext();  
  3.     T next();  
  4.     void remove();  
  5. }  
当要遍历一个容器时,用hasNext判断是否有下一个元素,用next取得下一个元素,如果没有下一个元素next方法会抛出NoSuchElementException。通过remove删除当前元素,删除后当前的元素不再可用。有些Iterator不支持remove,这种情况下这个方法会简单抛出一个UnSupportedOperationException。
一旦容器被修改,之前获得的Iterator就会失效。如果这时还试图访问这个Iterator就会得到异常:
[java]  view plain copy
  1. Collection<Integer> c = new ArrayList<Integer>();  
  2. c.add(1);  
  3. Iterator<Integer> itor = c.iterator();  
  4. c.add(3); // modify the collection  
  5. if (itor.hasNext())  
  6.     itor.next(); // java.util.ConcurrentModificationException  
List可以返回一个 ListIterator 类型。看看它的定义:
[java]  view plain copy
  1. interface ListIterator<T> {  
  2.     void add(T e);  
  3.     boolean hasPrevious();  
  4.     int nextIndex();  
  5.     T previous();  
  6.     int previousIndex();  
  7.     void set(T e);  
  8. }  
可以看到ListIterator支持了向前和向后遍历,同时还支持添加元素和修改当前元素的操作。


实现List接口的类有ArrayListLinkedList。一个用数组实现,另一个用双向链表实现。除了这两个类外,还有早期开发的不再推荐使用的Vector和Stack类。


实现Set接口的类有HashSetTreeSetLinkedHashSet
HashSet用散列的方式存储集合元素。它支持快速查找。放入HashSet的元素必须支持hashCode和equals方法。
LinkedHashSet可以达到HashSet一样的查找速度,同时内部维护了一个链表。当使用跌代器遍历LinkedHashSet时,遍历的顺序和元素插入的顺序一样。放入LinkedHashSet的元素同样需要支持hashCode。
TreeSet使用红黑树实现集合,它对内部元素排序。在遍历元素时按照由小到大的顺序。放入TreeSet的元素必须实现Comparable接口。TreeSet实现了SortedSet接口:

[java]  view plain copy
  1. interface SortedSet<T> {  
  2.     Comparator<? super T> comparator();  
  3.     T first();  
  4.     SortedSet<T> headSet(T toElement);  
  5.     T last();  
  6.     SortedSet<T> subSet(T fromElement, T toElement);  
  7.     SortedSet<T> tailSet(T fromElement);  
  8. }  
first方法返回集合中最小的元素。last返回最大的元素。headSet、subSet和tailSet返回子集。

实现Queue接口的类有LinkedListPriorityQueue
其实LinkedList同时实现了List接口和Deque接口。Deque是双向队列:

[java]  view plain copy
  1. interface Deque<T> extends Queue<T> {  
  2.     void addFirst(T e);  
  3.     void addLast(T e);  
  4.     Iterator<T> descendingIterator();  
  5.     T getFirst();  
  6.     T getLast();  
  7.     boolean offerFirst(T e);  
  8.     boolean offerLast(T e);  
  9.     T peekFirst();  
  10.     T peekLast();  
  11.     T pollFirst();  
  12.     T pollLast();  
  13.     T pop();  
  14.     void push(T e);  
  15.     T removeFirst();  
  16.     boolean removeFirstOccurrence(Object o);  
  17.     T removeLast();  
  18.     boolean removeLastOccurrence(Object o);  
  19. }  
PriorityQueue 根据优先级确定下一个移出对列的元素。放入PriorityQueue的元素需要实现Comparable接口,最小的元素会先在队首。看下面的示例代码:
[java]  view plain copy
  1. PriorityQueue<Integer> pq = new PriorityQueue<Integer>();  
  2. pq.offer(5);  
  3. pq.offer(13);  
  4. pq.offer(9);  
  5. pq.offer(12);  
  6. pq.offer(6);  
  7. while (!pq.isEmpty()) {  
  8.     System.out.println(pq.poll()); // 5, 6, 9, 12, 13  
  9. }  


接下来看看Map接口的定义:

[java]  view plain copy
  1. interface Map<K, V> {  
  2.     void clear();  
  3.     boolean containsKey(Object key);  
  4.     boolean containsValue(Object value);  
  5.     Set<java.util.Map.Entry<K, V>> entrySet();  
  6.     V get(Object key);  
  7.     boolean isEmpty();  
  8.     Set<K> keySet();  
  9.     V put(K key, V value);  
  10.     void putAll(Map<? extends K, ? extends V> m);  
  11.     V remove(Object key);  
  12.     int size();  
  13.     Collection<V> values();  
  14. }  
注意到 entrySet 方法返回一个 Map.Enry 的集合。Map.Entry也是一个接口:
[java]  view plain copy
  1. Map.Entry<K, V> {  
  2.     K getKey();  
  3.     V getValue();  
  4.     V setValue(V value);  
  5. }  
还可以看到Map通过 values 方法转成一个Collection类型。


实现Map接口的类有 HashMap LinkedHashMap TreeMap WeakHashMap ConcurrentHashMap IdentityHashMap 等。
其实可以把Map看成特殊的Collection。Collection里的元素是一个value而Map里的元素是一个键值对(Entry)。从这种角度看 HashMap LinkedHashMap TreeMap 与HashSet、LinkedHashSet和TreeSet其实大同小异。其中TreeMap同样也实现了一个 SortedMap 的接口,它和SortedSet很相似:
[java]  view plain copy
  1. interface SortedMap<K,V> extends Map<K,V> {  
  2.     Comparator<? super K> comparator();  
  3.     K firstKey();  
  4.     SortedMap<K, V> headMap(K toKey);  
  5.     K lastKey();  
  6.     SortedMap<K, V> subMap(K fromKey, K toKey);  
  7.     SortedMap<K, V> tailMap(K fromKey);  
  8. }  
WeakHashMap 里的某个键如果没有在Map外被引用时,这个键会被垃圾回收器回收并且该键的条目会从Map中删除。这个键其实是一个弱引用,即它不会增加对所引用对象的计数。
ConcurrentHashMap 是一种线程安全的Map,它不涉及同步加锁。
IdentityHashMap 在比较键值时是调用“==”操作符,而不是对象的equals方法。


说到WeakHashMap里的弱引用,我们来研究下java里的引用对象(reference object)。在java.lang.ref包里有一个Reference对象。它有三个子类SoftReference,WeakReferencePhantomReference

通常情况一 下,我们用new创建一个对象并把它赋给一个引用,这个引用是一个强引用(Strong Reference)。比如“Object o = new Integer(3);”里的“o”就是一个强引用。垃圾回收器是绝对不会释放一个强引用所引用的对象的。

软引用(soft reference)比强引用的程度稍弱些。一般情况下,垃圾回收器是不会清理软引用所引用的对象的,看这个例子:

[java]  view plain copy
  1. class MyClass {  
  2.     protected void finalize() {  
  3.         System.out.println("MyClass.finalize");  
  4.     }  
  5. }  
  6.   
  7. // caller  
  8. MyClass mc = new MyClass();  
  9. SoftReference sr = new SoftReference(mc, rq);  
  10. mc = null// free the strong reference!!  
  11. System.gc(); // output: nothing.  
当使用System.gc强行清理垃圾的时候,软引用所引用的对象并不会被清理。但如果内存不够的情况下,被软引用的对象就会被清理掉:
[java]  view plain copy
  1. // caller  
  2. MyClass mc = new MyClass();  
  3. SoftReference sr = new SoftReference(mc, rq);  
  4. mc = null;  
  5.   
  6. // now try to use out the memory  
  7. ArrayList al = new ArrayList();  
  8. while (true)  
  9.     al.add(new int[204800]);  
  10. // output: MyClass.finalize  
  11. // output: java.lang.OutOfMemoryError  
可以看到在抛出异常前,虚拟机把软引用所指的对象清理掉了。软引用的这样的特性特别适合作为内存的Cache。

弱引用(Weak reference)要比软引用的程度还弱些。如果某对象没有被强引用或软引用,而只是被弱引用,垃圾回收器会清理掉这个对象:

[java]  view plain copy
  1. MyClass mc = new MyClass();  
  2. WeakReference wr = new WeakReference(mc);  
  3. mc = null// free the strong reference!!  
  4. System.gc(); // output: MyClass.finalize  

虚引用(Phantom reference)不干涉对象的生命周期,它只保证当它所引用的对象被清理时,虚引用本身被放进一个ReferenceQueue里。

[java]  view plain copy
  1. ReferenceQueue rq = new ReferenceQueue();  
  2. MyClass mc = new MyClass();  
  3. PhantomReference pr = new PhantomReference(mc, rq);  
  4. mc = null;  
  5. System.gc();  
  6.       
  7. Reference r = null;  
  8. try {  
  9.     r = rq.remove();  
  10. catch (InterruptedException e) {  
  11.     e.printStackTrace();  
  12. }  
  13. if (r != null)  
  14.     System.out.println("You die!!!");  
当被虚引用的对象被gc回收时,gc会把PhantomReference放入ReferenceQueue里。由于finalize方法的不可靠,虚引用可以更好地保证一些清理工作。


最后介绍一个很节省存储空间的容器BitSet。它的每个元素是一个位,你可以设置它的各个位,并且可以进行与或操作等。BitSet的最小size是64位。下面给出一个例子:

[java]  view plain copy
  1. void print(BitSet bs) {   
  2.     for (int i = 0; i < bs.size(); ++i)  
  3.         System.out.print(bs.get(i) ? '1' : '0');  
  4.     System.out.println();  
  5. }  
  6. // caller  
  7. BitSet bs1 = new BitSet();  
  8. bs1.set(215);  
  9. print(bs1); // 0011111111111110000000000000000000000000000000000000000000000000  
  10. bs1.set(4false);  
  11. print(bs1); // 0011011111111110000000000000000000000000000000000000000000000000  
  12. BitSet bs2 = new BitSet();  
  13. bs2.set(827);  
  14. print(bs2); // 0000000011111111111111111110000000000000000000000000000000000000  
  15. bs2.and(bs1);  
  16. print(bs2); // 0000000011111110000000000000000000000000000000000000000000000000  


Java I/O系统

先看一下java.io.File类。它其实应该命名为FileInfo会好一些。看看它能做什么:
它能用来创建一个文件

[java]  view plain copy
  1. File file = new File("MyFile");  
  2. if (!file.exists()) {  
  3.     try {  
  4.         file.createNewFile();  
  5.     } catch (IOException e) {  
  6.         e.printStackTrace();  
  7.     }  
  8. }  
查看一个文件的属性
[java]  view plain copy
  1. boolean canRead = file.canRead();  
  2. long lastModified = file.lastModified();  
  3. String absolutePath = file.getAbsolutePath();  
  4. long freeSpace = file.getFreeSpace();  
改变一个文件的属性
[java]  view plain copy
  1. file.setExecutable(false);  
  2. file.setWritable(true);  
  3. file.setLastModified(System.currentTimeMillis());  
移动/重命名一个文件
[java]  view plain copy
  1. File otherFile = new File("MyOtherFile");  
  2. file.renameTo(otherFile);  
  3. try {  
  4.     Thread.sleep(5000);  
  5. catch (InterruptedException e) {  
  6.     e.printStackTrace();  
  7. }  
  8. boolean exists = file.exists(); // false;  
创建一个文件夹
[java]  view plain copy
  1. File folder = new File("MyFolder");  
  2. if (!folder.exists())  
  3.     folder.mkdir();  
列出一个文件夹下的所有文件和文件夹
[java]  view plain copy
  1. String fileNamesInFolder[] = folder.list();  
  2. File filesInFolder[] = folder.listFiles();  
当然你也可以 列出一个文件夹下你感兴趣的文件和文件夹
[java]  view plain copy
  1. String interestedfileNames[] = folder.list(new FilenameFilter() {  
  2.     public boolean accept(File dir, String name) {  
  3.         return name.endsWith(".txt");  
  4.     }  
  5. });  
用File的静态方法 得到系统所有的根目录
[java]  view plain copy
  1. File roots[] = File.listRoots();  
  2. System.out.println(Arrays.toString(roots));  
得到一个文件或文件夹的父文件夹 (只对绝对路径有效):
[java]  view plain copy
  1. System.out.println(file.getParent()); // null!!  
  2. File absFile = file.getAbsoluteFile();  
  3. System.out.println(absFile.getParent()); // works!!  
最后是 删除一个文件或文件夹
[java]  view plain copy
  1. boolean deleted = file.delete();  
  2. deleted = folder.delete();  

输入输出(I/O)的对象并不仅仅是文件,可以是任何形式的设备,比如屏幕或是网络。下面介绍四个I/O最最基本的类,它们是InputStreamOutputStreamReaderWriter

InputStream是所有“输入流”的基类,它是一个纯虚类并定义了一个读取字节的纯虚方法:“public int read() throws IOException”,你可以这样定义InputStream的子类:

[java]  view plain copy
  1. class MyInputStream extends InputStream {  
  2.     @Override  
  3.     public int read() throws IOException {  
  4.         if (itor >= data.length)  
  5.             return -1;  
  6.         return data[itor++];  
  7.     }  
  8.       
  9.     private int itor = 0;  
  10.     private byte data[] = { 259834 };  
  11. }  
在定义好这个纯虚read方法只读取一个字节,但返回一个int值。这个int值如果为-1说明读取已经完毕,但如果这个值作为有效值的话,它的前三个字节会被忽略。read方法定义好后,InputStream的 read(byte[] b) read(byte[] b, int off, int len) 方法会调用read()方法来实现更复杂的功能。

OutputStream是所有“输出流”的基类,它也是一个纯虚类,定义了两个写入字节的纯虚方法:“public void write(int b) throws IOException”。定义子类:

[java]  view plain copy
  1. class MyOutputStream extends OutputStream {  
  2.     @Override  
  3.     public void write(int b) throws IOException {  
  4.         data.add((byte)b);  
  5.     }  
  6.       
  7.     private ArrayList<Byte> data = new ArrayList<Byte>();  
  8. }  
Write方法写入传入int值的最后一个字节。同样的,OutputStream的 write(byte[] b) write(byte[] b, int off, int len) 方法会调用write()方法来完成更复杂的功能。

Reader是所有“字符读取器”的纯虚基类,它有两个纯虚方法:“public void close() throws IOException”、“public int read(char[] cbuf, int off, int len) throws IOException”。定义子类:

[java]  view plain copy
  1. class MyReader extends Reader {  
  2.     @Override  
  3.     public void close() throws IOException {  
  4.         closed = true;  
  5.     }  
  6.   
  7.     @Override  
  8.     public int read(char[] cbuf, int off, int len) throws IOException {  
  9.         if (closed)   
  10.             throw new IOException();  
  11.         if (index >= data.length())  
  12.             return -1;  
  13.         int count = 0;  
  14.         for (int i = 0; i < len && index < data.length(); ++i) {  
  15.             cbuf[i+off] = data.charAt(index++);  
  16.             ++count;  
  17.         }  
  18.         return count;  
  19.     }  
  20.       
  21.     private boolean closed = false;  
  22.     private String data = "This is the data. You are happy~";  
  23.     private int index = 0;  
  24. }  

Reader是InputStream的补充,它提供了读取字符的功能,而不仅仅是字节。在定义好read(char[] cbuf, int off, int len)方法后,Reader的“read()”、“read(char[] cbuf)”和“read(CharBuffer target)”方法就可以利用定义好的方法来提供更简单的读取字符的方法。

Writer是所有“写入字符器”的纯虚基类,它定义了三个纯虚方法:“public void close() throws IOException”、“public void flush() throws IOException”和“public void write(char[] cbuf, int off, int len) throws IOException”。定义子类:

[java]  view plain copy
  1. class MyWriter extends Writer {  
  2.     @Override  
  3.     public void close() throws IOException {  
  4.         closed = true;  
  5.     }  
  6.   
  7.     @Override  
  8.     public void flush() throws IOException {  
  9.         if (closed)  
  10.             throw new IOException();  
  11.         System.out.println(data);  
  12.     }  
  13.   
  14.     @Override  
  15.     public void write(char[] cbuf, int off, int len) throws IOException {  
  16.         if (closed)  
  17.             throw new IOException();  
  18.         for (int i = 0; i < len; ++i)  
  19.             data += cbuf[i+off];  
  20.     }  
  21.       
  22.     private boolean closed = false;  
  23.     private String data = new String();  
  24. }  
定义好这个纯虚方法后,Writer类的“ append(char c) ”、“ append(CharSequence csq) ”、“ append(CharSequence csq, int start, int end) ”、“ write(char[] cbuf)”、“writer.write(int c) ”、“ write(String str) ”和“ write(String str, int off, int len) ”方法也都可以用了。


现在回到一个比较基本的问题,怎么从读写一个文件的数据?java提供了两个类:FileInputStreamFileOutputStream。我们可以用它们基类里定义的方法:InputStream.read(byte[] bytes)OutputStream.write(byte[] bytes)。提起精神来,下面的代码有点长,虽然不复杂:

[java]  view plain copy
  1. void testFileIOStream() throws IOException {  
  2.     long ldata = -328910192;  
  3.     int idata = 2305910;  
  4.     short sdata = 4652;  
  5.     char cdata = 'A';  
  6.     double ddata = 98323.8253221;  
  7.     float fdata = 2382.784f;  
  8.           
  9.     // Write to file  
  10.     FileOutputStream fos = new FileOutputStream("MyFile");  
  11.     fos.write(DataConvertor.longToBytes(ldata));  
  12.     fos.write(DataConvertor.intToBytes(idata));  
  13.     fos.write(DataConvertor.shortToBytes(sdata));  
  14.     fos.write(DataConvertor.charToBytes(cdata));  
  15.     fos.write(DataConvertor.doubleToBytes(ddata));  
  16.     fos.write(DataConvertor.floatToBytes(fdata));  
  17.     fos.flush();  
  18.     fos.close();  
  19.           
  20.     byte[] lBytes = new byte[Long.SIZE/8];  
  21.     byte[] iBytes = new byte[Integer.SIZE/8];  
  22.     byte[] sBytes = new byte[Short.SIZE/8];  
  23.     byte[] cBytes = new byte[Character.SIZE/8];  
  24.     byte[] dBytes = new byte[Double.SIZE/8];  
  25.     byte[] fBytes = new byte[Float.SIZE/8];  
  26.           
  27.     // Read from file  
  28.     FileInputStream fis = new FileInputStream("MyFile");  
  29.     fis.read(lBytes);  
  30.     fis.read(iBytes);  
  31.     fis.read(sBytes);  
  32.     fis.read(cBytes);  
  33.     fis.read(dBytes);  
  34.     fis.read(fBytes);  
  35.     fis.close();  
  36.           
  37.     // Print Values  
  38.     System.out.println("Long data: " + DataConvertor.bytesToLong(lBytes));  
  39.     System.out.println("Int data: " + DataConvertor.bytesToInt(iBytes));  
  40.     System.out.println("Short data: " + DataConvertor.bytesToShort(sBytes));  
  41.     System.out.println("Char data: " + DataConvertor.bytesToChar(cBytes));  
  42.     System.out.println("Double data: " + DataConvertor.bytesToDouble(dBytes));  
  43.     System.out.println("Float data: " + DataConvertor.bytesToFloat(fBytes));  
  44. }  
看到上面的代码里有个 DataConvertor 的类,它可以把基本类型和字节数组进行转换。它可不是java里自带的,我们得自己实现它:
[java]  view plain copy
  1. class DataConvertor {  
  2.     public static byte[] longToBytes(long l) {  
  3.         return numberToBytes(l, Long.SIZE/8);  
  4.     }  
  5.       
  6.     public static long bytesToLong(byte[] bytes) {  
  7.         return bytesToNumber(bytes, Long.SIZE/8).longValue();  
  8.     }  
  9.   
  10.     public static byte[] intToBytes(int n) {  
  11.         return numberToBytes(n, Integer.SIZE/8);  
  12.     }  
  13.       
  14.     public static int bytesToInt(byte[] bytes) {  
  15.         return bytesToNumber(bytes, Integer.SIZE/8).intValue();  
  16.     }  
  17.       
  18.     public static byte[] shortToBytes(short s) {  
  19.         return numberToBytes(s, Short.SIZE/8);  
  20.     }  
  21.       
  22.     public static short bytesToShort(byte[] bytes) {  
  23.         return bytesToNumber(bytes, Short.SIZE/8).shortValue();  
  24.     }  
  25.       
  26.     public static byte[] doubleToBytes(double d) {  
  27.         return longToBytes(Double.doubleToLongBits(d));  
  28.     }  
  29.       
  30.     public static double bytesToDouble(byte[] bytes) {  
  31.         return Double.longBitsToDouble(bytesToLong(bytes));  
  32.     }  
  33.       
  34.     public static byte[] floatToBytes(float f) {  
  35.         return intToBytes(Float.floatToRawIntBits(f));  
  36.     }  
  37.       
  38.     public static float bytesToFloat(byte[] bytes) {  
  39.         return Float.intBitsToFloat(bytesToInt(bytes));  
  40.     }  
  41.       
  42.     public static byte[] charToBytes(char c) {  
  43.         return numberToBytes((int)c, Character.SIZE/8);  
  44.     }  
  45.       
  46.     public static char bytesToChar(byte[] bytes) {  
  47.         return (char)(bytesToNumber(bytes, Character.SIZE/8).intValue());  
  48.     }  
  49.       
  50.     private static byte[] numberToBytes(Number n, final int size) {  
  51.         byte[] bytes = new byte[size];  
  52.         long l = n.longValue();  
  53.         for (int i = 0; i < size; i++)  
  54.             bytes[i] = (byte)((l >> 8*i) & 0xff);  
  55.         return bytes;  
  56.     }  
  57.       
  58.     private static Number bytesToNumber(byte[] bytes, final int size) {  
  59.         long l = 0;  
  60.         for (int i = 0; i < size; i++)  
  61.             l |= ((long)(bytes[i] & 0xff) << (8*i));  
  62.         return l;  
  63.     }  
  64. }  
是不是有点复杂?如果我们每次写文件总要和这么底层的字节打交道的话,那样总是显得比较繁琐。java提供了另一组I/O流: DataInputStream DataOutputStream ,它可以外嵌在其它的I/O流对象外,比如FileInputStream。用这两个类重写上面的读写文件的功能:
[java]  view plain copy
  1. void testDataIOStream() throws IOException {          
  2.     DataOutputStream dos = new DataOutputStream(new FileOutputStream("MyFile"));  
  3.     dos.writeLong(ldata);  
  4.     dos.writeInt(idata);  
  5.     dos.writeShort(sdata);  
  6.     dos.writeChar(cdata);  
  7.     dos.writeDouble(ddata);  
  8.     dos.writeFloat(fdata);  
  9.     dos.flush();  
  10.     dos.close();  
  11.           
  12.     DataInputStream dis = new DataInputStream(new FileInputStream("MyFile"));  
  13.     long l = dis.readLong();  
  14.     int n = dis.readInt();  
  15.     short s = dis.readShort();  
  16.     char c = dis.readChar();  
  17.     double d = dis.readDouble();  
  18.     float f = dis.readFloat();  
  19.     dis.close();  
  20. }  
java的I/O类库用的是 装饰者模式 ,比如DataInputStream可以是任何一个InputStream的装饰者。通过这种模式,你可以组合出各种功能的I/O对象。


如果需要向文件存取字符串,而不是字节,就可以使用FileReaderFileWriter了:

[java]  view plain copy
  1. testFileRW() throws IOException {  
  2.     FileWriter fw = new FileWriter("MyFile");  
  3.     fw.write("Hello, ");  
  4.     fw.write("hava a good day\n");  
  5.     fw.append("Here's another line!");  
  6.     fw.flush();  
  7.     fw.close();  
  8.           
  9.     FileReader fr = new FileReader("MyFile");  
  10.     CharBuffer cb = CharBuffer.allocate(1000);  
  11.     int n = fr.read(cb);  
  12.     fr.close();  
  13.           
  14.     System.out.println(cb.array());  
  15. }  
我们同样可以给Reader和Writer添加装饰者,比如 BufferedReader BufferedWriter 。它们提供一个缓冲区来防止每次都执行实际读写操作,同时还提供读写行的能力:
[java]  view plain copy
  1. void testBufferRW() throws IOException {  
  2.     BufferedWriter bw = new BufferedWriter(new FileWriter("MyFile"));  
  3.     bw.write("Write into buffer");  
  4.     bw.newLine(); // BufferedWriter only!!  
  5.     bw.write("Another line");  
  6.     bw.flush();  
  7.     bw.close();  
  8.           
  9.     BufferedReader br = new BufferedReader(new FileReader("MyFile"));  
  10.     String line;  
  11.     do {  
  12.         line = br.readLine(); // BufferedReader only!!  
  13.         System.out.println(line);  
  14.     } while (line != null);  
  15. }     


除了文件,我们也可以对其它类型的目标进行读写,比如内存。我们可以用CharArrayWriter/CharArrayReader读写内存(或者ByteArrayInputStream/ByteArrayOutputStream读写字节)。下面给了一个在内存中读写字符串例子,同时也介绍一个PrintWriter和StringReader:

[java]  view plain copy
  1. void testCharArrayRW() throws IOException {  
  2.     // write to memory instead of file  
  3.     CharArrayWriter baw = new CharArrayWriter();  
  4.     PrintWriter pw = new PrintWriter(baw);  
  5.     pw.print((int)3);  
  6.     pw.print((boolean)true);  
  7.     pw.println("OK");  
  8.     pw.flush();  
  9.     pw.close();  
  10.           
  11.     // read from memory using CharArrayReader  
  12.     CharArrayReader car = new CharArrayReader(baw.toCharArray());  
  13.     BufferedReader br = new BufferedReader(car);  
  14.     String line;  
  15.     while ((line = br.readLine()) != null)  
  16.         System.out.println(line);  
  17.     br.close();  
  18.           
  19.     // read from memory using StringReader  
  20.     StringReader sr = new StringReader(baw.toString());  
  21.     br = new BufferedReader(sr);  
  22.     sr.skip(2);  
  23.     while ((line = br.readLine()) != null)  
  24.         System.out.println(line);  
  25.     sr.close();  
  26. }  

InputStreamReaderOutputStreamWriter可以把InputStream和OutputStream转成Reader和Writer:

[java]  view plain copy
  1. void testIOStreamRW() throws IOException {  
  2.     OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("MyFile"));  
  3.     osw.write("Just for fun");  
  4.     osw.flush();  
  5.     osw.close();  
  6.           
  7.     InputStreamReader isr = new InputStreamReader(new FileInputStream("MyFile"));  
  8.     int c;  
  9.     while ((c = isr.read()) >= 0)  
  10.         System.out.print((char)c);  
  11.     isr.close();  
  12. }  

系统的标准I/O包括System.inSystem.outSystem.err。System.out和System.err是两个PrintStream对象,System.in是一个InputStream对象。可以使用System的setIn(InputStream)setOut(PrintStream)setErr(PrintStream)方法来重定向这些标准I/O的目标。

I/O库里有一个类RandomAccessFile,它不是Reader/Writer或InputStream/OutputStream的子类,而是直接继承自Object类并实现了DataInputDataOuput这两个接口。(DataInputStream和DataOutputStream也分别实现了这两个接口)。同时它还可以定位到文件的随机位置进行读写:

[java]  view plain copy
  1. void testRandomAccessFile() throws IOException {  
  2.     RandomAccessFile raf = new RandomAccessFile("MyFile""rw");  
  3.     for (int i = 1; i <= 10; ++i)  
  4.         raf.writeInt(i);  
  5.           
  6.     // return to the beginning  
  7.     raf.seek(0);  
  8.     for (int i = 0; i < 10; ++i)  
  9.         System.out.print(raf.readInt() + " "); // 1 2 3 4 5 6 7 8 9 10  
  10.           
  11.     // modify the 5th int  
  12.     raf.seek(Integer.SIZE/8 * 4);  
  13.     raf.writeInt(555);  
  14.           
  15.     raf.seek(0);  
  16.     for (int i = 0; i < 10; ++i)  
  17.         System.out.print(raf.readInt() + " "); // 1 2 3 4 555 6 7 8 9 10  
  18. }  

java里还提供压缩文件的读写。最常用的两种压缩算法是Zip和GZiP。先看看GZip的简单例子:

[java]  view plain copy
  1. testGZipIOStream() throws FileNotFoundException, IOException {  
  2.     GZIPOutputStream gzos = new GZIPOutputStream(new FileOutputStream("MyZip.gz"));  
  3.     DataOutputStream dos = new DataOutputStream(gzos);  
  4.     dos.writeInt(38);  
  5.     dos.flush();  
  6.     dos.close();  
  7.           
  8.     GZIPInputStream gzis = new GZIPInputStream(new FileInputStream("MyZip.gz"));  
  9.     DataInputStream dis = new DataInputStream(gzis);  
  10.     int n = dis.readInt();  
  11.     dis.close();  
  12.           
  13.     System.out.println(n);  
  14. }  
上面的代码会生成一个MyZip.gz文件,它里面带一个名为“MyZip”的Entry。用解压缩软件就可以看到。

java对Zip文件的支持比较好,可以添加多个Entry,甚至是子文件夹。下例把一个文件压缩到一个zip文件里,同时创建了一个子文件夹下的Entry:

[java]  view plain copy
  1. void testZipIOStream() throws IOException {  
  2.     // create a file to compress into a zip file  
  3.     FileWriter fw = new FileWriter("MyFile");  
  4.     fw.write("Just for fun");  
  5.     fw.flush();  
  6.     fw.close();  
  7.           
  8.     ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("MyZip.zip"));  
  9.     zos.setComment("Here's some comment");  
  10.     zos.putNextEntry(new ZipEntry("MyFile"));  
  11.     // compress MyFile into the zip file  
  12.     FileReader fr = new FileReader("MyFile");  
  13.     int b;  
  14.     while ((b=fr.read()) >= 0)  
  15.         zos.write(b);  
  16.     // another entry in a sub folder.  
  17.     zos.putNextEntry(new ZipEntry("Parent/SubFile"));  
  18.     zos.write('A');  
  19.     zos.flush();  
  20.     zos.close();  
  21.           
  22.     ZipInputStream zis = new ZipInputStream(new FileInputStream("MyZip.zip"));  
  23.     // read MyFile  
  24.     ZipEntry ze = zis.getNextEntry();  
  25.     System.out.println(ze.getName()); // MyFile  
  26.     InputStreamReader isr = new InputStreamReader(zis);  
  27.     int c;  
  28.     while ((c=isr.read()) >= 0)  
  29.         System.out.print((char)c); // Output: Just for fun  
  30.     System.out.println();  
  31.     // read SubFile  
  32.     ze = zis.getNextEntry();  
  33.     System.out.println(ze.getName()); // Parent/SubFile  
  34.     System.out.println((char)isr.read()); // 'A'   
  35.     isr.close();  
  36. }  

JDK1.4引入了一个新的I/O类库:java.nio.*(nio = new io)。它通过通道(java.nio.channels.Channel)缓冲器(java.nio.ByteBuffer)来提高I/O的效率。FileInputStream、FileOutputStream和RandomAccessFile都提供了一个getChannel的方法来使用通道传输数据。下面给一个简单的例子:

[java]  view plain copy
  1. void testChannel() throws IOException {  
  2.     FileChannel fc = new FileOutputStream("MyFile").getChannel();  
  3.     fc.write(ByteBuffer.wrap("string data".getBytes()));  
  4.     fc.close();  
  5.     fc = new RandomAccessFile("MyFile""rw").getChannel();  
  6.     // locate to the end   
  7.     fc.position(fc.size());  
  8.     fc.write(ByteBuffer.wrap("\nAppend to the end".getBytes()));  
  9.     fc.close();  
  10.           
  11.     fc = new FileInputStream("MyFile").getChannel();  
  12.     ByteBuffer bb = ByteBuffer.allocate(1024);  
  13.     fc.read(bb);  
  14.     bb.flip(); // prepare to read the buffer  
  15.     while (bb.hasRemaining())  
  16.         System.out.print((char)bb.get());  
  17. }  

ByteBuffer就是唯一可以直接和Channel交互的缓冲器。它有一点不方便的地方就是,当读取这个缓冲器的数据前,必须调用ByteBuffer.flip()方法,而在向这个缓冲器写数据前,要调用ByteBuffer.clear()方法。(clear方法并不清空已有的数据,待会再作详细说明)。这种不便利性也是为了提高存取速度而作的牺牲。

ByteBuffer只能通过静态方法ByteBuffer.wrapByteBuffer.allocate创建,它在被创建后大小(Capacity)就是固定的。它用一个position的整型值来指示当前读写的位置,同时用一个limit的整型成员来表示当前数据的大小(字节数)。
当向ByteBuffer里写数据时,应该先调用clear方法,它会把postion设为0同时把limit设为和Capacity一样。之后每调用put方法写数据时,position就向后移n个字节。当position超过limit时,就抛出异常。
读数据前,应该先调用flip方法,它会把limit值设为已写数据的尾部,并把position设为0。读时可以用hasRemaining方法来判断是否还有可读数据。remaining等于limit与position的差值。
你还可以用markreset方法来标记和重置缓冲区的某个特定的位置。

在上面的代码里,我们使用ByteBuffer直接读写字符串。我们也可以使用CharBuffer

[java]  view plain copy
  1. testCharBuffer() throws UnsupportedEncodingException {  
  2.     ByteBuffer bb = ByteBuffer.wrap("some text".getBytes());  
  3.     System.out.println(bb.asCharBuffer()); // Output: ????  
  4.           
  5.     bb = ByteBuffer.allocate(1024);  
  6.     bb.put("some text".getBytes("UTF-16BE"));  
  7.     bb.flip(); // after "put" call this to read  
  8.     bb.rewind(); // return to the beginning  
  9.     System.out.println(bb.asCharBuffer()); // Output: some text  
  10.           
  11.     bb.clear(); // only reset the position, won't erase the content  
  12.     CharBuffer cb = bb.asCharBuffer();  
  13.     cb.put("other");  
  14.     cb.flip();  
  15.     System.out.println(cb); // Output: other  
  16. }  
CharBuffer是ByteBuffer的一个 视图缓冲器 ,即它底层和ByteBuffer共用一块缓冲区,但维护独自的position和limit等成员。此外还有IntBuffer、DoubleBuffer等视图缓冲器。注意到上面的代码里,在用裸缓冲器写字符串我们必须把对它进行 UTF-16BE 的编码,否则CharBuffer不能识别。


你可以用Channel的TransferToTransferFrom方法在两个通道间传递数据:

[java]  view plain copy
  1. void testTransferChannel() throws IOException {  
  2.     FileChannel in = new FileInputStream("MyFile").getChannel();  
  3.     FileChannel out = new FileOutputStream("Other").getChannel();  
  4.     in.transferTo(0, in.size(), out);  
  5. }  


有了通道,我们就可以使用 MappedByteBuffer把一个文件(整个或部份地)映射到内存中进行读写:
[java]  view plain copy
  1. void testMappedBuffer() throws FileNotFoundException, IOException {  
  2.     int start = 2;  
  3.     int length = 12;  
  4.     MappedByteBuffer mbb = new RandomAccessFile("MyFile""rw").getChannel().  
  5.                 map(FileChannel.MapMode.READ_WRITE, start, length);  
  6.     while (mbb.hasRemaining())  
  7.         System.out.print((char)mbb.get());  
  8. }  
内存映射的读写比直接使用I/O stream要快速很多,同时还能解决大文件不能全部装入内存的问题。

我们可以对文件或其中的某一部份进行 加锁
[java]  view plain copy
  1. void testFileLock() throws IOException {  
  2.     FileOutputStream fos = new FileOutputStream("MyFile");   
  3.     FileLock fl = fos.getChannel().tryLock();  
  4.     if (fl != null) {  
  5.         System.out.println("Got lock!");  
  6.         fl.release();  
  7.     }  
  8.           
  9.     int start = 2;  
  10.     int length = 12;  
  11.     boolean shared = false;  
  12.     FileLock fl_part = fos.getChannel().tryLock(start, length, shared);  
  13.     if (fl_part != null) {  
  14.         System.out.println("Got part lock!!");  
  15.         fl.release();  
  16.     }  
  17.           
  18.     fos.close();  
  19. }  

java对序列化的支持作的非常好,只要让需要序列化的类实现Serializable接口,而这个接口没有任何方法。之后使用ObjectOutputStreamObjectInputStream就可以读写对象了:

[java]  view plain copy
  1. class MyClass implements Serializable {  
  2.     public void print() {  
  3.         System.out.println("MyClass - " + i);  
  4.     }  
  5.     private int i;  
  6.     {  
  7.         i = new Random().nextInt(199);  
  8.     }  
  9. }  
  10.   
  11. void testSerialization() throws FileNotFoundException, IOException, ClassNotFoundException {  
  12.     MyClass mc = new MyClass();  
  13.     mc.print();  
  14.           
  15.     ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("MyFile"));  
  16.     oos.writeObject(mc);  
  17.     oos.close();  
  18.           
  19.     ObjectInputStream ois = new ObjectInputStream(new FileInputStream("MyFile"));  
  20.     MyClass mc2 = (MyClass)ois.readObject();  
  21.     ois.close();  
  22.     mc2.print();  
  23. }  

如果你不想使用java自带的序列化方式(直接把二进制数据写入内存),可以实现Externalizable接口。它有两个方法:writeExternalreadExternal。想要使用Externalizable接口的类必须提供一个public的不带参数的构造器。看一个例子:

[java]  view plain copy
  1. class MyExternalizable implements Externalizable {  
  2.   
  3.     @Override  
  4.     public void readExternal(ObjectInput in) throws IOException,  
  5.             ClassNotFoundException {  
  6.         i = in.readInt();  
  7.     }  
  8.   
  9.     @Override  
  10.     public void writeExternal(ObjectOutput out) throws IOException {  
  11.         out.writeInt(i);  
  12.     }  
  13.           
  14.     // A public constructor without arguments is a must!!  
  15.     public MyExternalizable() {}  
  16.           
  17.     public void print() {  
  18.         System.out.println(i);  
  19.     }  
  20.           
  21.     private int i;  
  22.     {  
  23.         i = new Random().nextInt(198);  
  24.     }  
  25. }  
接下来就可以和Serializable一样进行序列化了。如果你还是想用Serializable,但只想序列化对象里的其中一部份成员,可以用 transient 关键字:
[java]  view plain copy
  1. class MySerializable implements Serializable {  
  2.     private int i;  
  3.     private transient int j;  
  4. }  
如果你想实现Serializable,同时又想定义自己序列化的方法,也是可以的。只要实现 readObject writeObject 方法。这不是覆盖,而是通过反射被调用:
[java]  view plain copy
  1. class MySerializable implements Serializable {  
  2.     private int i;  
  3.     private transient int j;  
  4.       
  5.     private void writeObject(ObjectOutputStream out) throws IOException {  
  6.         out.defaultWriteObject(); // use default behavior  
  7.         out.writeInt(j);  
  8.     }  
  9.       
  10.     private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException {  
  11.         in.defaultReadObject();  
  12.         j = in.readInt();  
  13.     }  
  14. }  


我们还可以用java.util.prefs.Preferences注册表读写数据:

[java]  view plain copy
  1. void testPreferences() throws BackingStoreException {  
  2.     Preferences p = Preferences.userNodeForPackage(IOTest4.class);  
  3.     p.putInt("MyKey"8); // write to registry  
  4.           
  5.     int n = p.getInt("MyKey"0); // read from registry  
  6.     System.out.println(n);  
  7.           
  8.     p.removeNode();  
  9. }  


枚举类型

java的enum和C++的enum很不同。C++里的enum就是一个简单的整型值,而java的enum其实是一个类。编译器根据你enum的定义会为你生成一个Enum<E>的子类。Enum<E>是一个抽象类,定义了很多final方法,也就是说你定义的enum类里不能覆盖它们,你唯一能覆盖的是Object.toString类。同时你定义的enum类是不能被继承的。

先看看enum能做什么:

[java]  view plain copy
  1. enum Number {  
  2.     One,  
  3.     Two,  
  4.     Three,  
  5. }  
  6.   
  7. void testCommonMethods() {  
  8.     for (Number n: Number.values()) {  
  9.         System.out.println("ordinal: " + n.ordinal()); // enum's int value, start from 0 by default  
  10.         System.out.println(n.compareTo(Number.Two));  
  11.         System.out.println(n == Number.Three);  
  12.         System.out.println(n.name());  
  13.         System.out.println(n.getDeclaringClass()); // Number  
  14.     }  
  15.     Number n = Enum.valueOf(Number.class"Two");  
  16.     System.out.println(n);  
  17. }  
在使用枚举时,必须用类型名作前缀,比如Number.One,而不能直接用One。如果你需要这种便利的话,可以用 import static  Number.*;来达到目的。

enum也可以用在switch语句里:

[java]  view plain copy
  1. void testSwitch(Number n) {       
  2.     switch (n) {  
  3.     case One:  
  4.         break;  
  5.     case Two:  
  6.         break;  
  7.     case Three:  
  8.         break;  
  9.     default:  
  10.         break;  
  11.     }  
  12. }  
我们可以往enum里添加属性和方法:
[java]  view plain copy
  1. enum Number {  
  2.     One("This is one"), // need a constructor of string parameter  
  3.     Two("This is two"),  
  4.     Three("This is one and two");  
  5.       
  6.     // constructor  
  7.     Number(String discription) {  
  8.         this.discription = discription;  
  9.     }  
  10.       
  11.     private String discription;  
  12.       
  13.     // a normal method  
  14.     void print() {  
  15.         System.out.println(discription);  
  16.     }  
  17. }  
用javap反编译下上面的Number类,看看编译器究竟把它翻译成什么样子:
[java]  view plain copy
  1. // Compiled from "EnumerationTest.java"  
  2. final class tommy.enumeration.test.Number extends java.lang.Enum<tommy.enumeration.test.Number> {  
  3.   public static final tommy.enumeration.test.Number One;  
  4.   public static final tommy.enumeration.test.Number Two;  
  5.   public static final tommy.enumeration.test.Number Three;  
  6.   static {};  
  7.   void print();  
  8.   public static tommy.enumeration.test.Number[] values();  
  9.   public static tommy.enumeration.test.Number valueOf(java.lang.String);  
  10. }  
因为enum是Enum<T>的一个子类,所以它不能继承别的类,但是可以实现其它的接口。我们还可以看到编译器还额外帮我们生成了两个方法 values valueOf


EnumSet是专门用来存储enum的集合:

[java]  view plain copy
  1. void testEnumSet() {  
  2.     EnumSet<Number> es = EnumSet.noneOf(Number.class);  
  3.     es.add(One); // [One]  
  4.     es.addAll(EnumSet.of(Two, Three)); // [One, Two, Three]  
  5.     bool b = es.contains(Two); // true  
  6.     es.remove(Two); // [One, Three]  
  7.     b = es.contains(Two); // false  
  8.     es.clear(); // []  
  9.     es.addAll(EnumSet.allOf(Number.class)); // [One, Two, Three]  
  10.     b = es.containsAll(Arrays.asList(One, Two, Three)); // true  
  11.     es.removeAll(Arrays.asList(One, Two)); // [Three]  
  12.     es = EnumSet.complementOf(es); // [One, Two]  
  13. }  
EnumMap 是以enum为key的map:
[java]  view plain copy
  1. void testEnumMap() {  
  2.     EnumMap<Number, String> em = new EnumMap<Number, String>(Number.class);  
  3.     em.put(One, "first");  
  4.     em.put(Two, "second");  
  5.     em.put(Three, "third");  
  6.     System.out.println(em.get(Two)); // second  
  7. }  

enum里的每个常量都可以覆盖enum类内定义有这个方法:

[java]  view plain copy
  1. enum Season {  
  2.     SPRING {  
  3.         void print() { System.out.println("It's warm"); }   
  4.     },  
  5.     SUMMER {  
  6.         void print() { System.out.println("It's hot"); }  
  7.     },  
  8.     FALL {  
  9.         void print() { System.out.println("It's cool"); }  
  10.     },  
  11.     WINTER {  
  12.         void print() { System.out.println("It's cold"); }  
  13.         void feel() { System.out.println("I don't like winter"); }  
  14.     };  
  15.       
  16.     abstract void print();  
  17.     void feel() { System.out.println("I like this season"); }  
  18.       
  19.     static void test() {  
  20.         for (Season s: Season.values()) {  
  21.             s.print();  
  22.             s.feel();  
  23.         }  
  24.     }  
  25. }  

如果查看生成的class文件的话,会发现四个匿名类:Season$1.class、Season$2.class、Season$3.class和Season$4.class。



注解

java里的另一个语言特性是注解(Annotataion),它专门为代码里的各种定义(比如类、方法、变量等)提供附属信息。java内置三种标准的注解:@Override用于表示一个方法是覆盖父类的方法,如果父类里没有这个方法会导致编译错误;@Deprecated修饰的元素不能被使用,否则编译出错;@SuppressWarnings用于忽略编译警告信息。

我们可以用@interface关键字自定义注解,就和定义一个class一样,可以用自定义的注解修饰元素:

[java]  view plain copy
  1. @interface MyAnnotation {}  
  2.   
  3. @MyAnnotation  
  4. class MyClass {  
  5.     @MyAnnotation void MyMethod() {  
  6.         @MyAnnotation int n = 0;  
  7.     }  
  8.     @MyAnnotation int myAttribute;  
  9. }  
如果你只想用这个注解修饰一个类,而不能修饰方法或其它元素,可以定义它的 @Target
[java]  view plain copy
  1. @Target(ElementType.TYPE)  
  2. @interface MyAnnotation {}  
这样的话,当你用这个注解修饰方法或其它不是类的元素时,会得到编译错误。如果你想让它同时修饰某种元素,可以在传给@Target一个ElementType数组:
[java]  view plain copy
  1. @Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE})  
  2. @interface MyAnnotation {}  
ElementType 的类型有: ANNOTATION_TYPE CONSTRUCTOR FIELD LOCAL_VARIABLE METHOD PACKAGE PARAMETER TYPE


注解里还可以附带注解元素,比如:

[java]  view plain copy
  1. @interface MyAnnotation {  
  2.     int MyIntElement();  
  3. }  
  4.   
  5. @MyAnnotation(MyIntElement=5)  
  6. class MyClass {}  
你可以为注解元素提供 默认值
[java]  view plain copy
  1. @interface MyAnnotation {  
  2.     int MyIntElement() default 5;  
  3. }  
  4.   
  5. @MyAnnotation()  
  6. class MyClass {}  

当使用注解元素时,必须把元素名(MyIntElement)显式的写出来,如果不想这样,可以定义名为 value的元素:
[java]  view plain copy
  1. @interface MyAnnotation {  
  2.     int value();  
  3. }  
  4.   
  5. @MyAnnotation(5)  
  6. class MyClass {}  
可以作为元素的类型可以是基本类型(int、double…)、String、Class、enum、注解(Annotation)和上述类型的数组。我们可以定义多个元素:
[java]  view plain copy
  1. @interface MyAnnotation {  
  2.     int MyIntElement() default 5;  
  3.     String MyStringElement();  
  4.     Class MyClassElement();  
  5.     double[] MyDoubleArrayElement();  
  6.     char[] MyCharArrayElement();  
  7. }  
  8.   
  9. @MyAnnotation(MyIntElement=10, MyStringElement="hello",   
  10.     MyClassElement=Enum.class,MyDoubleArrayElement=232.02,  
  11.     MyCharArrayElement={'A','B','C','D'})  
  12. class MyClass {}  
注意到一个单独的值也是可以传给数组类型的元素的,比如MyDoubleArrayElement。


定义了注解,可是我们到底能用它做什么事呢?首先我们可以用反射机制获得注解的信息:

[java]  view plain copy
  1. @Target({ElementType.TYPE, ElementType.METHOD})  
  2. @Retention(RetentionPolicy.RUNTIME)  
  3. @interface MyAnnotation {  
  4.     int MyIntElement() default 5;  
  5. }  
  6.   
  7. @MyAnnotation(MyIntElement=10)  
  8. class MyClass {  
  9.     @MyAnnotation(MyIntElement=9)   
  10.     public void MyMethod() {}  
  11.   
  12.     static void testRuntimeAnnotation() throws NoSuchMethodException, SecurityException {   
  13.         Class<MyClass> c = MyClass.class;  
  14.         MyAnnotation a = c.getAnnotation(MyAnnotation.class);  
  15.         int n = a.MyIntElement(); // 10  
  16.       
  17.         Method m = c.getMethod("MyMethod");  
  18.         a = m.getAnnotation(MyAnnotation.class);  
  19.         n = a.MyIntElement(); // 9  
  20.     }  
  21. }  
为了能在使用反射时找到注解的信息,我们定义的注解并须使用 @Retention(RetentionPolicy.RUNTIME) 修饰,这样可以保证注解的信息可以一直保持到运行时期。除了 RUNTIME RetentionPolicy 枚举还定义了其它两个常量值: SOURCE 表示注解只存在于源码中,编译成class文件后就不再存在; CLASS 表示注解会存在于class文件里,但在载入到内存后运行时会丢失。

上面看到的@Target和@Retention都是java定义好的元注解,专门用于修饰别的注解。java一共定义了四个元注解,其它两个是@Document表示被修饰的注解会在JavaDoc中,@Inherited表示某类所使用的注解可以被其子类继承(注意,不是某个注解继承别的注解,即@interface SomAnnotation extends MyAnnotation是不允许的)。举个@Inherited的例子:

[java]  view plain copy
  1. @Retention(RetentionPolicy.RUNTIME)  
  2. @Inherited  
  3. @interface MyAnnotation {  
  4.     int MyIntElement() default 5;  
  5. }  
  6.   
  7. @MyAnnotation(MyIntElement=10)  
  8. class MyClass {}  
  9.   
  10. class MySubClass extends MyClass {  
  11.     public void MyMethod() {}  
  12.       
  13.     static void testInheritedAnnotation() throws NoSuchMethodException, SecurityException {   
  14.         Class<MySubClass> c = MySubClass.class;  
  15.         MyAnnotation a = c.getAnnotation(MyAnnotation.class);  
  16.         int n = a.MyIntElement(); // 10, inherited from super class  
  17.         System.out.println(n);  
  18.     }  
  19. }  

SOURCE的注解可以通过实现javax.annotation.processing.Processor接口还处理。看看该接口的定义:

[java]  view plain copy
  1. interface Processor {  
  2.     Iterable<? extends Completion> getCompletions(Element element,  
  3.             AnnotationMirror annotation, ExecutableElement member,  
  4.             String userText);  
  5.     Set<String> getSupportedAnnotationTypes();  
  6.     Set<String> getSupportedOptions();  
  7.     SourceVersion getSupportedSourceVersion();  
  8.     void init(ProcessingEnvironment processingEnv);  
  9.     boolean process(Set<? extends TypeElement> annotations,  
  10.             RoundEnvironment roundEnv);  
  11. }  

书上用的是tools.jar里的com.sun.mirror.apt.AnnotationProcessor,但是它已经被Deprecated了…关于javax里的Processor,参考java文档http://jdk7.ru/api/javax/annotation/processing/Processor.html?is-external=true。(之后如果有机会我会再作研究并更新这一部份,现在先暂时搁置在这里。



并发

java里的Thread可以启动一个Runnable

[java]  view plain copy
  1. class MyRunnable implements Runnable {  
  2.     public void run() {  
  3.         System.out.println("OK");  
  4.     }  
  5. }  
  6.   
  7. void testRunnable() {  
  8.     Thread t = new Thread(new MyRunnable());  
  9.     t.start();  
  10. }  
Thread 本身也实现了 Runnable
[java]  view plain copy
  1. class MyThread extends Thread {  
  2.     public void run() {  
  3.         System.out.println("OK");  
  4.     }  
  5. }  
  6.   
  7. void testThread() {  
  8.     Thread t = new MyThread();  
  9.     t.start();  
  10. }  
你可以设置Thread的 优先级
[java]  view plain copy
  1. Thread t1 = new MyThread();  
  2. t1.setPriority(Thread.MAX_PRIORITY);  
  3. Thread t2 = new MyThread();  
  4. t2.setPriority(Thread.NORM_PRIORITY);  
  5. Thread t3 = new MyThread();  
  6. t3.setPriority(Thread.MIN_PRIORITY);  
也可以把一个线程设为 后台线程(daemon)
[java]  view plain copy
  1. Thread t = new MyThread();  
  2. t.setDaemon(true);  
  3. t.start();  
一个程序会等待所有非后台线程运行结束后才会退出,而后台线程会在所有非后台线程运行结束后立即终止。

调用Thread.join()方法可以等待某线程的结束:

[java]  view plain copy
  1. Thread t = new MyThread();  
  2. t.start();  
  3. t.join();  
  4. System.out.println("Thread quits");  

线程可以通过yield来放弃当前的时间片,通过sleep来睡眠一段时间:

[java]  view plain copy
  1. class MyRunnable implements Runnable {  
  2.     public void run() {  
  3.         try {  
  4.             for (int n = 0; n < 1000000; ++n) {  
  5.                 if (n % 50000 == 0)  
  6.                     Thread.yield();  
  7.                 else if (n % 20000 == 0)  
  8.                     Thread.sleep(500);  
  9.                 else if (n % 1000 == 0)  
  10.                     TimeUnit.MICROSECONDS.sleep(200);  
  11.             }  
  12.         } catch (InterruptedException e) {}  
  13.     }  
  14. }  

使用Executors来使用线程池:

[java]  view plain copy
  1. ExecutorService es = Executors.newCachedThreadPool();  
  2. for (int i = 0; i < 5; ++i)  
  3.     es.execute(new MyRunnable());  
上面的代码等价于创建了5个Thread并调用它们的start。你可以提供工厂类( ThreadFactory )来定制创建的Thread:
[java]  view plain copy
  1. ExecutorService es = Executors.newCachedThreadPool(new ThreadFactory() {  
  2.     @Override  
  3.     public Thread newThread(Runnable r) {  
  4.         Thread t = new Thread();  
  5.         t.setDaemon(true);  
  6.         return t;  
  7.     }  
  8. });  
  9. es.execute(new MyRunnable());  
cachedThreadPool 可以根据需要创建线程,当一个线程结束时并不释放它而是把它回收到池里,以便下次使用。而 fixedThreadPool 一次性创建固定数量的线程,如果某任务需要线程时而池里没有剩余线程时,则该任务要等待直接某个线程运行结束才能获得线程。给出一个例子:
[java]  view plain copy
  1. void testFixedThreadPool() {  
  2.     class InnerRunnable implements Runnable {  
  3.         InnerRunnable(int id) {  
  4.             this.id = id;  
  5.         }  
  6.   
  7.         public void run() {  
  8.             for (int i = 0; i < 10; ++i)  
  9.                 try {  
  10.                     Thread.sleep(500);  
  11.                 } catch (InterruptedException e) {}  
  12.             System.out.println("thread-" + id + " ends");  
  13.         }  
  14.   
  15.         private int id;  
  16.     }  
  17.   
  18.     ExecutorService es = Executors.newFixedThreadPool(5);  
  19.     for (int i = 0; i < 10; ++i)  
  20.         es.execute(new InnerRunnable(i));  
  21. }  
另一种线程池是 singleThreadPool ,它等同于线程数为1的FixedThreadPool。你可以用 Executors.newSingleThreadExecutor() 来创建。


Runnable不能返回值,如果需要返回值,使用Callable,并通过Executors.submit来启动线程:

[java]  view plain copy
  1. class MyThread extends Thread {  
  2.     public void run() {  
  3.         System.out.println("OK");  
  4.     }  
  5. }  
  6.   
  7. void testCallable() throws InterruptedException, ExecutionException {  
  8.     ExecutorService es = Executors.newCachedThreadPool();  
  9.     Future<Integer> f = es.submit(new MyCallable());  
  10.     int i = f.get();  
  11.     System.out.println(i); // 10  
  12. }  


线程里的异常是不能被其它线程捕获的。比如下面的代码是不能工作的:

[java]  view plain copy
  1. void testThreadException() {  
  2.     try {  
  3.         new Thread() {  
  4.             public void run() {  
  5.                 throw new RuntimeException();  
  6.             }  
  7.         }.start();  
  8.     } catch (RuntimeException re) {  
  9.         System.out.println("never be called!!");  
  10.     }  
  11. }  
如果要捕获线程抛出的异常,调用 Thread.setUncaughtExceptionHandler
[java]  view plain copy
  1. void testUncaughtException() {  
  2.     Thread t = new Thread() {  
  3.         public void run() {  
  4.             throw new RuntimeException();  
  5.         }  
  6.     };  
  7.     t.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {  
  8.         public void uncaughtException(Thread t, Throwable e) {  
  9.             System.out.println("Catch the exception!!");  
  10.         }  
  11.     });  
  12.     t.start();  
  13. }  

为了制造出线程间的共享资源的情况,下面给出一个例子:

[java]  view plain copy
  1. class MyClass{  
  2.     void print(int id) {  
  3.         for (int i = 0; i < 5; ++i)  
  4.         {  
  5.             System.out.print(id + " ");  
  6.             Thread.yield();  
  7.         }  
  8.     }  
  9.       
  10.     class MyThread extends Thread {  
  11.         MyThread(int id) { this.id = id; }  
  12.         public void run() {  
  13.             print(id);  
  14.         }  
  15.       
  16.         int id;  
  17.     }  
  18.       
  19.     static void testSynchronized() {  
  20.         MyClass mc = new MyClass();  
  21.         for (int i = 0; i < 5; ++i) {  
  22.             mc.new MyThread(i).start();  
  23.         }  
  24.     }  
  25. }  
上面的5个线程共享同一个MyClass对象,并调用它的testSynchronized方法。该方法会输出类似于以下的字符串:1 3 0 2 4 0 2 4 0 2 4 0 2 4 0 2 4 1 1 1 1 3 3 3 3 ,可见print方法被其它线程中断。为了同步print方法,可以用 synchroized 关键字修饰上面的print方法:
[java]  view plain copy
  1. synchronized void print(int id) {...}  
这样输出的字符串会类似于:0 0 0 0 0 4 4 4 4 4 2 2 2 2 2 3 3 3 3 3 1 1 1 1 1 ,说明print已经成了一个原子操作。其实synchronized等价于给整个方法加了一个锁,我们完全可以用锁来实现:
[java]  view plain copy
  1. class MyClass {  
  2.     Lock l = new ReentrantLock();      
  3.     void print_lock(int id) {  
  4.         l.lock();  
  5.         for (int i = 0; i < 5; ++i)  
  6.         {  
  7.             System.out.print(id + " ");  
  8.             Thread.yield();  
  9.         }  
  10.         l.unlock();  
  11.     }  
  12. }  
我们可以用trylock来防止阻塞:
[java]  view plain copy
  1. void print_trylock(int id) {  
  2.     try {  
  3.         boolean b = l.tryLock(300, TimeUnit.MILLISECONDS);  
  4.         if (b) {  
  5.             for (int i = 0; i < 5; ++i)  
  6.             {  
  7.                 System.out.print(id + " ");  
  8.                 Thread.yield();  
  9.             }  
  10.             l.unlock();  
  11.         }  
  12.     } catch (InterruptedException e) {}  
  13. }  
很明显还是synchroized来得简单些。synchronized也可以同步某一段代码,而不是整个方法:
[java]  view plain copy
  1. void print_synchronized_part(int id) {  
  2.     synchronized(this) {  
  3.         for (int i = 0; i < 5; ++i)  
  4.         {  
  5.             System.out.print(id + " ");  
  6.             Thread.yield();  
  7.         }  
  8.     }  
  9. }  
synchronized可以接受一个Object作为参数,要注意需要同步的参数必须是同一个对象,比如两个方法f() { synchronized(object_one) {} }和g() { synchronized(object_one){} }是可以同步的,但f() { synchronized(object_one) {} } 和 g() {synchronized(object_two) {} }是不能同步的。

现在我们再来模拟一下多个线程同时对同一资源进行修改的情况。下面的程序让两个线程分别对共享的整型数加N次和减N次,预期的正确结果是当两个线程运行结束后,整型值应该为0:

[java]  view plain copy
  1. class MyClass{  
  2.     int resource;  
  3.     void changeValue(boolean increase) {  
  4.         for (int i = 0; i < 600000; ++i)  
  5.             if (increase)  
  6.                 ++resource;  
  7.             else  
  8.                 --resource;  
  9.     }  
  10.     class InnerThread extends Thread {   
  11.         public InnerThread(boolean increase) {this.increase = increase;};  
  12.         private boolean increase;  
  13.         public void run() {  
  14.             changeValue(increase);  
  15.         }  
  16.     }<span style="white-space:pre">  </span>  
  17.     static void testAtomicOperation() throws InterruptedException {  
  18.         MyClass mc = new MyClass();  
  19.         Thread t1 = mc.new InnerThread(true);  
  20.         Thread t2 = mc.new InnerThread(false);  
  21.         t1.start();  
  22.         t2.start();  
  23.         t1.join();  
  24.         t2.join();  
  25. <span style="white-space:pre">      </span>  
  26.         System.out.println(mc.resource); // might be 321296, but it's supposed to be zero  
  27.     }  
  28. }  
java提供了一个关键字 volatile ,它的本意是表示某个变量的修改操作是原子操作,不需要同步的, 但是它是不工作的 。你可以在上面的代码里给resource加入volatile修饰,但得到的结果仍可能不是0。
[java]  view plain copy
  1. volatile int resource = 0// thread unsafe  
可以使用 AtomicInteger 来表示原子操作。修改上例的部份代码:
[java]  view plain copy
  1. AtomicInteger resource = new AtomicInteger(0);  
  2.   
  3. void changeValue(boolean increase) {  
  4. for (int i = 0; i < 600000; ++i)  
  5.     if (increase)  
  6.         resource.addAndGet(1);  
  7.     else  
  8.         resource.addAndGet(-1);  
  9. }  
你还可以在 java.util.concurrent.atomic 包里找到其它的原子操作的类,比如 AtomicBoolean AtomicLong 等。
其实线程冲突的最根本的原因有共享的资源,如果每个线程都有自己的变量,那自然不会有冲突。我们可以用 ThreadLocal 定义这样的变量:
[java]  view plain copy
  1. ThreadLocal<Integer> resource = new ThreadLocal<Integer>() {  
  2.     @Override  
  3.     protected Integer initialValue() {  
  4.         return 0;  
  5.     }  
  6. };  
  7. void changeValue(boolean increase) {  
  8.     for (int i = 0; i < 600000; ++i)  
  9.         resource.set(resource.get() + 1);  
  10.     System.out.println(resource.get()); // each thread should shows 600000  
  11. }  
注意一定要覆盖 ThreadLocal 的initialValue方法,否则你的变量(resource)在新线程里会为null。


线程有四种状态:新建(new)、就绪(runnable)、阻塞(blocked)和死亡(dead)。当线程睡眠、等待、申请锁或响应I/O时,可以从就绪状态进入阻塞状态。睡眠和等待是可以被中断的。当被中断时,会得到一个InterruptException。下面中断一个sleep操作:

[java]  view plain copy
  1. void testInterruptSleep() throws InterruptedException {  
  2.     Thread t = new Thread() {  
  3.         public void run() {  
  4.             long time = System.currentTimeMillis();  
  5.             try {  
  6.                 Thread.sleep(100000);                     
  7.             } catch (InterruptedException e) {  
  8.                 System.out.println("Interrupt!!");  
  9.             } finally {  
  10.                 time -= System.currentTimeMillis();  
  11.                 System.out.println("Slept for " + Math.abs(time) + " milliseconds");  
  12.             }  
  13.         }  
  14.     };  
  15.     t.start();  
  16.     Thread.sleep(2000);  
  17.     t.interrupt();  
  18. }  
等待(wait) 睡眠(sleep) 的区别在于,等待的线程可以被 通知(notify) 信号(signal) 唤醒。同时线程在等待是会放弃它获得的锁,而睡眠不会,所以wait操作必须和同步一起使用。下面用Future对象来中断一个等待阻塞:
[java]  view plain copy
  1. void testInterruptWait() throws InterruptedException {  
  2.     Runnable r = new Runnable() {  
  3.         public void run() {  
  4.             long time = System.currentTimeMillis();  
  5.             try {  
  6.                 synchronized (this) {  
  7.                     wait(8888);  
  8.                 }  
  9.             } catch (InterruptedException e) {  
  10.                 System.out.println("Interrupt!!");  
  11.             } finally {  
  12.                 time -= System.currentTimeMillis();  
  13.                 System.out.println("Slept for " + Math.abs(time) + " milliseconds");  
  14.             }  
  15.         }  
  16.     };  
  17.     ExecutorService es = Executors.newCachedThreadPool();  
  18.     Future<?> f = es.submit(r);  
  19.     Thread.sleep(2000);  
  20.     f.cancel(true); // interrupt it  
  21. }  
我们用它去 中断一个处于就绪状态的线程
[java]  view plain copy
  1. void testInterrupted() throws InterruptedException {  
  2.         Runnable r = new Runnable() {  
  3.             public void run() {  
  4.                 while (!Thread.interrupted()) {  
  5.                     System.out.println("I'm still alive");  
  6.                     Thread.yield();  
  7.                 }  
  8.                 System.out.println("interrupted!!");  
  9.             }  
  10.         };  
  11.         ExecutorService es = Executors.newCachedThreadPool();  
  12.         Future<?> f = es.submit(r);  
  13.         Thread.sleep(2000);  
  14.         es.shutdownNow();  
  15.     }  

线程可以通过判断Thread.interrupted来确定自己是否被中断。ExecutorService.shutdownNow()可以中断线程池里所有的阻塞线程。


下面给个例子看怎么样使用waitnotify

[java]  view plain copy
  1. class NotifyTester {  
  2.     synchronized void turnOn() throws InterruptedException {  
  3.         while (!Thread.interrupted()) {  
  4.             while (on)  
  5.                 wait();  
  6.             on = true;  
  7.             System.out.println("turn on!!");  
  8.             notify();  
  9.         }       
  10.     }  
  11.       
  12.     synchronized void turnOff() throws InterruptedException {  
  13.         while (!Thread.interrupted()) {  
  14.             while (!on)  
  15.                 wait();  
  16.             on = false;  
  17.             System.out.println("turn off!!");  
  18.             notify();  
  19.         }  
  20.     }  
  21.       
  22.     class TurnOnThread extends Thread {  
  23.         public void run() {  
  24.             try {  
  25.                 turnOn();  
  26.             } catch (InterruptedException e) {}  
  27.         }  
  28.     }  
  29.       
  30.     class TurnOffThread extends Thread {  
  31.             public void run() {  
  32.             try {  
  33.                 turnOff();  
  34.             } catch (InterruptedException e) {}  
  35.      }  
  36.     }  
  37.       
  38.     static void test() {  
  39.         NotifyTester nt = new NotifyTester();  
  40.         Thread t_on = nt.new TurnOnThread();  
  41.         Thread t_off = nt.new TurnOffThread();  
  42.           
  43.         t_on.start();  
  44.         t_off.start();  
  45.           
  46.         try {  
  47.             Thread.sleep(9000);  
  48.         } catch (InterruptedException e) {}  
  49.           
  50.         t_on.interrupt();  
  51.         t_off.interrupt();  
  52.     }  
  53.   
  54.     boolean on;  
  55. }  
notify 只是唤醒一个等待的线程,而 notifyAll 会唤醒所有等待的线程。我们还可以用Lock和Condition来完成同样的工作:
[java]  view plain copy
  1. class LockSignalTester {  
  2.     private Lock l = new ReentrantLock();  
  3.     private Condition c = l.newCondition();  
  4.     private boolean on;  
  5.       
  6.     void turnOn() {  
  7.         while (!Thread.interrupted()) {  
  8.             l.lock();  
  9.             try {   
  10.                 while (on)  
  11.                     c.await();  
  12.                 on = true;  
  13.                 System.out.println("turn on!!");  
  14.                 c.signal();  
  15.             } catch (InterruptedException ie) {  
  16.             } finally {  
  17.                 l.unlock();  
  18.             }  
  19.         }       
  20.     }  
  21.       
  22.     void turnOff(){  
  23.         while (!Thread.interrupted()) {  
  24.             l.lock();  
  25.             try {  
  26.                 while (!on)  
  27.                     c.await();  
  28.                 on = false;  
  29.                 System.out.println("turn off!!");  
  30.                 c.signal();  
  31.             } catch (InterruptedException ie) {  
  32.             } finally {  
  33.                 l.unlock();  
  34.             }  
  35.         }  
  36.     }  
  37.       
  38.     class TurnOnThread extends Thread {  
  39.         public void run() { turnOn(); }  
  40.     }  
  41.       
  42.     class TurnOffThread extends Thread {  
  43.         public void run() { turnOff(); }  
  44.     }  
  45.       
  46.     static void test() {  
  47.         NotifyTester nt = new NotifyTester();  
  48.         Thread t_on = nt.new TurnOnThread();  
  49.         Thread t_off = nt.new TurnOffThread();  
  50.           
  51.         t_on.start();  
  52.         t_off.start();  
  53.           
  54.         try {  
  55.             Thread.sleep(9000);  
  56.         } catch (InterruptedException e) {}  
  57.           
  58.         t_on.interrupt();  
  59.         t_off.interrupt();  
  60.     }  
  61. }  
Condition.await Condition.signal/signalAll Object.wait Object.notify/notifyAll 等价。


CountDownLatch可以用来同步多个线程。它会有一个初始数,每次调用countDown的时候数值减一但不阻塞,而调用await的时候会阻塞直到数值被减为0为止:

[java]  view plain copy
  1. class CountDownLatchTester {  
  2.     int counter = 0;  
  3.     CountDownLatch cdl = new CountDownLatch(5);  
  4.       
  5.     class IncreaseThread extends Thread {  
  6.         public void run() {  
  7.             try {  
  8.                 Thread.sleep(new Random().nextInt(10) * 1000);  
  9.             } catch (InterruptedException e1) {}  
  10.             synchronized (this) {  
  11.                 ++counter;  
  12.             }  
  13.             try {  
  14.                 cdl.countDown();  
  15.                 cdl.await();  
  16.             } catch (InterruptedException e) {}  
  17.             System.out.println(counter);  
  18.         }  
  19.     }  
  20.       
  21.     static void test() {  
  22.         CountDownLatchTester cdlt = new CountDownLatchTester();  
  23.         for (int i = 0; i < 5; i++)  
  24.             cdlt.new IncreaseThread().start();  
  25.     }  
  26. }  
上面代码里的5个线程都会睡眠随机的一段时间,之后增加counter的值,再调用 countDown 来降低CountDownLatch的值,最后调用 await 来等待CountDownLatch的值变为0,即所有其它的线程全部结束。程序的输出是5个线程同时打印出数字“5”。

CountDownLatch只做一次同步操作,如果计数器减为0后,所有的await调用都会立即返回。CyclicBarrier可以循环地进行同步,当计数器降为0后,会自动设置为初值以便下一次的同步。CyclicBarrier没有countDown操作,它的await会将计数器减一并阻塞,如果计数器减为0所有阻塞在await操作上的线程都会被唤醒:

[java]  view plain copy
  1. class CyclicBarrierTester {  
  2.     int counter = 0;  
  3.     CyclicBarrier cb = new CyclicBarrier(5);  
  4.       
  5.     class IncreaseThread extends Thread {  
  6.         public void run() {  
  7.             for (int i = 0; i < 3; ++i)  
  8.             {  
  9.                 try {  
  10.                     Thread.sleep(new Random().nextInt(10) * 1000);  
  11.                 } catch (InterruptedException e1) {}  
  12.                     synchronized (this) {  
  13.                     ++counter;  
  14.                 }  
  15.                 try {  
  16.                     cb.await();  
  17.                 } catch (InterruptedException e) {  
  18.                 } catch (BrokenBarrierException e) {  
  19.                 }  
  20.                 System.out.println(counter);  
  21.             }  
  22.         }  
  23.     }  
  24.       
  25.     static void test() {  
  26.         CyclicBarrierTester cbt = new CyclicBarrierTester();  
  27.         for (int i = 0; i < 5; i++)  
  28.             cbt.new IncreaseThread().start();  
  29.     }  
  30. }  
上例我在thread的run方法里添加了一个循环,从运行结果可以看到这些线程确实进行了三次同步。


DelayQueue可以用来存放Delayed对象。Delayed是一个接口,它可以通过getDelay方法来表示有多久到期或者已经过期了多久。DelayQueue是一个优先级队列,优先级更高的Delayed会最先取出,不管它过期的时间如何。在同等优先级的情况下,会取出某个过期的Delayed。如果当前还有元素但没有元素过期,DelayQueue.take就会阻塞。下面给一个例子:

[java]  view plain copy
  1. class DelayQueueTester {  
  2.     DelayQueue<MyDelayed> dq = new DelayQueue<MyDelayed>();  
  3.     long currentTime = 0;  
  4.       
  5.     class MyDelayed implements Delayed {  
  6.         long start = System.currentTimeMillis();  
  7.         int priority = new Random().nextInt(5);  
  8.           
  9.         @Override  
  10.         public int compareTo(Delayed o) {  
  11.             return priority - ((MyDelayed)o).priority;  
  12.         }  
  13.   
  14.         @Override  
  15.         public long getDelay(TimeUnit unit) {  
  16.             return unit.convert(start - currentTime, TimeUnit.MILLISECONDS);   
  17.         }  
  18.     }  
  19.       
  20.     static void test() throws InterruptedException {  
  21.         DelayQueueTester dqt = new DelayQueueTester();  
  22.         for (int i = 0; i < 10; i++)  
  23.         {  
  24.             TimeUnit.MILLISECONDS.sleep(200);  
  25.             dqt.dq.offer(dqt.new MyDelayed());  
  26.         }  
  27.           
  28.         Thread.sleep(1000);  
  29.         dqt.currentTime = System.currentTimeMillis();  
  30.           
  31.         while (dqt.dq.size() > 0) {  
  32.             MyDelayed d = dqt.dq.take();  
  33.             System.out.println(d.priority + "\t" + d.start);  
  34.         }  
  35.     }  
  36. }  
Delay接口的两个方法compareTo和getDelay。前者用于比较优先级,值越小的优先级越高;后者用于返回离到期的时间,正值说明还没到期,负值说明已经过期。从输出上看,优先级高的会先被取出, 但是过期时间更长的并不优先取出 ,它只保证当前可以取出一个过期元素,但不保证取出的顺序。(书上说的可能不对)


PirorityBlockingQueue当队列里没有元素而试图取元素时,会发生阻塞,同时优先级最高(值最小)的元素会最先被取出:

[java]  view plain copy
  1. class PriorityBlockingQueueTester {  
  2.     PriorityBlockingQueue<Integer> pbq = new PriorityBlockingQueue<Integer>();  
  3.       
  4.     class ReadThread extends Thread {   
  5.         public void run() {  
  6.             for (int i = 0; i < 1000; i++)  
  7.                 try {  
  8.                     int n = pbq.take();  
  9.                     System.out.println(n);  
  10.                 } catch (InterruptedException e) {}  
  11.         }  
  12.     }  
  13.       
  14.     class WriteThread extends Thread {  
  15.         public void run() {  
  16.             for (int i = 0; i < 200; i++)  
  17.             {  
  18.                 for (int j = 0; j < 5; j++)  
  19.                     pbq.offer(10 - j);  
  20.                 try {  
  21.                     Thread.sleep(1000);  
  22.                 } catch (InterruptedException e) {}  
  23.             }  
  24.         }  
  25.     }  
  26.       
  27.     static void test() throws InterruptedException {  
  28.         PriorityBlockingQueueTester pbqt = new PriorityBlockingQueueTester();  
  29.         pbqt.new ReadThread().start();  
  30.         pbqt.new WriteThread().start();  
  31.     }  
  32. }  
上面的代码会循环输出“6 7 8 9 10”。 DelayQueue PriorityBlockingQueue 都实现了接口 BlockingQueue ,这种队列都是可以阻塞线程的。其它实现了这个接口的类还有 ArrayBlockingQueue (队列大小固定)、 LinkedBlockingQueue SynchronousBlockingQueue (每个put方法都会阻塞直到一个take被调用,这个队列本身没有容量,一次只能传递一个数据)。在BlockingQueue引入java前,PipedWriter和PipedReader用来在线程间传递数据。


ShceduledThreadPoolExecutor可以让一个线程延后一段时间再启动:

[java]  view plain copy
  1. class ScheduledExecutorTester {  
  2.     static class MyRunnable implements Runnable {  
  3.         public void run() {  
  4.             System.out.println("Start time: " + new Date(System.currentTimeMillis()));  
  5.         }  
  6.     }  
  7.       
  8.     static void test() {  
  9.         ScheduledThreadPoolExecutor stpe = new ScheduledThreadPoolExecutor(10);  
  10.         System.out.println("Sheduled time: " + new Date(System.currentTimeMillis()));  
  11.         stpe.schedule(new MyRunnable(), 2, TimeUnit.SECONDS);  
  12.     }  
  13. }  
上面的schedule方法只执行一次,而 scheduleAtFixedRate 方法会在固定的频率启动一个新线程:
[java]  view plain copy
  1. stpe.schedule(new MyRunnable(), 23, TimeUnit.SECONDS);  
第二个参数是第一次启动的延迟,第三个参数是第一次启动后,每次启动的间隔数。上面一行的意思是2秒后执行Runnable,之后每隔3秒执行新的Runnable。

另一个类似的方法是 scheduledWithFixedRate ,它与scheduleAtFixedRate的区别在于,它在 前一个线程结束后 过一段时间间隔,才会启动新的线程。
[java]  view plain copy
  1. stpe.scheduleWithFixedDelay(new MyRunnable(), 13, TimeUnit.SECONDS);  
上面这行代码的意思是,第一个线程在1秒后启动,之后在线程结束后,再等3秒启动新线程,如此反复。


信号量(Semaphore)允许多个线程同时访问一个资源(锁只允许一个线程)。用Semaphore.aquire来获得资源访问权,如果获得该资源的线程数已经超出限制的话就阻塞,直到有别的线程调用Semaphore.release来释放该资源。

[java]  view plain copy
  1. class SemaphoreTester {  
  2.     static Semaphore s = new Semaphore(5true);  
  3.       
  4.     static class MyThread extends Thread {  
  5.         int id;  
  6.         MyThread(int id) { this.id = id; }  
  7.         public void run() {  
  8.             try {  
  9.                 s.acquire();  
  10.                 System.out.println("start - " + id);  
  11.                 Thread.sleep(3000);  
  12.                 System.out.println("end - " + id);  
  13.                 s.release();  
  14.             } catch (InterruptedException e) {};  
  15.         }  
  16.     }  
  17.       
  18.     static void test() {  
  19.         for (int i = 0; i < 20; ++i)   
  20.             new MyThread(i).start();  
  21.     }  
  22. }  

Extranger可以让线程之间交换数据:

[java]  view plain copy
  1. class ExchangerTester {  
  2.     static Exchanger<Integer> e = new Exchanger<Integer>();  
  3.     static class MyThread extends Thread {  
  4.         int id;  
  5.         int extrangedId;  
  6.         MyThread(int id) { this.id = id; }  
  7.         public void run() {  
  8.             try {  
  9.                 extrangedId = e.exchange(id);  
  10.             } catch (InterruptedException e) {}  
  11.         }  
  12.           
  13.         public void print() {  
  14.             System.out.println("My id: " + id + " - Exchanged id: " + extrangedId);  
  15.         }  
  16.     }  
  17.       
  18.     static void test() {  
  19.         MyThread t1 = new MyThread(1);  
  20.         MyThread t2 = new MyThread(2);  
  21.         t1.start();  
  22.         t2.start();  
  23.           
  24.         try {  
  25.             t1.join();  
  26.             t2.join();  
  27.         } catch (InterruptedException e) {}  
  28.           
  29.         t1.print(); // My id: 1 - Exchanged id: 2  
  30.         t2.print(); // My id: 2 - Exchanged id: 1  
  31.     }  
  32. }  


图形化用户界面

不想花太多时间在UI上,因为GUI一般都大同小异,需要使用时再翻阅文档应该也够了。java里的图形界面由javax.swing.*支持。现在给出一个粗糙的例子:

[java]  view plain copy
  1. public class SwingTest {  
  2.     static void testJFrame() {  
  3.         JFrame frame = new JFrame("Window Title");  
  4.         frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  
  5.         frame.setSize(400300);  
  6.         frame.setLayout(new FlowLayout());  
  7.           
  8.         final JLabel label = new JLabel("label text");  
  9.           
  10.         final JTextArea textArea = new JTextArea();  
  11.         textArea.addKeyListener(new KeyAdapter() {  
  12.             @Override  
  13.             public void keyTyped(KeyEvent e) {  
  14.                 label.setText(label.getText() + e.getKeyChar());  
  15.             }  
  16.         });  
  17.           
  18.         final JButton button = new JButton("button text");  
  19.         button.addActionListener(new ActionListener() {  
  20.             @Override  
  21.             public void actionPerformed(ActionEvent e) {  
  22.                 textArea.append(((JButton)e.getSource()).getText());  
  23.             }  
  24.         });  
  25.         button.addMouseListener(new MouseAdapter() {  
  26.             @Override  
  27.             public void mouseClicked(MouseEvent e) {  
  28.                 JPopupMenu menu = new JPopupMenu();  
  29.                 menu.add(new JMenuItem("Item"));  
  30.                 menu.show(button, 2335);  
  31.             }  
  32.         });  
  33.           
  34.         frame.add(label);  
  35.         frame.add(button);  
  36.         frame.add(textArea);  
  37.         frame.setVisible(true);  
  38.     }  



猜你喜欢

转载自blog.csdn.net/oMengHen1/article/details/24260575
今日推荐