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

2

15

AFAIK, in Java implicit constructors are always generated for a class without constructors [1], [2].

But in bytecode I could not find such restriction on the JVMS.

So:

  • is it valid according to the JVMS to define a class without constructor only to use its static methods as in the following jasmin hello world?

  • does it have any further consequences besides not being able to create instances of it? I won't be able to use invokespecial to initialize instances, which renders new useless according to https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.10.2.4 (can't use uninitialized object).

Jasmin code:

.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

that is, without a constructor:

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

?

Running with java Main gives the expected output Hello World!.

I have checked the javap -v output and unlike Java, jasmin did not generate the default constructor.

I have also tried to call new Main(); anyway to see what happens with:

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

and as expected it gives a compilation error cannot find symbol. If I add the constructor to the jasmin then TestMain works.

Output of javap -v for completeness:

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"

If anyone can generate that with javac (in particular no ACC_INTERFACE nor ACC_SYNTHETIC) that would be a good argument for validity.

Ardy answered 6/4, 2015 at 19:24 Comment(4)
Have you tried writing a Java object that calls new Main();?Neeley
@Neeley just did, and got error: cannot find symbol as expected. Then if I add the constructor to the Jasmin, new Main() works.Ardy
Well, then. Then your question is more or less philosophical. Is it valid Java? No. Is it valid Jasmin? Yes. Can it be used from a Java program? Yes. Does it work as people expect a Java class to work? Not entirely. So, what is "Valid"?Neeley
@Neeley 1) valid according to JVMS. The fact that it runs on my implementation does not guarantee that 2) does anything else bad happen besides not being able to create instances?Ardy
S
12

It is legal. The JVMS does not say otherwise.

Sometimes, the Java compiler does even create such classes in order to create accessor constructors for inner classes:

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

In order to make this private constructor accessible to the outer clasd, the Java compiler adds a package-private constructor to the inner class that takes an instance of the randomly created constructor-less class as its single argument. This instance is always null and the accessor only invokes the parameterless constructor without using the argument. But because constrors cannot be named, this is the only way to avoid collissions with other constructors. In order to keep the class file minimal, no constructor is added.

On a side note: It is always possible to create instances of classes without constructors. This can be achieved by, for example, absusing deserialization. If you use Jasmin to define a class without a constructor that implements the Serializable interface, you can create a byte stream manually that resembles the class if it was serialized. You can than deserialize this class and receive an instance of it.

In Java, a constructor call an an object allocation are two seperate steps. This is even exposed by the byte code of creating an instance. Something like new Object() is represented by two instuctions

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

the first being the allocation, the second being the constructor's invocation. The JVM's verifier always checks that a constructor is called before the instance is used but in theory, the JVM is perfectly capable of detaching both, as proven by deserialization (or internal calls into the VM, if serialization is not an option).

Serpentine answered 7/4, 2015 at 19:34 Comment(5)
For other newbs like me, this generates three classes as per https://mcmap.net/q/271408/-why-does-java-code-with-an-inner-class-generates-a-third-someclass-1-class-file-duplicate . The third class, called Foo$1.class does not have a constructor and it not an interface. But it does have ACC_SYNTHETIC, and EnclosingMethod attribute set unlike the minimal Jasmin output, which makes this argument as good as apangin's which sets ACC_INTERFACE.Ardy
Your question was on classes. Interfaces and annotation types do not have constructors by the jls. It is however possible to define constructors even in interfaces, using a code generation tool like ASM, as those are just methods named <init> on byte code level. The above example generates three classes as Foo and Foo$Bar are two seperate classes.Serpentine
What I mean is, the "javac generates it" argument is only valid if you can generate the same bytecode as the Jasmin snippet. Interfaces have ACC_INTERFACE, and this has ACC_SYNTHETIC, while the jasmin code does not. But true, this may be considered closer as it generates classes.Ardy
sun.misc.Unsafe.allocateInstance(clazz) will create an instance of a class, even a new enum without calling a constructor.Minister
@PeterLawrey using Unsafe is cheating :-) (I didn't know that BTW)Ardy
L
7

You've already answered the question yourself: a class without a constructor is absolutely valid according to JVMS. You cannot write such a class in pure Java, but it can be constructed using bytecode generation.

Think of interfaces: they are also classes without a constructor from JVM point of view. And they can also have static members (you can even invoke interface's main method from the command line).

Lonnalonnard answered 6/4, 2015 at 20:23 Comment(1)
Interfaces are a reasonable argument, although they have ACC_INTERFACE set on them, which could make them valid only in that case.Ardy

© 2022 - 2024 — McMap. All rights reserved.