作为学习 Groovy 的一部分,我正在尝试探索字符串插值提供的所有复杂的可能性。我的一个小实验给出的结果对我来说没有意义,现在我想知道我是否完全误解了 Groovy 中惰性插值和急切插值的基本概念。
这是我运行的代码:
def myVar1 = 3
// An eager interpolation containing just a closure.
def myStr = "${{->myVar1}}"
print ("Just after the creation of myStr\n")
print (myStr as String)
myVar1 += 1                                           // Bump up myVar1.
print ("\nJust after incrementing myVar1\n")
print (myStr as String) 
这是我得到的输出:
Just after the creation of myStr
3
Just after incrementing myVar1
4
显然,闭包已被第二次调用。重新执行闭包的唯一方法是重新评估包含插值。但是,包含插值本身并不是一个闭包,尽管它包含一个闭包。那么,为什么要重新评估它呢?
这就是GString.toString()方法的实现方式。如果你看一下类的源代码GString,你会发现类似这样的内容:
public String toString() {
    StringWriter buffer = new StringWriter();
    try {
        writeTo(buffer);
    }
    catch (IOException e) {
        throw new StringWriterIOException(e);
    }
    return buffer.toString();
}
public Writer writeTo(Writer out) throws IOException {
    String[] s = getStrings();
    int numberOfValues = values.length;
    for (int i = 0, size = s.length; i < size; i++) {
        out.write(s[i]);
        if (i < numberOfValues) {
            final Object value = values[i];
            if (value instanceof Closure) {
                final Closure c = (Closure) value;
                if (c.getMaximumNumberOfParameters() == 0) {
                    InvokerHelper.write(out, c.call());
                } else if (c.getMaximumNumberOfParameters() == 1) {
                    c.call(out);
                } else {
                    throw new GroovyRuntimeException("Trying to evaluate a GString containing a Closure taking "
                            + c.getMaximumNumberOfParameters() + " parameters");
                }
            } else {
                InvokerHelper.write(out, value);
            }
        }
    }
    return out;
}
请注意,该writeTo方法检查为插值传递的值是什么,并且在闭包的情况下,它会调用它。这是 GString 处理插值的延迟计算的方式。
现在让我们看几个例子。假设我们想要打印一个 GString 并插入某个方法调用返回的值。此方法还会向控制台打印一些内容,以便我们可以查看该方法调用是急切触发的还是延迟触发的。
class GStringLazyEvaluation {
    static void main(String[] args) {
        def var = 1
        def str = "${loadValue(var++)}"
        println "Starting the loop..."
        5.times {
            println str
        }
        println "Loop ended..."
    }
    static Integer loadValue(int val) {
        println "This method returns value $val"
        return val
    }
}
输出:
class GStringLazyEvaluation {
    static void main(String[] args) {
        def var = 1
        def str = "${loadValue(var++)}"
        println "Starting the loop..."
        5.times {
            println str
        }
        println "Loop ended..."
    }
    static Integer loadValue(int val) {
        println "This method returns value $val"
        return val
    }
}
默认的急切行为。该方法loadValue()在我们打印str到控制台之前被调用。
class GStringLazyEvaluation {
    static void main(String[] args) {
        def var = 1
        def str = "${ -> loadValue(var++)}"
        println "Starting the loop..."
        5.times {
            println str
        }
        println "Loop ended..."
    }
    static Integer loadValue(int val) {
        println "This method returns value $val"
        return val
    }
}
输出:
This method returns value 1
Starting the loop...
1
1
1
1
1
Loop ended...
在第二个示例中,我们利用惰性求值。我们定义str一个调用loadValue()方法的闭包,当我们显式地将 打印str到控制台时(更具体地说 - 当GString.toString()方法被执行时),该调用就会被执行。
class GStringLazyEvaluation {
    static void main(String[] args) {
        def var = 1
        def closure = { -> loadValue(var++)}
        def str = "${closure.memoize()}"
        println "Starting the loop..."
        5.times {
            println str
        }
        println "Loop ended..."
    }
    static Integer loadValue(int val) {
        println "This method returns value $val"
        return val
    }
}
输出:
class GStringLazyEvaluation {
    static void main(String[] args) {
        def var = 1
        def str = "${ -> loadValue(var++)}"
        println "Starting the loop..."
        5.times {
            println str
        }
        println "Loop ended..."
    }
    static Integer loadValue(int val) {
        println "This method returns value $val"
        return val
    }
}
这是您最有可能寻找的示例。在这个例子中,由于闭包参数,我们仍然利用惰性求值。然而,在这种情况下,我们使用闭包的记忆功能。字符串的评估被推迟到第一次GString.toString()调用,并且闭包的结果被记住,因此下次调用它时,它会返回结果而不是重新评估闭包。
${{->myVar1}}和 和有什么区别${->myVar1}?正如前面提到的,GString.toString()方法使用GString.writeTo(out)检查给定占位符是否存储闭包以进行惰性求值。每个 GString 实例都在数组中存储占位符值GString.values,并在 GString 初始化期间对其进行初始化。让我们考虑以下示例:
Starting the loop...
This method returns value 1
1
This method returns value 2
2
This method returns value 3
3
This method returns value 4
4
This method returns value 5
5
Loop ended...
现在让我们进行GString.values数组初始化:
class GStringLazyEvaluation {
    static void main(String[] args) {
        def var = 1
        def closure = { -> loadValue(var++)}
        def str = "${closure.memoize()}"
        println "Starting the loop..."
        5.times {
            println str
        }
        println "Loop ended..."
    }
    static Integer loadValue(int val) {
        println "This method returns value $val"
        return val
    }
}
正如您所看到的,在第一个和第三个示例中,它执行的操作完全相同 - 它计算表达式并将其存储在GString.values类型为 的数组中Object[]。这是关键部分:表达式 like{->something}不是闭包调用表达式。评估闭包的表达式是
{->myVar1}()
或者
{->myVar1}.call()
可以用下面的例子来说明:
def str = "${println 'B'; 2 * 4} ${{ -> println 'C'; 2 * 5}} ${{ -> println 'A'; 2 * 6}.call()}"
println str
值初始化如下:
Starting the loop...
This method returns value 1
1
1
1
1
1
Loop ended...
这就是为什么在GString对象初始化之后我们最终会得到values如下数组:
def str = "${myVar1} ... ${-> myVar1} ... ${{-> myVar1}}"
现在,这个创建GString引起了副作用 - 控制台上显示以下字符:
${myVar1}      --> evaluates `myVar1` expression and copies its return value to the values array
${-> myVar1}   --> it sees this is closure expression so it copies the closure to values array
${{-> myVar1}} --> evaluates `{-> myVar1}` which is closure definition expression in this case and copies its return value (a closure) to the values array
这是因为第一个和第三个值评估调用了println方法调用。
现在,当我们最终调用println strwhich 调用GString.toString()方法时,所有值都会得到处理。当插值过程开始时,它会执行以下操作:
{->myVar1}()
这就是为什么最终控制台输出如下所示:
{->myVar1}.call()
这就是为什么在实践中像这样的表达式${->myVar1}和 这样的表达式${{->myVar1}}是相似的。在第一种情况下,GString 初始化不会计算闭包表达式并将其直接放入值数组中,在第二个示例中,占位符被计算,它计算的表达式创建并返回闭包,然后闭包存储在值数组中。
如果您尝试在 Groovy 3.x 中执行该表达式,${{->myVar1}}您最终会遇到以下编译器错误:
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
General error during conversion: java.lang.NullPointerException
java.lang.NullPointerException
    at org.apache.groovy.parser.antlr4.AstBuilder.lambda$visitGstring$28(AstBuilder.java:3579)
    at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
    at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1382)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
    at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
    at org.apache.groovy.parser.antlr4.AstBuilder.visitGstring(AstBuilder.java:3591)
    at org.apache.groovy.parser.antlr4.AstBuilder.visitGstring(AstBuilder.java:356)
    at org.apache.groovy.parser.antlr4.GroovyParser$GstringContext.accept(GroovyParser.java:4182)
    at groovyjarjarantlr4.v4.runtime.tree.AbstractParseTreeVisitor.visit(AbstractParseTreeVisitor.java:20)
    at org.apache.groovy.parser.antlr4.AstBuilder.visit(AstBuilder.java:4287)
    .....
    at org.codehaus.groovy.control.CompilationUnit.compile(CompilationUnit.java:565)
    at org.codehaus.groovy.tools.FileSystemCompiler.compile(FileSystemCompiler.java:72)
    at org.codehaus.groovy.tools.FileSystemCompiler.doCompilation(FileSystemCompiler.java:240)
    at org.codehaus.groovy.tools.FileSystemCompiler.commandLineCompile(FileSystemCompiler.java:163)
    at org.codehaus.groovy.tools.FileSystemCompiler.commandLineCompileWithErrorHandling(FileSystemCompiler.java:203)
    at org.codehaus.groovy.tools.FileSystemCompiler.main(FileSystemCompiler.java:187)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.codehaus.groovy.tools.GroovyStarter.rootLoader(GroovyStarter.java:114)
    at org.codehaus.groovy.tools.GroovyStarter.main(GroovyStarter.java:136)
1 error
| 归档时间: | 
 | 
| 查看次数: | 809 次 | 
| 最近记录: |