Welcome! Please see the About page for a little more info on how this works.

0 votes
ago in Clojure by

Using the qualified name of a subclass compiled by gen-class to access the superclass' members throws an exception. Using the simple name proceeds as expected.

Here's an example that I've tried to cut down as much as possible, but it's still lengthy. All files are relative to the project root directory, indicated as <project root>.

Define a class in file <project root>/src/com/example/SomeClass.java with two members, a static field and a static method.

package com.example;

public class SomeClass {

    public static int aField = 99;

    public static String aStaticMethod(int i) {
	    return "aStaticMethod received " + i;
    }
}

Define a subclass in file <project root>/src/com/example/SomeSubClass.java that extends the previous class.

package com.example;

public class SomeSubClass extends SomeClass {
}

Compile with

$ javac -d target/classes/ src/com/example/SomeClass.java
$ javac -d target/classes/ -cp target/classes/ src/com/example/SomeSubClass.java

Now we have files

<project root>/target/classes/com/example/SomeClass.class
<project root>/target/classes/com/example/SomeSubClass.class

Let's see how Clojure handles those two classes. In file <project root>/src/com/example2/demo.clj

(ns demo)

;; javac-compiled SuperClass
(import com.example.SomeClass)

(com.example.SomeClass/aField) ;; 99
(com.example.SomeClass/aStaticMethod 123) ;; "aStaticMethod received 123"

;; javac-compiled SubClass
(import com.example.SomeSubClass)

(com.example.SomeSubClass/aField) ;; 99
(com.example.SomeSubClass/aStaticMethod 456) ;; "aStaticMethod received 456"

All fine and good: Clojure can resolve the superclass members aField and aStaticMethod by using the qualified name com.example.SomeSubClass.

Now, in file <project root>/src/com/example/ExtendSomeClass.clj, define another subclass --- ExtendSomeClass --- this time using Clojure's gen-class.

(ns com.example.ExtendSomeClass)

(gen-class
 :name com.example.ExtendSomeClass
 :extends com.example.SomeClass)

Back in file <project root>/src/com/example2/demo.clj, compile ExtendSomeClass by evaluating

(compile 'com.example.ExtendSomeClass)

which produces in directory <project root>/target/classes/com/example/ the following four files

ExtendSomeClass.class
ExtendSomeClass.class$fn__NNNN.class
ExtendSomeClass.class$loading__NNNN__auto__NNNN.class
ExtendSomeClass__init.class

I would expect the behavior of the gen-class-compiled ExtendSomeClass to be substantially the same as the javac-compiled SomeSubClass. However, I obtain an exception when I attempt to access the static members inherited from the superclass with the qualified classname.

Returning to file <project root>/src/com/example2/demo.clj

(import com.example.ExtendSomeClass)

(com.example.ExtendSomeClass/aField)
;; java.lang.RuntimeException
;; No such var: com.example.ExtendSomeClass/aField

(com.example.ExtendSomeClass/aStaticMethod 789)
;; java.lang.RuntimeException
;; No such var: com.example.ExtendSomeClass/aStaticMethod

Interestingly, using the unqualified classname works as expected.

(ExtendSomeClass/aField) ;; 99
(ExtendSomeClass/aStaticMethod 789) ;; "aStaticMethod received 789"

Q: How can I understand the difference in the behavior of a gen-class-compiled subclass and the javac-compiled subclass. Should I double-check how the classpaths align to the directory structure?

Appendix:
I disassembled the class files and observed the following:

Compiled from "SomeClass.java"
public class com.example.SomeClass ...

Compiled from "SomeSubClass.java"
public class com.example.SomeSubClass extends com.example.SomeClass ...

public class com.example.ExtendSomeClass extends com.example.SomeClass ...

(This last result is missing the "Compiled from ___.java" first line.)

And when I make a demonstration to see how the gen-class-compiled class works from java-land:

package com.example;

import com.example.ExtendSomeClass;

public class TestExtendSomeClass {

    public static void main(String[] args) {
	    System.out.println(com.example.ExtendSomeClass.aStaticMethod(123));
    }
}

I am able to access the superclass fields with the qualified class name.

$ java -cp target/classes/ com.example.TestExtendSomeClass 
aStaticMethod received 123

1 Answer

0 votes
ago by

I think you are confusing things by naming your namespace the same as the generated class. By default, a qualified symbol is going to first be treated as a namespaced Clojure var, and secondarily as an interop class.

Notice that the error you get is about vars, not fields:
"No such var: com.example.ExtendSomeClass/aField"

This is also why the unqualified version works - the unqualified one is definitely not a namespace in the current namespace context where the other matches the package you happen to be in.

So, change (ns com.example.ExtendSomeClass) to anything else so there isn't a conflict here between the generated class and namespace (which also compiles to a class of the same name). I tried this, and all your examples worked.

Aside: static fields should NOT be surrounded by parentheses to evaluate. This works right now (by accident) and we've chosen to retain the behavior for the moment as there is a lot of existing code accidentally relying on it. This will warn or error in the future.

...