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.
(write-java-class-definitions-to-file "java.io.File" "filename.lisp")
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.
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.
In addition, you can define callers dynamically at run time using the
setup-* functions setup-java-caller, setup-java-constructor and setup-field-accessor, which are functions that match the
define-* macros above.
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-java-caller my-probe-file "java.io.File" "exists")
(define-java-constructor my-make-file "java.io.File")
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 run time, 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.
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 run time 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.
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.
LispWorks User Guide and Reference Manual - 20 Sep 2017