编者注:本文为历史博文归档;涉及 JDK、框架与工具链版本请以当前官方文档为准。引用外链图片可能失效,阅读时请注意时效性。

每种编程语言都有其强大之处,无论你是初学者还是拥有多年项目经验的开发者,总会有未知的角落。就 Java 语言本身而言,即使你已开发多年、对其烂熟于心,是否能保证知晓所有的用法呢?今天我们就来整理一下 Java 中那些鲜为人知的隐藏特性。

一、双括号初始化语法(Double Brace Initialization

双括号初始化主要适用于集合类(如 ListMapSet 等)。当我们需要创建一个常量集合,或传递一个常量集合作为参数时,传统做法往往比较繁琐。

传统写法(以 Set 为例):

Set<String> validCodes = new HashSet<String>();
validCodes.add("XZ13s");
validCodes.add("AB21/X");
validCodes.add("YYLEX");
validCodes.add("AR2D");
removeProductsWithCodeIn(validCodes);

或在类中初始化常量集合:

private static final Set<String> VALID_CODES = new HashSet<String>();
static {
    validCodes.add("XZ13s");
    validCodes.add("AB21/X");
    validCodes.add("YYLEX");
    validCodes.add("AR2D");
}

每次这样写是否觉得费时费力?其实有更好的办法,那就是双括号语法

private static final Set<String> VALID_CODES = new HashSet<String>() {{
    add("XZ13s");
    add("AB21/X");
    add("YYLEX");
    add("AR2D");
}};

// 或者直接在方法调用中使用

removeProductsWithCodeIn(new HashSet<String>() {{
    add("XZ13s");
    add("AB21/X");
    add("YYLEX");
    add("AR5E");
}});

原理解析:
这里涉及两个括号的作用:

  1. 第一个括号 {} 创建了一个新的匿名内部类
  2. 第二个括号 {} 声明了匿名内部类实例化时运行的实例初始化块(Instance Initializer)。

使用双括号语法需要注意以下几点:

  1. 匿名子类限制:如果要在匿名内部类中建立子类,只能用于非 final 的类。这不仅局限于集合类,也可以用来实例化任何对象,例如 GUI 对象:

    add(new JPanel() {{
        setLayout(...);
        setBorder(...);
        add(new JLabel(...));
        add(new JSpinner(...));
    }});
  2. equals(Object o) 方法的兼容性:这种语法与常用的 equals(Object o) 方法可能不兼容。例如,若 Example 类中有如下方法:

    public boolean equals(final Object o) {
        if (o == null) {
            return false;
        } else if (!getClass().equals(o.getClass())) {
            return false;
        } else {
            Example other = (Example) o;
            // Compare this to other.
        }
    }

    那么,使用双括号初始化语法创建的对象(匿名内部类实例)不会与未使用该语法创建的对象相等(因为类类型不同)。因此,如果类中需要严谨的 equals(Object o) 方法,建议不要使用这种语法。不过集合类通常没有这个问题,可能是因为集合内部优化了比较逻辑。

  3. 替代方案:如果你使用的是集合类,且该类构造器接受另一个集合来生成实例,有更惯用的替代方法。例如 List 初始化可以使用 Arrays.asList()

    List<String> myList = new ArrayList<String>(Arrays.asList("One", "Two", "Three"));

    注意Arrays.asList 返回的是一个长度不可变的列表(固定大小),无法通过 addremove 来改变其长度。

总结建议:如果你只是要创建并初始化一个实例,而不是创建一个新类,或者创建任何不添加字段属性或重载方法的匿名类时,双括号语法是一个简洁的选择。

二、类型参数的交集(Type Parameter Joint Union

Java 支持在泛型参数中绑定多个类型,即类型交集(Intersection Types)。语法如下:

public class ClassName<T extends Class & Interface1 & Interface2 & ...> {}

注意extends 后面只有第一个可以是类(Class),后面通过 & 连接的全部都是接口(Interface),且类的声明必须在接口之前。

示例
如果你想要一个既是 Collection 类又是 Comparable 接口的参数,实现功能是判断两个给定集合是否相等,或集合中是否包含指定元素,可以使用下面的函数:

public static <A, B extends Collection<A> & Comparable<B>> boolean foo(B b1, B b2, A a) {
    return (b1.compareTo(b2) == 0) || b1.contains(a) || b2.contains(a);
}

这里 b1b2 同时具有 CollectionComparable 类型,因此既可以使用 Collectioncontains 方法,也可以使用 ComparablecompareTo 方法。

三、VisualVM 监控工具(Java VisualVM

VisualVM 是 JDK 6.0 Update 7 中自带的监控工具(位于 bin/jvisualvm.exe,Java 启动时不需要特定参数)。它能够监控线程、内存情况,查看方法的 CPU 时间和内存中的对象,以及已被 GC 的对象,甚至支持反向查看分配的堆栈(例如查看 100 个 String 对象分别由哪几个对象分配出来)。

双击打开后,从 UI 上来看,这个软件是基于 NetBeans 开发的。

界面比较简洁:

  • 左侧:树形结构,自动显示当前本机所运行的 Java 程序,还可以添加远程的 Java VM(括号里面的 PID 指的是进程 ID)。
  • Overview 界面:显示 VM 启动参数以及该 VM 对应的一些属性。
  • Monitor 界面:监控 Java 堆大小、PermGen 大小、Classes 和线程数量。
  • Profiler 界面:似乎可以动态地对某个 Java 程序进行调优。不过实际体验中,若有代码基础,在 NetBeans 里面调优可能更为自然;若无代码,调优的用处可能有限。

四、Classpath 支持通配符(Setting the class path

这是 Java 6 开始支持的功能。在工程中,我们经常会有这样的配置:

java -classpath ./lib/log4j.jar:./lib/commons-codec.jar:./lib/commons-httpclient.jar:./lib/commons-collections.jar:./lib/myApp.jar so.Main

这种写法比较复杂且容易出错。其实可以用通配符更加简洁方便:

java -classpath ./lib/* so.Main

五、协变返回类型(Covariant Return Types

这是 Java 5 添加的功能。在 Java 5 之前,我们在子类中覆盖基类的方法时,不能改变被覆盖方法的返回类型,即基类和子类的方法签名必须一模一样,想要改变只能在创建对象时进行强制类型转换(Cast)。

Java 5 过后,我们可以改变返回类型了,但需要注意的是:改变后的类型必须是原类型的子类型。举个例子就一目了然了:

public class CovariantReturnTypesTest {
    public static void main(String[] args) {
        Mill m = new Mill();
        Grain g = m.process();
        System.out.println(g);  // output: Grain
        
        m = new WheatMill();
        g = m.process();
        System.out.println(g); // output: Wheat
    }
}

class Grain {
    public String toString() {
        return "Grain";
    }
}

class Wheat extends Grain {
    public String toString() {
        return "Wheat";
    }
}

class Mill {
    Grain process() {
        return new Grain();
    }
}

class WheatMill extends Mill {
    // 这里返回类型改为了 Grain 的子类型 Wheat
    Wheat process() {
        return new Wheat();
    }
}

说明:本文涉及的部分特性(如协变返回类型、Classpath 通配符)自 Java 5/6 引入,VisualVM 工具版本较老。在新版 JDK 中,部分工具可能已独立发布或界面有所更新,具体请以官方最新文档为准。