将Java引入Perl

在这篇文章中,我将展示如何使用Inline::Java将Java代码引入Perl程序。我不会深入探讨InlineInline::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
    }

包以使用strictwarnings开始,就像所有优秀的包一样。使用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被定义,greetingset消息传递给Java对象。在任何情况下,它都会将当前值返回给调用者,就像Perl访问器通常那样。

纯代理

在上一个部分,我们看到了如何让Perl模块访问Java代码。我们还看到了如何让Perl模块在调用者的Perl对象期望和底层Java对象之间进行适配。在这里,我们将看到如何访问不能包含在Perl代码中的Java类。

Java 库非常多。这些库通常以编译形式分发给所谓的 .jarjava archive)文件。这是 Java 社区的良好设计,正如 Perl 社区使用模块也是好的设计一样。正如我们希望让 Hi Java 类可供大量程序使用——因此将其放入模块中——Java 人员将可重用代码放入 .jars 中。(是的,Java 人员继承了 Unix 人员的糟糕双关语传统,这让我们有了像 yaccbisonmoreless 这样的名字。)

假设我们谦逊的问候器非常受欢迎,已经大幅扩展并打包成 .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 和一个 StringInline::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 中的类,我需要做三件事

  1. 确保在 CLASSPATH 中有 .jar 文件的绝对路径,在 Inline 使用之前。一个放置得当的 BEGIN 块可以完成这个任务。
  2. 使用 STUDY 而不是提供 Java 源代码。
  3. 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 语言。更重要的是,有两个子目录:buildlib。如果您的代码编译成功,则 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 中的符号引用术语)来查找这些签名。

另请参阅

有关 InlineInline::Java 以及其他内联模块的更多信息,请参阅它们的 perldoc。如果您想加入,请注册 [email protected] 邮件列表,该列表存档在 nntp.x.perl.org/group/perl.inline

致谢

感谢 Brian Ingerson 的 Inline 和 Patrick LeBoutillier 的 Inline::Java。这两个出色的模块为我节省了大量时间和痛苦。事实上,我怀疑如果没有它们,我不会有勇气在 Perl 中使用 Java。特别感谢 Patrick LeBoutillier,因为他花时间阅读这篇文章并纠正了一些错误(包括我在问候列表中未能包含“Bonjour”的错误)。

标签

反馈

这篇文章有什么问题吗?请帮助我们通过在 GitHub 上打开一个问题或拉取请求。