Groovy est un langage dynamique conçu pour la JVM. Il s'intègre parfaitement au code et aux bibliothèques Java, s'appuyant sur les atouts de Java tout en ajoutant des fonctionnalités inspirées de langages comme Python et Ruby.
Contrairement aux langages de programmation indépendants tels que C, C ou Python, Groovy s'appuie sur Java. Les nouvelles fonctionnalités introduites dans Java sont également adoptées dans Groovy. L'objectif principal de Groovy est de réduire les tâches répétitives et le code passe-partout, ce qui en fait un excellent choix pour les scripts sur la JVM.
Java, connu pour ses fonctionnalités robustes, est souvent considéré comme un langage de programmation complexe et lourd. En revanche, Groovy propose une approche plus rationalisée du développement Java. En tant que langage basé sur JVM doté de capacités de typage statique et dynamique, Groovy simplifie de nombreux aspects du codage.
Les développeurs Java trouvent généralement Groovy facile à maîtriser, ce qui leur permet d'améliorer rapidement leurs compétences et d'étendre les fonctionnalités de Java. Un avantage notable de Groovy est sa réputation de simplification des tests unitaires.
Considérez le programme ci-dessous et interprétons la différence entre les résultats de Java et Groovy.
int method(String arg) { return 1; } int method(Object arg) { return 2; } Object o = "Object"; int result = method(o);
La méthode ci-dessus donne une sortie différente en Java et Groovy.
Sortie Java : 2
Sortie groovy : 1
Cela arrive parce que,
En Java, la surcharge des méthodes est résolue au moment de la compilation en fonction du type des arguments au moment de la compilation.
Cependant, dans la méthode groovy, la résolution se produit au moment de l'exécution.
Les autres différences notables entre Java et Groovy sont,
Importations par défaut :
Groovy fournit un ensemble d'importations par défaut pour les packages couramment utilisés, ce qui peut simplifier le code et réduire le besoin d'instructions d'importation explicites. Par exemple, Groovy importe automatiquement des classes à partir de packages tels que java.util, java.io et groovy.lang, entre autres. En revanche, Java exige que toutes les importations nécessaires soient explicitement spécifiées.
Les fermetures sont des blocs de code indépendants qui peuvent accepter des paramètres et exécuter du code. Ils peuvent être écrits une seule fois et utilisés plus tard. Contrairement aux méthodes ou fonctions standard, les fermetures peuvent capturer et utiliser des variables de leur contexte environnant. Bien que techniquement, il n'y ait pas beaucoup de différence entre les objets et les fermetures, les fermetures améliorent considérablement la lisibilité et la flexibilité du code.
Les fermetures In groovy peuvent être invoquées de deux manières.
def sumTwoNums = { a,b -> println "Adding ${a} and ${b}" return a+b } println sumTwoNums(2,4) println sumTwoNums.call(5,2)
6
def updateCounter = { def counter = 0 return { return counter = counter + 1; } } def updateCounterFunc = updateCounter.call() println updateCounterFunc() println updateCounterFunc()
1
2
Dans le programme ci-dessus, updateCounter définit un compteur de variable locale et renvoie une autre fermeture. La fermeture renvoyée a accès à la variable compteur en raison de la manière dont les fermetures capturent leur contexte environnant, ce qui est expliqué plus loin dans ce blog.
Lorsque updateCounter.call() est exécuté, il initialise le compteur à 0 et renvoie une nouvelle fermeture qui incrémente le compteur de 1 à chaque fois qu'il est appelé.
Afin de comprendre complètement les fermetures dans groovy, nous devons comprendre ce que c'est et le propriétaire d'une fermeture.
En groovy, ce mot-clé fait référence à la classe englobante. Par exemple, si nous y accédons à l’intérieur d’une fermeture présente dans une classe, par exemple User, alors cela fera référence à la classe User.
Si nous utilisons ceci à l'intérieur d'une fermeture qui est imbriquée dans une autre fermeture et que ces deux fermetures sont présentes à l'intérieur d'une classe, alors cela fait référence à la classe externe la plus proche.
Le propriétaire d'une fermeture est similaire à celui-ci, mais le mot-clé propriétaire fait référence à l'objet englobant ou à la fermeture d'une fermeture.
Voyons un exemple pour bien comprendre le propriétaire et celui d'une fermeture.
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()
Fermeture intérieure ---> Exemple@6e57e95e
Fermeture extérieure ---> Exemple@6e57e95e
Objet actuel ---> Exemple@6e57e95e
Dans les fermetures imbriquées ci-dessus sont déclarées, même si plusieurs couches de fermetures sont impliquées, la référence à l'objet actuel (this) pointe toujours vers l'instance de classe externe. Cela permet d'accéder aux propriétés et aux méthodes de la classe englobante depuis n'importe quelle fermeture imbriquée.
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()
Propriétaire de la fermeture intérieure ---> Exemple$_closure1@410954b
Propriétaire de la fermeture extérieure ---> Exemple@46cc127b
Objet actuel ---> Exemple@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
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")
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 fileNamefrom 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.
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()
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
Welcome! Walter
def name = "Walter" def greetingMsg = "Welcome! ${->name}" name = "White" println greetingMsg
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 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")
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.
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.
Groovy supports two types of 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()
Name : arun, age : 2, email :
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
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 ?.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!