SpecialistOff.NET / Вопросы / Статьи / Фрагменты кода / Резюме / Метки / Помощь / Файлы

Назад

Controlling Java from Jython


Метки: python

There’s a tremendous amount that you can accomplish by controlling Python from Java. But one of the amazing things about Jython is that it makes Java classes almost transparently available from within Jython. Basically, a Java class looks like a Python class. This is true for standard Java library classes as well as classes that you create yourself, as you can see here:

# Jython/JavaClassInPython.py
# Using Java classes within Jython
# run with: jython.bat JavaClassInPython.py
from java.util import Date, HashSet, HashMap
from Jython.javaclass import JavaClass
from math import sin

d = Date() # Creating a Java Date object
print(d) # Calls toString()

# A "generator" to easily create data:
class ValGen:
    def __init__(self, maxVal):
        self.val = range(maxVal)
    # Called during 'for' iteration:
    def __getitem__(self, i):
        # Returns a tuple of two elements:
        return self.val[i], sin(self.val[i])

# Java standard containers:
jmap = HashMap()
jset = HashSet()

for x, y in ValGen(10):
    jmap.put(x, y)
    jset.add(y)
    jset.add(y)

print(jmap)
print(jset)

# Iterating through a set:
for z in jset:
    print(z, z.__class__)

print(jmap[3]) # Uses Python dictionary indexing
for x in jmap.keySet(): # keySet() is a Map method
    print(x, jmap[x])

# Using a Java class that you create yourself is
# just as easy:
jc = JavaClass()
jc2 = JavaClass("Created within Jython")
print(jc2.getVal())
jc.setVal("Using a Java class is trivial")
print(jc.getVal())
print(jc.getChars())
jc.val = "Using bean properties"
print(jc.val)

Todo

rewrite to distinguish python generator from above description, or choose different name.

Note that the import statements map to the Java package structure exactly as you would expect. In the first example, a Date() object is created as if it were a native Python class, and printing this object just calls toString().

ValGen implements the concept of a “generator” which is used a great deal in the C++ STL (Standard Template Library, part of the Standard C++ Library). A generator is an object that produces a new object every time its “generation method” is called, and it is quite convenient for filling containers. Here, I wanted to use it in a for iteration, and so I needed the generation method to be the one that is called by the iteration process. This is a special method called __getitem__(), which is actually the overloaded operator for indexing, ‘[ ]‘. A for loop calls this method every time it wants to move the iteration forward, and when the elements run out, __getitem__() throws an out-of-bounds exception and that signals the end of the for loop (in other languages, you would never use an exception for ordinary control flow, but in Python it seems to work quite well). This exception happens automatically when self.val[i] runs out of elements, so the __getitem__() code turns out to be simple. The only complexity is that __getitem__() appears to return twoobjects instead of just one. What Python does is automatically package multiple return values into a tuple, so you  still only end up returning a single object (in C++ or Java you would have to create your own data structure to accomplish this). In addition, in the for loop where ValGen is used, Python automatically “unpacks” the tuple so that you can have multiple iterators in the for. These are the kinds of syntax simplifications that make Python so endearing.

The jmap and jset objects are instances of Java’s HashMap and HashSet, again created as if those classes were just native Python components. In the for loop, the put() and add() methods work just like they do in Java. Also, indexing into a Java Map uses the same notation as for dictionaries, but note that to iterate through the keys in a Map you must use the Map method keySet() rather than the Python dictionary method keys().

The final part of the example shows the use of a Java class that I created from scratch, to demonstrate how trivial it is. Notice also that Jython intuitively understands JavaBeans properties, since you can either use the getVal() and setVal() methods, or assign to and read from the equivalent val property. Also, getChars() returns a Character[] in Java, and this automatically becomes an array in Python.

The easiest way to use Java classes that you create for use inside a Python program is to put them inside a package. Although Jython can also import unpackaged java classes (import JavaClass), all such unpackaged java classes will be treated as if they were defined in different packages so they can only see each other’s public methods.

Java packages translate into Jython modules, and Jython must import a module in order to be able to use the Java class. Here is the Java code for JavaClass:

// Jython/javaclass/JavaClass.java
package Jython.javaclass;
import java.util.*;

public class JavaClass {
  private String s = "";
  public JavaClass() {
    System.out.println("JavaClass()");
  }
  public JavaClass(String a) {
    s = a;
    System.out.println("JavaClass(String)");
  }
  public String getVal() {
    System.out.println("getVal()");
    return s;
  }
  public void setVal(String a) {
    System.out.println("setVal()");
    s = a;
  }
  public Character[] getChars() {
    System.out.println("getChars()");
    Character[] r = new Character[s.length()];
    for(int i = 0; i < s.length(); i++)
      r[i] = new Character(s.charAt(i));
    return r;
  }
  public static void main(String[] args) {
    JavaClass
      x1 = new JavaClass(),
      x2 = new JavaClass("UnitTest");
    System.out.println(x2.getVal());
    x1.setVal("SpamEggsSausageAndSpam");
    System.out.println(Arrays.toString(x1.getChars()));
  }
}

You can see that this is just an ordinary Java class, without any awareness that it will be used in a Jython program. For this reason, one of the important uses of Jython is in testing Java code [2]. Because Python is such a powerful, flexible, dynamic language it is an ideal tool for automated test frameworks, without making any changes to the Java code that’s being tested.