相关下载:
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是不支持操作符重载的。
Java中没有sizeof操作符,因为Java中所有的数据类型在所有机器中的大小都是相同的。
"=="仅比较对象的引用,但比较基本类型时是进行值的比较。看下面代码:
- Integer x = new Integer(7);
- Integer y = new Integer(7);
- boolean b = x == y; // false
- int i = 7;
- int j = 7;
- b = i == j; // true;
如果要自定义比较,就需要覆盖Object类中的equal方法。
控制执行流程
Foreach 语法:
- int a[] = {1, 2, 3, 4, 5};
- for (int x: a)
- System.out.print(x);
- int x = 10;
- int y = 5;
- outer: // this is the label
- while (x-- > 0)
- {
- while (y++ < 10)
- {
- if (x + y == 9)
- break outer;
- }
- }
初始化与清理
所有局部变量都必须显式给一个初始值,使用未初始化的局部变量会得到编译错误。(C++中未初始的局部变量会是一个未定义的值。)
- void f()
- {
- int i;
- i++; // error - i is not initialized
- }
同样你也可以显式地初始化成员变量:
- public class MyClass
- {
- int a = 5;
- bool b = true;
- char c = 'c';
- SomeClass sc = new SomeClass();
- }
- public class MyClass
- {
- int a = f();
- int b = g(a);
- int f() { return 10; }
- int g(int n) { return n * 3; }
- }
- public class MyClass
- {
- int b = g(a); // error - a is not defined yet
- int a = f();
- }
static成员变量的初始方法与非静态成员变量的初始化相同。
- public class MyClass
- {
- int a = 5;
- bool b = true;
- static char c = 'c';
- }
1. 当MyClass对象被创建(new MyClass)或MyClass的静态方法被调用时,Java解释器会定位MyClass.class文件;
2.载入MyClass.class,初始化所有静态成员变量;
3.在new MyClass调用时,在堆上分配足够存储空间;
4.分配出的存储空间全部清零,这也是为什么成员变量在未显式初始化时会有默认的初始值;
5.执行所有成员变量的显式初始化;
6.执行构造器。
多个静态初始化可以组织成“静态子句”
- public class MyClass
- {
- int a = 5;
- bool b = true;
- static char c = 'c';
- static {
- double d = 4.0;
- int e = 7;
- }
- }
实例初始化 和静态子句相似,用来初始化非静态成员,并且可以保证在构造器之前调用,这在有多个构造器的时候是非常方便的:
- public class MyClass
- {
- int a;
- boolean b;
- {
- a = 5;
- b = true;
- System.out.println("look!");
- }
- }
数组 的定义可以是"int a[]"也可以是"int[] a"。你可以用“int a[] = new int[6];”的方式来初始化一个数组。
- Integer[] i = {
- new Integer(1),
- new Integer(2),
- 3 // Autoboxing
- };
- Integer[] i = new Integer{
- new Integer(1),
- new Integer(2),
- 3, // the last comma is also valid!
- };
可变参数列表 是非常有趣的特性:
- void f(Object... args)
- {
- for (Object o: args)
- System.out.println(o);
- }
- // caller
- f(1, 'c', false);
枚举不是一些简单的常数,它是可以用打印的。
- public enum E { A, B, C };
- //caller
- E e = E.C;
- System.out.println(e); // output: C
Java中的对象是不需要手动清理的,垃圾回收器会(在适当的时候)清理所有没被引用的对象。当然,你也可以强制让垃圾回收器进行清理:
- void f()
- {
- MyClass mc = new MyClass();
- mc = null;
- System.gc();
- }
垃圾回收器的机制:
有一种所谓的“引用计数机制”,当对象被引用时计数器加一,当取消引用时计数器减一。当计数器为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:
- public interface Interface {
- int ONE = 1, TWO = 2, Three = 3;
- }
接口里可以定义嵌套接口:
- public interface Outer {
- public interface Inner {
- void f();
- }
- }
- class Implementor implements Outer.Inner {
- pubilc void f() {}
- }
内部类
Java的内部类很有趣。你可以这样定义一个内部类:
- class Outer {
- class Inner {}
- }
Inner是必须与某一个Outer对象绑定的,通过外部创建内部类的方法就可以得知:
- Outer oo = new Outer();
- Outer::Inner ii = oo.new Inner();
所以每一个Inner都有对应的一个外部类对象,而且内部类可以访问外部类对象中的所有属性和方法,包括private权限的。内部类可以用Outer.this来得到外部类对象的引用:
- class Outer {
- class Inner {
- Outer GetOuter()
- {
- return Outer.this;
- }
- }
- }
在方法中或一个作用域“{}”中也可以定义内部类,用一段比较变态的代码来说明问题:
- class Outer {
- int a = 6
- class Inner {
- int b = 3;
- void f()
- {
- class FunInner { // class defined in a funciton!!
- int sum()
- {
- return a + b; // Outer.a + Inner.b
- }
- }
- FunInner fi = new FunInner();
- System.out.print(fi.sum()); // 9!!
- }
- }
- }
还有一种东西叫“匿名内部类”:
- interface I {
- void f();
- }
- class Outer {
- I getI()
- {
- return new I() { // anonymous inner class !!
- public void f() {}
- };
- }
- }
如果不需要和外部类绑定,可以定义一个嵌套类:
- class Outer {
- static class Nested {} // static
- }
嵌套类和C++中的嵌套类基本相同,只能访问外部类的静态数据。它的创建方法为:
- Outer.Nested nested = new Outer.Nested();
接口里面也可以定义嵌套类,但不需要static修饰,因为接口里定义的变量都是默认为public static的:
- interface I {
- void f();
- class MyI implements I {
- void void f() {}
- }
- }
这样做的好处是接口的定义同时还能提供默认的实现。
内部类的作用:
内部类可以继承别的类,同时又可以完全访问外部类的成员,这就等同于多重继承的效果。
Java里没有指针,所以不能像C++一样把函数指针传递给出去作为回调函数(callback)。通过内部类可以实现:
- interface Callback { void call(); }
- class MyClass {
- void f() {} // want to be a callback
- Callback getCallback()
- {
- return new Callback() {
- void call() { f(); }
- };
- }
- }
- class Caller {
- void operation(Callback callback)
- {
- callback.call();
- }
- static void main(String[] args)
- {
- Caller caller = new Caller();
- MyClass mc = new MyClass();
- caller.operation(mc.getCallback());
- }
- }
内部类也可以被继承。因为外部类和内部类有着千丝万缕的联系,所以继承的类同样需要某种机制建立起这种联系:
- class Outer { class Inner{} }
- public class InheritInner extends Outer.Inner {
- // InheritInner() {} // compile error!
- InheritInner(Outer oo) {
- oo.super(); // It's a must to connect to the outer class
- }
- static void main(String[] args)
- {
- Outer oo = new Outer();
- InheritInner ii = new InheritInner(oo);
- }
- }
内部类同样会生成.class文件,它的文件名会是这样的形式:Outer$Inner.class。如果是匿名内部类,在$后会简单加入一个数字作为标识符。
持有对象
这里仅概述Java中的容器。Java也是支持范型的,比如“ArrayList<Integer> a = new ArrayList<Integer>”。不带范型参数的容器默认为Object对象的集合。
容器包括两大类:Collection和Map,它们包括:
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的类:
- class MyCollection implements Iterable<Integer> {
- public Iterator<Integer> iterator()
- {
- return new Iterator<Integer>() {
- int i = 0;
- public boolean hasNext() { return i < 10; }
- public Integer next() { return i++; }
- public void remove() { throw new UnsupportedOperationException(); }
- };
- }
- public static void main(String[] args)
- {
- MyCollection mc = new MyCollection();
- for (int i: mc)
- System.out.print(i);
- }
- }
通过异常处理错误
Java中可以通过throw抛出所有继承Throwable的类。任何抛出的异常(RuntimeException除外)被编译器强制需要catch,如果不catch会出现编译错误。
- void makeExcetion()
- {
- // throw new MyException(); // error: this exception must be handled.
- }
- void makeException() throws MyException
- {
- throw new MyException();
- }
Throwable分为两种类型:Error和Exception。Error用来表示编译时和系统错误,所以我们一般不用关心。Exception类有一个方法printStackTrace可以打印出异常抛出点的调用栈,另一个函数fillStackTrace可以在catch后更新调用栈信息再抛出。
RuntimeException是Exeption的子类,用来表示运行时发现的编程错误,比如访问空指针(NullPointerException)和数组越界(ArrayIndexOfBoundsException)。这种类型的异常是唯一不被编译器强制需要捕获的。如果一个RuntimeException抛出后没有被catch,程序会退出并调用printStackTrace方法。
使用finally块可以保证不管是否有异常发生,该块的代码都会被运行:
- try
- {
- // dosomething
- }
- catch (Exception e)
- {
- }
- finally
- {
- // always be executed. Usually dispose.
- }
有时候你可能需要把一些异常串成一个异常链。Exception.initCause可以支持这个需求:
- class LevelOneException extends Exception {}
- class LevelTwoException extends Exception {}
- class ExceptionMaker {
- void makeLevelOne() throws LevelOneException
- {
- throw new LevelOneException();
- }
- void makeLevelTwo() throws LevelTwoException
- {
- try {
- makeLevelOne();
- }
- catch (LevelOneException loe)
- {
- LevelTwoException lte = new LevelTwoException();
- lte.initCause(loe); // combine the exceptions
- throw lte;
- }
- }
- void Test()
- {
- try {
- makeLevelTwo();
- }
- catch (LevelTwoException lte)
- {
- Throwable t = lte.getCause(); // get the previous exception in the link
- }
- }
- }
在C++里,当一个异常没有被Catch的情况下,再抛出一个新的异常,比如析构函数里的异常,这个程序基本就over了。但在Java中,这种情况会导致旧的异常丢失,新的异常被传递出去:
- void loseException() {
- try {
- try {
- throw new LevelOneException();
- } finally {
- throw new LevelTwoException();
- }
- } catch (LevelTwoException lte) {
- lte.printStackTrace();
- }
- }
这应该算是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相似:
- void format()
- {
- PrintStream o = System.out;
- Formatter f = new Formatter(o);
- f.format("%d + %d = %d", 1, 1, 2);
- }
String支持正则表达式。String的matches、split、replaceFirst和replaceAll等方法都和正则表达式有很好的交互。java.lang.regex包中的Pattern类更好地支持正则表达式。这里不多做研究,或许将来单独开一篇博客来探讨正则表达式。
类型信息
Java中的每一个类都对应着一个Class对象(java.lang.Class)。通过这个Class对象你可以在运行时得到很多类中的有用的信息。用Class.forName来得到一个Class对象。
- try {
- Class c = Class.forName("MyClass");
- String name = c.getName(); // "MyPackge.MyClass"
- name = c.getSimpleName(); // "MyClass"
- name = c.getCanonicalName(); // "MyPackge.MyClass"
- Class superClass = c.getSuper();
- if (superClass != null) // Object's super class is null
- superClass.newInstance();
- boolean b = c.isInterface();
- for(java.lang.Class face: c.getInterfaces())
- name = face.getName();
- } catch (ClassNotFoundException) {}
除了Class.forName,还可以直接用MyClass.class来得到一个Class对象。这种方式不会抛出异常。所有的类,包括基本类型,都可以使用“.class”。比如int.class。基本类型的包装类有TYPE成员,比如Integer.TYPE与int.class是一样的。
不像C++在程序启动时就把所有的静态数据与执行代码载入到内存中,java根据需要在运行时把字节码载入到内存,它分三个步骤:1、加载:类加载器查找到字节码(.class文件)并根据这些字节码创建一个Class对象;2、链接:验证类中的字节码,为静态域分配存储空间,需要的话同时解析这个类其它类的所有引用;3、初始化:当类的静态方法(构造器是特殊的静态方法)或者非常数静态域(即不是编译器常量)被首次引用时,执行静态初始化块和初始化静态数据。
Class类是支持范型的:
- Class<Integer> c = int.class;
- Class<Interger> c = int.class;
- // c = double.class; // error
- Class c = int.class;
- Class<Integer> cc = int.class;
- Object o = c.newInstance();
- Integer i = cc.newInstance(); // not a simple Object
- Class<?> c = int.class;
- c = double.class; // compiling passes
Class<?>其实与普通的Class类是一样的,只不过显式声明你确实需要一个接受任何类型的Class对象,而不是忘了加范型参数。
如果你想让一个Class范型类既能接受int.class,又能接受double.class,但是不想接受其它非数值的类型,可以这样:
- Class<? extends Number> c = int.class;
- c = double.class;
- Number n = c.newInstance(); // not a simple Number
- class Base {}
- class Derived extends Base {}
- // Class<Base> c = Derived.class.getSuperclass(); // won't compile
- Class<? super Derived> c = Derived.class.getSuperclass();
- Object o = c.newInstance(); // a simple Object
看到上例的Class<Base>不能接受子类的getSuperClass的返回值,还是挺奇怪的,毕竟Base是Derived的基类是在编译时就确定的。不过既然编译器规定Class<? super Derived>到Class<Base>的转换,那也只能遵从了。
Class范型提供一个cast方法:- Base b = new Derived;
- Class<Derived> c = Derived.class;
- Derived d = c.cast(b);
- Class<? extends Base> c = Derived.class.asSubclass(Base.class);
- Derived d = (Derived)c.newInstance();
- Base o = new Derived();
- boolean b = o instanceof Derived; // true
- b = Derived.class.isInstance(o); // true
- import java.lang.reflect.*;
- class SomeClass {
- public SomeClass() {}
- public int SomeMethod(double d, char c) { return 2; }
- public int a;
- }
- public class ReflectTest {
- public static void main(String[] args) {
- Class c = Class.forName("SomeClass");
- for (Constructor<?> constructor: c.getConstructors())
- System.out.println(constructor);
- for (Method method: c.getMethods())
- System.out.println(method);
- for (Field field: c.getFields())
- System.out.println(field);
- SomeClass sc = new SomeClass();
- Method method = c.getMethod("SomeMethod", double.class, char.class);
- Integer returnedValue = (Integer)method.invoke(sc, 3, '4');
- Field field = c.getField("a");
- int value = field.getInt(sc);
- System.out.println(value);
- }
- }
设计模式里有个"代理模式"(点击链接进入博客:设计模式 —— 《Head First》)。代理模式里会定义一个接口,真正工作的类和代理类都会实现这个接口,但用户只会看到代理类,而不知道真正工作的类。这个模式的好处就是可以隐藏实现细节,经常改动的地方对用户是不可见的。Java里提供了一个自动生成代理类的机制,主要使用java.lang.reflect包里的Proxy类和InvocationHandler接口:
- import java.lang.reflect.*;
- interface MyInterface {
- void doSomething();
- }
- class RealWorker implements MyInterface {
- public void doSomething() { System.out.println("RealWorker"); }
- }
- class MyHandler implements InvocationHandler {
- public Object invoke(Object proxy, Method method, Object[] args)
- throws Throwable {
- return method.invoke(worker, args);
- }
- private MyInterface worker = new RealWorker();
- }
- public class ProxyTest {
- public static void main(String[] args) {
- MyInterface myProxy = (MyInterface)Proxy.newProxyInstance(
- MyInterface.class.getClassLoader(),
- new Class[] {MyInterface.class}, new MyHandler());
- // call proxy's methods
- myProxy.doSomething();
- }
- }
通过反射机制可以做一些很违反规定的事情。 你可以使用一个某个包里不对外开放的类,以及它的私有方法 :
- ///////////// HiddenClass.java ///////////////
- package hidden;
- class HiddenClass {
- private void invisible() { System.out.println("invisible"); }
- }
- ///////// HiddenClassTest.java ///////////////////
- import java.lang.reflect.*;
- import hidden.*;
- public class HiddenClassTest {
- public static void main(String[] args) {
- Class c = Class.forName("hidden.HiddenClass");
- // Object obj = c.newInstance(); // IllegalAccessExcetion
- Constructor constructor = c.getDeclaredConstructor();
- constructor.setAccessible(true);
- Object obj = constructor.newInstance(); // new HiddenClass()
- Method method = c.getDeclaredMethod("invisible");
- method.setAccessible(true);
- method.invoke(obj); // call HiddenClass.invisible()
- }
- }
范型
java里也有范型的概念,可以自定义范型类、范型接口、范型内部类和范型方法等:
- class MyGeneric<T> { public T value; }
- class MyOtherGeneric<A, B> { void f(A a, B b) {} }
- interface MyGenericInterface<T> { T getT(); }
- class NormalOuter {
- class GenericInner<T> {}
- public <T> void GenericMethod(T value) {}
- }
java中的范型使用了一种“擦除”的机制,即范型只在编译期存在,编译器去除了(不是替代)范型参数,而java虚拟机根本不知道范型的存在。带有不同的范型参数的范型类在运行时都是同一个类:
- MyGeneric<Integer> mg1 = new MyGeneric<Integer>();
- MyGeneric<Double> mg2 = new MyGeneric<Double>();
- boolean b = mg1.getClass() == mg2.getClass(); // true;
- class MyGeneric<T> { public T value; }
- // class MyGeneric<A, B> { public T value; } // The type MyGeneric is already defined
- MyOtherGeneric<Integer, Double> mog = new MyOtherGeneric<Integer, Double>();
- TypeVariable<?>[] types = mog.getClass().getTypeParameters();
- System.out.println(Arrays.toString(types)); // [A, B]
java的范型在定义时不是假定范型参数支持哪些方法,尽管C++是可以做到的。看下面的代码:
- class MyGeneric<T> {
- // public void GuessT(T t) { t.f(); } // error: The method f() is undefined for the type T
- }
- class SupportT { void f() {} }
- class MyGeneric<T extends SupportT> {
- public void GuessT(T t) { t.f(); }
- }
java中之所以用擦除的方式来实现范型,而不是像C++一样根据不同的范型参数具现化不同的类,是由于java语言在发布初期并不支持范型,在发布很长时间后才开始引入范型,为了很原来的类库兼容才使用了擦除。擦除会把参数T换成Object。
因为擦除,在范型类里是不能创建范型参数类型的对象,因为编译器不能保证T拥有默认构造器:
- class ClassMaker<T> {
- T create() {
- // return new T(); // error: Cannot instantiate the type T
- }
- }
- class ClassMaker<T> {
- ClassMaker(Class<T> c) { type = c; }
- T create() {
- T t = null;
- try { t = type.newInstance(); }
- catch (InstantiationException e) { e.printStackTrace(); }
- catch (IllegalAccessException e) { e.printStackTrace(); }
- return t;
- }
- private Class<T> type;
- }
- class ClassMaker<T> {
- ClassMaker(Class<T> c) { type = c; }
- T[] createArray(int size) {
- // return new T[size]; // error: Cannot create a generic array of T
- // return (T[]) new Object[size]; // success!
- return (T[])Array.newInstance(type, size); // success!
- }
- private Class<T> type;
- }
范型的边界(extends)可以加入多个限定,但只能是一个类和多个接口:
- interface face1 {}
- interface face2 {}
- class class1 {}
- class Bound<T extends class1 & face1 & face2> {}
- interface face3 {}
- class BoundDerived<T extends class1 & face1 & face2 & face3> extends Bound<T> {}
试试把一个子类的数组赋给父类数组的引用:
- class Base1 {}
- class Derived1 extends Base1 {}
- Base1[] base = new Derived1[3];
- base[0] = new Base1(); // java.lang.ArrayStoreException
如果你希望在编译期时就发现这样的错误,而不是等到异常发生时才发现,可以使用范型:
- // ArrayList<Base1> alb = new ArrayList<Derived1>(); // Type mismatch: cannot convert from ArrayList<Derived1> to ArrayList<Base1>
有时候你可能就是希望一个合法的向上转型,这时可以使用通配符:
- ArrayList<? extends Base1> aleb = new ArrayList<Derived1>(); // success!
- // 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)
- // aleb.add(new Derived1()); error
- // aleb.add(new Object()); // error
- aleb.add(null); // success
- Base1 b = aleb.get(0); // success
与通配符相对的是 超类型通配符 ,即 <? super T>
- ArrayList<? super Derived1> alsb = new ArrayList<Base1>();
- alsb.add(new Derived1()); //success
- // 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)
- Object d = alsb.get(0); // return an Object
无界通配符 ,即 <?> ,与 原生类型 (非范型类)大体相似,但仍有少许不同:
- ArrayList<?> al_any = new ArrayList<Base1>();
- // 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)
- Object obj = al_any.get(0); // return an Object
- ArrayList al_raw = new ArrayList<Base1>();
- 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
- Object obj2 = al_raw.get(0);
有一种看起来很诡异的范型参数定义,即自限定的类型。它是这样的形式:
- class SelfBounded<T extends SelfBounded<T>> {
- void f(T t) {}
- }
- class Derived2 extends SelfBounded<Derived2> {}
- class OtherType {}
- 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>
自限定类型一般用于参数协变。Java是支持返回值协变的:
- class BaseFace { Base1 getObject() { System.out.println("BaseFace"); return new Base1(); } }
- class DerivedFace extends BaseFace { Derived1 getObject() { System.out.println("DerivedFace"); return new Derive1(); } }
- void test(DerivedFace df) {
- Derived1 d1 = df.getObject(); // output: DerivedFace
- BaseFace bf = df;
- Base1 b1 = bf.getObject(); // output: DerivedFace
- }
尽管支持返回值协变,Java并不支持参数协变。改写父类和子类的定义:
- class BaseFace { setObject(Base1 b) { System.out.println("BaseFace"); } }
- class DerivedFace extends BaseFace { void setObject(Derived1 d) { System.out.println("DerivedFace"); } }
- void test(DerivedFace df) {
- Derived1 d1 = new Derived1();
- df.setObject(d1); // output: DerivedFace
- df.setObject((Base1)d1); // output: BaseFace set
- BaseFace bf = df;
- bf.setObject(d1); // output: BaseFace
- }
- class BaseFace2<T extends BaseFace2<T>> { void setObject(T t) {} }
- class DerivedFace2 extends BaseFace2<DerivedFace2> {}
- void test(DerivedFace2 df, BaseFace2 bf) {
- df.setObject(df); // Output: DerivedFace
- // df.setObject(bf); // error: The method setObject(DerivedFace2) in the type BaseFace2<DerivedFace2> is not applicable for the arguments (BaseFace2)
- bf.setObject(bf); // Output: BaseFace
- bf.setObject(df); // Output: BaseFace<span style="white-space:pre"> </span>
- }
Java容器的插入方法并不检查类型,所以很可能会插入错误的类型:
- List l_raw = new ArrayList<Integer>();
- l_raw.add(new Double(3)); // success
- List<Integer> l = Collections.checkedList(new ArrayList<Integer>(), Integer.class);
- List l_raw = l;
- l_raw.add(new Double(3)); // throws exception!
数组
Java中的数组是在堆中分配的连续内存,它可以用来存放基本类型的值或对象类型的引用。除了操作符“[]”外,数组同样还有一个length属性,这和C++里的数组不同。
我们还可以使用多维数组:
- int a[][][] = new int[2][3][2];
- a[1][2][1] = 5;
- int a[][][] = new int[2][][];
- a[0] = new int[3][];
- a[1] = new int[5][];
- a[0][1] = new int[6];
- a[1][2] = new int[8];
- 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]]
- int a[][][] = new int[][][] {
- { {1,2,3,4}, {5,6} },
- { {7}, {8}, {9,10,11}, {12} },
- { {13,14}, {15,16}, {17,18} }
- };
Java提供了一些对数组的操作,比如fill、equal、copy等。下面演示下:
Arrays.fill:
- void fillArray() {
- int a[] = new int[5];
- Arrays.fill(a, 3);
- System.out.println(Arrays.toString(a)); // [3, 3, 3, 3, 3]
- int b[] = new int[8];
- Arrays.fill(b, 2, 6, 3);
- System.out.println(Arrays.toString(b)); // [0, 0, 3, 3, 3, 3, 0, 0]
- }
- void copyArray() {
- int a[] = {1, 2, 3, 4, 5};
- int b[] = new int[a.length+1];
- System.arraycopy(a, 0, b, 1, a.length);
- System.out.println(Arrays.toString(b)); // [0, 1, 2, 3, 4, 5]
- }
- void equalsArray() {
- int a[] = {1, 2, 3, 4, 5};
- int b[] = {1, 2, 3, 4, 5};
- Arrays.equals(a, b); // true
- }
- class MyElement implements Comparable<MyElement> {
- public int compareTo(MyElement other) {
- return this.value - other.value;
- }
- static MyElement createNext() {
- MyElement me = new MyElement();
- me.value = r.nextInt(20);
- return me;
- }
- public String toString() {
- return String.valueOf(value);
- }
- private int value;
- private static Random r = new Random();
- }
- static void sortArray() {
- MyElement meArray[] = new MyElement[8];
- for (int i = 0; i < meArray.length; ++i) {
- meArray[i] = MyElement.createNext();
- }
- System.out.println(Arrays.toString(meArray)); // [8, 18, 4, 2, 16, 0, 13, 3]
- Arrays.sort(meArray);
- System.out.println(Arrays.toString(meArray)); // [0, 2, 3, 4, 8, 13, 16, 18]
- }
- class MyElementComparator implements Comparator<MyElement> {
- @Override
- public int compare(MyElement left, MyElement right) {
- return right.compareTo(left); // reverse
- }
- }
- void reverseSortArray() {
- MyElement meArray[] = new MyElement[8];
- for (int i = 0; i < meArray.length; ++i) {
- meArray[i] = MyElement.createNext();
- }
- System.out.println(Arrays.toString(meArray)); // [17, 14, 12, 9, 4, 7, 19, 11]
- Arrays.sort(meArray, new MyElementComparator()); // add a comparator
- System.out.println(Arrays.toString(meArray)); // [19, 17, 14, 12, 11, 9, 7, 4]
- }
- void binarySearchArray() {
- int a[] = { 3, 5, 2, 4, 9, 7, 1 };
- Arrays.sort(a);
- int index = Arrays.binarySearch(a, 7); // a[5]=7
- }
编译器不允许你创建一个范型数组,但是允许你使用范型数组的引用:
- // List<Integer> liArray[] = new List<Integer>[8]; // Cannot create a generic array of List<Integer>
- List<Integer> liArray[] = new List[8]; // success!
- // liArray[0] = new ArrayList<Double>(3); // Type mismatch: cannot convert from ArrayList<Double> to List<Integer>
容器深入研究
Java容器分为两大类Collection和Map,它们分别对应着两个接口。我们先来研究Collection这一大类。
Collection又分为三类List、Set和Queue,它们三个同样对应着三个接口。看一下Collection接口的定义:
- interface Collection<T> {
- boolean add(T e);
- boolean addAll(Collection<? extends T> c);
- void clear();
- boolean contains(Object o);
- boolean containsAll(Collection<?> c);
- boolean isEmpty();
- Iterator<T> iterator();
- boolean remove(Object o);
- boolean removeAll(Collection<?> c);
- boolean retainAll(Collection<?> c);
- int size();
- Object[] toArray();
- <T> T[] toArray(T[] a);
- }
再看看List接口的定义:
- interface List<T> extends Collection<T> {
- void add(int index, T element);
- boolean addAll(int index, Collection<? extends T> c);
- T get(int index);
- int indexOf(Object o);
- int lastIndexOf(Object o);
- ListIterator<T> listIterator();
- ListIterator<T> listIterator(int index);
- T remove(int index);
- T set(int index, T element);
- List<T> subList(int fromIndex, int toIndex);
- }
接下来是Set接口的定义:
- interface Set<T> extends Collection<T> {
- }
还有Queue接口的定义:
- interface Queue<T> extends Collection<T> {
- T element();
- boolean offer(T e);
- T peek();
- T poll();
- T remove();
- }
虽然这些接口定义了各种方法,但实现它们的类并不必支持这些方法。实现接口的类在方法的实现中抛出UnSupportedOperationException来表示它不支持该方法。比如一个ArrayList的长度是可以变化的,但是如果由Arrays.asList()得到的话,那它就不再支持改变长度的方法了:
- List<Integer> arrayAsList = Arrays.asList(1, 3, 5);
- List<Integer> modifiableList = new ArrayList<Integer>(arrayAsList);
- modifiableList.clear(); // success!
- arrayAsList.set(2, 3); // success! Able to change an element
- arrayAsList.clear(); // UnsupportedOperationException: cannot change the size
- List<Integer> unmodifiableList = Collections.unmodifiableList(modifiableList);
- unmodifiableList.set(2, 3); // UnsupportedOperationException: cannot modify the list
Collections.unmodifiableList返回的其实是一个内部类Collections$UnmodifiableRandomAccessList的一个实例。
相应的,Collections.synchronizedCollection、Collections.synchronizedList、Collections.synchronizedMap、Collections.synchronizedSet、Collections.synchronizedSortedMap和Collections.synchronizedSortedSet会返回支持线程同步的容器。它们也都是Collections的内部类。
每种类型的接口都有一个Abstract类的实现,比如Collection接口被AbstractCollection实现。AbstractCollection实现了Collection里绝大多数的方法,除了iterator()方法和size()方法。那些被实现的方法仅仅是抛出一个UnSupportedOperationException。Abstract容器类存在的价值在于,当你需要写一个容器类,可以继承自这个Abstract容器类并覆盖你仅需要支持的方法,其它的不需要支持的方法可以继续抛出异常。这样你就不必耗费大量精力去实现容器接口的所有方法。相应的,你还可以发现AbstractList、AbstractSet、AbstractMap等Abstract容器类。Java类库里所有的具体容器类都继承自某个Abstract容器类。
现在来看看Iterator类。一个Collection都会返回一个Iterator用于遍历这个容器。看看Iterator的定义:
- interface Iterator<T> {
- boolean hasNext();
- T next();
- void remove();
- }
一旦容器被修改,之前获得的Iterator就会失效。如果这时还试图访问这个Iterator就会得到异常:
- Collection<Integer> c = new ArrayList<Integer>();
- c.add(1);
- Iterator<Integer> itor = c.iterator();
- c.add(3); // modify the collection
- if (itor.hasNext())
- itor.next(); // java.util.ConcurrentModificationException
- interface ListIterator<T> {
- void add(T e);
- boolean hasPrevious();
- int nextIndex();
- T previous();
- int previousIndex();
- void set(T e);
- }
实现List接口的类有ArrayList和LinkedList。一个用数组实现,另一个用双向链表实现。除了这两个类外,还有早期开发的不再推荐使用的Vector和Stack类。
实现Set接口的类有HashSet、TreeSet和LinkedHashSet。
HashSet用散列的方式存储集合元素。它支持快速查找。放入HashSet的元素必须支持hashCode和equals方法。
LinkedHashSet可以达到HashSet一样的查找速度,同时内部维护了一个链表。当使用跌代器遍历LinkedHashSet时,遍历的顺序和元素插入的顺序一样。放入LinkedHashSet的元素同样需要支持hashCode。
TreeSet使用红黑树实现集合,它对内部元素排序。在遍历元素时按照由小到大的顺序。放入TreeSet的元素必须实现Comparable接口。TreeSet实现了SortedSet接口:
- interface SortedSet<T> {
- Comparator<? super T> comparator();
- T first();
- SortedSet<T> headSet(T toElement);
- T last();
- SortedSet<T> subSet(T fromElement, T toElement);
- SortedSet<T> tailSet(T fromElement);
- }
实现Queue接口的类有LinkedList和PriorityQueue。
其实LinkedList同时实现了List接口和Deque接口。Deque是双向队列:
- interface Deque<T> extends Queue<T> {
- void addFirst(T e);
- void addLast(T e);
- Iterator<T> descendingIterator();
- T getFirst();
- T getLast();
- boolean offerFirst(T e);
- boolean offerLast(T e);
- T peekFirst();
- T peekLast();
- T pollFirst();
- T pollLast();
- T pop();
- void push(T e);
- T removeFirst();
- boolean removeFirstOccurrence(Object o);
- T removeLast();
- boolean removeLastOccurrence(Object o);
- }
- PriorityQueue<Integer> pq = new PriorityQueue<Integer>();
- pq.offer(5);
- pq.offer(13);
- pq.offer(9);
- pq.offer(12);
- pq.offer(6);
- while (!pq.isEmpty()) {
- System.out.println(pq.poll()); // 5, 6, 9, 12, 13
- }
接下来看看Map接口的定义:
- interface Map<K, V> {
- void clear();
- boolean containsKey(Object key);
- boolean containsValue(Object value);
- Set<java.util.Map.Entry<K, V>> entrySet();
- V get(Object key);
- boolean isEmpty();
- Set<K> keySet();
- V put(K key, V value);
- void putAll(Map<? extends K, ? extends V> m);
- V remove(Object key);
- int size();
- Collection<V> values();
- }
- Map.Entry<K, V> {
- K getKey();
- V getValue();
- V setValue(V value);
- }
实现Map接口的类有 HashMap 、 LinkedHashMap 、 TreeMap 、 WeakHashMap 、 ConcurrentHashMap 和 IdentityHashMap 等。
其实可以把Map看成特殊的Collection。Collection里的元素是一个value而Map里的元素是一个键值对(Entry)。从这种角度看 HashMap 、 LinkedHashMap 和 TreeMap 与HashSet、LinkedHashSet和TreeSet其实大同小异。其中TreeMap同样也实现了一个 SortedMap 的接口,它和SortedSet很相似:
- interface SortedMap<K,V> extends Map<K,V> {
- Comparator<? super K> comparator();
- K firstKey();
- SortedMap<K, V> headMap(K toKey);
- K lastKey();
- SortedMap<K, V> subMap(K fromKey, K toKey);
- SortedMap<K, V> tailMap(K fromKey);
- }
ConcurrentHashMap 是一种线程安全的Map,它不涉及同步加锁。
IdentityHashMap 在比较键值时是调用“==”操作符,而不是对象的equals方法。
说到WeakHashMap里的弱引用,我们来研究下java里的引用对象(reference object)。在java.lang.ref包里有一个Reference对象。它有三个子类SoftReference,WeakReference和PhantomReference。
通常情况一 下,我们用new创建一个对象并把它赋给一个引用,这个引用是一个强引用(Strong Reference)。比如“Object o = new Integer(3);”里的“o”就是一个强引用。垃圾回收器是绝对不会释放一个强引用所引用的对象的。
软引用(soft reference)比强引用的程度稍弱些。一般情况下,垃圾回收器是不会清理软引用所引用的对象的,看这个例子:
- class MyClass {
- protected void finalize() {
- System.out.println("MyClass.finalize");
- }
- }
- // caller
- MyClass mc = new MyClass();
- SoftReference sr = new SoftReference(mc, rq);
- mc = null; // free the strong reference!!
- System.gc(); // output: nothing.
- // caller
- MyClass mc = new MyClass();
- SoftReference sr = new SoftReference(mc, rq);
- mc = null;
- // now try to use out the memory
- ArrayList al = new ArrayList();
- while (true)
- al.add(new int[204800]);
- // output: MyClass.finalize
- // output: java.lang.OutOfMemoryError
弱引用(Weak reference)要比软引用的程度还弱些。如果某对象没有被强引用或软引用,而只是被弱引用,垃圾回收器会清理掉这个对象:
- MyClass mc = new MyClass();
- WeakReference wr = new WeakReference(mc);
- mc = null; // free the strong reference!!
- System.gc(); // output: MyClass.finalize
虚引用(Phantom reference)不干涉对象的生命周期,它只保证当它所引用的对象被清理时,虚引用本身被放进一个ReferenceQueue里。
- ReferenceQueue rq = new ReferenceQueue();
- MyClass mc = new MyClass();
- PhantomReference pr = new PhantomReference(mc, rq);
- mc = null;
- System.gc();
- Reference r = null;
- try {
- r = rq.remove();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- if (r != null)
- System.out.println("You die!!!");
最后介绍一个很节省存储空间的容器BitSet。它的每个元素是一个位,你可以设置它的各个位,并且可以进行与或操作等。BitSet的最小size是64位。下面给出一个例子:
- void print(BitSet bs) {
- for (int i = 0; i < bs.size(); ++i)
- System.out.print(bs.get(i) ? '1' : '0');
- System.out.println();
- }
- // caller
- BitSet bs1 = new BitSet();
- bs1.set(2, 15);
- print(bs1); // 0011111111111110000000000000000000000000000000000000000000000000
- bs1.set(4, false);
- print(bs1); // 0011011111111110000000000000000000000000000000000000000000000000
- BitSet bs2 = new BitSet();
- bs2.set(8, 27);
- print(bs2); // 0000000011111111111111111110000000000000000000000000000000000000
- bs2.and(bs1);
- print(bs2); // 0000000011111110000000000000000000000000000000000000000000000000
Java I/O系统
先看一下java.io.File类。它其实应该命名为FileInfo会好一些。看看它能做什么:
它能用来创建一个文件:
- File file = new File("MyFile");
- if (!file.exists()) {
- try {
- file.createNewFile();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- boolean canRead = file.canRead();
- long lastModified = file.lastModified();
- String absolutePath = file.getAbsolutePath();
- long freeSpace = file.getFreeSpace();
- file.setExecutable(false);
- file.setWritable(true);
- file.setLastModified(System.currentTimeMillis());
- File otherFile = new File("MyOtherFile");
- file.renameTo(otherFile);
- try {
- Thread.sleep(5000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- boolean exists = file.exists(); // false;
- File folder = new File("MyFolder");
- if (!folder.exists())
- folder.mkdir();
- String fileNamesInFolder[] = folder.list();
- File filesInFolder[] = folder.listFiles();
- String interestedfileNames[] = folder.list(new FilenameFilter() {
- public boolean accept(File dir, String name) {
- return name.endsWith(".txt");
- }
- });
- File roots[] = File.listRoots();
- System.out.println(Arrays.toString(roots));
- System.out.println(file.getParent()); // null!!
- File absFile = file.getAbsoluteFile();
- System.out.println(absFile.getParent()); // works!!
- boolean deleted = file.delete();
- deleted = folder.delete();
输入输出(I/O)的对象并不仅仅是文件,可以是任何形式的设备,比如屏幕或是网络。下面介绍四个I/O最最基本的类,它们是InputStream、OutputStream、Reader和Writer。
InputStream是所有“输入流”的基类,它是一个纯虚类并定义了一个读取字节的纯虚方法:“public int read() throws IOException”,你可以这样定义InputStream的子类:
- class MyInputStream extends InputStream {
- @Override
- public int read() throws IOException {
- if (itor >= data.length)
- return -1;
- return data[itor++];
- }
- private int itor = 0;
- private byte data[] = { 2, 5, 9, 8, 3, 4 };
- }
OutputStream是所有“输出流”的基类,它也是一个纯虚类,定义了两个写入字节的纯虚方法:“public void write(int b) throws IOException”。定义子类:
- class MyOutputStream extends OutputStream {
- @Override
- public void write(int b) throws IOException {
- data.add((byte)b);
- }
- private ArrayList<Byte> data = new ArrayList<Byte>();
- }
Reader是所有“字符读取器”的纯虚基类,它有两个纯虚方法:“public void close() throws IOException”、“public int read(char[] cbuf, int off, int len) throws IOException”。定义子类:
- class MyReader extends Reader {
- @Override
- public void close() throws IOException {
- closed = true;
- }
- @Override
- public int read(char[] cbuf, int off, int len) throws IOException {
- if (closed)
- throw new IOException();
- if (index >= data.length())
- return -1;
- int count = 0;
- for (int i = 0; i < len && index < data.length(); ++i) {
- cbuf[i+off] = data.charAt(index++);
- ++count;
- }
- return count;
- }
- private boolean closed = false;
- private String data = "This is the data. You are happy~";
- private int index = 0;
- }
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”。定义子类:
- class MyWriter extends Writer {
- @Override
- public void close() throws IOException {
- closed = true;
- }
- @Override
- public void flush() throws IOException {
- if (closed)
- throw new IOException();
- System.out.println(data);
- }
- @Override
- public void write(char[] cbuf, int off, int len) throws IOException {
- if (closed)
- throw new IOException();
- for (int i = 0; i < len; ++i)
- data += cbuf[i+off];
- }
- private boolean closed = false;
- private String data = new String();
- }
现在回到一个比较基本的问题,怎么从读写一个文件的数据?java提供了两个类:FileInputStream和FileOutputStream。我们可以用它们基类里定义的方法:InputStream.read(byte[] bytes)和OutputStream.write(byte[] bytes)。提起精神来,下面的代码有点长,虽然不复杂:
- void testFileIOStream() throws IOException {
- long ldata = -328910192;
- int idata = 2305910;
- short sdata = 4652;
- char cdata = 'A';
- double ddata = 98323.8253221;
- float fdata = 2382.784f;
- // Write to file
- FileOutputStream fos = new FileOutputStream("MyFile");
- fos.write(DataConvertor.longToBytes(ldata));
- fos.write(DataConvertor.intToBytes(idata));
- fos.write(DataConvertor.shortToBytes(sdata));
- fos.write(DataConvertor.charToBytes(cdata));
- fos.write(DataConvertor.doubleToBytes(ddata));
- fos.write(DataConvertor.floatToBytes(fdata));
- fos.flush();
- fos.close();
- byte[] lBytes = new byte[Long.SIZE/8];
- byte[] iBytes = new byte[Integer.SIZE/8];
- byte[] sBytes = new byte[Short.SIZE/8];
- byte[] cBytes = new byte[Character.SIZE/8];
- byte[] dBytes = new byte[Double.SIZE/8];
- byte[] fBytes = new byte[Float.SIZE/8];
- // Read from file
- FileInputStream fis = new FileInputStream("MyFile");
- fis.read(lBytes);
- fis.read(iBytes);
- fis.read(sBytes);
- fis.read(cBytes);
- fis.read(dBytes);
- fis.read(fBytes);
- fis.close();
- // Print Values
- System.out.println("Long data: " + DataConvertor.bytesToLong(lBytes));
- System.out.println("Int data: " + DataConvertor.bytesToInt(iBytes));
- System.out.println("Short data: " + DataConvertor.bytesToShort(sBytes));
- System.out.println("Char data: " + DataConvertor.bytesToChar(cBytes));
- System.out.println("Double data: " + DataConvertor.bytesToDouble(dBytes));
- System.out.println("Float data: " + DataConvertor.bytesToFloat(fBytes));
- }
- class DataConvertor {
- public static byte[] longToBytes(long l) {
- return numberToBytes(l, Long.SIZE/8);
- }
- public static long bytesToLong(byte[] bytes) {
- return bytesToNumber(bytes, Long.SIZE/8).longValue();
- }
- public static byte[] intToBytes(int n) {
- return numberToBytes(n, Integer.SIZE/8);
- }
- public static int bytesToInt(byte[] bytes) {
- return bytesToNumber(bytes, Integer.SIZE/8).intValue();
- }
- public static byte[] shortToBytes(short s) {
- return numberToBytes(s, Short.SIZE/8);
- }
- public static short bytesToShort(byte[] bytes) {
- return bytesToNumber(bytes, Short.SIZE/8).shortValue();
- }
- public static byte[] doubleToBytes(double d) {
- return longToBytes(Double.doubleToLongBits(d));
- }
- public static double bytesToDouble(byte[] bytes) {
- return Double.longBitsToDouble(bytesToLong(bytes));
- }
- public static byte[] floatToBytes(float f) {
- return intToBytes(Float.floatToRawIntBits(f));
- }
- public static float bytesToFloat(byte[] bytes) {
- return Float.intBitsToFloat(bytesToInt(bytes));
- }
- public static byte[] charToBytes(char c) {
- return numberToBytes((int)c, Character.SIZE/8);
- }
- public static char bytesToChar(byte[] bytes) {
- return (char)(bytesToNumber(bytes, Character.SIZE/8).intValue());
- }
- private static byte[] numberToBytes(Number n, final int size) {
- byte[] bytes = new byte[size];
- long l = n.longValue();
- for (int i = 0; i < size; i++)
- bytes[i] = (byte)((l >> 8*i) & 0xff);
- return bytes;
- }
- private static Number bytesToNumber(byte[] bytes, final int size) {
- long l = 0;
- for (int i = 0; i < size; i++)
- l |= ((long)(bytes[i] & 0xff) << (8*i));
- return l;
- }
- }
- void testDataIOStream() throws IOException {
- DataOutputStream dos = new DataOutputStream(new FileOutputStream("MyFile"));
- dos.writeLong(ldata);
- dos.writeInt(idata);
- dos.writeShort(sdata);
- dos.writeChar(cdata);
- dos.writeDouble(ddata);
- dos.writeFloat(fdata);
- dos.flush();
- dos.close();
- DataInputStream dis = new DataInputStream(new FileInputStream("MyFile"));
- long l = dis.readLong();
- int n = dis.readInt();
- short s = dis.readShort();
- char c = dis.readChar();
- double d = dis.readDouble();
- float f = dis.readFloat();
- dis.close();
- }
如果需要向文件存取字符串,而不是字节,就可以使用FileReader和FileWriter了:
- testFileRW() throws IOException {
- FileWriter fw = new FileWriter("MyFile");
- fw.write("Hello, ");
- fw.write("hava a good day\n");
- fw.append("Here's another line!");
- fw.flush();
- fw.close();
- FileReader fr = new FileReader("MyFile");
- CharBuffer cb = CharBuffer.allocate(1000);
- int n = fr.read(cb);
- fr.close();
- System.out.println(cb.array());
- }
- void testBufferRW() throws IOException {
- BufferedWriter bw = new BufferedWriter(new FileWriter("MyFile"));
- bw.write("Write into buffer");
- bw.newLine(); // BufferedWriter only!!
- bw.write("Another line");
- bw.flush();
- bw.close();
- BufferedReader br = new BufferedReader(new FileReader("MyFile"));
- String line;
- do {
- line = br.readLine(); // BufferedReader only!!
- System.out.println(line);
- } while (line != null);
- }
除了文件,我们也可以对其它类型的目标进行读写,比如内存。我们可以用CharArrayWriter/CharArrayReader读写内存(或者ByteArrayInputStream/ByteArrayOutputStream读写字节)。下面给了一个在内存中读写字符串例子,同时也介绍一个PrintWriter和StringReader:
- void testCharArrayRW() throws IOException {
- // write to memory instead of file
- CharArrayWriter baw = new CharArrayWriter();
- PrintWriter pw = new PrintWriter(baw);
- pw.print((int)3);
- pw.print((boolean)true);
- pw.println("OK");
- pw.flush();
- pw.close();
- // read from memory using CharArrayReader
- CharArrayReader car = new CharArrayReader(baw.toCharArray());
- BufferedReader br = new BufferedReader(car);
- String line;
- while ((line = br.readLine()) != null)
- System.out.println(line);
- br.close();
- // read from memory using StringReader
- StringReader sr = new StringReader(baw.toString());
- br = new BufferedReader(sr);
- sr.skip(2);
- while ((line = br.readLine()) != null)
- System.out.println(line);
- sr.close();
- }
InputStreamReader和OutputStreamWriter可以把InputStream和OutputStream转成Reader和Writer:
- void testIOStreamRW() throws IOException {
- OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("MyFile"));
- osw.write("Just for fun");
- osw.flush();
- osw.close();
- InputStreamReader isr = new InputStreamReader(new FileInputStream("MyFile"));
- int c;
- while ((c = isr.read()) >= 0)
- System.out.print((char)c);
- isr.close();
- }
系统的标准I/O包括System.in、System.out和System.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类并实现了DataInput和DataOuput这两个接口。(DataInputStream和DataOutputStream也分别实现了这两个接口)。同时它还可以定位到文件的随机位置进行读写:
- void testRandomAccessFile() throws IOException {
- RandomAccessFile raf = new RandomAccessFile("MyFile", "rw");
- for (int i = 1; i <= 10; ++i)
- raf.writeInt(i);
- // return to the beginning
- raf.seek(0);
- for (int i = 0; i < 10; ++i)
- System.out.print(raf.readInt() + " "); // 1 2 3 4 5 6 7 8 9 10
- // modify the 5th int
- raf.seek(Integer.SIZE/8 * 4);
- raf.writeInt(555);
- raf.seek(0);
- for (int i = 0; i < 10; ++i)
- System.out.print(raf.readInt() + " "); // 1 2 3 4 555 6 7 8 9 10
- }
java里还提供压缩文件的读写。最常用的两种压缩算法是Zip和GZiP。先看看GZip的简单例子:
- testGZipIOStream() throws FileNotFoundException, IOException {
- GZIPOutputStream gzos = new GZIPOutputStream(new FileOutputStream("MyZip.gz"));
- DataOutputStream dos = new DataOutputStream(gzos);
- dos.writeInt(38);
- dos.flush();
- dos.close();
- GZIPInputStream gzis = new GZIPInputStream(new FileInputStream("MyZip.gz"));
- DataInputStream dis = new DataInputStream(gzis);
- int n = dis.readInt();
- dis.close();
- System.out.println(n);
- }
java对Zip文件的支持比较好,可以添加多个Entry,甚至是子文件夹。下例把一个文件压缩到一个zip文件里,同时创建了一个子文件夹下的Entry:
- void testZipIOStream() throws IOException {
- // create a file to compress into a zip file
- FileWriter fw = new FileWriter("MyFile");
- fw.write("Just for fun");
- fw.flush();
- fw.close();
- ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("MyZip.zip"));
- zos.setComment("Here's some comment");
- zos.putNextEntry(new ZipEntry("MyFile"));
- // compress MyFile into the zip file
- FileReader fr = new FileReader("MyFile");
- int b;
- while ((b=fr.read()) >= 0)
- zos.write(b);
- // another entry in a sub folder.
- zos.putNextEntry(new ZipEntry("Parent/SubFile"));
- zos.write('A');
- zos.flush();
- zos.close();
- ZipInputStream zis = new ZipInputStream(new FileInputStream("MyZip.zip"));
- // read MyFile
- ZipEntry ze = zis.getNextEntry();
- System.out.println(ze.getName()); // MyFile
- InputStreamReader isr = new InputStreamReader(zis);
- int c;
- while ((c=isr.read()) >= 0)
- System.out.print((char)c); // Output: Just for fun
- System.out.println();
- // read SubFile
- ze = zis.getNextEntry();
- System.out.println(ze.getName()); // Parent/SubFile
- System.out.println((char)isr.read()); // 'A'
- isr.close();
- }
JDK1.4引入了一个新的I/O类库:java.nio.*(nio = new io)。它通过通道(java.nio.channels.Channel)和缓冲器(java.nio.ByteBuffer)来提高I/O的效率。FileInputStream、FileOutputStream和RandomAccessFile都提供了一个getChannel的方法来使用通道传输数据。下面给一个简单的例子:
- void testChannel() throws IOException {
- FileChannel fc = new FileOutputStream("MyFile").getChannel();
- fc.write(ByteBuffer.wrap("string data".getBytes()));
- fc.close();
- fc = new RandomAccessFile("MyFile", "rw").getChannel();
- // locate to the end
- fc.position(fc.size());
- fc.write(ByteBuffer.wrap("\nAppend to the end".getBytes()));
- fc.close();
- fc = new FileInputStream("MyFile").getChannel();
- ByteBuffer bb = ByteBuffer.allocate(1024);
- fc.read(bb);
- bb.flip(); // prepare to read the buffer
- while (bb.hasRemaining())
- System.out.print((char)bb.get());
- }
ByteBuffer就是唯一可以直接和Channel交互的缓冲器。它有一点不方便的地方就是,当读取这个缓冲器的数据前,必须调用ByteBuffer.flip()方法,而在向这个缓冲器写数据前,要调用ByteBuffer.clear()方法。(clear方法并不清空已有的数据,待会再作详细说明)。这种不便利性也是为了提高存取速度而作的牺牲。
ByteBuffer只能通过静态方法ByteBuffer.wrap和ByteBuffer.allocate创建,它在被创建后大小(Capacity)就是固定的。它用一个position的整型值来指示当前读写的位置,同时用一个limit的整型成员来表示当前数据的大小(字节数)。
当向ByteBuffer里写数据时,应该先调用clear方法,它会把postion设为0同时把limit设为和Capacity一样。之后每调用put方法写数据时,position就向后移n个字节。当position超过limit时,就抛出异常。
在读数据前,应该先调用flip方法,它会把limit值设为已写数据的尾部,并把position设为0。读时可以用hasRemaining方法来判断是否还有可读数据。remaining等于limit与position的差值。
你还可以用mark和reset方法来标记和重置缓冲区的某个特定的位置。
在上面的代码里,我们使用ByteBuffer直接读写字符串。我们也可以使用CharBuffer:
- testCharBuffer() throws UnsupportedEncodingException {
- ByteBuffer bb = ByteBuffer.wrap("some text".getBytes());
- System.out.println(bb.asCharBuffer()); // Output: ????
- bb = ByteBuffer.allocate(1024);
- bb.put("some text".getBytes("UTF-16BE"));
- bb.flip(); // after "put" call this to read
- bb.rewind(); // return to the beginning
- System.out.println(bb.asCharBuffer()); // Output: some text
- bb.clear(); // only reset the position, won't erase the content
- CharBuffer cb = bb.asCharBuffer();
- cb.put("other");
- cb.flip();
- System.out.println(cb); // Output: other
- }
你可以用Channel的TransferTo和TransferFrom方法在两个通道间传递数据:
- void testTransferChannel() throws IOException {
- FileChannel in = new FileInputStream("MyFile").getChannel();
- FileChannel out = new FileOutputStream("Other").getChannel();
- in.transferTo(0, in.size(), out);
- }
- void testMappedBuffer() throws FileNotFoundException, IOException {
- int start = 2;
- int length = 12;
- MappedByteBuffer mbb = new RandomAccessFile("MyFile", "rw").getChannel().
- map(FileChannel.MapMode.READ_WRITE, start, length);
- while (mbb.hasRemaining())
- System.out.print((char)mbb.get());
- }
- void testFileLock() throws IOException {
- FileOutputStream fos = new FileOutputStream("MyFile");
- FileLock fl = fos.getChannel().tryLock();
- if (fl != null) {
- System.out.println("Got lock!");
- fl.release();
- }
- int start = 2;
- int length = 12;
- boolean shared = false;
- FileLock fl_part = fos.getChannel().tryLock(start, length, shared);
- if (fl_part != null) {
- System.out.println("Got part lock!!");
- fl.release();
- }
- fos.close();
- }
java对序列化的支持作的非常好,只要让需要序列化的类实现Serializable接口,而这个接口没有任何方法。之后使用ObjectOutputStream和ObjectInputStream就可以读写对象了:
- class MyClass implements Serializable {
- public void print() {
- System.out.println("MyClass - " + i);
- }
- private int i;
- {
- i = new Random().nextInt(199);
- }
- }
- void testSerialization() throws FileNotFoundException, IOException, ClassNotFoundException {
- MyClass mc = new MyClass();
- mc.print();
- ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("MyFile"));
- oos.writeObject(mc);
- oos.close();
- ObjectInputStream ois = new ObjectInputStream(new FileInputStream("MyFile"));
- MyClass mc2 = (MyClass)ois.readObject();
- ois.close();
- mc2.print();
- }
如果你不想使用java自带的序列化方式(直接把二进制数据写入内存),可以实现Externalizable接口。它有两个方法:writeExternal和readExternal。想要使用Externalizable接口的类必须提供一个public的不带参数的构造器。看一个例子:
- class MyExternalizable implements Externalizable {
- @Override
- public void readExternal(ObjectInput in) throws IOException,
- ClassNotFoundException {
- i = in.readInt();
- }
- @Override
- public void writeExternal(ObjectOutput out) throws IOException {
- out.writeInt(i);
- }
- // A public constructor without arguments is a must!!
- public MyExternalizable() {}
- public void print() {
- System.out.println(i);
- }
- private int i;
- {
- i = new Random().nextInt(198);
- }
- }
- class MySerializable implements Serializable {
- private int i;
- private transient int j;
- }
- class MySerializable implements Serializable {
- private int i;
- private transient int j;
- private void writeObject(ObjectOutputStream out) throws IOException {
- out.defaultWriteObject(); // use default behavior
- out.writeInt(j);
- }
- private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException {
- in.defaultReadObject();
- j = in.readInt();
- }
- }
我们还可以用java.util.prefs.Preferences向注册表读写数据:
- void testPreferences() throws BackingStoreException {
- Preferences p = Preferences.userNodeForPackage(IOTest4.class);
- p.putInt("MyKey", 8); // write to registry
- int n = p.getInt("MyKey", 0); // read from registry
- System.out.println(n);
- p.removeNode();
- }
枚举类型
java的enum和C++的enum很不同。C++里的enum就是一个简单的整型值,而java的enum其实是一个类。编译器根据你enum的定义会为你生成一个Enum<E>的子类。Enum<E>是一个抽象类,定义了很多final方法,也就是说你定义的enum类里不能覆盖它们,你唯一能覆盖的是Object.toString类。同时你定义的enum类是不能被继承的。
先看看enum能做什么:
- enum Number {
- One,
- Two,
- Three,
- }
- void testCommonMethods() {
- for (Number n: Number.values()) {
- System.out.println("ordinal: " + n.ordinal()); // enum's int value, start from 0 by default
- System.out.println(n.compareTo(Number.Two));
- System.out.println(n == Number.Three);
- System.out.println(n.name());
- System.out.println(n.getDeclaringClass()); // Number
- }
- Number n = Enum.valueOf(Number.class, "Two");
- System.out.println(n);
- }
enum也可以用在switch语句里:
- void testSwitch(Number n) {
- switch (n) {
- case One:
- break;
- case Two:
- break;
- case Three:
- break;
- default:
- break;
- }
- }
- enum Number {
- One("This is one"), // need a constructor of string parameter
- Two("This is two"),
- Three("This is one and two");
- // constructor
- Number(String discription) {
- this.discription = discription;
- }
- private String discription;
- // a normal method
- void print() {
- System.out.println(discription);
- }
- }
- // Compiled from "EnumerationTest.java"
- final class tommy.enumeration.test.Number extends java.lang.Enum<tommy.enumeration.test.Number> {
- public static final tommy.enumeration.test.Number One;
- public static final tommy.enumeration.test.Number Two;
- public static final tommy.enumeration.test.Number Three;
- static {};
- void print();
- public static tommy.enumeration.test.Number[] values();
- public static tommy.enumeration.test.Number valueOf(java.lang.String);
- }
EnumSet是专门用来存储enum的集合:
- void testEnumSet() {
- EnumSet<Number> es = EnumSet.noneOf(Number.class);
- es.add(One); // [One]
- es.addAll(EnumSet.of(Two, Three)); // [One, Two, Three]
- bool b = es.contains(Two); // true
- es.remove(Two); // [One, Three]
- b = es.contains(Two); // false
- es.clear(); // []
- es.addAll(EnumSet.allOf(Number.class)); // [One, Two, Three]
- b = es.containsAll(Arrays.asList(One, Two, Three)); // true
- es.removeAll(Arrays.asList(One, Two)); // [Three]
- es = EnumSet.complementOf(es); // [One, Two]
- }
- void testEnumMap() {
- EnumMap<Number, String> em = new EnumMap<Number, String>(Number.class);
- em.put(One, "first");
- em.put(Two, "second");
- em.put(Three, "third");
- System.out.println(em.get(Two)); // second
- }
enum里的每个常量都可以覆盖enum类内定义有这个方法:
- enum Season {
- SPRING {
- void print() { System.out.println("It's warm"); }
- },
- SUMMER {
- void print() { System.out.println("It's hot"); }
- },
- FALL {
- void print() { System.out.println("It's cool"); }
- },
- WINTER {
- void print() { System.out.println("It's cold"); }
- void feel() { System.out.println("I don't like winter"); }
- };
- abstract void print();
- void feel() { System.out.println("I like this season"); }
- static void test() {
- for (Season s: Season.values()) {
- s.print();
- s.feel();
- }
- }
- }
如果查看生成的class文件的话,会发现四个匿名类:Season$1.class、Season$2.class、Season$3.class和Season$4.class。
注解
java里的另一个语言特性是注解(Annotataion),它专门为代码里的各种定义(比如类、方法、变量等)提供附属信息。java内置三种标准的注解:@Override用于表示一个方法是覆盖父类的方法,如果父类里没有这个方法会导致编译错误;@Deprecated修饰的元素不能被使用,否则编译出错;@SuppressWarnings用于忽略编译警告信息。
我们可以用@interface关键字自定义注解,就和定义一个class一样,可以用自定义的注解修饰元素:
- @interface MyAnnotation {}
- @MyAnnotation
- class MyClass {
- @MyAnnotation void MyMethod() {
- @MyAnnotation int n = 0;
- }
- @MyAnnotation int myAttribute;
- }
- @Target(ElementType.TYPE)
- @interface MyAnnotation {}
- @Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE})
- @interface MyAnnotation {}
注解里还可以附带注解元素,比如:
- @interface MyAnnotation {
- int MyIntElement();
- }
- @MyAnnotation(MyIntElement=5)
- class MyClass {}
- @interface MyAnnotation {
- int MyIntElement() default 5;
- }
- @MyAnnotation()
- class MyClass {}
- @interface MyAnnotation {
- int value();
- }
- @MyAnnotation(5)
- class MyClass {}
- @interface MyAnnotation {
- int MyIntElement() default 5;
- String MyStringElement();
- Class MyClassElement();
- double[] MyDoubleArrayElement();
- char[] MyCharArrayElement();
- }
- @MyAnnotation(MyIntElement=10, MyStringElement="hello",
- MyClassElement=Enum.class,MyDoubleArrayElement=232.02,
- MyCharArrayElement={'A','B','C','D'})
- class MyClass {}
定义了注解,可是我们到底能用它做什么事呢?首先我们可以用反射机制获得注解的信息:
- @Target({ElementType.TYPE, ElementType.METHOD})
- @Retention(RetentionPolicy.RUNTIME)
- @interface MyAnnotation {
- int MyIntElement() default 5;
- }
- @MyAnnotation(MyIntElement=10)
- class MyClass {
- @MyAnnotation(MyIntElement=9)
- public void MyMethod() {}
- static void testRuntimeAnnotation() throws NoSuchMethodException, SecurityException {
- Class<MyClass> c = MyClass.class;
- MyAnnotation a = c.getAnnotation(MyAnnotation.class);
- int n = a.MyIntElement(); // 10
- Method m = c.getMethod("MyMethod");
- a = m.getAnnotation(MyAnnotation.class);
- n = a.MyIntElement(); // 9
- }
- }
上面看到的@Target和@Retention都是java定义好的元注解,专门用于修饰别的注解。java一共定义了四个元注解,其它两个是@Document表示被修饰的注解会在JavaDoc中,@Inherited表示某类所使用的注解可以被其子类继承(注意,不是某个注解继承别的注解,即@interface SomAnnotation extends MyAnnotation是不允许的)。举个@Inherited的例子:
- @Retention(RetentionPolicy.RUNTIME)
- @Inherited
- @interface MyAnnotation {
- int MyIntElement() default 5;
- }
- @MyAnnotation(MyIntElement=10)
- class MyClass {}
- class MySubClass extends MyClass {
- public void MyMethod() {}
- static void testInheritedAnnotation() throws NoSuchMethodException, SecurityException {
- Class<MySubClass> c = MySubClass.class;
- MyAnnotation a = c.getAnnotation(MyAnnotation.class);
- int n = a.MyIntElement(); // 10, inherited from super class
- System.out.println(n);
- }
- }
SOURCE的注解可以通过实现javax.annotation.processing.Processor接口还处理。看看该接口的定义:
- interface Processor {
- Iterable<? extends Completion> getCompletions(Element element,
- AnnotationMirror annotation, ExecutableElement member,
- String userText);
- Set<String> getSupportedAnnotationTypes();
- Set<String> getSupportedOptions();
- SourceVersion getSupportedSourceVersion();
- void init(ProcessingEnvironment processingEnv);
- boolean process(Set<? extends TypeElement> annotations,
- RoundEnvironment roundEnv);
- }
书上用的是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:
- class MyRunnable implements Runnable {
- public void run() {
- System.out.println("OK");
- }
- }
- void testRunnable() {
- Thread t = new Thread(new MyRunnable());
- t.start();
- }
- class MyThread extends Thread {
- public void run() {
- System.out.println("OK");
- }
- }
- void testThread() {
- Thread t = new MyThread();
- t.start();
- }
- Thread t1 = new MyThread();
- t1.setPriority(Thread.MAX_PRIORITY);
- Thread t2 = new MyThread();
- t2.setPriority(Thread.NORM_PRIORITY);
- Thread t3 = new MyThread();
- t3.setPriority(Thread.MIN_PRIORITY);
- Thread t = new MyThread();
- t.setDaemon(true);
- t.start();
调用Thread.join()方法可以等待某线程的结束:
- Thread t = new MyThread();
- t.start();
- t.join();
- System.out.println("Thread quits");
线程可以通过yield来放弃当前的时间片,通过sleep来睡眠一段时间:
- class MyRunnable implements Runnable {
- public void run() {
- try {
- for (int n = 0; n < 1000000; ++n) {
- if (n % 50000 == 0)
- Thread.yield();
- else if (n % 20000 == 0)
- Thread.sleep(500);
- else if (n % 1000 == 0)
- TimeUnit.MICROSECONDS.sleep(200);
- }
- } catch (InterruptedException e) {}
- }
- }
使用Executors来使用线程池:
- ExecutorService es = Executors.newCachedThreadPool();
- for (int i = 0; i < 5; ++i)
- es.execute(new MyRunnable());
- ExecutorService es = Executors.newCachedThreadPool(new ThreadFactory() {
- @Override
- public Thread newThread(Runnable r) {
- Thread t = new Thread();
- t.setDaemon(true);
- return t;
- }
- });
- es.execute(new MyRunnable());
- void testFixedThreadPool() {
- class InnerRunnable implements Runnable {
- InnerRunnable(int id) {
- this.id = id;
- }
- public void run() {
- for (int i = 0; i < 10; ++i)
- try {
- Thread.sleep(500);
- } catch (InterruptedException e) {}
- System.out.println("thread-" + id + " ends");
- }
- private int id;
- }
- ExecutorService es = Executors.newFixedThreadPool(5);
- for (int i = 0; i < 10; ++i)
- es.execute(new InnerRunnable(i));
- }
Runnable不能返回值,如果需要返回值,使用Callable,并通过Executors.submit来启动线程:
- class MyThread extends Thread {
- public void run() {
- System.out.println("OK");
- }
- }
- void testCallable() throws InterruptedException, ExecutionException {
- ExecutorService es = Executors.newCachedThreadPool();
- Future<Integer> f = es.submit(new MyCallable());
- int i = f.get();
- System.out.println(i); // 10
- }
线程里的异常是不能被其它线程捕获的。比如下面的代码是不能工作的:
- void testThreadException() {
- try {
- new Thread() {
- public void run() {
- throw new RuntimeException();
- }
- }.start();
- } catch (RuntimeException re) {
- System.out.println("never be called!!");
- }
- }
- void testUncaughtException() {
- Thread t = new Thread() {
- public void run() {
- throw new RuntimeException();
- }
- };
- t.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
- public void uncaughtException(Thread t, Throwable e) {
- System.out.println("Catch the exception!!");
- }
- });
- t.start();
- }
为了制造出线程间的共享资源的情况,下面给出一个例子:
- class MyClass{
- void print(int id) {
- for (int i = 0; i < 5; ++i)
- {
- System.out.print(id + " ");
- Thread.yield();
- }
- }
- class MyThread extends Thread {
- MyThread(int id) { this.id = id; }
- public void run() {
- print(id);
- }
- int id;
- }
- static void testSynchronized() {
- MyClass mc = new MyClass();
- for (int i = 0; i < 5; ++i) {
- mc.new MyThread(i).start();
- }
- }
- }
- synchronized void print(int id) {...}
- class MyClass {
- Lock l = new ReentrantLock();
- void print_lock(int id) {
- l.lock();
- for (int i = 0; i < 5; ++i)
- {
- System.out.print(id + " ");
- Thread.yield();
- }
- l.unlock();
- }
- }
- void print_trylock(int id) {
- try {
- boolean b = l.tryLock(300, TimeUnit.MILLISECONDS);
- if (b) {
- for (int i = 0; i < 5; ++i)
- {
- System.out.print(id + " ");
- Thread.yield();
- }
- l.unlock();
- }
- } catch (InterruptedException e) {}
- }
- void print_synchronized_part(int id) {
- synchronized(this) {
- for (int i = 0; i < 5; ++i)
- {
- System.out.print(id + " ");
- Thread.yield();
- }
- }
- }
现在我们再来模拟一下多个线程同时对同一资源进行修改的情况。下面的程序让两个线程分别对共享的整型数加N次和减N次,预期的正确结果是当两个线程运行结束后,整型值应该为0:
- class MyClass{
- int resource;
- void changeValue(boolean increase) {
- for (int i = 0; i < 600000; ++i)
- if (increase)
- ++resource;
- else
- --resource;
- }
- class InnerThread extends Thread {
- public InnerThread(boolean increase) {this.increase = increase;};
- private boolean increase;
- public void run() {
- changeValue(increase);
- }
- }<span style="white-space:pre"> </span>
- static void testAtomicOperation() throws InterruptedException {
- MyClass mc = new MyClass();
- Thread t1 = mc.new InnerThread(true);
- Thread t2 = mc.new InnerThread(false);
- t1.start();
- t2.start();
- t1.join();
- t2.join();
- <span style="white-space:pre"> </span>
- System.out.println(mc.resource); // might be 321296, but it's supposed to be zero
- }
- }
- volatile int resource = 0; // thread unsafe
- AtomicInteger resource = new AtomicInteger(0);
- void changeValue(boolean increase) {
- for (int i = 0; i < 600000; ++i)
- if (increase)
- resource.addAndGet(1);
- else
- resource.addAndGet(-1);
- }
其实线程冲突的最根本的原因有共享的资源,如果每个线程都有自己的变量,那自然不会有冲突。我们可以用 ThreadLocal 定义这样的变量:
- ThreadLocal<Integer> resource = new ThreadLocal<Integer>() {
- @Override
- protected Integer initialValue() {
- return 0;
- }
- };
- void changeValue(boolean increase) {
- for (int i = 0; i < 600000; ++i)
- resource.set(resource.get() + 1);
- System.out.println(resource.get()); // each thread should shows 600000
- }
线程有四种状态:新建(new)、就绪(runnable)、阻塞(blocked)和死亡(dead)。当线程睡眠、等待、申请锁或响应I/O时,可以从就绪状态进入阻塞状态。睡眠和等待是可以被中断的。当被中断时,会得到一个InterruptException。下面中断一个sleep操作:
- void testInterruptSleep() throws InterruptedException {
- Thread t = new Thread() {
- public void run() {
- long time = System.currentTimeMillis();
- try {
- Thread.sleep(100000);
- } catch (InterruptedException e) {
- System.out.println("Interrupt!!");
- } finally {
- time -= System.currentTimeMillis();
- System.out.println("Slept for " + Math.abs(time) + " milliseconds");
- }
- }
- };
- t.start();
- Thread.sleep(2000);
- t.interrupt();
- }
- void testInterruptWait() throws InterruptedException {
- Runnable r = new Runnable() {
- public void run() {
- long time = System.currentTimeMillis();
- try {
- synchronized (this) {
- wait(8888);
- }
- } catch (InterruptedException e) {
- System.out.println("Interrupt!!");
- } finally {
- time -= System.currentTimeMillis();
- System.out.println("Slept for " + Math.abs(time) + " milliseconds");
- }
- }
- };
- ExecutorService es = Executors.newCachedThreadPool();
- Future<?> f = es.submit(r);
- Thread.sleep(2000);
- f.cancel(true); // interrupt it
- }
- void testInterrupted() throws InterruptedException {
- Runnable r = new Runnable() {
- public void run() {
- while (!Thread.interrupted()) {
- System.out.println("I'm still alive");
- Thread.yield();
- }
- System.out.println("interrupted!!");
- }
- };
- ExecutorService es = Executors.newCachedThreadPool();
- Future<?> f = es.submit(r);
- Thread.sleep(2000);
- es.shutdownNow();
- }
线程可以通过判断Thread.interrupted来确定自己是否被中断。ExecutorService.shutdownNow()可以中断线程池里所有的阻塞线程。
下面给个例子看怎么样使用wait和notify:
- class NotifyTester {
- synchronized void turnOn() throws InterruptedException {
- while (!Thread.interrupted()) {
- while (on)
- wait();
- on = true;
- System.out.println("turn on!!");
- notify();
- }
- }
- synchronized void turnOff() throws InterruptedException {
- while (!Thread.interrupted()) {
- while (!on)
- wait();
- on = false;
- System.out.println("turn off!!");
- notify();
- }
- }
- class TurnOnThread extends Thread {
- public void run() {
- try {
- turnOn();
- } catch (InterruptedException e) {}
- }
- }
- class TurnOffThread extends Thread {
- public void run() {
- try {
- turnOff();
- } catch (InterruptedException e) {}
- }
- }
- static void test() {
- NotifyTester nt = new NotifyTester();
- Thread t_on = nt.new TurnOnThread();
- Thread t_off = nt.new TurnOffThread();
- t_on.start();
- t_off.start();
- try {
- Thread.sleep(9000);
- } catch (InterruptedException e) {}
- t_on.interrupt();
- t_off.interrupt();
- }
- boolean on;
- }
- class LockSignalTester {
- private Lock l = new ReentrantLock();
- private Condition c = l.newCondition();
- private boolean on;
- void turnOn() {
- while (!Thread.interrupted()) {
- l.lock();
- try {
- while (on)
- c.await();
- on = true;
- System.out.println("turn on!!");
- c.signal();
- } catch (InterruptedException ie) {
- } finally {
- l.unlock();
- }
- }
- }
- void turnOff(){
- while (!Thread.interrupted()) {
- l.lock();
- try {
- while (!on)
- c.await();
- on = false;
- System.out.println("turn off!!");
- c.signal();
- } catch (InterruptedException ie) {
- } finally {
- l.unlock();
- }
- }
- }
- class TurnOnThread extends Thread {
- public void run() { turnOn(); }
- }
- class TurnOffThread extends Thread {
- public void run() { turnOff(); }
- }
- static void test() {
- NotifyTester nt = new NotifyTester();
- Thread t_on = nt.new TurnOnThread();
- Thread t_off = nt.new TurnOffThread();
- t_on.start();
- t_off.start();
- try {
- Thread.sleep(9000);
- } catch (InterruptedException e) {}
- t_on.interrupt();
- t_off.interrupt();
- }
- }
CountDownLatch可以用来同步多个线程。它会有一个初始数,每次调用countDown的时候数值减一但不阻塞,而调用await的时候会阻塞直到数值被减为0为止:
- class CountDownLatchTester {
- int counter = 0;
- CountDownLatch cdl = new CountDownLatch(5);
- class IncreaseThread extends Thread {
- public void run() {
- try {
- Thread.sleep(new Random().nextInt(10) * 1000);
- } catch (InterruptedException e1) {}
- synchronized (this) {
- ++counter;
- }
- try {
- cdl.countDown();
- cdl.await();
- } catch (InterruptedException e) {}
- System.out.println(counter);
- }
- }
- static void test() {
- CountDownLatchTester cdlt = new CountDownLatchTester();
- for (int i = 0; i < 5; i++)
- cdlt.new IncreaseThread().start();
- }
- }
CountDownLatch只做一次同步操作,如果计数器减为0后,所有的await调用都会立即返回。CyclicBarrier可以循环地进行同步,当计数器降为0后,会自动设置为初值以便下一次的同步。CyclicBarrier没有countDown操作,它的await会将计数器减一并阻塞,如果计数器减为0所有阻塞在await操作上的线程都会被唤醒:
- class CyclicBarrierTester {
- int counter = 0;
- CyclicBarrier cb = new CyclicBarrier(5);
- class IncreaseThread extends Thread {
- public void run() {
- for (int i = 0; i < 3; ++i)
- {
- try {
- Thread.sleep(new Random().nextInt(10) * 1000);
- } catch (InterruptedException e1) {}
- synchronized (this) {
- ++counter;
- }
- try {
- cb.await();
- } catch (InterruptedException e) {
- } catch (BrokenBarrierException e) {
- }
- System.out.println(counter);
- }
- }
- }
- static void test() {
- CyclicBarrierTester cbt = new CyclicBarrierTester();
- for (int i = 0; i < 5; i++)
- cbt.new IncreaseThread().start();
- }
- }
DelayQueue可以用来存放Delayed对象。Delayed是一个接口,它可以通过getDelay方法来表示有多久到期或者已经过期了多久。DelayQueue是一个优先级队列,优先级更高的Delayed会最先取出,不管它过期的时间如何。在同等优先级的情况下,会取出某个过期的Delayed。如果当前还有元素但没有元素过期,DelayQueue.take就会阻塞。下面给一个例子:
- class DelayQueueTester {
- DelayQueue<MyDelayed> dq = new DelayQueue<MyDelayed>();
- long currentTime = 0;
- class MyDelayed implements Delayed {
- long start = System.currentTimeMillis();
- int priority = new Random().nextInt(5);
- @Override
- public int compareTo(Delayed o) {
- return priority - ((MyDelayed)o).priority;
- }
- @Override
- public long getDelay(TimeUnit unit) {
- return unit.convert(start - currentTime, TimeUnit.MILLISECONDS);
- }
- }
- static void test() throws InterruptedException {
- DelayQueueTester dqt = new DelayQueueTester();
- for (int i = 0; i < 10; i++)
- {
- TimeUnit.MILLISECONDS.sleep(200);
- dqt.dq.offer(dqt.new MyDelayed());
- }
- Thread.sleep(1000);
- dqt.currentTime = System.currentTimeMillis();
- while (dqt.dq.size() > 0) {
- MyDelayed d = dqt.dq.take();
- System.out.println(d.priority + "\t" + d.start);
- }
- }
- }
PirorityBlockingQueue当队列里没有元素而试图取元素时,会发生阻塞,同时优先级最高(值最小)的元素会最先被取出:
- class PriorityBlockingQueueTester {
- PriorityBlockingQueue<Integer> pbq = new PriorityBlockingQueue<Integer>();
- class ReadThread extends Thread {
- public void run() {
- for (int i = 0; i < 1000; i++)
- try {
- int n = pbq.take();
- System.out.println(n);
- } catch (InterruptedException e) {}
- }
- }
- class WriteThread extends Thread {
- public void run() {
- for (int i = 0; i < 200; i++)
- {
- for (int j = 0; j < 5; j++)
- pbq.offer(10 - j);
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {}
- }
- }
- }
- static void test() throws InterruptedException {
- PriorityBlockingQueueTester pbqt = new PriorityBlockingQueueTester();
- pbqt.new ReadThread().start();
- pbqt.new WriteThread().start();
- }
- }
ShceduledThreadPoolExecutor可以让一个线程延后一段时间再启动:
- class ScheduledExecutorTester {
- static class MyRunnable implements Runnable {
- public void run() {
- System.out.println("Start time: " + new Date(System.currentTimeMillis()));
- }
- }
- static void test() {
- ScheduledThreadPoolExecutor stpe = new ScheduledThreadPoolExecutor(10);
- System.out.println("Sheduled time: " + new Date(System.currentTimeMillis()));
- stpe.schedule(new MyRunnable(), 2, TimeUnit.SECONDS);
- }
- }
- stpe.schedule(new MyRunnable(), 2, 3, TimeUnit.SECONDS);
另一个类似的方法是 scheduledWithFixedRate ,它与scheduleAtFixedRate的区别在于,它在 前一个线程结束后 过一段时间间隔,才会启动新的线程。
- stpe.scheduleWithFixedDelay(new MyRunnable(), 1, 3, TimeUnit.SECONDS);
信号量(Semaphore)允许多个线程同时访问一个资源(锁只允许一个线程)。用Semaphore.aquire来获得资源访问权,如果获得该资源的线程数已经超出限制的话就阻塞,直到有别的线程调用Semaphore.release来释放该资源。
- class SemaphoreTester {
- static Semaphore s = new Semaphore(5, true);
- static class MyThread extends Thread {
- int id;
- MyThread(int id) { this.id = id; }
- public void run() {
- try {
- s.acquire();
- System.out.println("start - " + id);
- Thread.sleep(3000);
- System.out.println("end - " + id);
- s.release();
- } catch (InterruptedException e) {};
- }
- }
- static void test() {
- for (int i = 0; i < 20; ++i)
- new MyThread(i).start();
- }
- }
Extranger可以让线程之间交换数据:
- class ExchangerTester {
- static Exchanger<Integer> e = new Exchanger<Integer>();
- static class MyThread extends Thread {
- int id;
- int extrangedId;
- MyThread(int id) { this.id = id; }
- public void run() {
- try {
- extrangedId = e.exchange(id);
- } catch (InterruptedException e) {}
- }
- public void print() {
- System.out.println("My id: " + id + " - Exchanged id: " + extrangedId);
- }
- }
- static void test() {
- MyThread t1 = new MyThread(1);
- MyThread t2 = new MyThread(2);
- t1.start();
- t2.start();
- try {
- t1.join();
- t2.join();
- } catch (InterruptedException e) {}
- t1.print(); // My id: 1 - Exchanged id: 2
- t2.print(); // My id: 2 - Exchanged id: 1
- }
- }
图形化用户界面
不想花太多时间在UI上,因为GUI一般都大同小异,需要使用时再翻阅文档应该也够了。java里的图形界面由javax.swing.*支持。现在给出一个粗糙的例子:
- public class SwingTest {
- static void testJFrame() {
- JFrame frame = new JFrame("Window Title");
- frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- frame.setSize(400, 300);
- frame.setLayout(new FlowLayout());
- final JLabel label = new JLabel("label text");
- final JTextArea textArea = new JTextArea();
- textArea.addKeyListener(new KeyAdapter() {
- @Override
- public void keyTyped(KeyEvent e) {
- label.setText(label.getText() + e.getKeyChar());
- }
- });
- final JButton button = new JButton("button text");
- button.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent e) {
- textArea.append(((JButton)e.getSource()).getText());
- }
- });
- button.addMouseListener(new MouseAdapter() {
- @Override
- public void mouseClicked(MouseEvent e) {
- JPopupMenu menu = new JPopupMenu();
- menu.add(new JMenuItem("Item"));
- menu.show(button, 23, 35);
- }
- });
- frame.add(label);
- frame.add(button);
- frame.add(textArea);
- frame.setVisible(true);
- }