• Swing 绘制 - [java graphics]

    2009-11-19

    用 Java Swing 写的程序,默认是这个样子的, 很“好看”是吧……

    我总是喜欢在用户界面上做挑剔。从用户的角度讲,好看的程序,会让人在使用的时候感到舒服,难看的程序就会让人难以接受。在做界面的时候,程序应该做到看上去像原生程序一样,特别在细小的地方。

    Swing 所有内置的外观都很难看,那些所谓的“原生外观”看起来也并不自然。大概 Java 的开发者都在忙别的功能……

    虽然你可以重写构件的一些 method 来改变它们的样子,但需要一些功夫,特别是那些复杂的构件。这样做的好处就是能让构件看起来好看一点:

    这个背景是一个 JPanel, 重写了 paintComponent 来实现渐变的效果。按钮的效果是改写了按钮的 UI 类,这个 UI 类是用来画按钮的。所有构件都是用Java 绘制出来的,Java 的绘图库还是不错的。

    代码在这里,没有版权,喜欢就拿去吧~
    http://docs.google.com/View?id=dcqtwkjn_20d9kf4xgs

  • 软件设计模式中的单列模式,是一个既简单又复杂的模式。当你一开始学它的时候,看到的也许是这样的一段代

    public class Singleton {
        private static Singleton instance = null;
        private Singleton() {}
        public static Singleton getInstance() {
            if (instance == null) {
                instance = new Singleton();
            }
        }
    }

    在单线程的环境下,这段代码并没有什么问题,如果是处在多线程的环境之中,那就不行了。

    举个例子:如果两个(或更多)线程,A 和 B 同时进入到 “if (instance == null)”,它们同时看到 “instance”是 null,然后执行 “instance = new Singleton()”,那么 “instance”就会被初始化了两次, A 和 B 同时就创造了两个不同的 Singleton,然后你的程序就会出现问题。

    下面我们来看看一些解决方法。

    1. 最简单的就是使用 eager initialization:

    public class Singleton {
        private static final Singleton INSTANCE = new Singleton();
        private Singleton() {}
        public static Singleton getInstance() {
            return INSTANCE ;
        }
    }

    当 Singleton 被加载的时候,INSTANCE 就会先被初始化,然后可以安全地被其他的进程所使用。使用这个方法有一点要注意的,就是如果 Singleton 被加载后 INSTANCE 不被使用,而且 INSTANCE 还携带着一些资源,那么就有可能会影响到程序的性能。

    达成 eager initialization 的另一个方法就是使用 Enum,效果一样:

    public enum Singleton {
        INSTANCE;

    }

    2. 另一个方法,就是使用 double-checked locking:

    public class Singleton {
        private static volatile Singleton instance = null;
        private Singleton() {}
        public static Singleton getInstance() {
            if (instance == null) {
                synchronized(this) {
                    if (instance == null) {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }

    Double-checked locking 只能使用在 JDK 1.5 或以上,还有 instance 前必须带有 volatile 关键字。

    当进程 A 进到第一个“if (instance == null)”,它看到 instance 还没被初始化,所以会执行 “synchronized(this)”把代码锁定。这个时候进程 B 也许也到了第一个 if 里面,但是无法继续前进,必须先让 A 完成。当 A 把 instance 初始化之后解锁,B 继续前进,但是当它进入第二个 “if (instance == null)”的时候,看到 instance 已经被初始化了,所以就会直接推出。这样 instance 在任何情况下都只会被初始化一次。

    使用“synchronized”这个关键字对代码进行锁定,会对程序的性能有所影响,所以我们把它放到了第一个 if 里面,这样当 instance 被初始化了以后,就不需要再次进入锁定状态,也就不会影响到程序的性能。

    3. 最后介绍的方法,叫做 initialization on demand holder,原作者是 Bill Pugh:

    public class Singleton {
        private Singleton() {}
        public static Singleton getInstance() {
            return SingletonHolder.INSTANCE;
        }
        private static class SingletonHolder {
            private static final Singleton INSTANCE = new Singleton();
        }
    }

    这个方法使用了一个 static 类(SingletonHolder)来容纳 INSTANCE,只有在第一次调用 getInstance() 的时候,SingletonHolder 里面的 INSTANCE 才会被初始化,也就是说用其它的方法来加载 Singleton 的时候,INSTANCE 是不会被初始化的,这样就保持了 lazy initialization 和线程安全。

    网络上还有很多关于这方面的资料,这篇文章算是对它们的作出的一个总结吧。

  • Java Collections Framework,是开发者不可或缺的库,因为真的是太有用了~ 但是在使用中有一点是不可疏忽的,就是 ConcurrentModificationException,当你在迭代一个 collection 的时候,如果同时还不正确地修改了这个 collection (例如添加或删除元素),那么这个异常就会被抛出,因为这样做会让迭代器(Iterator)混乱。很多新手(包括我哈)在使用 collections 的时候都没有注意到这一点,于是程序在运行的时候就会出错了。

    我们先看看这段代码:

    Set<String> things = ...
    ...
    for (String s : things) {
        if (s.equals("trouble")) {
            things.remove(s);
        }
    }

    这段代码一眼看上去没有什么问题,在 IDE 中也不会告诉你有什么不妥的地方。但是这段代码犯了我们刚才说的错误:在迭代的同时,如果找到了 "trouble" 这词,就会把它从这个 collection 里面删掉。这样做的话很容易产生 ConcurrentModificationException,然后你的程序就会挂掉~

    解决方法1:使用 Iterator。

    Iterator<String> it = things.iterator();
    while (it.hasNext()) {
        if (it.next().equals("trouble")) {
            it.remove();
        }
    }

    这段代码的用途跟刚才的代码一模一样,但是不会产生 ConcurrentModificationException,因为当我们删除元素的时候使用的是迭代器的 remove() 方法,这是安全的。

    Iterator 类中没有 add 方法,也就是说我们不能使用它来添加元素,Iterator 的子接口 ListIterator 中带有 add(),但是只有 List 类才能获得 ListIterator。那如果我们的 collection 不是 List,而我们又需要在迭代的时候添加元素呢?

    解决方法2:把原来的 collection 复制一遍,用来迭代,原来的 collection 就用来添加或删除元素。

    List<String> copyOfThings = new LinkedList<String>(things);
    for (String s : copyOfThings) {
        if (s.equals("trouble")) {
            things.add("No ConcurrentModificationException!");
        }
    }

    在这段代码中,我们使用 LinkedList 把原来的 collection 复制了一遍,用来迭代,然后当我们需要添加元素的时候,我们使用的是原来的 collection,这样问题就解决了~ 因为我们只用复制的 collection 来迭代而不去修改,然后用原来的 collection 来修改而不去迭代。

    复制版的 collection 我用的是 LinkedList 类,这样的话,原来的 collection 的迭代顺序就得到了保留,因为我们也许并不知道原来的 collection 是什么类,可能是 HashSet,也可能是 TreeSet,用 LinkedList (或者别的 Linked* 类) 来复制就不用担心复制以后迭代的顺序不一样了。

    多线程:
    如果你的 collection 会用在多线程的环境下,那么线程一在迭代的时候,线程二把它修改了,那么异常同样会被抛出。你可以使用 java.util.Collections 类中的某些方法把你的 collection 转换成线程安全的,具体的使用方法请看相应的 Javadoc。但是最佳的解决方法,就是不要把程序设计成这样,因为由多线程所产生的臭虫是很难搞定的~

    迭代的时候调用别的 method:
    在迭代中调用别的 method 的时候要小心,确定你的 collection 不会被这些 method 所修改。就算现在没有被修改,将来别人也有可能会把这些 method 改掉,在其中添加或删除 collection 的元素,那么异常会再度出现哦~

    ;-)