没有任何构造函数的 JVM 字节码 class 是否有效?

Is it valid to have a JVM bytecode class without any constructor?

AFAIK,在 Java 中,总是为没有构造函数 [1], [2].

的 class 生成隐式构造函数

但是在字节码中我找不到关于JVMS的这样的限制。

所以:

茉莉代码:

.class public Main
.super java/lang/Object
.method public static main([Ljava/lang/String;)V
    .limit stack 2
    getstatic java/lang/System/out Ljava/io/PrintStream;
    ldc "Hello World!"
    invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
    return
.end method

也就是说,没有构造函数:

.method public <init>()V
    aload_0
    invokenonvirtual java/lang/Object/<init>()V
    return
.end method

?

运行 java Main 给出预期的输出 Hello World!.

我检查了 javap -v 输出,与 Java 不同,jasmin 没有生成默认构造函数。

我也尝试过调用 new Main(); 看看会发生什么:

public class TestMain {
    public static void main(String[] args) {
        Main m = new Main();
    }
}

正如预期的那样,它给出了一个编译错误 cannot find symbol。如果我将构造函数添加到 jasmin 然后 TestMain 工作。

javap -v 的完整性输出:

public class Main
  minor version: 0
  major version: 46
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Utf8               Main.j
   #2 = Class              #17            // Main
   #3 = NameAndType        #21:#23        // out:Ljava/io/PrintStream;
   #4 = Utf8               ([Ljava/lang/String;)V
   #5 = Utf8               java/lang/Object
   #6 = Class              #5             // java/lang/Object
   #7 = Utf8               Hello World!
   #8 = Class              #16            // java/io/PrintStream
   #9 = String             #7             // Hello World!
  #10 = Class              #19            // java/lang/System
  #11 = Utf8               Code
  #12 = Utf8               main
  #13 = Fieldref           #10.#3         // java/lang/System.out:Ljava/io/PrintStream;
  #14 = Utf8               SourceFile
  #15 = NameAndType        #18:#22        // println:(Ljava/lang/String;)V
  #16 = Utf8               java/io/PrintStream
  #17 = Utf8               Main
  #18 = Utf8               println
  #19 = Utf8               java/lang/System
  #20 = Methodref          #8.#15         // java/io/PrintStream.println:(Ljava/lang/String;)V
  #21 = Utf8               out
  #22 = Utf8               (Ljava/lang/String;)V
  #23 = Utf8               Ljava/io/PrintStream;
{
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #13                 // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #9                  // String Hello World!
         5: invokevirtual #20                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
}
SourceFile: "Main.j"

如果有人可以用 javac 生成它(特别是没有 ACC_INTERFACE 也没有 ACC_SYNTHETIC),那将是有效性的一个很好的论据。

您自己已经回答了这个问题:根据 JVMS,没有构造函数的 class 是绝对有效的。你不能在纯 Java 中编写这样的 class,但可以使用字节码生成来构造它。

想想接口:从 JVM 的角度来看,它们也是 class没有构造函数的。它们也可以有静态成员(你甚至可以从命令行调用接口的 main 方法)。

这是合法的。 JVMS 没有另外说明。

有时,Java 编译器甚至会创建这样的 classes 以便为内部 classes 创建 accessor constructors:

class Foo {
  { new Bar(); }
  class Bar() {
    private Bar() { }
  }
}

为了使外部类可以访问此私有构造函数,Java 编译器向内部 class 添加了一个包私有构造函数,它采用随机创建的无构造函数的实例class 作为它的单一参数。此实例始终为 null,访问器仅调用无参数构造函数而不使用参数。但是因为构造函数不能命名,这是避免与其他构造函数冲突的唯一方法。为了使 class 文件最小化,未添加构造函数。

旁注:总是可以在没有构造函数的情况下创建 classes 的实例。这可以通过例如滥用反序列化来实现。如果您使用 Jasmin 定义一个 class 而没有实现 Serializable 接口的构造函数,您可以手动创建一个类似于 class 的字节流(如果它已被序列化)。您可以反序列化此 class 并接收它的实例。

在Java中,构造函数调用和对象分配是两个独立的步骤。这甚至被创建实例的字节码所暴露。像 new Object() 这样的东西由两个指令表示

NEW java/lang/Object
INVOKESPECIAL java/lang/Object <init> ()V

第一个是分配,第二个是构造函数的调用。 JVM 的验证程序总是在使用实例之前检查构造函数是否被调用,但理论上,JVM 完全能够分离两者,反序列化(或内部调用 VM,如果序列化不是一个选项)证明了这一点。