Skip to content

Accessing Java Objects in Python

Ben Steffensmeier edited this page May 9, 2023 · 1 revision

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.

Passing Java Objects into Python

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

PyJObject

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.

Extra functionality

In addition to the defined Java methods and fields every PyJObject automatically gets some extra functionality for compatibility with Python.

  1. Python __str__ is mapped to Java toString()
  2. Python __hash__ is mapped to Java hashCode()
  3. Python __eq__ is mapped to Java equals()
  4. A synchronized() method is added that returns a ContextManager used for synchronizing with Java

MultiMethods

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.

Extended PyJObjects

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

Builtin Conversion

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.

Java Primitives

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

Custom Conversion Functions

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

}

Special cases

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.