Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”

This article also participated in the “Digitalstar Project” to win a creative gift package and creative incentive money

preface

In the last article, I covered Gradle and the basic syntax of Groovy. In this article, I will continue with the following points.

  1. Groovy classes and methods
  2. Groovy closure

No more words, just get started.

Groovy classes and methods

1.1 Multiple Access GET/SET modes

class Car {
    def miles
    private def year

    def getMiles() {
        println "getMiles"
        return miles
    }

    private void setMiles(miles) {
        println "setMiles"
        this.miles = miles
    }
}

def car = new Car()
car.miles = 20000
println car.miles+"\n\n"

car.@miles=10000
println car.@miles+"\n\n"

car.year = 2027
println car.year
Copy the code

There is nothing to say here, except that I assign and value the member variables in the class, but notice that I have deliberately set the corresponding setMiles method and year variable to private. Let’s see what happens next

setMiles
getMiles
20000


10000


2027
Copy the code

In order to distinguish the corresponding function, I deliberately use a line break, from this operation effect can be seen:

  • All variables are public by default
  • If you want to set private to deny direct access, just declaring private does not work. You can still access it directly using ‘.’
  • Getters, setters are generated by default (see year)
  • And you can call getters/setters automatically just like you do with member variables (see Miles)
  • Setter methods are called when an assignment is made
  • The getter method is called when the value is accessed directly
  • Using ‘.@’ is really direct access to variables, skipping the default getter/setter method calls (see.@miles)

Of course, this is the standard get/set access, but you can also use ‘. The variable name ‘is accessed

def car = new Car()
car.@year=2021
car.@miles=30000
// Access via '. Variable name '
println car.'miles'+"\n\n"
println car.@'miles'+"\n\n"
def str = "year"
println car."$str"
Copy the code

Running effect

getMiles
30000


30000


2021
Copy the code

You can see that accessing the ‘. Variable name ‘method still calls the corresponding getter/setter method, so your code is more dynamic and flexible this way!

1.2 Construction method

class Car {
    def miles
    private def year

    def getMiles() {
        println "getMiles"
        return miles
    }

    private void setMiles(miles) {
        println "setMiles"
        this.miles = miles
    }
}
def car = new Car(miles: 20000.year: 2021)
println(car.@miles+"\n\n")
car=new Car(miles: 30000)
println(car.@miles)
Copy the code

Note that there is no constructor for Car with arguments, but the instantiation of Car forces variables and values to be specified

setMiles
20000


setMiles
30000
Copy the code

From this running effect we know:

  • Constructor overloading rules are the same as in Java.
  • You pass in named parameters in the constructor, but note that the parameters passed in are key-value pairs, which are actually Map types
  • The parameters passed in this way will automatically disassemble the Map and call the corresponding setter method for assignment

Here we see that Map key-value pairs are automatically disintegrated when there is no parameter constructor, so what happens if there is a parameter constructor?

class Car {
    def miles
    private def year
    Car(){

	}
    
    Car(def miles){
        this.miles=miles
    }
    
    Car(def miles,def year){
        this.year=year
        this.miles=miles
    }
    def getMiles() {
        println "getMiles"
        return miles
    }

    private void setMiles(miles) {
        println "setMiles"
        this.miles = miles
    }
}

def car = new Car(miles: 20000.year: 2021)
println(car.@miles)
println car.@miles.getClass()
println "\n\n"

car=new Car(miles: 30000)
println(car.@miles)
println car.@miles.getClass()
println "\n\n"

car=new Car(40000)
println(car.@miles)
println car.@miles.getClass()
Copy the code

So now we see, let me just say that all the constructors are implemented, and now let’s see how it works.

[miles:20000, year:2021]
class java.util.LinkedHashMap



[miles:30000]
class java.util.LinkedHashMap



40000
class java.lang.Integer
Copy the code

Here we can see that if there are non-key-value pairs in the parameter, those key-value pairs are treated as maps and no auto-disassembly assignment is performed. So you have to have a constructor.

1.3 Operator overloading

For those of you who have learned Kotlin, operator overloading can be done on any object. Now what about Groovy

class Car {
    def miles
    private def year
    Car(){

	}
	
    def plus(Car car){
    	// The result of the last sentence determines the method return value and return type
        this.year+car.year
    }
}

def car=new Car()
car.@year=2020
def car2=new Car()
car2.@year=2021
println car+car2
Copy the code

Let’s see how it works

Connected to the target VM, address: '127.0.0.1:57371', transport: 'socket'
4041
Process finished with exit code 0
Copy the code

So here we have object + object, which is the normal thing to do with numeric objects. That’s right! You can do this in Groovy, but you just need to implement overloaded methods for the corresponding operators. I’ve just cited one classic addition, and I’ll post the usual ones for the rest of you to try.

2. Groovy closure

In Groovy, closures are executable blocks of code, or anonymous functions. Closures have much in common with functions in their use, but closures can be used as arguments to a function.

2.1 Common closure operations

def closure = {
        // 1. By default, you can accept a single parameter, which can be used directly with it
        //-> 2. Closures can no longer pass in arguments in this way
    a = 1, b = 2, c -> // 3. Define multiple parameters and use the same method to set default parameter values
        println "a=$a b=$b c=$c"
}

closure(30)
println closure.class
Copy the code

Running effect

a=1 b=2 c=30
class com.zee.gradle.test01.GroovyTest02$_run_closure1
Copy the code

This should be easy for those of you who use Kotlin to understand at a glance, as explained in Java code.

void closure(int c){
	int a=1;
	int b=2;
	System.out.println("a="+a+" b="+b+" c="+c)
}
Copy the code

I’m sure you’ll all get it in seconds.

2.2 Corrified closures

In Groovy, the concept of Corrification is only partially applied. It doesn’t fit the real concept of Curryification in functional programming, because Groovy applies different scoping rules to closures. The cremation in Groovy will allow you to set the value of one parameter in a closure, and it will return a new closure that accepts the remaining parameters.

2.2.1 Zocollization

// The closure defines two parameters,
def content={int count,String str -> str*count} 
// The curry method sets the first argument to the closure above to 2, and calling TWICE requires only passing in the second argument string to complete the closure call
def twice=content.curry(2)
// Verify that left Collization is STR *count in the closure expression
println twice('a') = ='aa'
// Verify that calling the closure directly is the same as the result of left-Curryification
println content(2.'a')==twice('a')
Copy the code

Running effect

true
true
Copy the code

At the beginning, you define a closure for two arguments, and when you execute the Content.curry (2) code, twice becomes the closure

def twice={
	int count=2,String str->str*count
}
Copy the code

The twice method is called just like the normal closure above. If there’s left Collerization, there must be right Collerization. So let’s see.

2.2.2 Right Corrification

// The closure defines two parameters,
def content={int count,String str -> str*count}
The //rcurry method sets the second argument to the closure above to 'b', and calling TWICE requires only the first argument string to complete the closure call
def twice=content.rcurry('b')
// Verify that right Currization equals the final result
println twice(3) = ='bbb'
// Verify that the right Currization is equal to the result of the direct call
println twice(3)==content(3.'b')
Copy the code

As you can see, both left and right Colerization are for closures with two parameters. How do you use colerization if there are closures with more than two parameters?

2.2.3 Index-based Curritization

// Defines a closure for three parameters
def volume={double a,double b,double c->a*b*c}
// Set index=1 (the second parameter is initially 2d)
def fixedWidthVolume=volume.ncurry(1.2d)
// Compare the closure set to the initial value of the second argument passed to the first and third variables with the direct call method
println volume(3d,2d,4d)==fixedWidthVolume(3d,4d)

// Assign the initial values starting at inde=1, and then get the new closure
def fixedWidthAndHeight=volume.ncurry(1.2d,4d)
// Compare the new closure with the direct invocation
println volume(3d,2d,4d)==fixedWidthAndHeight(3d)
Copy the code

Here comments write quite full, see how it works

true
true
Copy the code

2.2.4 Summary of Corrified closures

  • Left and right Kerritization is only for closures with two parameters
  • Using left-corrification requires the Curry key
  • To use right corrification requires the rcurry key method
  • Index-based Currization can be used for closures with more than two parameters
  • Using index-based corrification requires the ncurry key method
  • The first argument of the ncurry method is the subscript corresponding to the start, and the second variable can be multiple, indicating where to start the assignment

2.3 Closures are converted to interfaces/classes

2.3.1 Closures can be used as arguments to methods

def func3(closure2) {
    closure2()
}
func3 {
    // The code executed by the closure
    println "call"
}
Copy the code

Running effect

call
Copy the code

Func3 is defined, and then a closure corresponding to func3 is written. The closure code is called inside the method, that is, the closure2 parameter to func3 is the closure of func3 below, and the closure code is run.

2.3.2 Closures and classes are converted

demo1

interface Action {
    void call()

    void call2()
}

Action closure1 = new Action() {
    @Override
    void call() {
        println "call1"
    }

    @Override
    void call2() {
        println("call2")}}def func2(closure3) {
    closure3()
}
println closure1.class
func2(closure1)
Copy the code

Running effect

class com.zee.gradle.test01.GroovyTest02The $1
call1
Copy the code

Even if a defined method passes in a closure, it is possible to execute the call method of an object of type that also has a Call method. In fact, the closure also executes a Call method.

demo2

class Action {
    void call(a) {
        println "$a"}}def a = new Action()
a(111)
Copy the code

Running effect

111
Copy the code

The same explanation applies here: if the type of the object passed in also has a Call method, then it is possible to execute the call method of that object. In fact, the closure also executes the Call method

2.4 Closure important member variables

// Points to owner by default, but can be set to another object
private Object delegate;
// If the closure is created in another closure, owner points to the other closure, otherwise it points to this
private Object owner; 
// Create the closure object (context)
private Object thisObject; 
// The default value is 0, which is related to the proxy policy
private int resolveStrategy = OWNER_FIRST;
// Store an array of each parameter type
protected Class[] parameterTypes;
// The maximum number of parameters can be placed
protected int maximumNumberOfParameters;
Copy the code

So let’s do this in code

def closure = {
    int a, int b ->
      a+b
}
closure(1.2)
println closure.delegate
println closure.owner
println closure.thisObject
println closure.resolveStrategy
println closure.parameterTypes
println closure.maximumNumberOfParameters
Copy the code

Running effect

com.zee.gradle.test01.GroovyTest02@7dac3fd8
com.zee.gradle.test01.GroovyTest02@7dac3fd8
com.zee.gradle.test01.GroovyTest02@7dac3fd8
0
[int, int]
2
Copy the code

There’s nothing to talk about, just a bunch of variables, but they’re all tied to the corresponding closure strategy, so let’s move on to the closure proxy strategy.

2.5 Proxy policies in closures

class Test2 {
    def func() {
        println "Test2 func"}}def func() {
    println "Script func"
}
def closure = {
    func()
}
closure()
closure.delegate = new Test2()// Select owner first
//* @see groovy.lang.Closure#DELEGATE_FIRST // delegate first
//* @see groovy.lang.Closure#DELEGATE_ONLY
//* @see groovy.lang.Closure#OWNER_FIRST // owner first
//* @see groovy.lang.Closure#OWNER_ONLY
//* @see groovy.lang.Closure#TO_SELF
closure.resolveStrategy=Closure.DELEGATE_FIRST
closure()
Copy the code

There is a closure that calls func methods inside the closure, and then there are func methods inside and outside the class, and the closures are called before and after the policy is set. Now select DELEGATE_FIRST or DELEGATE_ONLY to see the effect

Script func
Test2 func
Copy the code

That is, when DELEGATE_FIRST or DELEGATE_ONLY is selected, the first call to the closure with the policy set will call the method in the class.

Let’s see what happens if you select OWNER_FIRST or OWNER_ONLY

Script func
Script func
Copy the code

That is, when OWNER_FIRST or OWNER_ONLY is selected, the external code is called directly and the corresponding method in the class is invalidated.

3. The conclusion

Well, at this point, I’m sure you’ve taken a step closer to understanding Groovy syntax. In the next article, I’ll take a closer look at Groovy metaprogramming.