-
Notifications
You must be signed in to change notification settings - Fork 151
Accessing Java Objects in Python
One of the primary features of Jep is the ability to use Java Objects in a Python environment. Jep provides some conversions for basic Java classes to make them easy to use from Python and for more complex classes it provides wrapper types in Python that expose all the functionality of the Java Objects to the Python environment.
There are many ways for a Python environment to create and access Java Objects. The snippet below demonstrates the two most common ways: setting Java Objects directly into the Interpreter and importing Java classes into a Python interpreter
try (SharedInterpreter interpreter = new SharedInterpreter()) {
interpreter.set("javaSet", Set.of("Hello", "World"));
interpreter.exec("from java.util import ArrayList");
interpreter.exec("javaList = ArrayList()");
interpreter.exec("javaList.addAll(javaSet)");
interpreter.exec("print(javaList)");
}
There are many other ways that a Python environment can access a Java Object and they all provide the capabilities described on this page to wrap or convert the Java Object into an object that can be used from Python.
- Calling a Java method from Python will return a wrapped/converted Java Object
- Calling Interpreter.invoke() will wrap/convert the Java arguments.
- Accessing elements in a Java Array from Python
- Accessing fields of Java Objects
- Calling a Python function using PyCallable
- Setting Python attributes to Java Objects using PyObject
For most Java Objects accessed in Python you can look at the type of the Python object and see familiar Java class names such as java.lang.Object
or java.util.HashMap
. These object have fields and methods in Python that map directly to the fields and methods of the Java Class.
Within Jep these Java Objects have been wrapped in a PyJObject. PyJObject is the low level name for the data structure that holds the JNI reference to the Object and implements the Python Object structure defined in the Python c-api. These objects are considered "wrapped" because both languages access the exact same object and changes from one language are visible in the other. No data is copied when an object is wrapped.
In addition to the defined Java methods and fields every PyJObject automatically gets some extra functionality for compatibility with Python.
- Python
__str__
is mapped to JavatoString()
- Python
__hash__
is mapped to JavahashCode()
- Python
__eq__
is mapped to Javaequals()
- A
synchronized()
method is added that returns a ContextManager used for synchronizing with Java
One of the philosophical differences between Java and Python that Jep must overcome is that Java allows method overloading and Python does not. This means that when a Java Object has multiple methods of the same name there can be only be one Python method.
Jep will intercept any method call to overloaded Java methods from Python and it will attempt to pick the best method based off the arguments provided from Python. In most cases Jep will pick the correct method but it is a complicated problem and nearly every release of Jep includes small improvements to help make this work better.
Some PyJObjects implement extra functionality to interoperate more smoothly with Python. This functionality is typically activated when an Object implements a particular Java interface or extends a Java class. Below is a summary of the classes with extra capabilities:
Java Class | Python Behavior | Python functions |
---|---|---|
Arrays | Behaves like Python list
|
__getitem__ __setitem__ __iter__ __len__ __contains__
|
java.util.Collection |
Can be used with len() builtin and in operator |
__len__ __contains__
|
java.lang.Iterable |
Iterable in Python | __iter__ |
java.lang.Iterator |
Iterator in Python | __next__ |
java.lang.List |
Behaves like Python list
|
__getitem__ __setitem__
|
java.util.Map |
Behaves like a Python dict
|
__getitem__ __setitem__ items() keys()
|
java.lang.Number |
Can be used with most Math operators | many |
java.lang.AutoCloseable |
Is a Python ContextManager |
__enter__ __exit__
|
java.nio.Buffer |
When isDirect() returns true the buffer implements the Python Buffer Protocol
|
N/A |
Below is a Python script demonstrating some of the extended functionality Jep provides:
from java.util import ArrayList, HashMap
from java.util.concurrent.atomic import AtomicInteger
jlist = ArrayList()
jlist.add("One")
jlist.add("Two")
jlist.add("Three")
print(len(jlist)) # __len__ added to java.util.Collection
print("One" in jlist) # __contains__ added to java.util.Collection
print(jlist[1]) # __getitem__ added by java.util.List
jlist[1] = 2 # __setitem__ added by java.util.List
jmap = HashMap()
jmap.put("One", 1) # Java method
jmap["Two"] = 2 # __setitem__ added by java.util.Map
print(jmap["One"]) # __getitem__ added by java.util.Map
atomicInt = AtomicInteger(9)
print(atomicInt/3) # Added by java.util.Number
Some Objects are automatically converted to a Python object rather than being wrapped in a PyJObject. This means you cannot call Java instance methods on these objects but in these cases it is considered more useful to have the full capability of the Python type. The conversions builtin to Jep are in the table below.
Java Class | Python Type |
---|---|
java.lang.Float |
float |
java.lang.Double |
float |
java.lang.Integer |
int |
java.lang.Long |
int |
java.lang.Short |
int |
java.lang.Byte |
int |
java.lang.Boolean |
boolean |
java.lang.Character |
string |
java.math.BigInteger |
int |
java.lang.String |
string |
jep.NDArray * |
numpy.ndarray |
jep.DirectNDArray * |
numpy.ndarray |
(*) The jep.NDArray
and jep.DirectNDArray
conversions are only enabled when Jep is built with support for numpy.
In addition to converting Objects, Jep must also convert Java primitives. Although these cannot be passed in the same way as most Java objects this conversion is used in cases such as calling a Java method returning a primitive or when a primitive array element is accessed.
Java Primitive | Python Type |
---|---|
float |
float |
double |
float |
int |
int |
long |
int |
short |
int |
byte |
int |
boolean |
boolean |
char |
string |
Note: This section describes functionality that is not yet available in the released version of Jep, it is currently scheduled for Jep 4.2.
You can extend the Jep conversion capability by registering a custom conversion function in Jep. A conversion function is a Python function that takes a PyJObject wrapper as an argument and returns a converted Python object. The conversion function is registered for a particular Java class. Anytime Jep encounters a Java Object that extends or implements the class the conversion function is called before the object is available to Python.
For example the following Python code registers a conversion function that will turn a java.util.Date
into a Python datetime
.
import jep
from datetime import datetime
from java.util import Date
def date_to_datetime(jdate):
return datetime.utcfromtimestamp(jdate.getTime()/1000)
jep.setJavaToPythonConverter(Date, date_to_datetime)
Below is the Java code that would use that converter. Note that before the converter is registered any java.util.Date
is using the PyJObject wrapper functionality so it is possible to call Java methods like getTime()
. After registering the converter any new java.util.Date
used in Python will become a Python datetime
so it is no longer possible to call Java methods but it is possible to use Python features like subtracting times to create a timedelta.
try (SharedInterpreter interpreter = new SharedInterpreter()) {
interpreter.set("theDate", new Date());
interpreter.exec("print(type(theDate))"); // <class 'java.util.Date'>
interpreter.exec("print(theDate.getTime())"); // Current time as long
interpreter.runScript("date_converter.py");
interpreter.set("theDate", new Date());
interpreter.exec("print(type(theDate))"); // <class 'datetime.datetime'>
interpreter.set("anotherDate", new Date());
interpreter.exec("print(anotherDate - theDate)"); // an instance of <class 'datetime.timedelta'>
}
Note that builtin and custom conversions are used everywhere a Java Object is available in a Python environment except when a constructor is called from Python. Calling a Java constructor from Python will always return a PyJObject wrapper.