Подтвердить что ты не робот

Допустимо ли иметь класс байт-кода JVM без какого-либо конструктора?

AFAIK, в Java неявные конструкторы всегда генерируются для класса без конструкторов [1], [2].

Но в байт-кодеке я не мог найти такого ограничения на JVMS.

Итак:

  • Действительно ли в соответствии с JVMS определить класс без конструктора только для использования его статических методов, как в следующем мире jasmin hello?

  • Есть ли какие-либо дальнейшие последствия, кроме того, что они не могут создавать экземпляры? Я не смогу использовать invokespecial для инициализации экземпляров, что делает new бесполезным в соответствии с https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.10.2.4 (не может используйте неинициализированный объект).

Код Jasmin:

.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 (в частности, no ACC_INTERFACE и ACC_SYNTHETIC), что было бы хорошим аргументом для достоверности.

4b9b3361

Ответ 1

Это законно. JVMS не говорит иначе.

Иногда компилятор Java даже создает такие классы, чтобы создавать конструкторы доступа для внутренних классов:

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

Чтобы сделать этот частный конструктор доступным для внешнего класса, компилятор Java добавляет конструктор private-private к внутреннему классу, который принимает экземпляр случайно созданного класса без конструктора в качестве его единственного аргумента. Этот экземпляр всегда является нулевым, а аксессор только вызывает конструктор без параметров без использования аргумента. Но поскольку константы не могут быть названы, это единственный способ избежать коллизий с другими конструкторами. Чтобы сохранить файл класса минимальным, конструктор не добавляется.

На стороне примечания: всегда можно создавать экземпляры классов без конструкторов. Это может быть достигнуто, например, абсурдом десериализации. Если вы используете Jasmin для определения класса без конструктора, реализующего интерфейс Serializable, вы можете создать поток байтов вручную, похожий на класс, если он был сериализован. Вы можете выполнить десериализацию этого класса и получить его экземпляр.

В Java конструктор вызывает выделение объекта - это два отдельных шага. Это даже проявляется в байтовом коде создания экземпляра. Что-то вроде new Object() представлено двумя установками

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

первое - это распределение, второе - вызов конструктора. Верификатор JVM всегда проверяет, что конструктор вызывается до того, как экземпляр будет использован, но, теоретически, JVM отлично способен отделить оба, что подтверждается десериализацией (или внутренними вызовами в виртуальной машине, если сериализация не является вариантом).

Ответ 2

Вы уже сами ответили на вопрос: класс без конструктора абсолютно прав в соответствии с JVMS. Вы не можете написать такой класс в чистой Java, но он может быть создан с использованием генерации байт-кода.

Подумайте о интерфейсах: они также являются классами без конструктора с точки зрения JVM. И они также могут иметь статические члены (вы даже можете вызывать метод интерфейса main из командной строки).