This is a document with tips and usage details about Jython that I’ve come across. I intend to document handy features of Python as well as some clever inter-op facilities provided by Jython.
I’m going to assume you’re not a complete beginner to Java and Python languages. If you find anything off or have a suggestion to add, please do write to me. Thanks!
Logging and Printing¶
When using Apache’s log4j, we can get an instance of a Logger
using the API just as we would in
Java:
>>> from org.apache.log4j import Logger
>>> log = Logger.getLogger('jython_script')
When getting a Logger
instance for a module that is imported, a logger with a category specific to
that module can be obtained using the following code:
log = Logger.getLogger(__name__)
The __name__
name is a variable containing the current module’s name as a string. Note that
__name__
is set to the string '__main__'
if the module is run as a script and not imported from
another script. This should be kept in mind when using the above code.
The standard printing functions of Java can be imported into Python and used directly in the following way:
>>> from java.lang import System
>>> System.out.println('Hola')
Hola
>>> System.err.println('Hello there')
Hello there
>>> System.out.print('Hola\n')
Hola
However, it’s usually more convenient to use Python’s print
statement to output things to standard
output and error:
print 'Hello world!'
Here’s a table illustrating the print
statement equivalents of the Java print*
functions:
Java | Python |
---|---|
System.out.println("!") |
print '!' |
System.out.print("!") |
print '!', |
System.err.println("!") |
print >> sys.stderr, '!' |
System.err.print("!") |
print >> sys.stderr, '!', |
Bean Properties¶
Jython can implicitly call the .get*
and .set*
methods that are widely used in Java classes to
get and set the values of instance attributes. Here’s an illustration of how this inter-op works:
Jython | Java equivalent |
---|---|
obj.somePropertyValue |
obj.getSomePropertyValue() |
obj.somePropertyValue = 123 |
obj.setSomePropertyValue(123) |
Of course, when such .get*
and .set*
methods are not available, this falls back gracefully to
trying get/set the property values directly, just as Java would treat those statements.
Strings¶
Strings in Java (i.e., objects of type java.lang.String
) are converted to unicode
objects when
passed in to Python world. Whereas str
and unicode
objects in Python are converted to
java.lang.String
instances when passed in to Java world. This conversion is seamless and we
usually don’t have to worry about it.
However, if needed, we can explicitly create an instance of java.lang.String
from a unicode
object in Python:
>>> from java.lang import String
>>> greeting = String('Hello')
>>> greeting
Hello
>>> type(greeting)
<type 'java.lang.String'>
String formatting using %
operator in Python cannot be applied to Java String
objects. They have
to converted to str
or unicode
first.
Maps as Dictionaries¶
For the purposes of the following examples, let’s work with the following
Map
:
java.util.Map<String, Integer> data = new java.util.HashMap<>();
data.put("a", 1);
data.put("b", 2);
data.put("c", 3);
Map
s support the getitem syntax very well so it is usually convenient to think of them as
python-style dictionaries. Here’s an example:
>>> print data['a'] # data.get("a")
1
>>> print data['b'] # data.get("b")
2
>>> data['d'] = 4 # data.put("d", 4)
>>> data['d'] # data.get("d")
4
>>> len(data) # data.size()
4
>>> 'c' in data # data.containsKey("c")
True
>>> del data['c'] # data.remove("c")
>>> 'c' in data # data.containsKey("c")
False
>>> data
{a=1, b=2, d=4}
>>> len(data) # data.size()
3
Although this resembles the usage of a traditional python dictionary, the methods you’d expect in a
dictionary are not all available. This is a Map
object after all and it has the methods of the
Map
class. However, it is easy to get see the parallels among some of the most used methods.
dict method |
Map method |
---|---|
.keys |
.keySet |
.values |
.values |
.clear |
.clear |
.items (gives 2-tuples) |
.entrySet (gives Entry objects with .key and .value ) |
.update |
.putAll (accepts dict as well as a Map ) |
The dict
builtin can be called on the Map
object to get a python-style dictionary, if needed.
Additionally, just like a python dictionary, calling list
(or set
) on the Map
object gives a
list
(or set
) of the keys in the Map
.
Using for
loops to iterate over Map
s yields the keys in the Map
, which is consistent with how
for
loops work with python dictionaries.
for key in data:
print key, data[key]
Prints the following:
a 1
b 2
d 4
In python, the .items
method returns each entry as a tuple
which lets us write the for loop like
the following:
# !!! Only works if `data` is a python-style dictionary, not if it is a `Map`.
for key, value in data.items():
print key, value
But unfortunately, since Map
doesn’t have the .items
method, this is not possible. However, we
can use the .entrySet
method to construct something slightly similar.
for entry in data.entrySet():
print entry.key, entry.value
To iterate over the values of a Map
, since the method is called .values
in both dict
and
Map
, the same piece of code would work with any object.
for value in data.values():
print value
Empty Map
objects are treated as False
in boolean contexts, just as with python’s dictionaries.
Collections¶
The two main collection types in Python are list
and set
. The equivalents in java are the
interfaces List
and
Set
. Let’s prepare some data for
our examples.
java.util.List<String> planets = new java.util.ArrayList<>();
planets.add("Mercury");
planets.add("Venus");
planets.add("Earth");
java.util.Set<String> colors = new java.util.HashSet<>();
colors.add("White");
colors.add("Black");
colors.add("Red");
colors.add("Green");
colors.add("Blue");
The getitem syntax can be used with List
s seamlessly:
>>> planets[0]
u'Mercury'
>>> planets[1]
u'Venus'
The slicing syntax, returns List
s of the same type, not python-style list
s.
>>> planets[:2]
[Mercury, Venus]
>>> type(_) # `_` is a variable set to the return value of last expression.
<type 'java.util.ArrayList'>
>>> planets[::-1]
[Earth, Venus, Mercury]
>>> type(_)
<type 'java.util.ArrayList'>
However, the getitem syntax is not supported for Set
s as it doesn’t make sense there since
Set
s are unordered collections. But the operator support available for set
s in python are
available with Java Set
objects as well.
>>> 'Red' in colors
True
>>> len(colors)
5
The for
loop can be used on any
Collection
type objects to
iterate over the object’s contents.
>>> for x in planets:
... print x
...
Mercury
Venus
Earth
>>> for x in enumerate(planets):
... print x
...
(0, u'Mercury')
(1, u'Venus')
(2, u'Earth')
Here’s equivalents for some of the methods available in Java’s Collection
s and Python’s collection
types.
Java | Jython |
---|---|
Collection.add |
list.append / set.add |
Collection.addAll |
list.extend / set.update (Prefer list + list or set.union ) |
Collection.contains |
in list or in set |
Collection.isEmpty |
bool(list) or bool(set) (Can be used directly in a boolean context) |
Collection.size |
len(list) or len(set) |
Empty Collection
s are treated as False
in boolean contexts, just as with python’s collections.
Java Arrays¶
Just as Java’s List
is mirrored in Python with list
, Java’s arrays are mirrored using the array
structure available in Jython’s array
module.
That official documentation is quite exhaustive on this topic, so I suggest going over it to get an
idea of handling arrays in Jython.
The Iteration Protocol¶
Java’s Iterator
style
iteration is supported by Jython’s for
statements. For example, consider the following Java
Iterator
that’s trying to emulate a small fraction of Python’s range
function:
package ssk.experiments;
import java.util.Iterator;
public class RangeIterator implements Iterator<Integer> {
private Integer current = 0, max;
public RangeIterator(int max) { this.max = max; }
@Override
public boolean hasNext() { return current < max; }
@Override
public Integer next() { return current++; }
}
Since classes are instantiated without a new
keyword in Python, combined with the fact that
Jython’s for
statement supports Java’s Iterator
s, we can use the above in the following way:
from ssk.experiments import RangeIterator
for n in RangeIterator(5):
print n
This gives the following output:
0
1
2
3
4
Since Jython’s for
statement supports iterating over Java’s
Enumeration
type, the
above same for
loop would work with a RangeEnumeration
class as defined below:
package ssk.experiments;
import java.util.Enumeration;
public class RangeEnumeration implements Enumeration<Integer> {
private Integer current = 0, max;
public RangeEnumeration(int max) { this.max = max; }
@Override
public boolean hasMoreElements() { return current < max; }
@Override
public Integer nextElement() { return current++; }
}
Jython seamlessly handles the getting of an instance of an Iterator
from a Java
Iterable
. This is actually
how the for
statement works with the List
and Set
collections discussed earlier (Collection
is a sub-interface of Iterable
).
Patching Java Classes¶
In Python, new methods and attributes can be added to existing classes. This comes from the dynamic nature of the programming language and the runtime. The JVM is also a dynamic runtime, but the Java language doesn’t allow us to modify existing classes. This is where Jython comes in. Jython lets us add and override methods on existing Java classes. Although this is seldom needed, this can illustrate the extent of Jython’s integration with the JVM.
Here’s a Java class:
package ssk.experiments;
import java.util.List;
public class Country {
private String name;
public Country(String name) { this.name = name; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
There’s nothing fancy with the above class. It’s a regular class with one property with a .get
and
.set
methods. Now, let’s add a new method to this class.
from ssk.experiments import Country
def upcase(self):
self.name = self.name.upper()
Country.upcase = upcase
# Create a `Country` object and call `upper_name` method.
largest_country = Country('Russia')
largest_country.upcase()
print largest_country.name
This would print RUSSIA
, as expected.
Note that this is an advanced feature and should be used with caution. In almost all cases, it is probably a better idea to modify the original Java class definition directly. But when that is not an option, creating a simple Python function that works with these objects should be considered. Modifying existing classes should only be used as a last resort.
Operator Overloading¶
One nice and practical case for adding methods on existing Java classes is to leverage Python’s
support for operator overloading with Java classes. One good example for this is with the
BigDecimal
class. Mathematical operations on objects of BigDecimal
are provided as individual
methods like .add
, .subtract
etc. We can add operator support (in Jython) for these objects
by adding the appropriate methods to the BigDecimal
class.
For instance, here’s how we can add support for the +
operator:
from java.math import BigDecimal
BigDecimal.__add__ = lambda self, other: self.add(other)
print BigDecimal(42) + BigDecimal(10)
This would print 52
, as expected. More methods can be added to support all the mathematical
operators such as __sub__
for subtraction and __mul__
for multiplication etc. The full list of
such method names can be found on the official data model documentation
page.
Conclusion¶
This is not intended to be an exhaustive guide to what Jython can do. I hoped to give you a taste of how well Jython handles inter-op with Java and hopefully I’ve helped you write better Python - Java inter-op code. Thank you and any suggestions and feedback are very welcome.