To call into Java you typically define Java callers. There are two possible approach for defining the callers: importing classes or defining specific callers. In addition, you can call Java methods and constructors and access Java fields without defining them first.
Importing a Java class means that the system generates definitions for all the public methods, constructors and fields for this class. For example, to generate and evaluate the definition, execute:
(import-java-class-definitions "java.io.File")
And to write the definitions to a file:
(write-java-class-definitions-to-file "java.io.File" "filename.lisp")
The import macros and functions all take various keyword arguments to control exactly what they generate.
import-java-class-definitions would normally appear as a top level form in your source file, and when the file is loaded it generates all the definitions. write-java-class-definitions-to-file can be used to generate all the definitions and write them to a file, which is an ordinary Lisp source file that can be compiled and loaded as usual. There is also write-java-class-definitions-to-stream, which writes the definitions to a stream, and generate-java-class-definitions, which returns a list of the definitions, which may be useful sometimes (they are actually used by import-java-class-definitions and write-java-class-definitions-to-file).
The actual definitions that the importing interface generates are the same as you would write yourself, using the appropriate defining forms: define-java-caller, define-java-constructor, define-field-accessor. These are discussed further in Defining specific callers.
Importing has the obvious advantage that you do not need to type all the method names. It has two disadvantages:
If the requirement for Java is an issue, you can work around it using write-java-class-definitions-to-file (or use write-java-class-definitions-to-stream), and use the resulting file as your source code. The call to write-java-class-definitions-to-file requires Java, but you need to do it only once, and it can be on a different computer to the one you develop on. For a public class (standard Java, standard Android) you can even ask Lisp Support to create the file for you. This approach also allows you to edit the definitions if you have any reason to. The definitions also contain the signatures of all the methods and constructors.
If you deliver your application without shaking (the default for levels 0 and 1), using import will also cause your application to be larger that it needs to be. If you import many classes this difference may be significant. If you deliver with shaking (default for level 2 or higher), the callers that are not used will get shaken out and so will not affect the size of your application.
You define specific callers by using the various definers, which are typical defining macros, but the body is automatically generated:
Defines a caller that calls a Java method.
Defines several method callers for the same class.
Defines a caller that calls a Java constructor method.
Defines callers to access (read and write) a field.
In addition, you can define callers dynamically at runtime using the setup-*
functions setup-java-caller, setup-java-constructor and setup-field-accessor, which are functions that match the define-*
macros above.
The setup-*
functions effectively do exactly what the define-*
macros do, but the code looks nicer with the macros, and the LispWorks Editor can find your definitions.
Methods and constructors are similar enough that they are described here together. Constructors are by definition always "static" in the Java terminology.
Defining a caller for a method or constructor defines a Lisp function that when called invokes the Java method. The Java method is supplied by its class name and name (except constructors, which implicitly map to the constructor methods of the class), which means that there may be more than one Java methods or constructor that are applicable.
Define a Lisp function my-probe-file
which invokes the Java method java.io.File.exists
:
(define-java-caller my-probe-file "java.io.File" "exists")
Define a Lisp function that calls one of the constructors of java.io.File
:
(define-java-constructor my-make-file "java.io.File")
At runtime, my-make-file
will check which of the constructors of java.io.File
matches the arguments, and then call it.
See Actual Java call for a description of how the callers actually work.
Defining a field accessor defines a Lisp function that reads the field value, potentially another Lisp function to set the value (if it is not final), and a symbol macro that expands to calls to the getter or setter. For ordinary (non-static) fields, the getter needs to be called with the object from which to read the value, and the setter must be called with the object and the value. For static fields, the getter takes no arguments, and the setter takes the new value.
Compared to importing classes, explicit definitions have the advantages that they do not need Java running until runtime, you define only the callers you need, and you select the names of the Lisp functions. The main disadvantage is that you have to type much more, and that you may have typing errors in the method names which are not reported during compilation.
The functions verify-java-caller and verify-java-callers are provided as a way to guard against such typing errors. These functions need Java running, and they check whether the callers have matching Java methods, and return information about missing methods. The intention is that at least during development, you will call verify-java-callers at the beginning of the application and log the result, which will allow you to check whether any method is missing. It may also be useful if you use classes whose definitions may change, for example when the Java code and Lisp code are developed in parallel, or when you use non-standardized Java code.
verify-java-caller and verify-java-callers force the caching of runtime information that the callers normally do in their first call.
You can call Java methods by passing the full method-name as a string "package.class.method"
to call-java-method. The actual runtime behavior is as described in Actual Java call.
Note: call-java-method caches the relevant information using the string as the key, while properly defined callers close over it. Therefore call-java-method is slightly slower, but the difference is not significant. The only significant difference is that you can verify the caller to check against typing errors, while with call-java-method you will find a typing error in the method or class name only when you call it. If you find using call-java-method convenient and do not need the verification, there is no reason not to use call-java-method in preference to defining the callers explicitly.
You can construct an object of a class by calling create-java-object, supplying the full class name followed by the arguments to the constructor. The actual runtime behavior is as described in Actual Java call.
You can access fields without defining accessors using read-java-field and set-java-field. There is also checked-read-java-field, which is like read-java-field but does not error on failure, check-java-field to check whether a field exists, and java-field-class-name-for-setting to find the class of the value.
When a Java caller is called the first time or a call without definition is done and not cached yet, the function finds the relevant method(s), their arguments and return value types, and caches it (see verify-java-caller or verify-java-callers for pre-caching). That includes finding the class, and then finding the relevant methods or constructors. It then uses this information to decide which method is applicable, how to convert the argument to Java where needed, and how to convert the return value back to Lisp. It also decides which JNI function to use to perform the actual call.
Before doing the call LispWorks checks whether the arguments are of the correct type, and in most of the cases can catch and give Lisp errors as appropriate before calling into Java.
For an ordinary (non-static) Java method, the arguments to the Lisp function must start with the actual Java object for which the method needs to be applied. The rest of the arguments to the Lisp function are passed to the method. Thus the number of the arguments to the Lisp function needs to be one more than the number of (explicit) arguments to the Java method. The invocation is virtual (normal Java invocation), which may mean that the actual Java method that is ultimately executed may be defined in a subclass of the class that passed to the definer, if the object belongs to this subclass.
For static Java methods (including constructors) the given argument list is passed to the method.
LispWorks User Guide and Reference Manual - 13 Feb 2015