将Java引入Perl
在这篇文章中,我将展示如何使用Inline::Java
将Java代码引入Perl程序。我不会深入探讨Inline
或Inline::Java
的内部机制,但我将告诉你如何使Java类在程序或模块中可用。程序/模块的区别只在语法的一个小部分中很重要,我会指出这一点。
文章从要嵌入到Perl中的Java代码开始,然后展示了几种实现方法。首先,代码直接放置在Perl程序中。其次,代码放置在程序使用的模块中。最后,通过模块中的一个小型Perl代理来访问代码。
Java代码
考虑以下Java类
public class Hi {
String greeting;
public Hi(String greeting) {
this.greeting = greeting;
}
public void setGreeting(String newGreeting) {
greeting = newGreeting;
}
public String getGreeting() {
return greeting;
}
}
这个类仅用于演示。它的每个对象不过是传递给构造函数的字符串的包装。唯一的操作是该字符串的访问器。但通过这个,我们将学习到使用Perl从Java中需要的大部分知识。稍后,我们将添加一些功能,以展示如何处理数组。这并不像听起来那么有趣,因为Inline::Java
几乎总是不需要帮助就能完成所有工作。
程序
由于我们在讨论Perl,所以将我们的简单Java类引入Perl程序的方法不止一种。(词汇说明:有些人把Perl程序称为“脚本”。我尽量避免这样做。)在这里,我将展示最直接的方法。随后的章节将转向更间接的方法,这些方法在实际中更为有用。
不出所料,最直接的方法是最容易理解的。看看你是否能跟得上这个
#!/usr/bin/perl
use strict; use warnings;
use Inline Java => <<'EOJ';
public class Hi {
// The class body is shown in the Java Code above
}
EOJ
my $greeter = Hi->new("howdy");
print $greeter->getGreeting(), "\n";
上面的Java类是Java类,所以我省略了除类声明之外的所有内容。Perl代码只是简单地包装它,所以非常小。要使用Inline::Java
,请使用use Inline Java => code
,其中code
告诉Inline
代码在哪里。在这种情况下,代码是内联的(聪明的命名,对吧?)。注意,单引号上下文在这里是最安全的。还有其他方法可以包含代码;我们稍后会看到我最喜欢的方法。好奇的人可以查阅perldoc来了解所有其他方法。
一旦Inline::Java
完成了它的魔法——它非常神奇——我们就可以像使用Perl包一样使用Java的Hi
类。 Inline::Java
提供了几种构造Java对象的方法。我通常使用这里显示的方法;也就是说,我假装Java构造函数被调用为new
,就像许多Perl构造函数一样。为了纪念Java,你可能会说my $greeter = new Hi("howdy");
,但我通常避免这种间接的对象形式。你甚至可以用类名来调用构造函数,如my $greeter = Hi->Hi("howdy");
(或者,你甚至可以说病态的my $greeter = Hi Hi("howdy");
)。类方法就像构造函数一样访问,只是它们的名称是Java方法名称。实例方法通过对象引用调用,就像引用是一个Perl对象。
请注意,Inline::Java
为我们执行类型转换,因此我们可以将Java原始类型传递和接收适当的Perl变量。这适用于数组等。当你思考幕后发生了什么时,你会意识到这是一个多么神奇的工具。
模块
我经常说,大多数Perl代码都是从程序开始的。随着时间的推移,代码中好的部分,即可以重用的部分,会被提取到模块中。假设我们的问候器非常受欢迎,所以许多程序都想使用它。我们不希望在每个程序中包含Java代码(并且可能要求每个程序编译自己的类文件)。因此,我们想要一个模块。我的模块看起来很像之前的程序,但有两个特性。首先,我改变了Inline
查找代码的方式,这与代码是否在程序或模块中无关。其次,从除main
以外的任何包访问类方法需要仔细的命名,尽管这不是特别困难。
package Hi;
use strict; use warnings;
use Inline Java => "DATA";
sub new {
my $class = shift;
my $greeting = shift;
return Hi::Hi->new($greeting);
}
1;
__DATA__
__Java__
public class Hi {
// The class body is shown in The Java Code above
}
包以使用strict
和warnings
开始,就像所有优秀的包一样。使用use Inline
语句几乎和之前的一样,但代码存在于__DATA__
段而不是内联。请注意,当您将代码放入__DATA__
段时,您必须包含一个语言标记,以便Inline
能够找到它。对于每种语言通常有几个标记选项;我选择了__Java__
。这允许Inline
将多种语言粘接到一个源文件中。
构造函数是必需的,这样调用者就不需要知道他们正在与Inline::Java
接口。他们像对典型的包Hi
那样使用构造函数调用Hi->new("greeting")
。然而,模块的构造函数必须做一些工作来为调用者获取正确的对象。它首先检索参数,然后返回对不寻常的调用Hi::Hi->new(...)
的结果。第一个Hi
是用于Perl包的,第二个是用于Java类的;两者都是必需的。就像上一节的程序一样,构造函数有多种调用方式。我选择了带名字new
的直接方法。您也可以使用间接宾语形式,或者按类名调用方法。返回的对象可以像正常一样使用,所以我只是将它传递回调用者。所有实例方法都直接通过Inline::Java
传递,而不需要Hi.pm
的帮助。如果有类方法(在Java中用static
关键字声明的),我可能必须提供一个包装器,或者调用者必须对名称进行限定。这两种解决方案都不是特别困难,但我更喜欢包装器,以将调用者的努力保持在最低。这是我的典型懒惰。由于可能会有多个调用者,我必须为他们编写,所以我希望将任何困难的部分推入模块。
如果您需要为Perl听众调整Java对象的行为,您可以在Hi.pm
中插入例程来完成。例如,也许您想要一个更典型的Perl访问器,而不是在Java代码中使用get
/set
对。在这种情况下,您必须创建自己的真正的Perl对象,并通过它代理到Java类。这可能看起来像这样
package Hi2;
use strict; use warnings;
use Inline Java => "DATA";
sub new {
my $class = shift;
my $greeting = shift;
bless { OBJECT => Hi2::Hi->new($greeting) }, $class;
}
sub greeting {
my $self = shift;
my $new_value = shift;
if (defined $new_value) {
$self->{OBJECT}->setGreeting($new_value);
}
return $self->{OBJECT}->getGreeting();
}
1;
__DATA__
__Java__
public class Hi {
// Body omitted again
}
在这里,从Inline::Java
返回的对象,我将简称为Java对象,被存储在返回给调用者的基于散列的Hi2
对象的OBJECT
键中。在构造函数调用中,Perl包和Java类之间的区别很明显。Perl包先出现,然后是Java类,然后是要调用的类方法。
greeting
方法将$new_value
传入,这是如果调用者想要更改值时提供的。如果$new_value
被定义,greeting
将set
消息传递给Java对象。在任何情况下,它都会将当前值返回给调用者,就像Perl访问器通常那样。
纯代理
在上一个部分,我们看到了如何让Perl模块访问Java代码。我们还看到了如何让Perl模块在调用者的Perl对象期望和底层Java对象之间进行适配。在这里,我们将看到如何访问不能包含在Perl代码中的Java类。
Java 库非常多。这些库通常以编译形式分发给所谓的 .jar (java archive)文件。这是 Java 社区的良好设计,正如 Perl 社区使用模块也是好的设计一样。正如我们希望让 Hi
Java 类可供大量程序使用——因此将其放入模块中——Java 人员将可重用代码放入 .jars 中。(是的,Java 人员继承了 Unix 人员的糟糕双关语传统,这让我们有了像 yacc
、bison
、more
和 less
这样的名字。)
假设我们谦逊的问候器非常受欢迎,已经大幅扩展并打包成 .jar 文件以供全球使用。除非我们提供前面显示的适配器,否则调用者必须以类似 Java 的方式使用 .jar 文件中的 Perl 代码。因此,我现在将展示三段代码:1)扩展后的问候器,2)使用它的 Perl 驱动程序,以及 3)驱动程序可以使用的轻微适配 Perl 模块。
下面是扩展后的问候器;两个 Perl 部分将在稍后展示
import java.util.Random;
public class Higher {
private static Random myRand = new Random();
private String[] greetings;
public Higher(String[] greetings) {
this.greetings = greetings;
}
public void setGreetings(String[] newGreetings) {
greetings = newGreetings;
}
public String[] getGreetings() {
return greetings;
}
public void setGreeting(int index, String newGreeting) {
greetings[index] = newGreeting;
}
public String getGreeting() {
float randRet = myRand.nextFloat();
int index = (int) (randRet * greetings.length);
return greetings[index];
}
}
现在有多个问候语,因此构造函数接受一个 Strings
数组。对于整个问候语列表和单个问候语,都有 get
/set
对。单个 get
访问器随机返回一个问候语。单个 set
访问器接受要替换的问候语索引及其新值。
请注意,Java 数组是固定大小的;不要让 Inline::Java
误导你认为否则。它非常擅长让你认为 Java 与 Perl 一样工作,尽管事实并非如此。如果索引越界调用 setGreeting
,则会导致致命错误,除非被捕获。是的,你可以使用 eval
和 $@
变量捕获 Java 异常。
此驱动程序通过 Hi3.pm
使用扩展后的问候器
#!/usr/bin/perl
use strict; use warnings;
use Hi3;
my $greeter = Hi3->new(["Hello", "Bonjour", "Hey Y'all", "G'Day"]);
print $greeter->getGreeting(), "\n";
$greeter->setGreeting(0, "Howdy");
print $greeter->getGreeting(), "\n";
Hi3
模块(直接位于下面)提供了对 Java 代码的访问。我使用匿名数组调用了构造函数。数组引用也可以,但简单的列表不行。构造函数返回一个 Java 对象(至少对我们来说是这样的);其他调用只是提供了一些额外的示例。请注意,特别是 setGreeting
期望一个 int
和一个 String
。 Inline::Java
检查参数,并将它们强制转换为它所能提供的最佳类型。这几乎总是按预期工作。如果不这样做,您需要查看文档中的“CASTING”部分。
最后,这是 Hi3.pm
(看看 Perl 的力量和 Inline
开发者的工作)
package Hi3;
use strict; use warnings;
BEGIN {
$ENV{CLASSPATH} .= ":/home/phil/jar_home/higher.jar";
}
use Inline Java => 'STUDY',
STUDY => ['Higher'];
sub new {
my $class = shift;
return Hi3::Higher->new(@_);
}
1;
要使用隐藏在 .jar 中的类,我需要做三件事
- 确保在
CLASSPATH
中有 .jar 文件的绝对路径,在Inline
使用之前。一个放置得当的BEGIN
块可以完成这个任务。 - 使用
STUDY
而不是提供 Java 源代码。 - 将
STUDY
指令添加到use Inline
语句中。这告诉Inline::Java
查找命名类。在这种情况下,列表只有一个元素:Higher
。如果相应的类有 Java 包,则此列表中的名称必须是完全限定的。
构造函数只是通过 Inline::Java
调用 Higher
构造函数,就像我们之前看到的那样。
是的,这就是整个模块,全部 15 行。
如果您需要在您的调用者和 Java 库之间建立适配器,您可以将它放在 Perl 或 Java 代码中。我更喜欢在可能的情况下用 Perl 编写这样的适配器,遵循上一节中看到的计划。但有时这太痛苦了,我就求助于 Java。例如,粘合模块 Java::Build::JVM
使用 Java 和 Perl 适配器来简化与真正的 javac
编译器的通信。有关详细信息,请参阅 CPAN 上的 Java::Build
发行版。
自动编译的剖析:简要讨论
那么,Inline::Java
为我们做了什么?当它找到我们的 Java 代码时,它会将该代码复制到 .java 文件中,文件名与类名相匹配(javac
强制要求类名和文件名匹配)。然后,它使用我们的 Java 编译器来构建程序的编译版本。它将这个版本放入一个目录中,使用 MD5 校验和来确保只有在代码更改时才重新编译。
您可以在目录中查看它执行的操作。如果出现问题,它甚至会为您提供查找问题的提示。以下是对一些目录的简要浏览。首先,有一个基本目录。如果您不进行任何特殊操作,它将被称为 _Inline,位于您启动程序的当前工作目录下。如果您的主目录下有一个 .Inline 目录,所有 Inline
模块都将使用它。如果您在 use Inline
语句中使用 DIRECTORY
指令,则将使用其值。为了方便讨论,我将称这个目录为 _Inline。
在 _Inline 下有一个配置文件,描述了您可用的各种 Inline
语言。更重要的是,有两个子目录:build 和 lib。如果您的代码编译成功,则 build 目录将被清理。(这是默认行为;您可以在 use Inline
语句中包含指令来控制此行为。)如果不成功,则 build 目录将包含一个用于您程序的子目录,其名称中包含 MD5 校验和的一部分。该目录将包含 .java 文件中的代码和 javac 的错误输出 cmd.out。
成功编译的代码最终会出现在 lib/auto 中。实际的 .class 文件将出现在一个子目录中,该子目录的名称由类名和 MD5 校验和组成。通常,那里会有三个文件。.class 文件是正常的。其他文件描述了该类。.inl 文件包含类的 Inline
描述。它包含完整的 MD5 校验和,因此只有在代码更改时才需要重新编译代码。它还说明了代码的编译时间,以及有关当前安装的 Inline::Java
的许多其他信息。.jdat 文件是 Inline::Java
特有的。它列出了类中可用的方法的签名。Inline::Java
使用 Java 的反射系统(反射是 Java 中的符号引用术语)来查找这些签名。
另请参阅
有关 Inline
和 Inline::Java
以及其他内联模块的更多信息,请参阅它们的 perldoc。如果您想加入,请注册 [email protected] 邮件列表,该列表存档在 nntp.x.perl.org/group/perl.inline。
致谢
感谢 Brian Ingerson 的 Inline
和 Patrick LeBoutillier 的 Inline::Java
。这两个出色的模块为我节省了大量时间和痛苦。事实上,我怀疑如果没有它们,我不会有勇气在 Perl 中使用 Java。特别感谢 Patrick LeBoutillier,因为他花时间阅读这篇文章并纠正了一些错误(包括我在问候列表中未能包含“Bonjour”的错误)。
标签
反馈
这篇文章有什么问题吗?请帮助我们通过在 GitHub 上打开一个问题或拉取请求。