• 要让Java这个面向“对象”的世界正常运作,创建对象就是一项不可或缺的操作。

    public class NewMain {
        public static void main(String[] args) {
            new Object();
        }
    }

    用javap反编译上面的代码,我们可以得到下面的指令,这里省去了javac暗中创建的构造函数。

    public class NewMain extends java.lang.Object{
        ...
    public static void main(java.lang.String[]);
      Code:
       0:   new     #3; //class java/lang/Object
       3:   invokespecial   #8; //Method java/lang/Object."<init>":()V
       6:   return
    }

    从这段代码中,我们可以清晰的看出创建对象(new)和调用构造函数(invokespecial)两个过程。关于这个问题,我在《对象的生命》中曾经进行过讨论。

    既然javac将一个new的动作被解释为两条指令,那在JVM的层面上,我们当然就可以将它们分开。下面是一段没什么实际用途的代码,只是证明这个观点可行性。

    public class NewGenerator {
        public static void main(String[] args) throws Exception {
            String className = "New";
            ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
            cw.visit(Opcodes.V1_2, Opcodes.ACC_PUBLIC, className, null, "java/lang/Object", null);
            Method m = Method.getMethod("void main (String[])");
            GeneratorAdapter mg = new GeneratorAdapter(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, m, null, null, cw);
            mg.newInstance(Type.getType(Object.class));
            Label label = mg.newLabel();
            mg.ifNonNull(label);
            mg.mark(label);
            mg.getStatic(Type.getType(System.class), "out", Type.getType(PrintStream.class));
            mg.push("new object is not null");
            mg.invokeVirtual(Type.getType(PrintStream.class), Method.getMethod("void println(java.lang.String)"));
            mg.pop();
            mg.returnValue();
            mg.endMethod();
            cw.visitEnd();

            OutputStream os = null;
            try {
                os = new FileOutputStream(className + ".class");
                os.write(cw.toByteArray());
            } finally {
                if (os != null) {
                    os.close();
                }
            }
        }
    }

    这段代码生成的类是能够运行的,有兴趣的可以自己试一下。这段代码的作用是new出一个对象之后,如果这个对象非空的话,就会产生一个输出:
        new object is not null

    当然,如果尝试用这个对象做一些其它的操作,会有错误等待着我们,因为这个对象并不是一个完整的对象。在JVM规范中有相关的解释:new指令并不能完整创建出一个新的对象,直到对未初始化的对象调用了实例初始化方法才会完成实例的创建。这段代码也正好符合《对象的生命》中的解释,它只是负责做出申请内存。当然,在JVM中,它的实际工作要略多一些,如果这个对象的类没有加载,就会加载相应的类。
  • 程序员最熟悉的是源代码,但是要让程序真正的发挥功效,少不了编译器的帮助。javac的作用就是将Java代码编译为JVM指令。由于Java语言和JVM同出一门,所以,稍微熟悉一下,我们便不难发现,二者几乎是直接对应的。当然,为了简化代码的编写,javac除了直接翻译之外,还暗地里帮我们做了不少工作,我们从最简单的情况看起。

    public class Test {
    }

    我们用javac编译这段代码,javap可以帮助我们反编译生成的类文件。
        javap -c Test

    下面就是反编译的结果。
    public class Test extends java.lang.Object{
    public Test();
      Code:
       0:   aload_0
       1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
       4:   return
    }

    抛开指令具体的内容,上面反编译的结果清清楚楚的告诉我们,我们编写的这个空类一点都不空,因为其中还有一个构造函数。这就是javac替我们做的工作。没错,这是javac做的,但未必是JVM要求的。其实,JVM上运行的类,完全可以没有构造函数。不过,前面的例子已经明明白白的告诉我们,因为javac的作用,直接用Java语言是无法构造出真正的空类。那我们就不妨直接从字节码入手,借助ObjectWeb ASM构造真正的“空“类。

    public class NoCtorGenerator {
        public static void main(String[] args) throws Exception {
            String className = "NoCtor";
            ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
            cw.visit(Opcodes.V1_2, Opcodes.ACC_PUBLIC, className, null, "java/lang/Object", null);
            cw.visitEnd();
            
            try {
                os = new FileOutputStream(className + ".class");
                os.write(cw.toByteArray());
            } finally {
                if (os != null) {
                    os.close();
                }
            }
        }
    }

    借助javap,我们可以看到生成的结果,确实没有构造函数。
    public class NoCtor extends java.lang.Object{
    }

    不过,因为没有构造函数存在,我们并不能用这个类创建对象,但是,下面的代码证明了这个类生成的类确实可用。
    public class NoCtorMain {
        public static void main(String[] args) {
            System.out.println(NoCtor.class);
        }
    }

    运行这段代码,我们可以得到下面的输出:
    class NoCtor

    关于Java虚拟机的指令,可以参考《深入Java虚拟机》,而ObjectWeb ASM的入门,可以参考我的《Hello, ASM——代码生成》。

  • Java 6发布了,想必很多人还不没有来得及玩熟Java 5,很多人还在与1.4打交道,稳定归稳定,发展是不能停止的。Java 6并不像Java 5那样在语法上下了很大的功夫,更多的力气用在丰富API上。其中有一项是我比较感兴趣的,就是Compiler API,也就是JDK为我们提供的访问Java编译器的接口。下面便是一个使用Compiler API编译Java程序的例子。

    import javax.tools.JavaCompiler;
    import javax.tools.JavaFileObject;
    import javax.tools.StandardJavaFileManager;
    import javax.tools.ToolProvider;

    public class Main {
        public static void main(String[] args) throws Exception {
            String sourceFile = "Hello.java";
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager fileManager =
                compiler.getStandardFileManager(null, null, null);
            Iterable compilationUnits = fileManager.getJavaFileObjects(sourceFile);
            JavaCompiler.CompilationTask task =
                compiler.getTask(null, fileManager, null, null, null, compilationUnits);
            task.call();
            fileManager.close();
        }
    }

    作为一个最简单的例子,代码本身没什么值得太多解释的地方。它实际上完成的就是调用系统的Java编译器去实现完成编译的工作,因此,基本上等价于
    javac Hello.java

    之前,刚刚在blog中提到ASM,里面的代码生成工作是通过直接写bytecode完成的。现在有了Compiler API,可以考虑生成代码以Java源码的形式完成,然后,通过调用Compiler API对源码进行动态编译,这样,可以达到同直接写bytecode类似的作用。使用Compiler API,肯定不如直接生成bytecode来得高效,但对于不了解JVM指令的人来说也许是一种解决方案。

    有了Compiler API,就可以在运行时直接编译Java程序,相当于把javac和java两个过程合二为一,这让Java代码拥有了类似于动态语言的特征。事实上,为了了解Compiler API,我确实做了一个这样的例子,不过,这样就要动不少手脚,不像这里展示的代码那样清晰简单。

  • OpenJDK
    https://openjdk.dev.java.net/

    Mobile and Embedded Community
    http://community.java.net/mobileandembedded/

    Open Source Java
    http://www.sun.com/software/opensource/java/

    Sun Opens Java
    http://www.sun.com/2006-1113/feature/index.jsp

    Java Story
    http://www.sun.com/2006-1113/feature/story.jsp

    James Gosling's Letter to the Java Community
    http://www.sun.com/software/opensource/java/gosling_letter.jsp

    CSDN报道
    http://blog.csdn.net/programmer_editor/archive/2006/11/14/1383027.aspx

    喊了好长时间的口号,Java终于开源了。这次主要是SE和ME,加上之前的EE,算是凑足了一条完整的线。

    和很多朋友一样,我有一个疑问。JDK的源码很早就可以得到了,开源的差别在哪呢?想来想去,根源可能在于我们对于开源的理解。我们一直认为有源码就叫开源,其实,开源的意义并不只是在代码开放,还有一个参与反馈的过程。原来的Java,我们可以从中学习,但并不能把自己的想法回馈到Java中,所以,算不得真正的开源。如果想参与,现在有机会了,因为Java开源了。

    不过,有一点需要清楚,实际上这里的开源只是开放SUN的Java实现,而并非Java语言,平台API和规范。这些东西依然控制伟大的JCP手中,这么多年了,JCP的办事效率大家都很清楚。所以,这种开源方式对Java而言,不会伤筋动骨,但是,想大踏步的前进也是不可能的。

    SUN对Java的开源采用了GPL 2,也就是说,可以发布自己的Java版本,但所做的改动要反馈回这个项目之中,SUN希望借此保持Java的纯正。谈及这次开源的意义,SUN把GNU/Linux也列了出来,至少以后发布的Linux发行版中加入Java可以名正言顺了,Richard Stallman可是最在乎名义的。

    SUN有做好事的传统,Solaris、NetBeans、OpenOffice等等全都送到了开源阵营中,这次是Java。SUN是商人和技术人矛盾的组合,它做出的东西都很不错,只是无法给自己带来足够的价值。SUN不傻,能赚钱的东西,它是不会开源的,送到开源社区也是无奈之举。

    顺便说一下,Java那个可爱吉祥物Duke也开源了。
    https://duke.dev.java.net/