I'm working with a platform where a few things might or might not be available at runtime. To check for their existence, I have a lazy property that goes (Path
is only used here as an example):
import java.nio.file.Path
val pathIsAvailable by lazy {
try {
Path::class
true
} catch (_: NoClassDefFoundError) {
false
}
}
Once compiled, the class reference disappears:
private static final boolean pathIsAvailable_delegate$lambda$0();
0: nop
1: iconst_1
2: istore_0
3: goto 9
6: astore_1
7: iconst_0
8: istore_0
9: iload_0
10: ireturn
One way to fix it is to assign the reference to a variable (e.g., val foo = Path::class
):
private static final boolean pathIsAvailable_delegate$lambda$0();
0: nop
1: ldc #26 // class java/nio/file/Path
3: astore_0
4: iconst_1
5: istore_0
6: goto 12
9: astore_1
10: iconst_0
11: istore_0
12: iload_0
13: ireturn
However, I'm not sure if this is good enough; the variable is also unused and thus it can also be optimized away.
How to check if class exists somewhere in package? recommends using Class.forName()
, but it would only cause runtime errors and not compile-time/static analysis ones, which is half the purpose.
Using both is a choice, but that sure won't look pretty. Plus, the aforementioned optimization doesn't seem to be applied when the reference is for a companion object (e.g., kotlin.text.Regex
).
Is there a better way, one that is less likely to be affect by optimizations and at the same time subjected to static analysis? I'm using IntelliJ IDEA; fewer warnings is more desirable, though I don't mind suppressing one or two of them.
If you think the compiler is likely to optimise an away something like val foo = Path::class
, pass the class literal to a function
fun Any?.discard() {}
Then in lazy { ... }
Path::class.discard()
The compiler is very unlikely to go inside the implementation of discard
and see that it does nothing. If you are still worried, put discard
in a totally different module.
If you want a guarantee from a specification, I recommend using Java instead.
// call this from 'lazy { ... }'
public static void checkPathExists() {
var x = Path.class;
}
JLS 15.8.2 states in detail how a class literal is evaluated:
A class literal evaluates to the
Class
object for the named class, interface, array type, or primitive type (or forvoid
), as defined by the defining class loader (§12.2) of the class of the current instance.
It can be inferred that evaluating a class literal requires the class to be loaded by some class loader. "As defined by" could be considered as referring to ClassLoader.defineClass
. This will cause a NoClassDefFoundError
to be thrown (as per §12.2.1) if the class loader cannot find the class.
Finally, according to §14.4.2, the execution of a local variable declaration statement involves evaluating the initialiser expression, so Path.class
is guaranteed to be evaluated.
Note that this is assuming that the class has not been loaded before. A class loader could be implemented to load Path
before you access pathIsAvailable
.