Groovy is a dynamic language built for the JVM. It seamlessly integrates with Java code and libraries, building on Java's strengths while adding features inspired by languages like Python and Ruby.
Unlike independent programming languages such as C, C++, or Python, Groovy relies on Java. New features introduced in Java are also adopted in Groovy. The primary goal of Groovy is to reduce repetitive tasks and boilerplate code, making it an excellent choice for scripting on the JVM.
Differences between Java
Java, known for its robust features, is often considered a complex and heavyweight programming language. In contrast, Groovy offers a more streamlined approach to Java development. As a JVM-based language with both static and dynamic typing capabilities, Groovy simplifies many aspects of coding.
Java developers typically find Groovy easy to pick up, allowing them to quickly enhance their skill set and extend Java's functionality. A notable advantage of Groovy is its reputation for simplifying unit testing.
Consider the below program and lets interpret the difference in the output from Java and Groovy.
int method(String arg) {
return 1;
}
int method(Object arg) {
return 2;
}
Object o = "Object";
int result = method(o);
The above method gives different output in Java and Groovy.
Output
Java output : 2
Groovy output : 1
This happens because,
In Java, method overloading is resolved at compile time based on the compile-time type of the arguments.
However, in groovy method resolution occurs at runtime.
The other notable differences between Java and Groovy are,
Default Imports:
Groovy provides a set of default imports for commonly used packages, which can simplify code and reduce the need for explicit import statements. For example, Groovy automatically imports classes from packages like java.util
, java.io
, and groovy.lang
, among others. In contrast, Java requires all necessary imports to be explicitly specified.
Closures
Closures are independent blocks of code that can accept parameters and execute code. They can be written once and used later. Unlike standard methods or functions, closures can capture and use variables from their surrounding context. While technically, there isn't much difference between objects and closures, closures significantly improve the readability and flexibility of code.
Calling closures
In groovy closures can be invoked in two ways.
- Can be called like a regular function.
- Using the .call() method of Closure.
def sumTwoNums = {
a,b ->
println "Adding ${a} and ${b}"
return a+b
}
println sumTwoNums(2,4)
println sumTwoNums.call(5,2)
Output
6
def updateCounter = {
def counter = 0
return {
return counter = counter + 1;
}
}
def updateCounterFunc = updateCounter.call()
println updateCounterFunc()
println updateCounterFunc()
Output
1
2
In the above program, the updateCounter
defines a local variable counter and returns another closure. The returned closure has access to the counter variable due to the way closures capture their surrounding context which is explained later in this blog.
When updateCounter.call()
is executed, it initializes counter
to 0 and returns a new closure that increments counter
by 1 each time it is called.
Features of Closures
In order to completely understand closures in groovy we need to understand what is this
and owner
of a closure.
this
In groovy, this
keyword refers to the enclosing class. For example, if we access this
inside a closure which is present inside a class say User, then this
will refer to the User class.
If we use this
inside a closure which is nested in another closure and both these closures are present inside a class, then this
refers to the nearest outer class.
owner
The owner
of a closure is similar to this
, but the owner
keyword refers to the enclosing object or closure of a closure.
Let's see an example to clearly understand owner
and this
of a closure.
class Example{
def outerClosure = {
def innerClosure = {
println "Inner closure ---> $this"
}
innerClosure()
println "Outer closure ---> $this"
}
def printCurrentObject(){
println "Current object ---> $this"
}
}
Example example = new Example()
example.outerClosure.call()
example.printCurrentObject()
Output
Inner closure ---> Example@6e57e95e
Outer closure ---> Example@6e57e95e
Current object ---> Example@6e57e95e
In the above nested closures are declared, even though multiple layers of closures are involved, the reference to the current object (this) still points to the outer class instance. This allows access to the properties and methods of the enclosing class from within any nested closure.
class Example{
def outerClosure = {
def innerClosure = {
println "Inner closure owner ---> " + getOwner()
}
innerClosure()
println "Outer closure owner ---> " + getOwner()
}
def printThis(){
println "Current object ---> $this"
}
}
Example example = new Example()
example.outerClosure.call()
example.printThis()
Output
Inner closure owner ---> Example$_closure1@410954b
Outer closure owner ---> Example@46cc127b
Current object ---> Example@46cc127b
Inner Closure Owner:
The innerClosure
is enclosed within the outerClosure
, so getOwner()
in the innerClosure
returns the outerClosure
.
Outer Closure Owner:
The outerClosure
itself is enclosed within the instance of the Example
class. Therefore, getOwner()
in the outerClosure
returns the Example
class instance
Delegation
In Groovy, delegation is a mechanism that allows an object to pass on method calls to another object (known as the delegate). This concept is particularly useful when working with closures. Groovy provides the delegate property in closures to allow you to specify another object that will handle method calls not defined within the closure.
The below is an example of how delegation works in groovy
class ServiceLogger{
def log(message){
println "Service: $message"
}
}
class DatabaseLogger{
def log(message){
println "Database: $message"
}
}
def logMessage = {
log(it)
}
logMessage.delegate = new ServiceLogger() -> 1
logMessage("User created successfully")
logMessage.delegate = new DatabaseLogger()
logMessage("User fetched from DB successfully")
Output
Service: User created successfully
Database: User fetched from DB successfully
In the above example, there are two classes ServiceLogger
and DatabaseLogger
and a closure named logMessage
.
First we are assigning the ServiceLogger
as a delegate
for the closure. So when the closure logMessage
is called then the delegate's (log
) function is invoked. Later when we change the delegate of the closure to DatabaseLogger
the log method present inside the DatabaseLogger
is invoked.
Let's see another example to understand delegation in detail.
class MediaPlayer{
def fileName
def play = { "Playing ${fileName}" }
}
class VideoPlayer{
def fileName
}
MediaPlayer mediaPlayer = new MediaPlayer(fileName:"theme-music.mp3")
VideoPlayer videoPlayer = new VideoPlayer(fileName:"trailer.mp4")
println mediaPlayer.play()
mediaPlayer.play.delegate = videoPlayer
println mediaPlayer.play()
Initially, when mediaPlayer.play()
is called, it uses the fileName
from the MediaPlayer
instance, resulting in the output: Playing theme-music.mp3
. Even after changing the delegate of the closure to VideoPlayer
, the play closure still prints the MediaPlayer
file name. This behaviour is due to the default delegation strategy in Groovy closures. To understand this, let's explore the different delegation strategies in Groovy closures.
Delegation Strategy
In Groovy, the delegation strategy defines how a closure resolves method calls or property references that are not explicitly defined within the closure itself.
Groovy provides several delegation strategies that dictate how a closure resolves calls to methods or properties:
Closure.OWNER_FIRST
(default): The closure first tries to resolve the call in the owner, then the delegate.
Closure.DELEGATE_FIRST
: The closure first looks for the method/property in the delegate, and if not found, it checks in the owner.
Closure.OWNER_ONLY
: The closure only looks for method/property in the owner and ignores the delegate.
Closure.DELEGATE_ONLY
: The closure only resolves method/property in the delegate and ignores the owner.
So in the above program though we have changed the delegate of the closure while executing the closure will use its owner's methods or properties. Here the owner of the closure play is MediaPlayer
.
class MediaPlayer{
def fileName
def play = { "Playing ${fileName}" }
}
class VideoPlayer{
def fileName
}
MediaPlayer mediaPlayer = new MediaPlayer(fileName:"theme-music.mp3")
VideoPlayer videoPlayer = new VideoPlayer(fileName:"trailer.mp4")
println mediaPlayer.play()
mediaPlayer.play.resolveStrategy = Closure.DELEGATE_FIRST
mediaPlayer.play.delegate = videoPlayer
println mediaPlayer.play()
Output
Playing theme-music.mp3
Playing trailer.mp4
Here we have changed the resolution strategy of the closure to DELEGATE_FIRST
, now the closure uses the delegate's methods and properties.
Another advantage of using Closures is lazy evaluation of strings. Here is an example
def name = "Walter"
def greetingMsg = "Welcome! ${name}"
name = "White"
println greetingMsg
Output
Welcome! Walter
def name = "Walter"
def greetingMsg = "Welcome! ${->name}"
name = "White"
println greetingMsg
Output
Welcome! White
In the first script, the GString ${name}
is evaluated when greetingMsg
is defined, capturing the value of name at that moment, which is "Walter"
.
In the second script, the GString ${->name}
uses a closure. The closure is evaluated at the moment of printing, not when greetingMsg
is defined. Since name has changed to "White"
by the time the closure is executed, it reflects the updated value.
Currying in Groovy
Currying in Groovy is a technique that allows you to create a new closure by pre-filling some of the parameters of an existing closure. This effectively reduces the number of arguments needed to call the new closure, making it a convenient way to create specialized functions from a general one.
def getUsers = { groupId, role, status ->
// Simulate fetching users from a database or API
println "Fetching users from group ${groupId} with role ${role} and status ${status}"
}
def getHRUsers = getUsers.curry("HR")
getHRUsers("Admin", "Active")
getHRUsers("Viewer", "Inactive")
def getActiveUsers = getUsers.rcurry("ACTIVE")
getActiveUsers("Development", "Tester")
def getAllDevelopers = getUsers.ncurry(1, "Developer")
getAllDevelopers("IT", "Active")
getAllDevelopers("Marketing", "Suspended")
Output
Fetching users from group HR with role Admin and status Active
Fetching users from group HR with role Viewer and status Inactive
Fetching users from group Development with role Tester and status ACTIVE
Fetching users from group IT with role Developer and status Active
Fetching users from group Marketing with role Developer and status Suspended
In Groovy, there are three currying methods used to partially apply parameters to closures:
curry(): Fixes the leftmost parameters of a closure.
Example: closure.curry(value1) fixes value1 for the first parameter.
rcurry(): Fixes the rightmost parameters of a closure.
Example: closure.rcurry(valueN) fixes valueN for the last parameter.
ncurry(): Fixes parameters at a specific index in the closure.
Example: closure.ncurry(index, value) fixes the parameter at the given index.
These methods simplify repeated calls by pre-filling some arguments, improving code readability and maintainability.
Metaprogramming
In simple terms metaprogramming refers to writing code that can create, modify, generate and analyze other programs.It accepts other codes as its data and do some operations with it.
A best example for metaprogamming is the eval
function in JavaScript, which accepts a string of JavaScript code and executes it.
Metaprograms are frequently used in everyday applications. For instance, integrated development environments (IDEs) like Visual Studio Code and IntelliJ IDEA use metaprogramming techniques to analyze code for syntax or compile-time errors even before the code is executed. The IDEs essentially act as programs that process and check the user's code for errors before runtime.
Metaprogramming in Groovy
Groovy supports two types of metaprogramming:
Runtime Metaprogramming:
Runtime metaprogramming in Groovy allows us to modify or extend the behaviour of classes and objects dynamically at runtime.
In Groovy, the invokeMethod()
is a special method available in Groovy objects that is triggered when an undefined method is called on an object. By overriding invokeMethod()
, we can customize how undefined method calls are handled.
Additionally, Groovy provides getProperty()
and setProperty()
methods. These methods intercept operations related to getting or setting properties in a class, allowing you to implement custom logic, such as validation, before retrieving or modifying property values.
class User{
def name
def age
def email
void setProperty(String name, Object value){
if (value == null){
value = ""
}
this.@"$name" = value.toString()
}
void print(){
println "Name : ${name}, age : ${age}, email : ${email}"
}
}
User user = new User()
user.name = "arun"
user.age = 2
user.email = null
user.print()
Output
Name : arun, age : 2, email :
Command Chains
Command chains in Groovy offer a concise and expressive way to call methods without using parentheses or dots (.) between method calls. This feature can make your code more readable, especially when you want to write more fluid and natural-looking expressions.
A command chain lets you call methods as if you were writing a sentence. Let’s look at an example:
class Car {
def start() {
println "Car started"
return this
}
def drive() {
println "Driving"
return this
}
def stop() {
println "Car stopped"
return this
}
}
def car = new Car()
car start drive stop
Output
Car started
Driving
Car stopped
In the above example, car start drive stop
demonstrates a command chain where methods are called in sequence. Each method returns this
, allowing the next method in the chain to be called on the same Car
object.
In this blog, we explored various features of Groovy, from its metaprogramming capabilities and dynamic method handling to expressive features like command chains and delegation strategies. Groovy's ability to enhance code readability and flexibility through dynamic behaviour makes it a powerful tool for developers. By understanding and leveraging these features, you can write more intuitive and maintainable code.
If you have any questions, suggestions, or additional insights about Groovy or any other programming topics, please share your feedback in the comments below 🙂.
Top comments (0)