Groovy之基本语法

ProjectDaedalus

共 7635字,需浏览 16分钟

 ·

2022-05-15 02:29

这里对Groovy的基本语法进行介绍

abstract.png

数字类型

不同于Java使用基本类型、引用类型进行区分。对于Groovy而言,其一切均是对象。虽然Groovy语法中保留并使用int、short、boolean这些关键字。但并不代表相应的数据类型是基本类型,其使用的依然是Java中相应基本类型的包装类型。示例代码如下所示

class NumberDemo {   

    static void testType1() {
        // Groovy 中的数字类型均使用Java中相应包装类型
        byte b1 = 1
        assert b1 instanceof Byte
        Byte b2 = 2
        assert b2 instanceof Byte
        // 通过as运算符强制类型
        def b3 = 3 as byte
        assert b3 instanceof Byte
        def b4 = 4 as Byte
        assert b3 instanceof Byte

        short s1 = 1
        assert s1 instanceof Short
        Short s2 = 2
        assert s2 instanceof Short

        int i1 = 1
        assert i1 instanceof Integer
        Integer i2 = 2
        assert i2 instanceof Integer

        long l1 = 1
        assert l1 instanceof Long
        Long l2 = 2
        assert l2 instanceof Long

        float f1 = 1
        assert f1 instanceof Float
        Float f2 = 2
        assert f2 instanceof Float

        double d1 = 1
        assert d1 instanceof Double
        Double d2 = 2
        assert d2 instanceof Double

        // 类似地对于布尔类型, Groovy boolean 同样使用Java Boolean进行包装
        boolean b6 = true
        assert b6 instanceof Boolean
        Boolean b7 = false
        assert b7 instanceof Boolean
    }
}

类似地,可以通过在数字后面添加相应的后缀来表示数字的类型。具体地如下所示

class NumberDemo {   
    static void testType2() {
        // 整型默认类型
        def num1 = 15
        assert num1 instanceof Integer

        def num2 = 15l
        def num3 = 15L
        assert num2 instanceof Long
        assert num3 instanceof Long

        // Integer.MAX_VALUE + 1
        def num4 = 2147483648
        //  Groovy会自动选择合适的整数类型
        assert num4 instanceof Long

        // 整数添加g/G后缀,使用Java BigInteger类型
        def num5 = 15g
        def num6 = 15G
        assert num5 instanceof BigInteger
        assert num6 instanceof BigInteger

        // 对于浮点数, Groovy默认使用BigDecimal类型
        def num7 = 7.7
        def num8 = 8.8g
        def num9 = 9.9G
        assert num7 instanceof BigDecimal
        assert num8 instanceof BigDecimal
        assert num9 instanceof BigDecimal

        def num10 = 5.55f
        def num11 = 6.66F
        assert num10 instanceof Float
        assert num11 instanceof Float

        def num12 = 7.77d
        def num13 = 8.88D
        assert num12 instanceof Double
        assert num13 instanceof Double
    }
}

前面提到既然数字是对象类型而不是基本类型的,那自然可以直接调用方法。这里就Groovy增强的方法进行实践,如下所示

class NumberDemo {   
    static void testMethod() {
        def msg1 = ""
        def num1 = 4
        // 对闭包执行指定次数
        num1.times { msg1 += "A" }
        assert msg1 == "AAAA"
        def msg2 = ""
        num1.times { e -> msg2 += "B$e" }
        assert msg2 == "B0B1B2B3"

        def msg3 = ""
        // 从3到5依次执行闭包
        3.upto(5) { num -> msg3 += num }
        assert msg3 == "345"

        def msg4 = ""
        // 从5到-2依次执行闭包
        5.downto(-2) { num -> msg4 += num }
        assert msg4 == "543210-1-2"

        def msg5 = ""
        // 从0.1按0.2的步长增长到1.1, 但不包括1.1
        0.1.step(1.10.2) { num -> msg5 += "H$num, " }
        assert msg5 == "H0.1, H0.3, H0.5, H0.7, H0.9, "
    }
}

字符串

Groovy中支持多种形式的字符串定义方式

单引号

直接利用单引号定义字符串,则其实际上是Java String类的实例

class StringDemo {
    static void main(args) {
        def x = 1
        /*********************** 单引号 ***********************/
        def str1 = 'str 1: $x'
        // 单引号字符串 是Java String的实例
        assert str1 instanceof String
        println("-------------------")
        // 由于不支持插值, 故会输出 str 1: $x
        println(str1)
    }
}

测试结果,如下所示

figure 1.jpeg

双引号

在Groovy中使用双引号定义字符串,则是有特殊含义的。即支持使用占位符${}进行插值,其中占位符的花括号{}在不引起歧义的前提下可以省略。具体地,字符串中如果不含占位符, 则其是Java String的实例;反正,则是Groovy GString的实例。示例代码如下所示,可以看到str2b中的x已经被替换为1了

class StringDemo {
    static void main(args) {
        def x = 1
        /*********************** 双引号 ***********************/
        def str2a = "str 2a: Hello"
        // 双引号字符串 中如果不含占位符, 则其是Java String的实例
        assert str2a instanceof String
        assert str2a == 'str 2a: Hello'
      
        def str2b = "str 2b: $x"
        // 双引号字符串 中如果含占位符, 则其是Groovy GString的实例
        assert str2b instanceof GString
        assert str2b == 'str 2b: 1'
    }
}

三重单引号

Groovy还支持通过三重单引号定义存在多行的字符串。由于其只是一个普通的Java String实例,故不支持通过占位符进行插值。由于在定义多行字符串过程中,字符串的任何缩进均会被视为有效的空格字符。故str3a变量中的字符串都需要顶格写。如果执意进行缩进,那么最后应该通过stripIndent方法移除指定数量的缩进

class StringDemo {
    static void main(args) {
        def x = 1
                /********************** 三重单引号 *********************/
        def str3a = '''str3a
line 1
$x
line 2
line 3'''

        // 三重单引号字符串 是Java String的实例, 不支持插值
        assert str3a instanceof String
        println("-------------------")
        println(str3a)

        // 使用 续航符\ 避免第一行出现换行符
        def str3b = '''\
            str3b
            hello'''

        println("-------------------")
        // 通过stripIndent方法移除指定数量的缩进
        println( str3b.stripIndent(12) )
    }
}

测试结果,如下所示

figure 2.jpeg

三重双引号

如果期望对多行字符串支持占位符进行插值,则可进一步通过三重双引号进行定义。类似地,字符串中如果不含占位符, 则其是Java String的实例;反正,则是Groovy GString的实例。示例代码如下所示

class StringDemo {
    static void main(args) {
        def x = 1
        /********************** 三重双引号 *********************/
        def str4a = """str4a
line 1
line 2
line 3"""

        // 如果不含占位符, 则其是Java String的实例
        assert str3a instanceof String
        println("-------------------")
        println(str4a)

        def str4b = """str4b
line 1
$x
line 3"""

        // 如果含占位符, 则其是Groovy GString的实例
        assert str4b instanceof GString
        println("-------------------")
        println(str4b)
    }
}

测试结果,如下所示

figure 3.jpeg

斜杠

字符串中如果存在一些特殊字符时,需要使用大量的反斜杠进行转义。为此Groovy提供一种通过斜杠定义字符串的方法。类似地,字符串中如果不含占位符, 则其是Java String的实例;反正,则是Groovy GString的实例。示例代码如下所示。显然此种方式十分适合用来定义、表示正则表达式

class StringDemo {
    static void main(args) {
        def x = 1
        // 斜杠表示字符串 Hello"12\World ,无需通过反斜杠进行转义
        def str5a = /Hello"12\World/
        // 如果不含占位符, 则其是Java String的实例
        assert str5a instanceof String
        // 可以看到如果使用双引号表示字符串时, 需要使用大量的反斜杠进行转义
        assert str5a == "Hello\"12\\World"

        def str5b = /a${x}c/
        // 如果含占位符, 则其是Groovy GString的实例
        assert str5b instanceof GString
        assert str5b == 'a1c'
    }
}

字符类型

这里对字符类型的字面量作一些补充说明,Groovy中没有专门用来表示单个字符的方式。故可以将只包含单个字符的字符串转换为字符。具体地转换方式如下述代码所示

class StringDemo {
    static void main(args) {
        // 此处实际上通过字符串强制转换为单个字符
        char c1 = "A"
        // 类似地对于字符类型, Groovy char 同样使用Java Character进行包装
        assert c1 instanceof Character

        // 此处实际上通过字符串强制转换为单个字符
        Character c2 = "B"
        assert c2 instanceof Character

        // 通过as运算符强制类型
        def c3 = "C" as char    
        assert c3 instanceof Character

        // 通过toCharacter方法转换为字符
        def c4 = "D".toCharacter()
        assert c4 instanceof Character

        // 强制类型转换
        def c5 = (char)'E'
        assert c5 instanceof Character
    }
}

表达式真值规则

对于Java而言,其要求表达式的结果必须是布尔类型才可以判断真/假。而在Groovy中表达式真值判定的限制被进一步放宽,其可以对任何一个表达式进行真值判定。下面就Groovy中的表达式的真值规则进行介绍

「布尔值可以直接 作为 真值结果」

// 布尔值true 表示 真
assert true
// 布尔值false 表示 假
assert !false

「布尔表达式计算的布尔值结果 作为 真值结果」

// 布尔表达式直接计算布尔值结果 作为 真/假
assert 2>1
assert !(2<1)

「对于数组、集合而言,非空为真、空为假」

def list1 = ["a"23]
def list2 = []
// 非空集合 表示 真
assert list1
// 空集合 表示 假
assert !list2

// 非空集合 表示 真
assert ["Ok":200"Error"404]
// 空集合 表示 假
def map1 = [:]
assert !map1

def array1 = [211,985as int[]
def array2 = [] as int[]
// 非空数组 表示 真
assert array1
// 空数组 表示 假
assert !array2

「对于字符串而言,非空为真、空为假」

// 非空字符 表示 真
assert 'A'
// 空字符 表示 假
assert !''

def str1 = "ABC"
def str2 = ""
// 非空字符串 表示 真
assert str1
// 空字符 表示 假
assert !str2

def str3 = "$str1"
def str4 = "$str2"
// 非空字符串 表示 真
assert str3
// 空字符 表示 假
assert !str4

「对于数字而言,非零为真、零为假」

// 非零 表示 真
assert 985
assert 3.14f
assert 3.14g
// 零 表示 假
assert !0
assert !0g
assert !0.0f
assert !0.0d

「对于引用而言,非NULL为真、NULL为假」

需要注意的是对于指向空集合的引用而言,即使非NULL,但根据集合的真值判定规则,空集合为假

def obj = new Object()
// 引用非null表示真
assert obj

def map2 = null
// 空指针null 表示 假
assert !map2

def map3 = new HashMap()
// 虽然引用map3不为null, 但其类型为集合
// 根据集合的真假判定规则, 空集合同样为假
assert !map3

「对于正则匹配而言,匹配成功为真、匹配失败为假」

// 正则匹配成功 表示 真
assert "Aaron" =~ /Aar/
// 正则匹配失败 表示 真
assert !("Aaron" =~ /Bob/)

异常

对于异常机制而言,Groovy与Java一样不仅支持传统的try/catch/finally语句,同样还支持资源自动管理ARM机制,即TWR语法。不同之处在于,Groovy方法无论是抛出CheckedException受查异常,还是抛出RuntimeException运行时异常。方法签名处的异常抛出声明均是可选的。示例如下所示,可以看出总体上与Java保持了一致

/**
 * Groovy 异常 示例
 */

class ExceptionDemo {

    static void main(String[] args) {
        def result1 = test1("Java 8 In Action")
        assert result1 =="JAVA 8 IN ACTION"

        /*********************** 支持传统的try/catch语句 ***********************/
        try {
            test1("Groovy In Action")
        } catch (e) {   // 如果期望捕获所有类型的异常, 异常类型可省略
            assert e instanceof FileNotFoundException
            assert e.getMessage() == "文件不存在异常"
        }

        /*********************** 支持传统的try/catch/finally语句 ***********************/
        def result2 = -1
        try{
            test2(null)
        } catch (NullPointerException | ArithmeticException e) {
            // 支持同时捕获多种类型异常
            assert e.getMessage() == "num不能为null" || "num不能为负数"
        } finally {
            result2 = 996
        }
        assert result2 == 996

        /*********************** 支持资源自动管理ARM机制, 即TWR语法 ***********************/
        try ( FileReader file = new FileReader("src/main/resources/ReadMe.txt") ) {
            file.each {line -> println line}
        }catch(e) {
            println ("Happen Exception: ${e.getMessage()}")
        }
    }

    /**
     * Groovy 方法抛出受查异常
     * Note: Groovy直接抛出CheckedException受查异常时, 在方法签名处的抛出异常声明则是可选的
     * @param fileName
     * @return
     */

    // 推荐在方法签名处显式添加声明: static def test1 (String fileName) throws FileNotFoundException
    static def test1 (String fileName) {
        if( fileName == "Groovy In Action" ) {
            throw new FileNotFoundException("文件不存在异常")
        }
        return fileName.toUpperCase()
    }

    /**
     * Groovy 方法抛出运行时异常
     * @param num
     * @return
     */

    static int test2 (def num) {
        if( num == null ) {
            // 类似地, 直接抛出RuntimeException
            throw new NullPointerException("num不能为null")
        } else if( num<0 ) {
            // 类似地, 直接抛出RuntimeException
            throw new ArithmeticException("num不能为负数")
        }
        return num
    }
}

控制结构

if语句

Groovy在if语句上与Java并无二致,只不过在条件表达式上更加丰富。可以使用任何的表达式,并应用上文所述的真值规则判定表达式的结果。示例代码如下所示

class StatementDemo {  
    static void testIf() {
        def x
        if (true) {
            x = 1
        } else {
            x = 2
        }
        assert x == 1

        if (false) {
            x = 1
        } else {
            x = 2
        }
        assert x == 2

        // 条件中 null 被处理为 false
        if (null) {
            x = 1
        } else {
            x = 2
        }
        assert x == 2

        // 条件中非空字符串 被处理为 true
        if ("Hello") {
            x = 1
        } else {
            x = 2
        }
        assert x == 1

        // 支持 if - else if 语句
        def foo = 99
        def bar
        if (foo==1) {
            bar = 1
        } else if (foo==2) {
            bar = 2
        } else {
            bar = 3
        }
        assert bar == 3
    }
}

while语句

whiile语句方面,同样支持while、do-while两种形式

class StatementDemo {   
    static void testWhile() {
        def bar = 0
        while ( bar < 10 ) {
            bar++
        }
        assert bar == 10

        // 支持 do - while 语句
        def count = 4
        def fact = 1
        do{
            fact *= count
            count--
        }while (count>1)
        assert fact == 24
    }
}

for语句

Groovy不仅支持传统for循环、Java经典的for each循环,还针对集合容器提供了for in循环

class StatementDemo {
    static testFor() {
        /************* 传统 for 循环 *************/
        def msg = ""
        for(int i=1; i<=5; i++) {
            msg += i
        }
        assert msg == "12345"

        // for语句 支持多赋值
        def str = ""
        fordef (x, y) = ["Hello"42]; y<45; x++, y++) {
            str += "$x $y, "
        }
        assert str == "Hello 42, Hellp 43, Hellq 44, "

        /************* for in 迭代集合容器 *************/
        // for in 迭代 List
        def list = [1,3,5]
        def x = 0
        for( e in list ) {
            x += e
        }
        assert x == 9

        // for in 迭代 Array
        def array = [264as int[]
        def y = 0
        for( e in array ) {
            y += e
        }
        assert y == 12

        // for in 迭代 Map
        def map = ["Aaron":1"Bob":3"Tina":2]
        def names = ""
        def sum = 0
        for( e in map ) {
            names += e.key + ","
            sum += e.value
        }
        assert names == "Aaron,Bob,Tina,"
        assert sum == 6

        def sum1 = 0
        for ( e in map.values()) {
            sum1 += e
        }
        assert sum1 == 6

        // for in 迭代 字符串中的字符
        def text = "Hello"
        def list2 = []
        for ( c in text ) {
            list2.add( c )
        }
        assert list2 == ["H""e""l""l""o"]

        // for in 迭代 range
        def range = 1..3
        def result2 = 0
        for( e in range ) {
            result2 += e
        }
        assert result2 == 6

        /************* 经典 for each 循环 *************/
        // for each 迭代 List
        def list3 = [1,3,5]
        def result = 0
        // 使用for each循环需要指定e变量的类型
        // 这里可以使用int, 也可以直接使用def
        for(def e : list3) {
            result += e
        }
        assert result == 9
    }
}

switch语句

Groovy支持传统的switch语句.需要注意的是,当匹配到某个case时如若未遇到break,则其会一直执行下去

class StatementDemo {
    static testSwitch1() {    
        def result = ""
        def num = 3
        switch (num) {
            case 0: result += "A"
            case 1: {
                result += "B"
                break
            }
            case 2: result += "C"
            case 3: result += "D"
            case 4: {
                result += "E"
                break
            }
            case 5: result += "F"
            default: result += "Z"
        }
        assert result == "DE"
    }
}

特别地,Groovy中的Switch语句还支持使用分类器,用来判断某个值是否属于某个分类。示例如下所示。事实上进行分类判断时,本质上是通过调用分类器的isCase方法实现的

class StatementDemo {
    static testSwitch2() {
        Closure closure1 = { num ->
            def result = "Z"
            switch (num) {
                case -1 : result = "A"break
                // 通过Range的isCase方法, 相当于 (1..<2).isCase(num)
                case 1..<2 : result = "B"break
                // 调用Range的isCase方法, 相当于 (1..<2).isCase(num)
                case 2..4 : result = "C"break
                // 调用闭包的isCase方法, 相当于 {it%5==0}.isCase(num)
                case {it%5==0} : result = "D"break
                // 调用列表的isCase方法, 相当于 [11,31].isCase(num)
                case [11,31] : result = "E"break
                // 调用Integer的isCase方法, 相当于 Integer.isCase(num)
                case Integer: result = "F"break

            }
            return result
        }

        assert closure1(-1) == "A"
        assert closure1(1) == "B"
        assert closure1(2) == "C"
        assert closure1(5) == "D"
        assert closure1(10) == "D"
        assert closure1(31) == "E"
        assert closure1(996) == "F"
    }
}

参考文献

  1. Groovy In Action · 2nd Edition   Dierk König、Guillaume Laforge著
浏览 142
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报