第4章 Java和Groovy集成
Groovy最大的卖点之一是它与Java的无缝集成。在本章中,我们将以各种方式探讨这种集成。我们将使用普通的旧Groovy对象(POGOs)作为普通旧Java对象(pojo)的完全替代。我们将从Java调用Groovy代码,从Groovy调用Java代码。最后,我们将探索如何使用Ant来编译整个项目,包括Groovy和Java类的健康组合。
4.1 Groovy的对象
package org.davisworld.bookstore
class Book{
String title
String author
Integer pages
}
正如我们在第16页的1.1节Groovy中所看到的,这就是POGO的全部内容。 Groovy将JavaBeans归结为纯粹的本质。
Packaging(打包)
在此示例中,您会注意到的第一件事是包装。 您可能永远不需要打包Groovy脚本,但是Groovy类的打包方式与Java类的打包方式相同。 (有关编写Groovy脚本的更多信息,请参见第86页的命令行中的第5章,Groovy。)在Java开发人员看来,唯一看起来奇怪的是缺少分号。 (正如我们在第42页的3.2节,可选分号中所讨论的,您可以根据需要将其重新添加。)
Public Classes, Private Attributes, Public Methods
// in Groovy:
class Book{
String title
String toString(){
return title
}
}
// in Java:
public class Book{
private String title;
public String toString(){
return title;
}
}
如果您未提供访问修饰符(公共,私有或受保护的),则Groovy中的类隐式是公共的
。 在Java中,如果不另行说明,则类是包私有的
。 如果您在两种语言之间来回移动时不注意,这可能是一个严重的“陷阱”。 (有关更多信息,请参见下页的侧栏。)
如果您不提供访问修饰符,则Groovy中的属性将隐式私有
。 您可以通过一些内省来证明这一点:
println Book.getDeclaredField("title")
===> private java.lang.String Book.title
Groovy中的方法默认情况下是公共的
。 这是证明:
println Book.getDeclaredMethod("toString")
===> public java.lang.String Book.toString()
那么,Groovy开发人员对包私有访问有什么反对意见呢?没什么,真的。他们的目标是默认允许类做正确的事情,而包私有访问是一个不幸的附带损害。
花一分钟想想你最近从事的主要Java项目。您有多少带有私有属性的公共POJOs? 在这里,您可能可以在此处安全地使用“ 80/20规则”,但是如果我按了您的要求,则最终可能会达到90%或更高。带有私有属性的公共类是编写的绝大多数Java代码,Groovy的智能默认设置反映了这一业务现实。
::: alert-info
Gotcha: No Package-Private Visibility(陷阱:没有包私有可见性)
在Java中,如果将访问修饰符保留在类,属性或方法之外,则意味着同一包中的其他类或另一个包中的直接子类可以直接访问它们。 这称为包私有访问。在Groovy中,没有访问修饰符的类被视为公共类。 没有访问修饰符的属性被视为私有。 没有访问修饰符的方法是公共的。 尽管可以说这种快捷方式对于主流用法更为有用,但是它代表了Java语义不同于Groovy语义的少数情况之一。
在Groovy中,无法为类,属性或方法包提供包私有
的可见性。 在Groovy中声明公共,私有和受保护元素的方式与在Java中声明方式相同。
∗. http://java.sun.com/docs/books/tutorial/java/javaOO/accesscontrol.html
:::
4.2 自动生成的Getter和Setter
class Book{
String title
}
Book book = new Book()
book.setTitle("Groovy Recipes")
println book.getTitle()
===> Groovy Recipes
尽管没有如此明显的public和private修饰符会稍微减少类的大小,但是真正自动产生getter和setter的才是真正的区别。 默认情况下,POGO中的每个属性都会获得一个匹配集。
再回想一下您的上一个Java项目。你是精心手工制作了每个getter和setter,还是让IDE生成了代码?
如果这段代码是死记硬背的,并且没有什么意思,那么就让Groovy编译器代替您的IDE来为您生成它,可以极大地减少项目中的视觉混乱。而且,如果您碰巧覆盖了默认值
getter或setter的行为,看看你的眼睛是如何被规则的例外吸引的:
class Book{
String title
String author
Integer pages
String getTitle(){
return title.toUpperCase()
}
}
Getter and Setter Shortcut Syntax(Getter和Setter快捷语法)
class Book{
String title
}
Book book = new Book()
book.title = "Groovy Recipes"
//book.setTitle("Groovy Recipes")
println book.title
//println book.getTitle()
===> Groovy Recipes
Groovy减少视觉混乱的另一个方法是在处理类属性时允许的语法捷径。book.title
在幕后调用book.getTitle()
。这是一种使它感觉更自然的尝试——似乎是直接处理图书的标题,而不是调用返回字符串值的Book类的getTitle()
方法。(有关更多信息,请参见下一页的侧栏。)
遗留的Java的getter
和setter
语法在Groovy中仍然是完全有效的。
Suppressing Getter/Setter Generation(抑制生成Getter/Setter)
class Book2{
private String title
}
println Book2.getDeclaredField("title")
===> private java.lang.String Book2.title
println Book2.methods.each{println it}; "DONE"
// neither getTitle() nor setTitle() should appear in the list
在Groovy中显式地将字段标记为private会抑制相应的getter和setter方法的创建。如果您希望字段对Java类真正隐藏起来,那么这个小工具非常有用。
::: alert-info
Groovy语法快捷键
如第4.2节“Getter和Setter快捷语法”所示,在前一页,book.getTitle()
可以缩写为book.title
。虽然这个getter/setter
快捷方式是Groovy中的默认行为,但是在语言中有很多地方它被选择性地覆盖,以表示完全不同的意思。
在第3.15节,Map快捷方式,在62页,一个像书一样的调用。hashmap上的book.title
是book.get("title")
的快捷方式。在第7章解析XML中,在第116页,同样的调用是解析XML片段的一种快速方法,如<book><title>Groovy Recipes</title></book>
。在第10.8节中,调用不存在的方法(invokeMethod),在第193页,您将学习如何接受该调用并使用它做几乎任何您想做的事情。
我不认为这是一个陷阱;事实上,我认为它是一个强大的语言特性。但如果你没有预料到,它会让你猝不及防。
:::
但是其他Groovy类的可见性又如何呢?好吧,这段代码应该非常清楚,字段仍然是可访问的,尽管缺乏getter和setter方法:
def b2 = new Book2()
b2.title = "Groovy Recipes"
println b2.title
===> Groovy Recipes
Groovy在隐私方面存在一些问题——简而言之,它忽略了私有修饰符。(是的,这是一个相当大的问题。有关更多信息,请参阅第80页的侧栏。)
如果希望保护私有字段不受Groovy中意外修改的影响,可以添加一对不做任何事的getter
和setter
。将这些方法标记为私有将防止它们扰乱公共API。
class Book3{
private String title
private String getTitle(){}
private void setTitle(title){}
}
def b3 = new Book3()
b3.title = "Groovy Recipes"
println b3.title
===> null
4.3 getProperty和setProperty
class Book{
String title
}
Book book = new Book()
book.setProperty("title", "Groovy Recipes")
//book.title = "Groovy Recipes"
//book.setTitle("Groovy Recipes")
println book.getProperty("title")
//println book.title
//println book.getTitle()
===> Groovy Recipes
这个例子展示了在POGO上设置和获取属性的第三种方法–book.getProperty()
和book.setProperty()
。在传统Java中,调用book.getTitle()
是第二天性。正如我们在4.2节中讨论的,Getter和Setter快捷语法,在第72页,Groovy允许您将book.getTitle()
缩短为book.title
。但是,如果希望使用更通用的方法来处理类的字段,该怎么办呢?
Groovy借鉴了 java.lang.System
的技巧,它提供了一种访问类属性的通用方法。 如第5.8节“获取系统属性”(第92页)中所述,您无法进行诸如 System.getJavaVersion()
之类的方法调用。 您必须以更通用的方式要求系统属性- System.getPropery(" java.version")
。 要获取所有适当关系的列表,请输入 System.getProperties()
。 现在,通过 groovy.lang.GroovyObject
接口可以在每个类上使用这些通用方法。
是的,您总是可以使用java.lang.reflect
包来完成这种事情,但是Groovy使该语法易于使用。 一旦您开始更常规地处理元编程,与类进行交互的这种方式将与 book.getTitle()
或book.title
一样自然。 有关此内容的更多信息,请参见第10.2节,发现类的字段,第183页。
Property Access with GStrings(使用GStrings进行属性访问)
class Book{
String title
}
def b = new Book()
def prop = "title"
def value = "Groovy Recipes"
b."${prop}" = value
println b."${prop}"
===> Groovy Recipes
与getProperty
和setProperty
方法一样,还有一种更“出色”的方法来处理字段。您可以将字段的名称传递给GString以获得最大的灵活性。(有关GStrings的更多信息,请参见第3.13节,GStrings,第57页。)
4.4 使属性只读
class Book{
final String title
Book(title){
this.title = title
}
}
Book book = new Book()
book.title = "Groovy Recipes"
//===>
//ERROR groovy.lang.ReadOnlyPropertyException:
//Cannot set readonly property: title for class: Book
Book book2 = new Book("GIS for Web Developers")
println book2.title
//===>
//GIS for Web Developers
最终修饰符在Groovy和Java中的工作方式相同。 具体来说,这意味着只能在实例化类时才能设置属性。 如果您尝试在事实之后修改属性,则会引发 groovy.lang.ReadOnlyPropertyException
。
4.5 构造函数快捷语法
class Book{
String title
String author
Integer pages
}
Book book1 = new Book(title:"Groovy Recipes", author:"Scott Davis", pages:250)
Book book2 = new Book(pages:230, author:"Scott Davis",
title:"GIS for Web Developers")
Book book3 = new Book(title:"Google Maps API")
Book book4 = new Book()
Groovy为构造函数提供了便利,这是您在Java中从未见过的。 通过支持命名参数和变长参数列表,您可以按照自己认为合适的任何方式实例化类。 book1和book2演示了由于已命名变量,因此您可以按任何顺序提供它们。 book3演示了等式的vararg部分:在这种情况下,您只需输入标题即可。 book4演示了Groovy便捷方法中的任何一个都不会干扰默认的Java构造函数。
这个构造函数快捷方式的特别之处在于,它也可以在纯Java类中使用。构造函数行为是在运行时添加的,因此它既适用于Groovy也适用于Java类。有关这方面的实际演示,请参见第4.9节,从Groovy调用Java,见第81页。
4.6 可选参数/默认值
class Payment{
BigDecimal amount
String type
public Payment(BigDecimal amount, String type="cash"){
this.amount = amount
this.type = type
}
String toString(){
return "${amount} ${type}"
}
}
def pmt1 = new Payment(10.50, "cash")
println pmt1
//===> 10.50 cash
def pmt2 = new Payment(12.75)
println pmt2
//===> 12.75 cash
def pmt3 = new Payment(15.99, "credit")
println pmt3
//===> 15.99 credit
在本例中,除非显式提供另一个值,否则输入默认值“cash”。这简化了开发过程,不需要您维护两个单独的重载构造函数—一个只接受数量,另一个接受数量和类型。可选参数的真正好处在于,它们可以用于任何类型的方法。请考虑以下简化电影票购买的方法:
class Ticket{
static String buy(Integer quantity=1, String ticketType="adult"){
return "${quantity} x ${ticketType}"
}
}
println Ticket.buy()
println Ticket.buy()
println Ticket.buy(2)
println Ticket.buy(4, "child")
===>
1 x adult
1 x adult
2 x adult
4 x child
在本例中,单一方法提供了极大的灵活性。如果您在没有参数的情况下调用它,它将对所有内容使用智能默认值。下一个最有可能的场景(理论上)是两个人约会——代码允许您覆盖数量,同时仍然默认票证类型为“adult”。
在支付示例中,金额没有默认值。您需要提供它每次您创建一个新的付款。另一方面,如果没有提供,则默认为“cash”。可选参数必须总是在所有必需参数之后。可选参数也应该按重要程度排序——最可能更改的参数应该在列表的最前面,其次是最可能更改的参数,以此类推,最后是最不可能被覆盖的参数。
static String buy(Integer quantity=1, String ticketType="adult",BigDecimal discount=0.0)
//won't compile
Ticket.buy(0.15)
//will compile
Ticket.buy(1, "adult", 0.15)
考虑到新buy()方法中参数的顺序,您不可能在不指定所有三个值的情况下请求一张成人票的15%折扣。optionals列表中的级联重要性顺序表明,可以安全地忽略右边的参数,但是必须指定左边的参数。
4.7 私有方法
class Book{
String title
private String getTitle(){
return title
}
private void setTitle(String title){
this.title = title
}
private void poke(){
println "Ouch!"
}
}
Book book = new Book()
// 注意,Groovy完全忽略了私有访问修饰符
book.title = "Groovy Recipes"
println book.title
===> Groovy Recipes
book.poke()
===> Ouch!
简单地说,Groovy不关心方法的私有访问修饰符。您可以像调用公共方法一样轻松地调用私有方法。(有关更多信息,请参见下一页的侧栏。)
私有方法不会显示在公共界面中。 也就是说,当您调用Book.methods.each {println it}
时, poke()
不会出现。 您知道 poke()
可用的唯一方法是,如果您前面有源代码。
Java尊重private修饰符。 在Java中实例化时,不能通过常规方式调用poke()。
4.8 从Java调用Groovy
public class BookstoreJava implements Bookstore {
private Book b; // written in Groovy
private Publisher p; // written in Java
public Book makeBook() {
b = new Book();
b.setAuthor("Scott Davis");
b.setTitle("Groovy Recipes");
b.setPages(250);
return b;
}
public Publisher makePublisher() {
p = new Publisher();
p.setName("Pragmatic Bookshelf");
return p;
}
}
此时,您可能正在寻找Book是用Groovy实现的,而Publisher是用Java实现的证据。这就是重点!一旦用Groovy编写的类被编译,它看起来与用Java编写的类没有什么不同。自动生成的getter和setter(第4.2节,自动生成的getter和setter,在第71页)与Java实现的getter和setter没有什么区别。这使得Groovy成为JavaBeans的完美替代品。
::: alert-info
陷阱:Groovy忽略私有修饰符
如第78页的第4.7节私有方法所示,Groovy允许您像调用公共方法一样轻松地调用类的私有方法。正如第4.2节(抑制Getter/Setter生成)中所演示的,在第72页,Groovy允许您像访问公共字段一样访问私有字段。
底线是Java尊重私有访问修饰符;Groovy不。Java是敲你前门的邻居,即使它知道你把钥匙藏在哪里。Groovy是一个让自己进来借一杯糖,然后在厨房桌子上给你留一张纸条的邻居。当我第一次使用Groovy时,这是我发现的最令人不安的(咳咳)特性。在最好的情况下,忽略私人修饰语似乎是不礼貌的。在最坏的情况下,这可能是完全危险的。
也许是Groovy对隐私的漫不经心的态度让我最初感到不舒服。很容易调用一个私有方法,你会想,“这一定是个bug。”当然,您也可以通过使用java.lang.reflect
绕过Java中的私有修饰符。但是由于某些原因,在Java中调用私有方法似乎更加谨慎。在Java中,您必须有意识地去调用私有方法。你必须知道你在做什么。我们远离了Java的老路——毫无疑问,我们正在做一些不同寻常的事情。
虽然Groovy中缺乏隐私的问题仍然偶尔困扰着我,但在实践中这并不是什么大问题。私有方法不会出现在公共接口中,所以通常我知道私有方法存在的唯一方法是打开源代码。如果我有那么多机会去上课,我就有责任不把事情搞砸。按照同样的思路,在为单元测试准备类时,访问私有方法和字段实际上非常有用,特别是如果编写的类不是很容易测试的话。
Bjarne Stroustrup曾有句著名的话:“ C可以很容易地将自己拍到脚上; C ++使它变得更难,但是当您这样做时,它会使您全力以赴。”有些人可能会说,在private methods
的案例中,Groovy让炸掉整条腿变得更容易了。我个人对这个问题的看法更务实一些:我宁愿要一把更锋利的手术刀和一个训练有素的外科医生,也不愿要一把更钝的刀片。开发人员有责任明智地使用这个特性。
:::
使用纯Java实现中必须使用的一小部分代码就可以获得相同的行为。这段代码唯一需要做的事情就是编译Groovy类(我们将在下一页的Groovy联合编译器4.11节中讨论),并且在$GROOVY_HOME/embeddable
中找到的单个Groovy JAR位于类路径的某个地方。
4.9 从Groovy调用Java
class BookstoreGroovy implements Bookstore{
Book b // written in Groovy
Publisher p // written in Java
Book makeBook(){
b = new Book(author:"Scott Davis", pages:250, title:"Groovy Recipes")
}
Publisher makePublisher(){
p = new Publisher(name:"Pragmatic Bookshelf")
}
}
在第79页的第4.8节“从Java调用Groovy”中,我们看到Groovy类在从Java运行时看起来就像Java类。在这个例子中,您可以看到Java类在从Groovy运行时看起来就像Groovy类。即使Publisher是用Java编写的,您仍然可以使用Groovy中提供的很酷的构造函数快捷方式(第4.5节,构造函数快捷语法,见第76页)。
4.10 Groovy和Java中的接口
// Bookstore.java
public interface Bookstore {
public Book makeBook();
public Publisher makePublisher();
}
// BookstoreGroovy.groovy
class BookstoreGroovy implements Bookstore{...}
// BookstoreJava.java
public class BookstoreJava implements Bookstore {...}
您在这里看到的是Groovy与Java无缝集成的另一个例子。Bookstore接口是用Java编写的。如前所述,Book是用Groovy编写的,Publisher是用Java编写的。这个接口可以很好地处理这两个类。
现在看看BookstoreGroovy。它是用Groovy编写的,但是它能够像BookstoreJava一样轻松地实现Bookstore(用Java编写)。
这段代码唯一需要做的事情就是编译您的Groovy类(我们将在第4.11节,Groovy联合编译器中对此进行讨论),并且在$GROOVY_HOME/embeddable
中找到的单个Groovy JAR位于类路径的某个地方。
4.11 Groovy联合编译器
// compile Groovy code
$ groovyc *.groovy
// compile Java code
$ javac *.java
// compile both Groovy and Java code
// using groovyc for the Groovy code and javac for the Java code
$ groovyc * -j -Jclasspath=$GROOVY_HOME/embeddable/groovy-all-1.5.0.jar:.
毫不奇怪,groovyc将Groovy源代码编译成字节码,就像javac编译Java源代码一样。然而,Groovy编译器增加了一个更微妙但非常有用的特性:使用单个命令联合编译Java和Groovy代码的能力。
满足依赖关系
为了理解groovyc的功能,让我们更深入地研究一下javac的生命周期。在javac编译您的代码之前,它必须满足所有的依赖项。例如,让我们尝试编译Bookstore接口:
$ javac Bookstore.java
// Bookstore.java
public interface Bookstore {
public Book makeBook();
public Publisher makePublisher();
}
javac要做的第一件事就是查找Book 和Publisher。没有它们,Bookstore就不可能被编辑。因此,javac在类路径中搜索"Book.class"和"Publisher.class"。它们可能存储在JAR中,也可能只是随意放置,但是如果javac能够在已经编译的状态中找到它们,那么它就可以继续编译Bookstore。
如果javac找不到"Book.class"或"Publisher.class",然后查找"Book.java"和"Publisher.java"。如果能够找到源代码,它将代表您编译它们,然后继续编译Bookstore。明白了吗?
好的,那么Groovy代码是如何在这个过程中产生干扰的呢?不幸的是,javac只知道如何编译Java代码。有几种可插入的编译器可用来管理许多不同类型的源代码——GNU GCC编译器[^411]就是一个很好的例子。遗憾的是,javac不是其中之一。如果它找不到Book.class or Book.java,它放弃了。在我们的例子中,如果Book是用Groovy编写的,javac会这样说:
$ javac Bookstore.java
Bookstore.java:2: cannot find symbol
symbol : class Book
location: interface Bookstore
public Book makeBook();
^
1 error
在这个简单的示例中,变通办法是“嘿,医生,这样做时会很痛”。 由于javac不会为您编译Groovy代码,因此请尝试先编译"Book.groovy",然后再编译"Bookstore.java":
$ groovyc Book.groovy
$ javac Bookstore.java
$ ls -al
-rw-r--r-- 1 sdavis sdavis 5052 Dec 10 17:03 Book.class
-rw-r--r--@ 1 sdavis sdavis 60 Dec 10 16:57 Book.groovy
-rw-r--r-- 1 sdavis sdavis 169 Dec 10 17:03 Bookstore.class
-rw-r--r--@ 1 sdavis sdavis 93 Dec 10 16:56 Bookstore.java
-rw-r--r-- 1 sdavis sdavis 228 Dec 10 17:03 Publisher.class
-rw-r--r--@ 1 sdavis sdavis 48 Dec 10 16:58 Publisher.java
世界一切都很好,对吧? 您将"Book.groovy"编译为字节码,这使javac可以在没有抱怨的情况下编译"Bookstore.java"。 (请注意,Publisher.java与Bookstore.java是免费编译的。)
尽管对于简单的项目,手动管理Groovy/Java依赖关系链是可行的,但是如果您拥有依赖于"依赖Groovy类的Java类的"Groovy类,它很快就会成为噩梦。
一个命令,两个编译器
$ groovyc * -j -Jclasspath=$GROOVY_HOME/embeddable/groovy-all-1.5.0.jar:.
由于无法帮助javac编译Groovy,因此您可以使用groovyc来实现此功能。 但是请不要误解,groovyc不会编译Java代码。 通过将-j
标志传递给编译器,它向编译器发出信号,使其将Javac用于Java代码,并将groovyc用于Groovy代码。 使用每种语言的本机编译器,您将获得两种语言之间的依赖关系解析的所有好处。
小写的-j标志打开联合编译。 您可以包括多个大写的-J标志,以将标准标志传递给javac编译器。 此示例确保javac可以通过传入classpath参数来找到Groovy JAR。 如果未设置CLASSPATH环境变量,则必须使用classpath标志。 如果您在类路径中没有Groovy JAR,则Java代码将无法针对Groovy类进行编译。
在此示例中,您告诉javac生成与Java 1.4兼容的类:
$ groovyc * -j -Jclasspath=$GROOVY_HOME/embeddable/groovy-all-1.5.0.jar:. -Jsource=1.4 -Jtarget=1.4
4.12 用Ant编译项目
<taskdef name="groovyc"
classname="org.codehaus.groovy.ant.Groovyc"
classpathref="my.classpath"/>
<groovyc
srcdir="${src}"
destdir="${dest}"
classpathref="my.classpath"
jointCompilationOptions="-j -Jsource=1.4 -Jtarget=1.4" />
很高兴知道您可以从命令行编译Groovy代码(Groovy Joint Compiler,第4.11节,第82页),但是大多数项目都使用Ant。 幸运的是,Groovy在这种情况下提供了Ant任务。
为了避免taskdef步骤,将Groovy JAR从 $GROOVY_HOME/embeddable
放到 $ANT_HOME/lib
目录中。文章来源:https://www.toymoban.com/news/detail-472071.html
4.13 使用Maven编译项目
http://mojo.codehaus.org/groovy
虽然Groovy不提供Maven 2.0开箱即用的支持,但是Mojo项目提供了这种支持。有一个Maven插件允许您联合编译Groovy代码(详细信息请参阅第82页的第4.11节,Groovy联合编译器)。还有一个Maven原型插件,它为您的Groovy项目生成一个框架。文章来源地址https://www.toymoban.com/news/detail-472071.html
到了这里,关于Groovy食谱: 第4章 Java和Groovy集成的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!