通过比照 Python 与 Java 虚拟机和库创建类地方法,以及它们如何使用解释器,Uche Ogbuji 向 Java 开发人员介绍了 Jython 2.1。通过提供 Java 库访问的样本,以及 Jython 解释器 shell 和代码文件,Uche 说明了其中的差别。
最初 Jython 称为 JPython,是一个十足的 Java 应用程序,它允许开发人员使用 Python 编程语言的语法和大多数特性。Java 程序员对 Jython 感兴趣的原因有几个:
- Python 解释器 shell 的 Jython 版本可以对某些想法和 API 作便利的实验和研究,而不必经历一般的 Java 编译/运行周期。
- Python 被设计成动态且通用的,因此您不必通过使用复杂的库(诸如那些用于 Java 反射和内省的库)来添加这些特性。这使某些开发更简单,而且它在自动测试框架中特别有用。
- 许多开发人员喜欢 Python 的语法和这种语言所带来的感觉;他们发现它是开发和维护 Java 应用程序的生产率更高得多的方法。
本文中,我提供了一些访问 Java 库、使用 Jython 解释器 shell 及显示 Jython 代码文件示例,以介绍 Jython 2.1,它是最新的发行版。要理解本文,您不必了解 Python,不过如果您计划进一步使用 Jython,而不是仅局限于了解本文的基本示例,那么就必须学习 Python 语言。
我使用的环境是 Red Hat 8.0(2.4.18 内核)和 J2SE 1.4.0。在 Jython 站点(请参阅 参考资料)上,查看特定于 Jython 平台的说明,以获取有关 Jython 用户可以选择的平台和 Java 环境的更多信息。
注:Jython 语言和 Java 语言都在 Java 运行时上操作。
Jython 入门
Jython 是作为包含安装程序的单个 Java 类文件而发布的。只要下载 jython-21.class
并将该文件放在 CLASSPATH
内,然后运行 java jython-21
。选择您要安装的组件(在示例中,我选择了所有缺省组件),接受其许可证(这是开放源码 BeOpen/CNRI 许可证)并指定安装目录,之后安装程序会完成其余安装。
如果安装时碰到问题,请参阅 Jython 网站上的安装信息页。对于 UNIX 平台,您可能想将选择的 Jython 安装路径添加到 PATH 环境变量。现在只要输入“jython”就可以运行交互式 PATH
:
清单 1. 运行 Jython shell
$ jython
Jython 2.1 on java1.4.0_01 (JIT: null)
Type "copyright", "credits" or "license" for more information.
>>>
>>>
提示符允许您输入命令并立即获得结果。在 Java 编程中,每个程序都必须至少定义一个类。清单 2 演示了一个完整的 Java 程序,它用于将一条消息写到屏幕:
清单 2. 完整的 Java 程序
class App { public static void main(String args[]) { System.out.println("I don't like spam!"); } }
JPython 将这些行减少为:
清单 3. Jython 减少了 Java 代码开销
>>> print "I don't like spam!" I don't like spam! >>>
print
关键字是重要的工具之一,特别是在交互式 shell 中,它会立即打印其参数,并随后向您返回 shell 提示符。不仅输入和调试的代码比较少,而且不必经历编译/运行周期就可以马上获得结果。可以一次打印多个值,并象下面显示的那样轻松使用简单的表达式:
清单 4. print 是一个重要的 Jython 工具
>>> print "one plus one is", 1+1 one plus one is 2 >>>
Jython 表达式类似于 Java 表达式。 1+1
的结果是一个整数,通过 print
它被强制转换成字符串,并被并置到由逗号定界符指定的初始字符串。
通过使用 Jython,您甚至不需要什么工具就可以访问标准 Java 库。以下示例访问了 java.util.Random
:
清单 5. 通过 Jython 访问标准 Java 库
>>> from java.util import Random >>> rng = Random() >>> i = rng.nextBoolean() >>> print i 1 >>>
Jython 的 import
关键字与 Java 语言版本的相似之处在于它使一个模块中的内容可以为其它模块所使用,但是语法和行为有所差别。
上面清单 5 中的示例使用了相关的 from
关键字以限制从 java.util
导入哪些符号。其后一行显示了 Random
类实例的创建。正如您所看到的,不需要 new
关键字。
也不需要对保存新实例的变量进行任何类型说明。这强调了 Jython 的一个重要简化,而且这是其动态本性的一个优点 ― 您不必再担心数据类型定义了。
清单 5 中的下一行演示了方法调用,这与 Java 语言完全相同,只是没有对结果进行类型声明。Java 代码中的 nextBoolean()
是布尔值。Jython 2.1 没有布尔类型(不过这可能会很快改变;Python 2.3 就添加了布尔类型),因此它替换为 0或 1这样的整数。类似地,要调用希望使用布尔值的 Java 方法,就要传递满足这些约束的整数值。
您也可以使用 import
关键字对导入的所有符号作全限定,如清单 6 所示:
清单 6. Import 对所有导入的符号名称作了全限定
>>> import java.util.Random >>> rng = java.util.Random() >>> print rng.nextFloat() 0.9567907452583313 >>>
Jython 的浮点值与 Java 语言中的完全相同。
直接在源文件中编写代码
Jython 解释器对于快速检查和作提示都很方便,但您不必在这其中完成所有工作 ― Jython 还允许您在源文件中编写代码,并随后运行该代码(虽然使用 Jython 时,编译步骤是可选的)。以下清单是一个独立 Jython 程序的示例:
清单 7. 模拟硬币投掷的样本 Jython 程序(保存在名为 listing7.py 的文件中)
from java.util import Random rng = Random() #This is a comment in Jython print "Flipping a coin..." if rng.nextBoolean(): print "Came up heads" else: print "Came up tails"
在解释如何运行该代码之前,让我们先解释一下代码。这个示例引入了 Jython 中的 if
语句,这是有些人对 Jython(及其先辈 Python)评论的首要方面之一。没有字符定界符标记出当 if
语句条件为真时要执行的代码块(Jython 中的条件不需要括起的圆括号,这与 Java 编程一样)。只是这些代码比周围的代码缩进一层。
Jython 代码块总是使用缩进进行标记,而不是使用其它标记,例如花括号。引入代码块的语句(例如 if
)以冒号作为结尾。Jython 的这个特性意味着在编写代码时必须小心,因为缩进代码的方式实际上可能会改变代码的含义。例如,清单 8a 产生的打印输出只有数字 3,因为它上面两个语句都属于其条件永远不为真的 if
块:
清单 8a. 缩进:只打印“3”
if 0: print "1" print "2" print "3"
如果我只更改其中一行的缩进,那么就会打印数字 2和数字 3:
清单 8b. 缩进:打印“2”和“3”
if 0: print "1" print "2" print "3"
缩进还必须一致,它必须与将代码组织成块的语句相关联,而且通常它还必须控制代码流。例如:
清单 8c. 缩进:语法错误
print "1" print "2" print "3"
这只会产生一个语法错误,因为没有任何控制语句要求将一个块与代码其余部分相分离。
使用缩进标记代码块是 Python 和 Jython 的更有争议的特性之一,但我认为这个问题常常被夸大了。毕竟,如果您遵循有关缩进的良好的编码标准,就不应该有这种问题。如果遵循了良好的编码缩进,那么机器会执行,同行评论家就无话可说,因此事实胜于雄辩。
此外,我知道当开发人员对这种语言使用一段时间后,没有谁会注意这种限制。适当缩进成为 Jython 的第二本性。缩进和语法之间的这种联系当然可能会引起以前未遇到过的错误,但是没有显式的定界符也消除了使用这些定界符的语言中的某些常见错误。
您可以不必编译就可运行 清单 7 中的文件(listing7.py),只需将该文件名作为 jython
命令的参数来调用,如下所示:
清单 9. 不编译就运行“硬币投掷”
$ jython listing7.py Flipping a coin... Came up tails $
在上个示例中, $
就是 UNIX shell 提示符,它非常象 Windows 系统上的 C:\>
。您还可以使用 jpythonc
命令将模块编译成 Java 字节码( .class
)文件,该命令允许您使用 java
或 jre
命令直接运行它。用这种方法编译的 Jython 模块有一些限制,但这个问题超出了本文的范围。
构建全局函数
即使 Java 语言不支持全局函数,您也可以用 Jython 轻松创建全局函数。您还可以定义全局变量(通常要设置常量,而不必为它们创建类包装器)。例如,看一下下面的清单:
清单 10. 全局函数以字符串形式返回一系列数字(保存在名为 listing10.py 的文件中)
START = 1 SPACER = " " def CounterString(length): buffer = "" for i in range(START, length): buffer = buffer + str(i) + SPACER return buffer print CounterString(10)
首先我们定义了两个全局变量 START
和 SPACER
,它们用作该程序的常量,其中一个是整数,而另一个是字符串。
接着我们使用 def
关键字定义了函数 CounterString
。该函数有一个称为 length
的整数参数。Jython 未显式检查该参数是否是整数,这一事实是 Jython 的动态特性的一个优点;但它同时也可能是一个缺点,因为某些类型错误只有在后面的 Java 编程中才能被捕获。
请注意函数特征符行以冒号结尾,从而引入了一个新块,它是通过使后续行缩进来标记的。这一新块的第一行将字符串 buffer 初始化为空字符串。对这个 buffer 进行操作以产生所期望的函数结果。
下一行创建了一个循环。Jython 的 for
语句与 Java 语言语句完全不同。在 Java 编程中,您可以设置初始和终止条件,以及每个循环步骤。Jython 的循环自始至终总使用一个特殊序列。该序列一般是一个列表,它是 Jython 的一种非常重要的数据类型。
由三个字符串组成的列表如下所示:
["a", b", "c"]
如果您想对从 1到 N 的数字作循环(如同我们这里所做的),那么可以使用函数 range()
,它返回给定范围内的数字列表。在交互式 Jython 提示符下做些实验应该会帮助您熟悉这个工具:
清单 11. range() 函数示例
>>> range(5) [0, 1, 2, 3, 4] >>> range(1, 5) [1, 2, 3, 4] >>> range(1, 10, 2) [1, 3, 5, 7, 9] >>> range(10, 1, -3) [10, 7, 4]
回过头看一下 清单 10, for
循环的每个迭代都作为一个代码块运行,该代码块从该函数体其余部分缩进一层。该块是将当前 buffer 并置到新数字的一行代码,首先使用 str()
函数(而不是 Java 编程中的 cast
)将新数字强制转换成字符串,随后追加一个分隔符。该循环终止后,返回最终的 buffer。该函数体之后的一行代码对它进行测试。Jython 同样允许您不使用任何特殊工具(如应用程序类上的 main
方法)就可以完成这个任务。清单 10 的输出显示如下:
清单 12. 清单 10 的输出
$ jython listing10.py 1 2 3 4 5 6 7 8 9
构建类与构建函数一样容易
用 Jython 创建类与创建全局函数一样容易。清单 13 提供了一个示例:
清单 13. 用户定义的类的简单示例(保存在名为 listing13.py 的文件中)
class Dog: def __init__(self, bark_text): self.bark_text = bark_text return def bark(self): print self.bark_text return def annoy_neighbors(self, degree): for i in range(degree): print self.bark_text return print "Fido is born" fido = Dog("Bow wow") print "Let's hear from Fido" fido.bark() print "Time to annoy the neighbors" fido.annoy_neighbors(5)
上述代码中,第一行命名该类,其定义完全是一个大的代码块。
定义的第一个方法是特殊的 初始化程序(类似于 Java 构造函数)。它总是命名为 __init__
,而且每当创建该类的新实例时就调用它。在 Jython 中,将正被调用(或在初始化程序的情况中,被创建)的当前实例显式声明为参数。传统上这个参数称为 self
。
在 Dog
初始化程序中, bark_text
参数是一个字符串,通过使用 self
将它存储为实例变量。在调用方法 bark()
时不采用任何显式参数,但仍须指定 self
。
方法 annoy_neighbors
确实采用了一个显式参数,它是除了 self
之外指定的另一个参数,并且它是狗为了烦扰邻居而叫嚷的次数。请注意代码运行时很容易进入深度嵌套,因此要进行缩进。在该类定义的方法 annoy_neighbors
定义内有一个循环块。以 print "Fido is born"
开始的代码再次演示了该类。清单 13 的输出类似如下:
清单 14. 清单 13 的输出
$ jython listing13.py Fido is born Let's hear from Fido Bow wow Time to annoy the neighbors Bow wow Bow wow Bow wow Bow wow Bow wow
把各种编程语言联系起来
本文中,我们仅简要描述了将 Jython 添加到 Java 编程库的优点:
- Jython 语言减少了执行任务所需的代码量。
- Jython 解释器允许您不编译就可以运行代码,从而有助于加速代码开发。
- 它允许您构建 Java 语言不支持的全局函数和变量。
- 它引入了动态的类型定义,同时在静态类型定义的虚拟机中使用类型推断和数据类型转换来正确操作。
- 它引入了泛型数据类型的使用(尽管诸如 Tiger 这样即将发布的 Java 版本也已引入了泛型类型)。
- 它允许开发人员轻松开发自动的测试框架。
通过一系列示例,我们还介绍了开发人员应该知道的语法和类型定义方面的某些差别,包括 Jython 中缩进的语法含义以及引入整数以替代当前不受支持的布尔类型。
使用 Jython 绝不是要您抛弃 Java 语言。Jython 会是一个非常方便的补充,对于快速检查和构造原型、测试以及处理它的方法比较适合的编码任务的选择很有用。