【问题标题】:Is it valid to have a JVM bytecode class without any constructor?没有任何构造函数的 JVM 字节码类是否有效?
【发布时间】:2015-04-06 19:24:30
【问题描述】:

AFAIK,在 Java 中,总是为没有构造函数 [1][2] 的类生成隐式构造函数。

但在字节码中我找不到对JVMS 的这种限制。

所以:

  • 根据JVMS定义一个没有构造函数的类只使用它的静态方法是否有效,如下面的jasmin hello world?

  • 除了不能创建它的实例之外,它还有其他后果吗?我将无法使用invokespecial 来初始化实例,这使得new 根据https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.10.2.4 无用(不能使用未初始化的对象)。

茉莉码:

.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_INTERFACEACC_SYNTHETIC),那将是一个很好的有效性论据。

【问题讨论】:

  • 您是否尝试过编写调用new Main(); 的Java 对象?
  • @RealSkeptic 刚刚做了,并按预期得到了error: cannot find symbol。然后,如果我将构造函数添加到 Jasmin,new Main() 就可以了。
  • 好吧,那么。那么你的问题或多或少是哲学的。它是有效的Java吗?不,它是有效的 Jasmin 吗?是的。可以从 Java 程序中使用吗?是的。它是否像人们期望的那样工作 Java 类?不是完全。那么,什么是“有效”?
  • @RealSkeptic 1) 根据 JVMS 有效。它在我的实现上运行的事实并不能保证 2) 除了无法创建实例之外,还会发生其他不好的事情吗?

标签: java constructor jvm bytecode


【解决方案1】:

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

有时,Java 编译器甚至会创建此类类,以便为内部类创建 访问器构造函数

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

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

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

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

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

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

【讨论】:

  • 对于像我这样的其他新手,这会根据 stackoverflow.com/a/2654699/895245 生成三个类。第三个类,称为Foo$1.class,没有构造函数,也没有接口。但它确实有 ACC_SYNTHETICEnclosingMethod 属性集,这与最小的 Jasmin 输出不同,这使得这个参数与设置 ACC_INTERFACE 的 apangin 一样好。
  • 您的问题是关于课程的。接口和注释类型没有 jls 的构造函数。然而,甚至可以在接口中定义构造函数,使用像 ASM 这样的代码生成工具,因为这些只是在字节码级别上名为 的方法。上面的例子生成了三个类,因为 Foo 和 Foo$Bar 是两个独立的类。
  • 我的意思是,“javac 生成它”参数只有在您可以生成与 Jasmin sn-p 相同的字节码时才有效。接口有 ACC_INTERFACE,这有 ACC_SYNTHETIC,而 jasmin 代码没有。但确实,这可能被认为更接近,因为它会生成类。
  • sun.misc.Unsafe.allocateInstance(clazz) 将创建一个类的实例,甚至是一个新的enum,而无需调用构造函数。
  • @PeterLawrey 使用 Unsafe 是在作弊 :-) (顺便说一句,我不知道)
【解决方案2】:

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

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

【讨论】:

  • 接口是一个合理的论据,尽管它们设置了ACC_INTERFACE,这可能使它们仅在这种情况下有效。
猜你喜欢
  • 1970-01-01
  • 2015-11-19
  • 1970-01-01
  • 1970-01-01
  • 2019-09-04
  • 1970-01-01
  • 1970-01-01
  • 2012-02-08
  • 2013-10-25
相关资源
最近更新 更多