前言
纠结了一个多星期,写一个Gradle教程还是打算先从 Groovy 基础讲起,虽说现在可以直接使用 kotlin,但毕竟大部分还是 Groovy。
因为因为已经有很多 Groovy 教程,所以不打算每个都讲一遍,与 Java 和 Kotlin 相比,罗列了11个不同点,希望你会喜欢~
简介
Groovy 是一个:
- 基于 Java
- 具有静态类型和静态编译功能
- 简洁高效
- 支持DSL
- 既能面向对象、又能纯粹用于脚本
的动态语言。
如果你是 Java 的使用者,那么恭喜你,你可以无缝接入Groovy,如果你还是一个 Kotlin
的使用者,那么我要再次恭喜你,你基本上可以起飞了,下面开始我们的学习之旅。
1. 分号可以省略
和 Kotlin 一样,Groovy 中的分号是可以省略的:
class Student {
static void main(String[] args){
println "Hello World!"
}
}
Java 要求一行代码结束的时候要求 ;
结尾。
2. 弱类型定义方式
Groovy 和 Java 不太一样,它有两种变量定义方式:
- 强类型定义方式
- 弱类型定义方式
强类型定义方式 指的是我们声明变量的时候,同时声明变量类型,比如像这样:
private String name
弱类型定义方式 需要使用关键字 def
:
Student {
static void main(String[] args){
// 弱类型定义方式
def name1 = "JiuXinDev"
println name1
// 强类型定义方式
String name2 = "Chen88"
println name2
}
}
def
给予了我们在一开始不需要声明变量类型的权利,等到运行时再做判断,这与 Java 中的编译时静态类型是完全不一样的。
我们知道,Kotlin 中的 var
和 val
也不需要声明变量类型,那它是否跟 groovy
中的 def
是否一样呢?
答案是不一样,因为 Kotlin 是静态类型语言,但是 Kotlin 具有类型推断的能力,所以它的类型在编译时就已经确定。
3. 范围
学习过 Kotlin 的同学应该对范围很熟悉了,Groovy 与之对应的类是 Range
,我们可以这么使用:
class Student {
static void main(String[] args){
def range = new IntRange(0,10);
println range;
}
}
输出
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Groovy 可以使用 ..
操作符表达 Range
,所以使用起来会更加方便:
class Student {
static void main(String[] args){
def r1 = 0..10;
println r1;
def r2 = 10..0;
println r2;
def r3 = 'a'..'z';
println r3;
}
}
输出:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
[a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z]
不仅可以操作数字,还可以操作字符,结合字符串和列表,使用起来更加优雅。
4. 字符串
Groovy 中的字符串可以使用单引号、双引号和三引号三种。
# 单引号
单引号的使用方式跟 Java 一致:
Student {
static void main(String[] args){
String name = 'JiuXinDev';
int age = 25;
println "name: " + name + ", age: " + age;
}
}
输出:
name: JiuXinDev, age: 25
# 双引号
双引号跟单引号相比,多了一个插值的功能,其实跟 Kotlin 中的字符串使用一致:
Student {
static void main(String[] args){
String name = 'JiuXinDev'
int age = 25
println "name: $name, age: $age"
}
}
输出:
name: JiuXinDev, age: 25
使用方式就是把 ${变量}
插在字符串中。
# 三引号
三引号的场景不多, 可以帮助我们保持输出格式:
class Student {
static void main(String[] args){
String str = '''冲天香阵破长安,
满城尽带黄金甲。
''';
println str;
}
}
输出:
冲天香阵破长安,
满城尽带黄金甲。
避免了换行符的使用。
# 字符串索引
和 Kotlin 一样,Groovy 有一个范围运算符,用 ..
表示,比如要表示 0 到 10,可以用 0..10
表示。代码:
class Student {
static void main(String[] args){
String str = "Hello World";
println str[1];
println str[0..4];
}
}
输出:
e
Hello
除了以上的特点,Groovy 还为字符串提供了其他的一些方法,感兴趣的同学可以去官网看一下。
5. 访问控制
Groovy 和 Java 一样,有三种访问修饰符,public
、private
和 protected
。
在不添加默认修饰符的情况下,Java 的默认访问权限是包访问权限,Groovy 和 Kotlin 一样,默认的访问权限是 public,包括类、方法和成员变量。
6. 相等性判断
Groovy 中的 ==
操作符等价于使用 equals
方法,该方法可以复写,地址比较请用 is
方法:
class Name{
String name;
static void main(String[] args){
def n = new Name()
n.name = "JiuXinDev"
def b = new Name()
b.name = "JiuXinDev"
assert n == b // 通过
assert n.is(b) // 不通过
}
@Override
boolean equals(Object obj) {
return obj instanceof Name && name == obj.name
}
}
其他文章说使用 ==
操作可以避免空指针异常,但是我在 Groovy 3.0.6
版本上直接使用 equals
方法也没有抛出异常。
7. Null处理
# 安全调用运算符 ?.
通过使用安全调用运算符 ?.
把一次 NULL 检查和一次方法调用合并成一个操作 :
class Name{
static void main(String[] args){
String s = null
println s?.length()
}
}
表达式 s.length()
等价于 if(s != null) s.length()
,不过,由于 s
为 null
,所以输出的也是 null
。当然了,这个调用符也可以访问成员变量。
# Elvis 操作符
Elvis 操作符 ?:
是一种特殊的三元操作符,借助它,你可以简化三元表达式:
class Student {
static void main(String[] args){
String a = null
// def result = a != null ? a : "name"
def resultA = a ?: "name"
println resultA
String b = "haha"
def resultB = b.length() > 5 ?: "JiuXinDev"
println resultB
}
}
对于一次赋值,可以将 def result = a != null ? a : "name"
简化成 def resultA = a ?: "name"
。
# 一切皆可转化为Boolean
任何为 null
、Void
的对象,或者等同于 0 或者为 null
的值,都会被解析为 false
,反之则为 true
。
所以一般对 String
判断可以从 str != null && str.length() > 0
可以改为 !str
。
8. 方法
方法也可以用 def
修饰符。
# 返回值
当我们声明了返回值类型,但是没有显示的声明 return
语句,默认使用最后一行作为返回语句。
class Student {
static void main(String[] args){
println getNum(5);
}
static def getNum(int a){
if(a > 2){
1;
}else {
0;
}
}
}
# 方法参数
当你使用 def
修饰参数的时候,那么你的 def
可以省略:
class Student {
static void main(String[] args){
println getNum(5);
}
static def getNum(a){
if(a > 2){
1;
}else {
0;
}
}
}
多个参数的时候,我们需要保证参数的名称不能一致。
我们也可以给参数设置默认参数,像这样:
class Student {
static void main(String[] args){
println getMax(5);
}
static def getMax(a,b=2){
if(a > b){
1;
}else {
0;
}
}
}
我看 w3c 上说,如果使用非默认和默认参数,则必须注意,默认参数应在参数列表的末尾定义。我直接将默认参数定义在头部,非默认参数定义在尾部也没出现什么问题。
不加具体类型的参数可以让代码看着更加简洁,但是对于后期维护的同学来说,可能一点都不友好,因此规定好开发规范还是很有必要的。
# 省略括号
在顶级表达式中,可以省略括号,比如我们在之前多次使用的 println
方法。
class Student {
static void main(String[] args){
println 2;
println 2*2
// println doSomeThing 2; 运行失败
println doSomeThing(2) * doSomeThing(2);
println doSomeThing(2);
// 使用闭包
callSomeThing {
println "Hello World!";
};
}
static int doSomeThing(int value){
return value + value;
}
static void callSomeThing(Closure c){
c.call();
}
}
在 println
后面,你去调用 2
、2*2
、函数等都是没问题的,但是你如果再使用一个省略括号的表达式,比如 doSomeThing 2
就是不行的,除了普通方法以外,另外一个常见场景就是使用闭包,我会在下文介绍。
9. 列表
Groovy 中的 List
的使用方法基本跟 Java 一致,不过,Groovy 增加了一些自己的Api,比如:
class Student {
static void main(String[] args){
def list1 = [2,3,4,6,9,12];
def list2 = [2,7];
// plus 两个集合合并,生成一个新的集合
println list1.plus(list2);
// minus 第一个集合减去和第二个集合相交的部分
println list1.minus(list2);
// 通过索引取值
println list1[1];
// 通过范围修饰符取值
println list1[1..3];
// 通过 << 往列表中增加新的值
list1 << 99
println list1
}
}
这段代码中涉及了 plus
、minus
、范围和 <<
操作符的时候,方法说明我已经写在注释里了,使用风格和 Kotlin 中的列表比较相似。
10. 特征
除了接口和类,Groovy中还有一个新的东西,它叫做特征,你可以理解其为具有默认实现和状态的类,或者具有多继承能力的类,需要使用 implements
关键字,它可以实现接口。
class Student {
static void main(String[] args){
def developer = new SeniorDevelop();
developer.drinkWater();
developer.name = "小李";
developer.writeCode();
}
}
interface Person{
void drinkWater();
}
trait Man implements Person{
void drinkWater(){
println "一天8杯水";
}
}
trait Coder{
String name = "小王";
void writeCode(){
println "一天200行代码";
}
}
class SeniorDevelop implements Man,Coder{
void writeCode(){
println getName() + "一天可以写500行代码";
}
}
SeniorDevelop
实现了特征 Man
和 Coder
,并且可以复写特征里面的方法和使用特征中的属性,输出:
一天8杯水
小李一天可以写500行代码
11. 闭包
Groovy 中的函数式编程称为闭包。
# 第一个闭包
闭包是一个匿名代码块:
class Name{
static void main(String[] args){
def printHelloWorld = { println "Hello World!" }
// 1. 直接使用 闭包名()
printHelloWorld()
// 2. 使用call方法 闭包名.call()
printHelloWorld.call()
}
}
调用方法有两种,一种直接调用,另外一种是使用 call
方法。
# 加入形参
如果有参数呢?
class Name {
static void main(String[] args) {
def greet = { name -> println "Hello, this is $name" }
greet("QiDian")
greet.call("JiuXin")
}
}
在有参数的情况,我们需要先声明形参,然后在调用处的括号里面加上实参即可。
# 引入变量
在闭包中,还可以引入变量,包括方法中的局部变量和类中的变量:
class Name {
static void main(String[] args) {
def company = "YueWen"
def greet = { name -> println "Hello, this is $name, it is from $company" }
greet("QiDian")
}
}
输出:
Hello, this is QiDian, it is from YueWen
# 用作参数
闭包可以被当作参数传递:
class Name {
static void main(String[] args) {
def clo = { String name, int level ->
int salary = level * 10000
println "$name salary is $salary yuan!"
}
calculateSalary(clo)
}
static void calculateSalary(Closure closure) {
closure.call("Chen", 3)
}
}
前提是你知道闭包中有哪些参数。
# with方法
Groovy 中的这个 with 方法跟 Kotlin 中的 apply
方法类似,它使用了闭包:
class Name {
String firstName;
String secondName
static void main(String[] args) {
def name = new Name()
name.with {
firstName = "乘"
secondName = "风"
println "$firstName $secondName"
}
}
}
在 with
方法中的这个闭包,我们可以直接使用 firstName
这个成员变量,也可以使用内部中的 public
方法。
# 委托
上述 with
方法之所以能够调用对象中的成员变量和方法,是因为它改变了委托策略。
闭包中的委托有三个重要的概念,它们分别是 this
\ owner
\ delegate
。它们的区别是:
-
this
:闭包定义处外部的类或者这个类对象。 -
owner
:闭包定义处外部的类或者类对象或者外层的闭包对象 -
delegate
:可以是任何对象,默认owner
我们以一个简单的demo为例:
class Name {
static void main(String[] args) {
def staticClo = {
println "staticMethod this: " + this.toString()
println "staticMethod owner: " + owner.toString()
println "staticMethod delegate: " + delegate.toString()
}
staticClo.call()
def name = new Name()
name.with {}
name.doSomeThing()
}
def clo = {
println "class this: " + this.toString()
println "class owner: " + owner.toString()
println "class delegate: " + delegate.toString()
def innerClo = {
println "innerClo this: " + this.toString()
println "innerClo owner: " + owner.toString()
println "innerClo delegate: " + delegate.toString()
}
innerClo.call()
}
void doSomeThing(){
clo.call()
}
}
输出:
staticMethod this: class Name
staticMethod owner: class Name
staticMethod delegate: class Name
class this: Name@6ef888f6
class owner: Name@6ef888f6
class delegate: Name@6ef888f6
innerClo this: Name@6ef888f6
innerClo owner: Name$_closure1@5223e5ee
innerClo delegate: Name$_closure1@5223e5ee
在静态方法中,闭包中的 this
、owner
和 delegate
都一样,指的是 Name
这个 class。在成员变量中,this
、owner
和 delegate
也一样,指的是当前的 Name
对象。最后我们在闭包中又定义了一个闭包,this
指的是 Name
对象,而 owner
和 delegate
指的是外部闭包对象。
虽然 delegate
和 owner
默认是一致的,但是我们可以更改 delegate
:
class Person {
String name
String level
def clo = {
println "$name - $level"
}
static void main(String[] args) {
def p1 = new Person()
p1.name = "XiaoWang"
p1.level = "1"
def p2 = new Person()
p2.clo.delegate = p1
p2.clo.resolveStrategy = Closure.DELEGATE_FIRST
p2.clo.call()
}
}
输出:
XiaoWang - 1
我们没有给 p2
的 name
和 level
设置任何字符串,只是因为我们给闭包 clo
更改了 delegate
,仅仅更改了 delegate
不能达到效果,还需要更改委托策略,委托策略有:
- OWNER_FIRST:默认策略,先调用
owner
中的方法和属性,没有,调用delegate
。 - DELEGATE_FIRST:先调用
delegate
中的方法和属性,没有,再调用owner
。 - OWNER_ONLY:仅获取
owner
中的方法和属性。 - DELEGATE_ONLY:仅获取
delegate
中的方法和属性。 - TO_SELF:需要自定义
Closure
子类,自定义解析策略。
我们将 OWNER_FIRST
修改为 DELEGATE_FIRST
,这样,p2
中闭包 clo
的 delegate
指向了 p1
,就可以优先使用 p1
的成员变量 name
和 level
了。
总结
整体而言,Groovy 的语言像是 Java 和 Kotlin 的结合体,学习成本也不算特别大。
网友评论