1. Install

Installing Groovy itself is pretty straightforward. For Windows users, unpack the SDK Bundle to any PATH on your disk, then configure Groovy’s GROOVY_PATH and PATH variables as if you were configuring Java, and Groovy is installed. As for the IDE, I chose to use IDEA IntelliJ, go to plugins for Groovy plugins and install it.

Here’s the point. The version I chose to install was GDK 3.0.8 (officially the latest stable release), which supports up to JDK 1.8, and running Groovy in later versions will cause an error of 1. This is not an error caused by inadvertent manipulation; it has more to do with changes in the JDK itself. If you want to run Groovy scripts in older JDK versions, you need to add the missing dependencies in your project (assuming you’re using it in your Maven project) :

<dependencies>
    <dependency>
        <groupId>javax.xml.bind</groupId>
        <artifactId>jaxb-api</artifactId>
        <version>2.3.1</version>
    </dependency>

    <dependency>
        <groupId>org.glassfish.jaxb</groupId>
        <artifactId>jaxb-runtime</artifactId>
        <version>2.3.1</version>
    </dependency>
</dependencies>
Copy the code

If you need to use a mix of Java and Groovy to develop some projects, Maven should also be configured with a compilation plug-in and minimal dependencies (groovy-all below) to ensure Groovy compiles properly. Please refer to this zhihu link for details.

<dependency>
    <groupId>org.codehaus.groovy</groupId>
    <artifactId>groovy-all</artifactId>
    <version>3.0.3</version>
    <type>pom</type>
</dependency>
Copy the code

2. Hello! new

First, it’s hard to distinguish Groovy from Java as “compiled versus interpreted,” because both require Javac or GroovyC to translate the source code into binary and hand it over to the JVM for interpretation. In this way, Java and Groovy should both be “compile-interpret” languages. The main difference between the two is that Java is typically a static language (all data is determined at compile time), while Groovy can be “dynamically distributed” and also supports static compilation.

The following details will help you make a quick transition from Java to Groovy.

2.1 the Groovy as Script

Groovy’s concise syntax (almost any new programming language is much cleaner than Java, so “concise” is not a Groovy Feature) and dynamic features make it easy to interact with system processes:

// Use groovy to execute "groovy-v ".
// In Windows, the prefix must be CMD /C.
println("cmd /C groovy -v".execute().text)
Copy the code

The execute() method treats the string as a command to be executed by the system, and.text gets the result of executing the command in the system. Here is an example of how, on Linux and Windows, the.groovy script can be used to “browse the contents of the current directory (that is, the directory in which the executor.groovy resides)” :

/ / Linux system
println("ls -l")

/ / Windows
println("cmd /C dir")
Copy the code

Note that ls runs directly in Linux, but dir is just a command defined in the CMD command line interpreter in Widows, so there is an extra prefix CMD /C added here. A Groovy code can also call Groovy code from another file store in real time. Such as:

evaluate(new File("C:\\Users\\i\\IdeaProjects\\GroovyHello\\src\\HelloWorld.groovy"))
// In the other./ helloworld.groovy script, we just used the line print 'HelloWorld! -by groovy' makes it output a paragraph.
Copy the code

For this reason, Groovy has been called a “scripting language on the JVM,” and rightly so.

2.2 Two styles of Writing Groovy logic

In.groovy files, you can write your code logic directly at the top level of the file without declaring any classes (as I just did). There is a synthetic class generated for script code. The synthetic class generated for script code generates a synthetic class generated for script code.

// Assume this code appears in the obj.groovy source file
class Obj{
    / /...
}

// This method call is at the 'top level' of the file, just like the class declaration.
// We don't need 'System.out.println(...) ' anymore.
print('hello groovy')
Copy the code

This makes sense from a compilation perspective, because when the.groovy file is compiled into a.class file and executed, the compiler actually generates a composite class for it, and it’s this step that causes the conflict: the class name we just defined is duplicated.

In fact, the presence of a class with the same name inside a.groovy file means that the.groovy file is treated as “Java code written in Groovy dialect” and is generally no longer used as a script, Instead, it becomes a “normal Class” (IDEA calls it a Groovy Class). A direct consequence of this is that we cannot write the code logic directly at the top level of the file.

// This code appears in the obj.groovy source file.
// It is equivalent to a public class definition in a.java file.
class Obj{
    // Just like writing Java code!
    // In Groovy, the public keyword is actually redundant. By default, all declarations are public.
	public static void main(String[] args){
         / / in Groovy, string can also use the "",", "" "" ""," ' ' ' ' ', packages, specific difference see later in this article.
		print('hello groovy')}}// It is equivalent to a non-public class definition in a.java file.
class OtherObj{}

// Code can no longer appear in the 'top' position of a file, because that's not how we did it in Java development.
print ('oops')
Copy the code

2.3 Exception Handling

Java always tells us to handle checked exceptions in the first place, or the proud Javac compiler refuses to execute. Such as:

public class Test {
    public static void main(String[] args) throws FileNotFoundException {
        File file = new File("src/HelloWorld.groovy");
        // FileInputStream throws a checked exception, so calling its function either continues to throw throws higher (of course, the main program throws exceptions without meaning),
        // Either try the catch internally.
        FileInputStream fileInputStream = newFileInputStream(file); }}Copy the code

Groovy takes a more relaxed approach to exception handling: if exceptions are not handled with a try-catch inside the block, they are automatically thrown to the parent without having to actively define them using throws in function declarations. Here’s the Groovy code:

Groovyc executes normally even if there is no throws declared and no try-catch defined.
File file = new File('HelloWorld.groovy')
FileInputStream fileInputStream = new FileInputStream(file)
Copy the code

2.4 Concise “non-null call” syntax

To avoid calling a null pointer method, we usually wrap an if block in Java code:

String maybeNull = "I'm Java";
if(maybeNull ! =null){System.out.println(nullString.length()); }Copy the code

This long list of logic can be used directly in Groovy. The. Operator resolves:

String maybeNull = 'I\'m groovy'print(maybeNull? .length())Copy the code

2.5 GString

In Groovy, short strings can be represented as ” or “”, while long strings that need to cross lines are usually ”’ ”’ or “”” “””. Double-quoted strings are also called gstrings, and they support ${} as placeholders (like printf) inside strings, rather than manual String concatenation.

name = 'Wangfang'
age = '18'

// Use placeholder concatenation
print("my name is ${name},my age is ${age}.")
Copy the code

2.6 Thin Javabeans

In Groovy, the compiler always automatically generates the corresponding Set and Get methods for attributes underneath:

class Student_ {
    String name
    Integer age
    
    Student_(String name,Integer age){
        this.name = name
        this.age = age
    }
}

Student_ s = new Student_()
// This getXXX method is generated by the compiler.
s.getName()
Copy the code

If you want a property to be immutable after the object has been constructed, you use the final keyword, and the compiler will not actively generate a Set method for it (meaning that the property is read-only). Alternatively, an attribute may not actively declare a type, in which case the original type is replaced by the def keyword.

class Student_{
	final name
	def age
	Student_(name,age){
		this.name = name 
		this.age = age
	}
}
Copy the code

Properties that are not actively typed are essentially Object objects, which is not conducive to subsequent operations on the property. To solve this problem, leave some clues in the constructor so that the compiler can “deduce” the target type (Groovy always deduces the actual type of a variable from its assignment).

class Student_{    
    final name
    def age

    // Enable the actual types of the name and age attributes to be deduced
    Student_(String name, Integer age){
        this.name = name
        this.age = age
    }
}

s = new Student_('Wang Fang'.23)
Copy the code

If a property is declared private, the compiler no longer automatically declares Get and Set methods for the property.

class Student_{    
    final name
    private age

    // Enable the actual types of the name and age attributes to be deduced
    Student_(String name, Integer age){
        this.name = name
        this.age = age
    }
}

s = new Student('Wang Fang'.23)

/ / an error
s.getName()
Copy the code

2.7 It may not be necessary to create a constructor manually

For the Student_ class above, it might need four constructors: a constructor with no arguments, a constructor with only the name attribute, a constructor with only the age attribute, and a complete constructor. Groovy allows flexible object creation with just one Map, and eliminates the need to manually supplement constructor notation:

class Student_{
    String name
    Integer age
}

// The Student_(name,age) constructor is not implemented, but can be used directly
stu1 = new Student_(name: "Wang Fang".age: 12)

// Again, we didn't implement the Student_(name) constructor manually.
stu2 = new Student_(name:"Wang Fang")
Copy the code

In some argument lists, we pass in an entire Map. Each k:v in it represents a key-value pair. K corresponds to each attribute name in the class, and v assigns values to those attributes. But we cannot do this:

stu1 = new Student_("Wang Fang".12)
stu2 = new Student_("Wang Fang")
Copy the code

Unless the corresponding constructor is manually added.

2.8 Optional parameters in the method

Java does not support optional parameters, and each parameter must be strictly assigned when a method is called. Groovy is different: In the method (or function) argument list, you can set a default value for the last parameter in advance, so that when the method is called, the last parameter can be omitted.

def add(Integer arg,Integer implicit = 10){arg + implicit}

/ / 11
print(add(1))
/ / 3
print(add(1.2))
Copy the code

This example also illustrates other details: At the very least, Groovy’s methods (functions) don’t require the return keyword explicitly, which always returns the result of the last call in the function body by default. However, the return value type of this function is obviously inferred, so the return value declaration of the function can also be replaced with the def keyword here.

2.9 Multiple Assignment

If the method (function) returns an array, Groovy supports the use of multiple variables to receive the contents of the elements in the array. Such as:

def swap(x, y) { return [y, x] }

Integer a, b
a = 10
b = 50

// Use multiple assignment to swap two numbers
(a, b) = swap(a, b)
print("a=$a,b=$b")
Copy the code

Taking advantage of this feature, Groovy’s methods (functions) can return multiple values. Other programming languages that support this include Go, Scala (by wrapping tuples), and so on. When the number of received variables does not match the actual number of returned values, Groovy does this:

  1. If more variables are received, null is assigned to variables that are not assigned.
  2. If there are more returns, the excess returns are discarded.

Groovy does provide tuples, of course, which will be familiar to Scala programmers:

Tuple2<Integer,Integer> swap(Integer a,Integer b){
    return new Tuple2<Integer,Integer>(b,a)
}

a = 10
b = 20

(a, b) = swap(a, b)
print("a=${a},b=${b}")
Copy the code

2.10 Interface Implementation

Suppose you have a single method interface like this:

interface Calculator<T> {
    T add(T a,T b)
}
Copy the code

Java might implement this:

Calculator<Integer> calculator = new Calculator<Integer>() {
    @Override
    public Integer add(Integer a, Integer b) {
        returna + b; }};Copy the code

Since Java 8, anonymous implementations have finally become a little more concise, but unfortunately Lambda expressions can only be used in single-method interfaces.

Calculator<Integer> calculator = (a, b) -> a + b;
Copy the code

Groovy offers a different approach: first, it gives a block of syntax for Lambda expressions wrapped in {}, which in Groovy is called a closure. This closure is then declared as an implementation of an interface using the AS keyword 2.

// Thanks to type derivation, we don't need to rewrite Calculator
      
        again.....
      
def a = {a,b ->return a+b} as Calculator<Integer>
Copy the code

If you want to implement a multi-method interface, wrap multiple closures in a Map and use k to indicate which method each closure implements:

interface Calculator<T> {
    T add(T a, T b)
    T sub(T a, T b)
}

def cal = [
        add: { a, b -> a + b },
        sub: { a, b -> a - b }
] as Calculator<Integer>

def c = cal.sub(1.2)
print(c)
Copy the code

Groovy never forces all methods of an interface to be implemented: if certain methods are not needed, there is no need to put the corresponding closure implementation into a Map. It is worth noting that if an interface method is called that is not implemented, the program throws a friendly NullPointerException.

2.11 Boolean evaluation

In the condition section of the If statement, Java enforces passing in a calculated Boolean value or an error.

int a = 10
// :) => if(a ! = 0)...
// :( => if(a)...
Copy the code

Groovy’s handling is a little more elegant. When a value is not a pure Boolean, Groovy makes some reasonable inferences based on the type passed in rather than reporting an error, as shown in the table below:

type When it is true
Boolean The value is true
Collection The collection itself is not null and has elements inside
Character Values are not zero
CharSequence Length greater than zero
Enumeration Has More Enumerations is True
Iterator HasNext () to True
Number Double is not 0
Map The map itself is not NULL, and the map itself is not null
Matcher At least one match
Object[] Length greater than zero
The other types References are not null

In most cases, passing a value directly to the if condition part is to determine whether it is null. If you want to decide whether to perform a series of actions based on whether the value is null, consider using the one mentioned above. The. Operator simplifies code.

2.12 Operator overloading

Groovy reserves method names that imply overloading operators 3:

Operator Method
a + b a.plus(b)
A – b a.minus(b)
a * b a.multiply(b)
a ** b a.power(b)
a / b a.div(b)
a % b a.mod(b)
a | b a.or(b)
a & b a.and(b)
a ^ b a.xor(b)
a++ or ++a a.next()
A – or – a a.previous()
a[b] a.getAt(b)
a[b] = c a.putAt(b, c)
a << b a.leftShift(b)
a >> b a.rightShift(b)
switch(a) { case(b) : } b.isCase(a)
~a a.bitwiseNegate()
-a a.negative()
+a a.positive()

These operators do not throw a null-pointer exception when null is encountered:

Operator Method
a == b a.equals(b) or a.compareTo(b) == 0 **
a ! = b ! a.equals(b)
a <=> b a.compareTo(b)
a > b a.compareTo(b) > 0
a >= b a.compareTo(b) >= 0
a < b a.compareTo(b) < 0
a <= b a.compareTo(b) <= 0

For example: define a complex number class in a program and then define the sum of the two copies to be the sum of the real and imaginary parts respectively:

class ComplexNumber {
    Integer real
    Integer imaginary

    // The plus method corresponds to the + operator
    def plus(ComplexNumber other) {
        new ComplexNumber(real: this.real + other.real,imaginary: this.imaginary+other.imaginary)
    }

    @Override
    String toString() { "${real} + ${imaginary}i"}}// This is equivalent to println(...)
New ComplexNumber(balabala).plus(new ComplexNumber(balabala))
println new ComplexNumber(real:3.imaginary:  2) + new ComplexNumber(real:3.imaginary:1)
Copy the code

But compared to Scala, I think this approach is a bit strange…… When we need to do this, we always have to go through the table and figure out which operator corresponds to which method, unless we memorize the table (ok, but not necessary). But either way, something is better than nothing.

2.13 a for loop

Here is a Java code example of a for loop where I starts at 0 and ends at 3 (excluding 3) :

for (int i = 0; i<3 ; i++){System.out.println("java loop")}
Copy the code

In Groovy, the closed and open ranges from 0 to 3 can use 0.. 2 to represent:

for (i in 0.2.){println "groovy loop"}
Copy the code

In is often used to traverse arrays of “fuzzy types”. If we are traversing an array of a certain type, we can also write:

String[] strings = ['java'.'scala'.'groovy'.'go']

// s must be specified as String.
// : replaces the in keyword.
for(String s : strings){
    print(s.length())
}

//
for (s in strings){
    print s.length()
}
Copy the code

. Can be thought of as a special binary symbol that can also be used alone outside of a loop statement to create a sequence of steps of 1.

// You can call... Regarded as a binocular operation symbol of Integer, where n.. M returns the sequence [n,n+1,n+2...m].
// seq is an IntRange type.
def seq = 0.10.

/ / 11
print seq.size()
Copy the code

2.14 About Import

Groovy generally imports the same way Java does, and does not force all imports to appear at the top of the file.

import java.lang.Math
print Math.random()
Copy the code

In addition, Groovy supports import static to import a static method of a class so that we can call that static method directly as a function in the current namespace. If you are concerned about naming duplication, you can rename the static method using the AS keyword.

// Statically import the static random method of the Math class
// The as keyword can alias the imported static method incidentally, and is often used to simplify or avoid naming conflicts.
import static math.random as rdm
print rdm()
Copy the code

2.15 Everything is a closure

Groovy specifically leaves [] to array declarations:

String[] str = ['java'.'groovy']
Copy the code

All {} code blocks are treated as closures in Groovy, and closures are a concrete Closure

type 4. (I’ll cover Groovy closures separately later.) In Java, we can use {} to represent a block of scoped child code:

{
    System.out.println("block1");
}

{
    System.out.println("block2");
}

// ()->void can also be understood as a subcode block.
Runnable runnable = () -> {
    System.out.println("block3");
};

runnable.run();
Copy the code

In Groovy, however, a closure expanded by {} cannot be declared on its own, except as an assignment:

// Failed to compile
{print "hello"}

This assignment explicitly states that {} is a closure.
def c = {print "hello"}
Copy the code

2.15.1 Avoid conflicts between closures and anonymous classes

If a function/method accepts closures as arguments, then it is syntactically possible to attach these closures to the end of the function call. To visualize, a method({… }, {… }) can be rewritten as method() {… } {… } (this makes it easier to design internal DSL syntax. Why can we write sentences like print “hello” in Groovy?) :

def aspect(before, after) {
    before()
    print("method")
    after()
}

// The normal way to call
aspect({print "before doing..."},{print "after doing..."})


// Migrate closures to the end of the call, and the parentheses () of aspect() can be written with or without.
aspect() {
    print("before doing...")
} {
    print("after doing...")}Copy the code

Sometimes the constructor of a class also needs to receive a closure, which can be ambiguous:

class Aspect{
        // Closure is a Groovy type of Closure. It actually has a generic type that refers to the return value of the closure.
        Aspect(Closure before){
            before()
        }
}
Copy the code

Depending on the initial call style, it can be written like this:

// The closure is written after the constructor.
def a  = new Aspect(){
	print "create a aspect..."
}
Copy the code

But to a Java programmer, this approach looks like creating an anonymous object — and even Groovy will be confused. In such cases, the grammar of () must be strictly used to avoid ambiguity.

def a = new Aspect({print "create a aspect..."})
Copy the code

2.15.2 Avoiding conflicts between closures and instance initializers

In some class definitions, we need to use a block of code expanded by {} as an instance initializer:

class Apple{

    String from = "China"

    // We do not consider it a closure, but an instance initializer
    {
        print("This code executes before Apple's constructor.")}}Copy the code

Groovy, however, takes the string “China” and what it considers a” closure “{… } as a whole, resulting in runtime errors. There are two solutions: either move the instance initializer to the top of the internal declaration, or use it explicitly; A semicolon separates the two:

// Solution 1, recommended
class Apple{

    {
        print("")
    }
    
    String from = "China"
}

// Solution 2 is not recommended because it confuses the instance initializer declaration with the normal closure declaration.
class Apple{

    String from = "China";

    {
        print("")}}Copy the code

2.16 Strong notes

There are probably a few official annotations to help with quick development, most of which come from the groovy.lang package, which means no additional external dependencies need to be imported via the import keyword:

Canonical replaces toString

If you want to print a class but don’t want to generate the toString() method yourself, you can use the @canonical annotation. This annotation has an additional excludes option: it allows us to ignore some attributes.

@Canonical
// If you don't want to print id and score, you can:
// @Canonical(excludes="id,score")
class Student {
    Integer id
    String name
    Integer age
    String major
    Integer score
}

// If there is no such annotation, the print is Student@Hashcode
Student(1,"Wang Fang",20,"CS","score")
print new Student(id: 1.name:"Wang Fang".age: 20.major: "CS".score: 90.0d)
Copy the code

2.16.2 @Delegate Indicates the Delegate

Using the @delegate annotation, it’s easy to implement method delegates in Groovy. Delegation is another way to reuse code besides inheritance. In the following code block, the Manager delegates the work() method to the internal worker property via an annotation:

class Worker{
    void work(){
        print("worker is working exactly.")}}// The Manager gets the Worker's public methods, even though the Worker property itself is private.
class Manager{
    @Delegate private Worker worker = new Worker()
}

// Check whether the Manager instance has a work method. If not, delegate the worker to execute the method.
new Manager().work()
Copy the code

2.16.3 @immutable object *

Immutable objects are inherently thread-safe. To create an immutable object, restrict all of its class attributes to be final and cannot be changed once the attributes have been initialized. The @immutable annotation provides a convenient solution:

@Immutable
class Student_{
    String id
    String name
}

def s = new Student_(id:"0001".name:"Wang Fang")

print s
Copy the code

Unlike the other annotations, it comes from the groovy.transform package. I’ve had some strange problems with this annotation. IDEA doesn’t seem to recognize the annotation well, causing further bugs such as code failure to paste, false pop-up warnings, and code prompts disappearing.

2.16.4 @Lazy Lazily loads class members

Lazy loading is a feature supported by most emerging languages. In Groovy, this is done through annotations, which note can only be used for class members.

class Connection{
    
    // The Connection takes 1 second to load
    Connection(){
        Thread.sleep(1000)
        print "Connection instance initialized"}}class Pool{
    
    // Since the code does not call conn, new Connection() is not actually executed
    @Lazy def conn = new Connection()
    Pool(){
        print "Pool instance initialization completed"}}def pool = new Pool();
Copy the code

Lazy-loaded members are initialized only the first time they are called, and Groovy internally ensures that the creation process is thread-safe through the voalTitle keyword.

2.16.5 @ Newify annotation

This annotation functions somewhat like the Apply method in Scala, allowing us to omit the new keyword when creating new objects (this feature also helps in designing DSLS). This annotation can be used on class declarations and method declarations, as well as on individual variable assignments:

class Student{
    String id
    String name
}

class Teacher{
    String id
    String name
}

@Newify(Student)
def getStudent(){
    // Omit the new keyword when creating Student inside the function.
    Student(id:"0001".name: "Wang Fang")}// Multiple types are arranged in an array.
@Newify([Student,Teacher])
static def getStudentAndTeacher(){
    [Student(id:"0001".name:"Wang Fang"),Teacher(id: "0002".name:"Cheng Yu")]}Copy the code

2.16.6@singleton Singleton pattern

In Groovy, you can implement a thread-safe, concise Singleton pattern with just the @Singleton annotation.

// Lazy-loaded singleton mode, with lazy items optional.
@Singleton(lazy = true)
class TheUnique{
    {
        println "created only once"}}// Call this singleton with.instance.
TheUnique.instance
Copy the code

Singleton mode can choose lazy loading by setting it to true in the annotations’ lazy option.

2.17 Notice Groovy’s == notation

In Java, == can compare values of two base data types, or compare HashCodes of two reference types. How.equals() compares depends on the developer’s rule: without doing anything,.equals is equivalent to ==.

For some common types, Java already has rules for comparing.equals() methods. In the case of strings, its.equals() implementation first determines whether references to two strings are the same using the == symbol, then whether they are the same length, and finally whether the characters in each position are the same by bits.

In Groovy, the confusion gets worse: Groovy’s == is equivalent to Java’s.equals() or compareTo() method (see the table of operator overloads), and Java’s original == semantics become the is() method in Groovy.

str1 = "111"
str2 = "222"

// str1 == str2 in Java semantics
str1.is(str2)

Str1.equals (str2) = str1.equals(str2)
str1 == str2
Copy the code

If the class being compared implements the Compareble interface, the semantics of == will preferentially choose the compareTo() method over equals() method.

3. Reference links


  1. Methods used by Groovy in later JDK versions↩
  2. Groovy ‘as’ is the keyword used to implement the 2+ interface↩
  3. Groovy: Operator overloading↩
  4. For a preview of closures, see this article: Groovy Closures – Jianshu.com ↩