Use jpy
jpy is a bi-directional Java-Python bridge that facilitates calling Java from Python and vice versa.
- Python programs that use
jpycan access Java functionalities. - Java programs that use
jpycan access Python functionalities.
For more details on jpy, see the jpy GitHub project, or use help("jpy") to see the package documentation.
The Deephaven query engine is implemented in Java, making it relatively easy to use Java from within Python. jpy is used as the bridge between the two languages. This guide covers how to use Java from Python. Calling Python from Java is much less common and won't be covered here.
Caution
jpy is a lower-level tool and is generally only used when Deephaven does not provide Python wrappings for Java objects to accomplish the same tasks. Many of the things done in this document would be better accomplished with the deephaven.dtypes Python module, which supports the creation and manipulation of Java primitives, arrays, and many different Java object types. However, much of this document will demonstrate jpy's functionality in the most straightforward cases, with primitive types and simple objects. This is to explain jpy's usage rather than encourage its use in all of these cases.
Note
To request a Python wrapping of a particular Java type or library, file a ticket in the Deephaven Core GitHub repository.
Get Java types
One of the primary functions of jpy is to retrieve Java types from Python. This is useful when the desired types do not have an existing Python wrapping. jpy provides the ability to get Java types with jpy.get_type.
Use jpy.get_type
To get a Java type from Python, pass the fully qualified class name of the desired type as a string argument to jpy.get_type. Here's a simple example using java.lang.String.
jpy.get_type can get any valid Java type that is present in the Java classpath, including Deephaven-specific types. Here is an example using Deephaven's DoubleVector.
What does get_type return?
When jpy.get_type is called, it returns a unique Python class that wraps the underlying Java type. You can inspect this class with familiar Python functions.
When this class is created, it defines methods that mirror those of the underlying Java type. Use Python's help function to print out all the available methods.
Check the Javadoc for java.net.URL to confirm that all of its methods are represented by the Python wrapping.
Call methods on Java types
Python code can use Java methods directly. This means you can call any Java function from your Python program.
Call static methods
Static methods are methods that belong to the type rather than to an instance of the type. Static methods can be called directly from the return value of jpy.get_type. This example uses the valueOf static method from the java.lang.String type.
Create objects and call instance methods
jpy.get_type can be used to create instances of Java data types. The code block below creates an instance of a java.lang.Float.
Unlike static methods, instance methods do require data stored in a type to perform their function. Therefore, they belong to the instance, rather than the type itself.
float_instance is an object and provides many instance methods for operating on the value 123.456. The Javadoc for java.lang.Float details all of the available methods. This example uses the toString, compareTo, and equals instance methods.
Return type conversion
When Java methods are called through jpy, jpy attempts to convert the return values to Python types if possible. Here is an example of this behavior.
When the return type of a Java method is a Java object that cannot be directly converted to Python, jpy wraps the result so that it can be used in Python.
Argument type conversion
In the previous examples, Java methods were called with Python objects as arguments. For this to work, jpy auto-converts the Python objects to Java objects with the appropriate types. Here is another example of this conversion.
Here, 4 and 5 are Python objects, so jpy must convert them to Java objects before calling methods.
Generally, this kind of auto-conversion is only possible with primitive types. To call a Java method with more complex argument types, objects of those types must first be created with jpy.
Here is an example of calling a method with a Java object argument. The dayOfWeek method from Deephaven's DateTimeUtils accepts a Java LocalDate as an argument. jpy cannot auto-convert any Python object to a Java LocalDate, so use jpy to create an instance of that type directly.
Note
This example is only illustrative, and this particular use case is best served by deephaven.time.
Method overloading
Java (unlike Python) allows method overloading, where multiple methods can have the same name but different signatures.
This example calls Java's max method on two different sets of inputs. When the inputs are integers, max(int, int) is invoked. When the inputs are floats, max(float, float) is invoked.
The same concept applies when both Python and Java objects are passed in as inputs. Here's an example that calls two overloads of the minus method with different combinations of Python and Java arguments.
Common errors
If jpy cannot find an overload matching the argument types provided, a "no matching Java method overloads found" error will be raised:
This error may arise from arguments having mismatched types, calling a method with the wrong number of arguments, or errors in auto-converting from a Python type. Here are examples of each scenario.
An "ambiguous Java method call" error is thrown if the argument type is too ambiguous to select a particular overload:
This can happen when passing non-native Python types, such as numpy types, to methods with multiple overloads. Here is an example of such behavior.
Create Java arrays
jpy.array creates Java arrays directly from Python iterables of the appropriate types or an integer indicating the new array length.
Scalar arrays
To create arrays of scalars with jpy.array, pass in the name of the type and a Python iterable containing elements that can be converted to the target type.
Arrays can also be specified by providing the number of elements rather than a Python iterable. This yields an array containing all zeros for primitives or nulls for objects.
If the Python iterable contains elements that cannot be converted to the requested Java type, the conversion will fail.
jpy.array can also be used to create arrays of non-primitive types, such as java.lang.String and java.lang.Float. The fully-qualified class name must be specified:
Nested arrays
Multi-dimensional arrays, or nested arrays, are common in Java. jpy can be used to create nested arrays by using nested calls to jpy.array. However, the classpath names do not work as they did previously. Consider the following example that attempts to create a nested array of integers.
This code will fail because the outer array's specified type is an int but should be an array of ints. Here are the rules for specifying nested array types, each followed by simple examples.
-
For primitive types, use the type signature. The type signatures for each primitive type are:
Primitive Type Type Signature booleanZbyteBcharCshortSintIlongJfloatFdoubleD
- For object types, use the full class name, prepended with
Land ending with;.
- The number of open square brackets determines the array's dimension. An
n-dimensional array requiresn-1open square brackets.
See the Java documentation for more information on type signatures.
To clarify these rules, here are some concrete examples.
Example 1: 2D boolean array
This example creates a 2D array of boolean values. Since it is a nested array of primitives, the type signature Z for the boolean type must be used. It is a 2D array, so the type signature must be prepended by a single open square bracket [.
Example 2: 3D int array
This example creates a 3D array of integer values. The int is primitive, so the type signature I must be used, along with two open square brackets since the array is three-dimensional.
Example 3: 2D java.lang.String array
This example creates a 2D array of Strings. Since this is a nested array of objects, the class name must be prepended with an L and end with a ;. Since this array is 2D, only one open square bracket is required.
Example 4: 3D java.lang.String array
Finally, this example creates a 3D array of Strings. This will require L and ;, as well as two open square brackets.
Use jpy with Deephaven
Because the Deephaven query engine is implemented in Java, all of its core data structures and operations are implemented in Java. Deephaven makes it easy to use Java objects directly with jpy.
The j_object property
To access the Java objects underlying Deephaven's Python API, use the j_object property.
Use Java libraries with Deephaven tables
Many use cases for jpy involve operating on Deephaven tables with Java libraries or methods that have not been wrapped in Python. Use jpy.get_type to get the desired class. Then, pass Deephaven tables to Java methods with the j_object property.
Deephaven libraries often return new Deephaven tables. These Deephaven tables are Java objects. To make them usable with the rest of the Python API, they must be explicitly wrapped in Python using the Table constructor from deephaven.table.
Use Java values in query strings
Java objects created with jpy can be used directly in Deephaven's query strings.
When a Java object is constructed, its methods can be called inside and outside query strings, depending on the use case.
When Java values are used directly in query strings, they do not require any Python-Java boundary crossings during their execution. Thus, such patterns will be very performant relative to other patterns that require boundary crossings. Here is an example that compares the efficiency of generating random numbers using a Python or a Java approach.
Tie it all together
Finally, here's an example that ties all of these concepts together.