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 /background/install
and that you've successfully followed tutorial-0
-- 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:
VOC_DIR=/path/to/voc
In case you don't really want to do that for some reason, you'll need to remember to replace $VOC_DIR
by the full VOC directory any time it appears in the commands below.
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 the 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.
The argument -cp
is the so-called Java classpath, and is used by the JVM to locate packages and classes needed by a program.
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 System.getProperty()
.
To know more about how this work, see the section about ../reference/typesystem
.
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 :
.
For extending a Java class, you will need to use some special syntax. Here is an example code which creates three Java threads, by extending the java.lang.Thread
:
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:
MyThread(extends=java.lang.Thread)
Note
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
...
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]
add an example with custom constructor
Forgetting to declare
self
as 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 is similar to extending a Java class: much like in Java, you simply use implements
instead of extends
.
Here is the threads example from earlier, re-written to use a Python class implementing the Java interface java.lang.Runnable
:
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()