- 浏览: 14040 次
- 来自: ...
最新评论
grails学习笔记——Groovy与java的比较
1.支持函数式编程,不需要main函数
2.默认导入常用的包,包括:
4.支持对对象进行布尔求值
5.类不支持default作用域,且默认作用域为public
6.受检查类型异常(Checked Exception)也可以不用捕获
7.一些新的运算符
8.groovy中基本类型也是对象,可以直接调用对象的方法,如:
11.一些集合类型的语法甜头(Syntax sugar for lists, maps, and ranges)
List fruit = [
1.支持函数式编程,不需要main函数
2.默认导入常用的包,包括:
- java.io
- java.math
- java.net
- java.util
- groovy.lang
- groovy.util
4.支持对对象进行布尔求值
Type | Evaluates to false |
Boolean | false, Boolean.FALSE |
Object reference | null |
Number | 0 |
String, GString | Zero-length string |
Collection | Empty Collection |
Map | Empty Map |
Iterator | hasNext() returns false |
Enumeration | hasMoreElements() returns false |
java.util.regex.Matcher | find() returns false |
6.受检查类型异常(Checked Exception)也可以不用捕获
7.一些新的运算符
运算符 | 用法 | 相当于JAVA |
?. | f (obj?.value != null) { . . . } |
if (obj != null && obj.value != null) { . . . } |
?: |
String name = person.getName() ?: "<unknown>" | String name = (person.getName() != null) ? person.getName() : "<unknown>" |
*. |
List names = people*.name | List names = new ArrayList(); for(Iterator it = people.iterator();it.hasNext();){ People people = (People)it.next(); names.add(people.getName()); } |
<=> | public int compare(int i1, int i2) { return i1 <=> i2; } |
public int compare(int i1, int i2) { if (i1 == i2) return 0; else if (i1 < i2) return -1; else return 1; } |
== | String str = null assert str == null assert str != "test" str = "test" assert str != null assert str == "test" str += " string" assert str == "test string" |
相当与equals方法,如果左侧的对象实现了compareTo方法,则调用compareTo方法且返回0时,返回true.而对象的比较则要调用is方法,如: BigDecimal x = 0.234 BigDecimal y = x assert y.is(x) assert !y.is(0.234) assert y == 0.234 |
assert (-12345).abs() == 12345
但浮点运算是基于BigDecimal类assert 0.25 instanceof BigDecimal
assert 0.1 * 3 == 0.3
assert 1.1 + 0.1 == 1.2
assert 1 / 0.25 == 4
9.字符串的处理assert 0.1 * 3 == 0.3
assert 1.1 + 0.1 == 1.2
assert 1 / 0.25 == 4
- String对象和java类似,但没有character的概念,没有迭代每个字符的方法。
- 使用单引号定义普通字符串,双引号定义的字符串可以包含Groovy运算符,$符号则需要转义("\$"),如:
String name = "Ben"
String greeting = "Good morning, ${name}"
assert greeting == 'Good morning, Ben'
String output = "The result of 2 + 2 is: ${2 + 2}"
assert output == "The result of 2 + 2 is: 4"
String greeting = "Good morning, ${name}"
assert greeting == 'Good morning, Ben'
String output = "The result of 2 + 2 is: ${2 + 2}"
assert output == "The result of 2 + 2 is: 4"
- 还可以使用三个连续的"来定义多行字符串,如:
String getEmailBody(String name) {
return """Dear ${name},
Thank you for your recent inquiry. One of our team members
will process it shortly and get back to you. Some time in
the next decade. Probably.
Warmest and best regards,
Customer Services
"""
return """Dear ${name},
Thank you for your recent inquiry. One of our team members
will process it shortly and get back to you. Some time in
the next decade. Probably.
Warmest and best regards,
Customer Services
"""
}
- char类型的使用方法:
char ch = 'D'
assert ch instanceof Character
String str = "Good morning Ben"
str = str.replace(' ' as char, '+' as char)
assert str == "Good+morning+Ben"
10.as运算符,用于没有集成关系的类型间强制类型转换,如:assert ch instanceof Character
String str = "Good morning Ben"
str = str.replace(' ' as char, '+' as char)
assert str == "Good+morning+Ben"
assert 543667 as String == "543667"
assert 1234.compareTo("34749397" as int) < 0
可通过实现asType(Class) 方法来实现自定义的as行为,默认的方法包括:assert 1234.compareTo("34749397" as int) < 0
源类型 | 目标类型 | 说明 |
String | Number (int, double, Long,BigDecimal, and so on) | 转换成相应的数字类型,如果字符串不是数字,则抛出NumberFormatException |
List | Array | 将List转换成Array,如myList as String[] |
List 或 array | Set | 将List或array直接转换为setb |
任意对象 |
boolean | 使用groovy的布尔运算逻辑进行求值 |
Collection | List | 复制原始collection中的对象创建一个List,对象在List中的顺序由原collection的迭代顺序决定 |
String | List | 将字符串转换为每个字符的序列,并放到List中 |
- 从语言层面支持List\Map\Range类型,而不是通过SDK中的类
- 使用[]创建创建和初始化List、Map,如:
List myList = [ "apple", "orange", "lemon" ]
Map myMap = [ 3: "three", 6: "six", 2: "two" ]
assert 3 == [ 5, 6, 7 ].size()
Map myMap = [ 3: "three", 6: "six", 2: "two" ]
assert 3 == [ 5, 6, 7 ].size()
- List\Map支持数组风格的用法
List numbers = [ 5, 10, 15, 20, 25 ]
assert numbers[0] == 5 //获取List中的对象
assert numbers[3] == 20
assert numbers[-1] == 25 //逆序获取List对象
assert numbers[-3] == 15
numbers[2] = 3 //更新List对象
assert numbers[2] == 3
numbers << 30 //添加数据
assert numbers[5] == 30
Map items = [ "one": "apple",
"two": "orange",
"three": "pear",
"four": "cherry" ]
assert items["two"] == "orange" //从Map中获得对象
assert items["four"] == "cherry"
items["one"] = "banana" //更新Map中对象
assert items["one"] == "banana"
items["five"] = "grape" //增加对象到中
assert items["five"] == "grape"
assert numbers[0] == 5 //获取List中的对象
assert numbers[3] == 20
assert numbers[-1] == 25 //逆序获取List对象
assert numbers[-3] == 15
numbers[2] = 3 //更新List对象
assert numbers[2] == 3
numbers << 30 //添加数据
assert numbers[5] == 30
Map items = [ "one": "apple",
"two": "orange",
"three": "pear",
"four": "cherry" ]
assert items["two"] == "orange" //从Map中获得对象
assert items["four"] == "cherry"
items["one"] = "banana" //更新Map中对象
assert items["one"] == "banana"
items["five"] = "grape" //增加对象到中
assert items["five"] == "grape"
- 新的类型:Range
Range实现了java.util.List,可以作为List使用,并扩展了包含(..)和排除(..<)运算符
// an inclusive rangedef range = 5..8
assert range.size() == 4
assert range.get(2) == 7
assert range[2] == 7
assert range instanceof java.util.List
assert range.contains(5)
assert range.contains(8)
// lets use an exclusive range
range = 5..<8
assert range.size() == 3
assert range.get(2) == 7
assert range[2] == 7
assert range instanceof java.util.List
assert range.contains(5)
assert ! range.contains(8)
//get the end points of the range without using indexes
def range = 1..10
assert range.from == 1
assert range.to == 10
assert range.size() == 4
assert range.get(2) == 7
assert range[2] == 7
assert range instanceof java.util.List
assert range.contains(5)
assert range.contains(8)
// lets use an exclusive range
range = 5..<8
assert range.size() == 3
assert range.get(2) == 7
assert range[2] == 7
assert range instanceof java.util.List
assert range.contains(5)
assert ! range.contains(8)
//get the end points of the range without using indexes
def range = 1..10
assert range.from == 1
assert range.to == 10
List fruit = [
"apple",
"pear",
"lemon",
"orange",
"cherry" ]
for (int i in 0..<fruit.size()) { //Iterates through an exclusive range B
println "Fruit number $i is '${fruit[i]}'"
}
List subList = fruit[1..3] //Extracts a list slice C
12.一些省时的特性"pear",
"lemon",
"orange",
"cherry" ]
for (int i in 0..<fruit.size()) { //Iterates through an exclusive range B
println "Fruit number $i is '${fruit[i]}'"
}
List subList = fruit[1..3] //Extracts a list slice C
- 行末的分号(;)不是必须的。在没有分号的情况下,groovy计算一行如果是有效的表达式,则认为下一行是新的表达式,否则将联合下一行共同作为一个表达式。分隔多行的表达式,可以用/符号,如:
String fruit = "orange, apple, pear, " \
+ "banana, cherry, nectarine"
+ "banana, cherry, nectarine"
- 方法调用时的圆括号()不是必须的(但建议保留)。但在无参方法调用,或第一个参数是集合类型定义时还是必须的:
println "Hello, world!"
println()
println([1, 2, 3, 4])
println()
println([1, 2, 3, 4])
- 方法定义中的return语句不是必须的,没有return的情况下,将返回方法体中最后一行的值,如下面的方法返回value+1:
int addOne(int value) { value + 1 }
13.语言级别的正则表达式支持- 使用斜线(/)定义正则表达式,避免java中的多次转义,如"\\\\\\w"相当于/\\\w/。
- 如果要作为java中的Pattern对象使用,可以使用~符号表示,如:
assert ~"London" instanceof java.util.regex.Pattern
assert ~/\w+/ instanceof java.util.regex.Pattern
assert ~/\w+/ instanceof java.util.regex.Pattern
- 使用=~运算符进行匹配
assert "Speaking plain English" =~ /plain/
- 使用==~运算符进行精确匹配
assert !("Speaking plain English" ==~ /plain/)
assert "Speaking plain English" ==~ /.*plain.*/ - 捕获分组,如:
import java.util.regex.Matcher
String str = "The rain in Spain falls mainly on the plain"
Matcher m = str =~ /\b(\w*)ain(\w*)\b/
if (m) {
for (int i in 0..<m.count) {
println "Found: '${m[i][0]}' - " +
"prefix: '${m[i][1]}'" +
", suffix: '${m[i][2]}'"
}
}
输出:
Found: 'rain' - prefix: 'r', suffix: ''
Found: 'Spain' - prefix: 'Sp', suffix: ''
Found: 'mainly' - prefix: 'm', suffix: 'ly'
Found: 'plain' - prefix: 'pl', suffix: ''
14.简化的javabeanString str = "The rain in Spain falls mainly on the plain"
Matcher m = str =~ /\b(\w*)ain(\w*)\b/
if (m) {
for (int i in 0..<m.count) {
println "Found: '${m[i][0]}' - " +
"prefix: '${m[i][1]}'" +
", suffix: '${m[i][2]}'"
}
}
输出:
Found: 'rain' - prefix: 'r', suffix: ''
Found: 'Spain' - prefix: 'Sp', suffix: ''
Found: 'mainly' - prefix: 'm', suffix: 'ly'
Found: 'plain' - prefix: 'pl', suffix: ''
- 直接使用“.属性名”的方法代替getter,如:
Date now = new Date()
println "Current time in milliseconds: ${ now.time }"
now.time = 103467843L
assert now.time == 103467843L
println "Current time in milliseconds: ${ now.time }"
now.time = 103467843L
assert now.time == 103467843L
- 属性定义不需要setter/getter。未指定作用域的属性,groovy自动认为是private并生为其成setter/getter,也可以根据需要进行覆写。如下除了最后一个字段,都是属性:
class MyProperties {
static String classVar
final String constant = "constant"
String name
public String publicField
private String privateField
}
static String classVar
final String constant = "constant"
String name
public String publicField
private String privateField
}
- 简化bean的初始化,可以使用Map进行初始化,或键值对的方法,如
DateFormat format = new SimpleDateFormat(
lenient: false,
numberFormat: NumberFormat.getIntegerInstance(),
timeZone: TimeZone.getTimeZone("EST"))
lenient: false,
numberFormat: NumberFormat.getIntegerInstance(),
timeZone: TimeZone.getTimeZone("EST"))
- 可以使用属性的方式读取map:
Map values = [ fred: 1, peter: 5, glen: 42 ]
assert values.fred == 1
values.peter = 10
assert values.peter == 10
注:groovy将map的key作为字符串处理,除非是数字或者用圆括号包含。这里的fred就是字符串"fred",但引号不是必须的,只有在key包含空格、句点或其他不能作为Groovy标示符的字符存在时才需要。如果需要使用一个变量的值作为key,则使用圆括号,如 [ (fred): 1 ]。
15.groovy不具备的java特性assert values.fred == 1
values.peter = 10
assert values.peter == 10
注:groovy将map的key作为字符串处理,除非是数字或者用圆括号包含。这里的fred就是字符串"fred",但引号不是必须的,只有在key包含空格、句点或其他不能作为Groovy标示符的字符存在时才需要。如果需要使用一个变量的值作为key,则使用圆括号,如 [ (fred): 1 ]。
- 不能用单引号定义字符类型,但可以使用as运算符将一个字母的字符串转换为字符类型
- for循环中不能用逗号分隔多个运算符,如下面的代码是不允许的:
for (int i = 0, j = 0; i < 10; i++, j++) { ... }
- 不支持DO...WHILE循环,但可以使用while...for运算代替
- 不支持内部类和匿名类,但支持闭包和在一个文件中定义多个类
- 可以看作一个匿名方法定义,可以赋予给一个变量名、作为参数传递给方法调用、或者被方法返回。也可以想象为只有一个方法定义的匿名类。
- 闭包的语法{ <arguments> -> <body> },如:
List fruit = [ "apple", "Orange", "Avocado", "pear", "cherry" ]
fruit.sort { String a, String b -> a.compareToIgnoreCase(b) }
println "Sorted fruit: ${fruit}"
注:sort方法只有一个闭包类型的参数,省略了圆括号;闭包中使用了默认的return值 - 当没有参数传入时,仍然需要保留箭头的存在{-> ... }
- 只有一个参数传入时,可以省略箭头,隐式的创建一个it参数,引用当前对象,如:
[ "apple", "pear", "cherry" ].each { println it } - 可以将闭包赋予一个变量,如
Closure comparator = { String a, String b ->
a.compareToIgnoreCase(b)
}
List fruit = [ "apple", "Orange", "Avocado", "pear", "cherry" ]
fruit.sort(comparator)
println "Sorted fruit: ${fruit}"
assert comparator("banana", "Lemon") < 0 - 只有一个参数的闭包,可以不传入参数,运行时隐式的传入null参数
- 当闭包是一个方法的最后一个参数时,可以写在圆括号外面,如:
List list = [ 1, 3, 5, 6 ]
list.inject(0, { runningTotal, value -> runningTotal + value })
可以这样写:
assert 15 == list.inject(0) { runningTotal, value -> runningTotal + value }
便于闭包中具有多行时代码更加清晰 - 不要滥用闭包。当闭包作为一个属性时,不要在子类中覆写,实在需要这样做,使用方法。使用闭包也无法利用java中很多AOP框架的特性
- 动态的使用属性,如下的java代码:
public void sortPeopleByGivenName(List<Person> personList) {
Collections.sort(personList, new Comparator<Person>() {
public int compare(Person p1, Person p2) {
return p1.getFamilyName().compareTo(p2.getFamilyName());
}
} ) ;
}
可使用下面的代替,当需要使用其他字段比较时,不需要修改代码
def sortPeople(people, property) {
people.sort { p1, p2 -> p1."${property}" <=> p2."${property}" }
} - 将一个String作为属性或方法名进行调用,如:
peopleList.sort()
peopleList."sort"() - 动态类型(duck typing:"if it walks like a duck and talks like a duck, it’s probably a duck):运行期解析对象的属性和方法,允许在运行时增加对象的属性和方法而不修改源代码,因此可能出现调用未定义方法的情况。
- 动态编程带来的危险:
- 编译器不能检查到类型错误、方法或属性的错误调用,应该养成编写测试的习惯
- 难以调试,使用“单步跳入(step into)”经常进入一些反射中,使用“运行到光标处(run to cursor)”代替
- 动态的类型定义使代码难以阅读,使用良好的命名、注释,尽量明确定义变量类型,便于IDE检测ht potential type errors in the call潜在的错误。
- Collection/Array/String具有size()方法
- Collection/Array/String具有each(closure)方法,方便的进行遍历
- Collection/Array/String具有find(closure)、findAll(closure)方法,find返回第一个符合条件的对象,findAll返回所有符合条件对象列表,如:
def glen = personList.find { it.firstName == "Glen" }
- Collection/Array/String具有collect(closure)方法,对集合中每个对象执行一段方法后,返回结果集,如:
def names = [ "Glen", "Peter", "Alice", "Graham", "Fiona" ]
assert [ 4, 5, 5, 6, 5 ] == names.collect { it.size() }
- Collection/Array/String具有sort(closure)方法,包括:
一个参数的闭包,如:
def names = [ "Glen", "Peter", "Ann", "Graham", "Veronica" ]
def sortedNames = names.sort { it.size() }
assert [ "Ann", "Glen", "Peter", "Graham", "Veronica" ] == sortedNames
两个参数的闭包,如:
def names = [ "Glen", "Peter", "Ann", "Graham", "Veronica" ]
def sortedNames = names.sort { name1, name2 ->
name1.size() <=> name2.size()
}
assert [ "Ann", "Glen", "Peter", "Graham", "Veronica" ] == sortedNames - Collection/Array具有join(String)方法
def names = [ "Glen", "Peter", "Alice", "Fiona" ]
assert "Glen, Peter, Alice, Fiona" == names.join(", ") - File.text属性读取文件内容作为字符串返回
- File.size()方法返回文件的byte值,相当于File.length()方法
- File.withWriter(closure)方法,从文件创建一个Writer对象传给闭包,闭包执行完毕后,依赖的输出流自动安全关闭。另外还有若干with...方法查看文档
- Matcher.count返回相应Matcher的匹配数量
- Number.abs()方法,对数字求绝对值
- Number.times(closure)执行n次闭包,将当前执行的次数作为参数传给闭包
- 示例的XML:
<root>
<item qty="10">
<name>Orange</name>
<type>Fruit</type>
</item>
<item qty="6">
<name>Apple</name>
<type>Fruit</type>
</item>
<item qty="2">
<name>Chair</name>
<type>Furniture</type>
</item>
</root> - 处理程序
import groovy.xml.MarkupBuilder
import groovy.util.XmlSlurper
def file = new File("test.xml")
def objs = [
[ quantity: 10, name: "Orange", type: "Fruit" ],
[ quantity: 6, name: "Apple", type: "Fruit" ],
[ quantity: 2, name: "Chair", type: "Furniture" ] ]
def b = new MarkupBuilder(new FileWriter(file)) 创建MarkupBuilder对象 b.root {
动态调用root方法,但builder对象并没有该方法,把它作为一个新的XML对象的根节点,并且把方法名作为根节点名称
objs.each { o ->
item(qty: o.quantity) {
name(o.name)
type(o.type)
}
}
}
遍历集合,创建节点,其中item/name/type也是动态的方法,以方法名作为节点名,方法参数作为节点的属性
def xml = new XmlSlurper().parse(file)
使用XmlSlurper对象解析内存中的XML文件 assert xml.item.size() == 3
assert xml.item[0].name == "Orange"
assert xml.item[0].@qty == "10"
使用动态的属性名读取XML节点
使用@字符读取节点属性
println "Fruits: ${xml.item.findAll {it.type == 'Fruit'}*.name }"
println "Total: ${xml.item.@qty.list().sum {it.toInteger()} }"
- 使用地道的Groovy语法:尽可能使用groovy中简化后的语法风格,减少代码量
- 实验:使用groovy console或shell可以方便的实验groovy代码
- 尽可能使用方法,而不是闭包。方法易于理解,也利于和java交互
- 在方法签名中尽可能的使用确定的类型,便于代码阅读和IDE的错误检测。在使用动态类型时要有清晰完善的文档注释
相关推荐
4. GString:Groovy的GString(类似于Java的String)支持模板语法,使字符串拼接更直观。 5. 极简的语法:Groovy的语法比Java更加精简,例如,可以省略括号和分号。 Grails框架: Grails是建立在Groovy之上的全栈...
### Groovy和Grails配置方法 #### 一、Groovy与Grails简介 Groovy是一种强大的面向对象编程语言,它运行在Java平台上...希望本教程能够帮助初学者快速入门Groovy和Grails的使用,并为后续的深入学习打下坚实的基础。
### Groovy轻松入门——Grails实战基础篇 #### 搭建Grails环境及创建Grails Demo程序 **Groovy**是一种面向对象的编程语言,它运行于Java平台上,能够与Java代码无缝集成。而**Grails**则是一款基于Groovy的高性能...
2. **简洁语法**:Groovy的语法比Java更简洁,例如,可以省略分号和大括号,使得代码更具可读性。 3. **Groovy Shell和 Grape**:Groovy Shell允许交互式地执行Groovy代码,Grape是Groovy的依赖管理系统,用于自动...
Groovy结合了Python、Ruby和Smalltalk等语言的特性,同时保留了与Java的无缝集成能力,使得它在编写脚本、构建工具、Web应用以及企业级应用程序开发中都有广泛的应用。 **一、Groovy基础** 1. **语法简洁**: ...
Groovy的语法与Java类似,但更加简洁和灵活。例如,Groovy支持闭包,这使得函数式编程变得简单。在Grails中,你可以在控制器的行动(Actions)中使用闭包来处理请求逻辑。此外,Groovy的动态特性允许你在运行时添加...
Grails是一个基于Groovy语言的开源Web应用框架,它简化了Java开发流程,提供了丰富的功能和高效能。下面将详细阐述书中涉及的主要知识点: 1. **Grails框架基础**: - **Groovy语言**:Grails的基础是Groovy,一种...
### 关于《从新手到专家:Groovy与Grails入门》 #### 一、书籍概述 本书《Beginning Groovy and Grails, From Novice to Professional》由Christopher M. Judd、Joseph Faisal Nusairat 和 James Shingler共同编写...
2. **简洁的语法**:Groovy语法比Java更为紧凑,减少了不必要的括号和分号,使得代码更易于阅读和编写。例如,函数调用可以省略括号,条件语句可以写得更加简洁。 3. **集成Java**:由于Groovy与Java高度兼容,可以...
1. **Groovy语言基础**:Grails是基于Groovy语言的,因此了解Groovy的基本语法、动态特性以及它与Java的相似性和差异性是学习Grails的前提。 2. **Grails框架结构**:Grails遵循MVC架构,包括Controller(控制器)...
《Grails与Spring Boot的深度集成解析》 在现代Java开发领域,Grails和Spring Boot都是非常流行的框架。Grails作为Groovy语言构建的Web开发框架,以其强大的生产力和灵活的特性受到开发者的喜爱,而Spring Boot则...
《初学者指南:Groovy与Grails》一书通过一系列循序渐进的教程,帮助读者从Groovy的基础语法开始学习,逐步深入到Grails框架的高级应用。书中不仅讲解了Groovy和Grails的基本概念和原理,还提供了大量的实践案例和...
根据提供的文件信息,我们可以从《Groovy and Grails Recipes》一书中提炼出多个与Groovy语言及Grails框架相关的知识点。下面将详细阐述这些知识点。 ### Groovy编程语言 **1. Groovy简介** - **定义**:Groovy是...
1. **Groovy语言**: Grails 的核心就是Groovy语言,它是一种动态、类型安全的编程语言,与Java语法兼容,但更加简洁和灵活。Groovy使得开发者能够快速编写代码,提高开发速度。 2. **MVC架构**: Grails 遵循Model-...
《Grails学习笔记:监听器与环境配置》 在Grails框架中,监听器和环境配置是提升开发效率的关键元素。监听器允许开发者在特定事件发生时执行自定义操作,而环境配置则确保了不同环境下应用的正确运行。本文将深入...
Groovy是一种基于Java平台的动态、灵活...综上所述,“Groovy轻松入门—Grails实战基础篇”将带你走进Groovy和Grails的世界,通过深入学习这些知识点,你可以快速掌握这个强大的开发工具,从而高效地构建现代Web应用。
4. **集成Java**:Groovy 可以无缝地与Java代码交互,可以直接调用Java库和API。 **Grails 架构和核心组件** 1. **GORM (Grails Object Relational Mapping)**:GORM 提供了一种方便的方式来处理数据库操作,它...
1. **Groovy语言**:Grails是用Groovy编写的,Groovy是一种动态、灵活的Java平台语言,它提供了Python和Ruby等脚本语言的便利性,同时又保持了与Java的互操作性。Groovy语法简洁,支持闭包,使得编写代码更加直观。 ...
通过《Grails权威指南》,你可以学习到如何利用Grails的强大功能来开发高效、可扩展的Web应用,同时也了解到如何利用Groovy语言和Grails框架的最佳实践,提升你的开发技能。这本书深入浅出地讲解了Grails的各个方面...
4. **集成Java**:Groovy可以直接调用Java库,无缝地与Java代码集成,这对于已有Java项目扩展非常方便。 5. **GroovyShell和GroovyConsole**:提供交互式的开发环境,便于测试和调试Groovy代码。 Grails框架的优势...