Tutorial 1 - Interoperating with Java¶
In this tutorial you’ll learn how to use VOC to write Python code interoperating with Java, namely, how to write Python code that calls Java and how to make your code callable by Java.
This tutorial assumes you’ve read and followed the instructions in Installation and that you’ve successfully followed Tutorial 0 - Hello, world! – if you haven’t done that yet, please do it before this.
Once you’ve followed that, it will be helpful if you set an environment variable
VOC_DIR to your local VOC repository. If you’re using Linux or Mac, you can do
that by writing in your current terminal:
Please remember to replace
/path/to/voc with the actual path to your VOC
/home/myusername/src/voc-dev/voc). In case you don’t really want
to do that for some reason, you’ll need to
remember to replace
$VOC_DIR with the full VOC directory any time it
appears in the commands below.
Calling a Java method¶
Many Java APIs can be called by simply using the imports and calling
with the equivalent Python types. Here is a simple example that calls
System.getProperty() Java method to show some information about
your Java runtime environment:
from java.lang import System print("Your JRE version is", System.getProperty('java.version')) print("You're using Java from vendor", System.getProperty('java.vendor')) print("The current class path is", System.getProperty('java.class.path'))
Try putting the above into a file named
javainfo.py, and compile it with
voc -v javainfo.py.
Then, run it with
java -cp $VOC_DIR/dist/python-java-support.jar:. python.javainfo.
You will see something like this
Your JRE version is 1.8.0_151 You're using Java from vendor Oracle Corporation The current class path is /home/elias/src/voc-dev/voc/dist/python-java-support.jar:.
The actual text will vary according to your installed version of Java JDK.
-cp is the so-called Java classpath, and is used by the JVM to locate packages and classes needed by a program.
How does it work?¶
Behind the scenes, VOC is generating code that uses proxy objects to access
Java code, converts Python types like strings into Java strings before calling
methods like Java’s
To know more about how this work, see the section about The VOC type system.
If you see an error message like:
Error: Could not find or load main class python.javainfo
This usually happens because the classpath is incorrect, causing the JVM to fail to find the class or something that was imported in a class.
Ensure you’re inside of the correct directory, and make sure to include the correct path to the
python-java-support.jar file and the current directory in the classpath, separated by
Extending a Java class¶
For extending a Java class, you will need to use some special syntax.
Here is an example code which creates three Java threads, by
from java.lang import Math class MyThread(extends=java.lang.Thread): def __init__(self, id): self.id = id self.counter = 0 def run(self) -> void: print('Starting thread %d' % self.id) for i in range(10): self.sleep(Math.random() * 1000) self.counter += 1 print('Thread %d executed %d times' % (self.id, self.counter)) MyThread(1).start() MyThread(2).start() MyThread(3).start()
There are two important syntax features to notice here:
- Extending a Java class:
Notice how we provide the full absolute path to the Java class being extended. This is required even if you import the class, because of a limitation of the current way the transpilation is implemented.
2) Annotating return type for the run() method:
-> void. This is needed in order
for the method to be executable from the Java side. In practice, VOC generates two
methods like these: one to be callable from Python code, and the other with the
Java types got from the annotations.
Compiling and running this will give you an output like:
Starting thread 1 Starting thread 2 Starting thread 3 Thread 3 executed 1 times Thread 3 executed 2 times Thread 1 executed 1 times Thread 3 executed 3 times Thread 2 executed 1 times Thread 1 executed 2 times Thread 3 executed 4 times Thread 2 executed 2 times Thread 3 executed 5 times ...
Example: extending HashMap¶
Here is another example, showing how to extend a Java class with slightly more involved type annotations, and calling the parent class method:
class SpyingHashMap(extends=java.util.HashMap): def __init__(self): self.counter = 0 def put(self, key: java.lang.Object, value: java.lang.Object) -> java.lang.Object: print('Putting %s in key %s' % (value, key)) return super().put(key, value) m = SpyingHashMap() m.put("hello", "it's me") m.put("from where?", "the other side") print('map entries are:', m.entrySet())
Here again it’s important to notice how the type annotations need the full absolute path for the types.
Compiling and running the above code will give you:
Putting it's me in key hello Putting the other side in key from where? map entries are: [hello=it's me, from where?=the other side]
Forgetting to declare
selfas argument for the run method, will give you an error like this:
Exception in thread "main" java.lang.ClassFormatError: Arguments can't fit into locals in class file python/extend_thread/MyThread
If you get the above error, double check that you’re declaring the
self as first argument in all methods of the Python classes.
Trying to extend a Java interface instead of implementing it, will give you this error:
Exception in thread "main" java.lang.IncompatibleClassChangeError: class python.error_extends.MyThread has interface java.lang.Runnable as super class
If you get the above error, make sure the thing you’re trying to extend is a class and not an interface. Look below to see how to implement a Java interface.
Implementing a Java interface¶
Implementing a Java interface is similar to extending a Java class: much like in Java,
you simply use
implements instead of
Here is the threads example from earlier, re-written to use a Python class
implementing the Java interface
from java.lang import Math, Thread class MyThread(implements=java.lang.Runnable): def __init__(self, id): self.id = id self.counter = 0 def run(self) -> void: print('Starting thread %d' % self.id) for i in range(10): Thread.sleep(Math.random() * 1000) self.counter += 1 print('Thread %d executed %d times' % (self.id, self.counter)) Thread(MyThread(1)).start() Thread(MyThread(2)).start() Thread(MyThread(3)).start()