All Manuals > LispWorks® User Guide and Reference Manual > 15 Java interface

15.2 Calling from Lisp to Java

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.

15.2.1 Importing classes

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 15.2.2 Defining specific callers.

Importing has the obvious advantage that you do not need to type all the method names. It has two disadvantages:

15.2.2 Defining specific callers

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 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.

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.

For example:

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 run time, my-make-file will check which of the constructors of java.io.File matches the arguments, and then call it.

See 15.2.5 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.

15.2.3 Verifying callers

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.

verify-java-caller and verify-java-callers force the caching of run time information that the callers normally do in their first call.

15.2.4 Calling methods without defining callers

You can call Java methods by passing the full method-name as a string "package.class.method" to call-java-method or call-java-static-method. You can also a non-static Java method by passing its name as a string method to lw-ji:jobject-call-method. The actual run time behavior is as described in 15.2.5 Actual Java call.

Note: These functions cache the relevant information using the string as the key, while properly defined callers close over it. Therefore they are 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 these functions you will find a typing error in the method or class name only when you call it. If you find these functions convenient and do not need the verification, there is no reason not to use them 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 15.2.5 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.

15.2.5 Actual Java call

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.

The call to Java from Lisp catches all Java exceptions. When the Java code throws an exception, the Java caller catches it and signals an error of type java-exception.


LispWorks® User Guide and Reference Manual - 01 Dec 2021 19:30:20