首頁  >  文章  >  Java  >  Groovy 相對於 Java 的特性和優勢

Groovy 相對於 Java 的特性和優勢

DDD
DDD原創
2024-09-18 14:32:06503瀏覽

Features and Advantages of Groovy Over Java

Groovy 是一種為 JVM 建構的動態語言。它與 Java 程式碼和函式庫無縫集成,以 Java 的優勢為基礎,同時添加受 Python 和 Ruby 等語言啟發的功能。
與 C、C 或 Python 等獨立程式語言不同,Groovy 依賴 Java。 Java 中引入的新特性也被 Groovy 所採用。 Groovy 的主要目標是減少重複任務和樣板程式碼,使其成為在 JVM 上編寫腳本的絕佳選擇。

Java 之間的差異

Java 以其強大的功能而聞名,通常被認為是一種複雜且重量級的程式語言。相較之下,Groovy 提供了更簡化的 Java 開發方法。作為一種基於 JVM 的語言,具有靜態和動態類型功能,Groovy 簡化了編碼的許多方面。

Java 開發人員通常會發現 Groovy 很容易上手,使他們能夠快速增強自己的技能並擴展 Java 的功能。 Groovy 的一個顯著優勢是其在簡化單元測試方面的聲譽。

考慮下面的程序,讓我們解釋一下 Java 和 Groovy 的輸出差異。

int method(String arg) {
    return 1;
}
int method(Object arg) {
    return 2;
}
Object o = "Object"; 
int result = method(o);

上述方法在 Java 和 Groovy 中給出了不同的輸出。

輸出

Java 輸出:2
Groovy 輸出:1

發生這種情況是因為,
在 Java 中,方法重載是在編譯時根據參數的編譯時類型來解決的。
然而,在 groovy 方法中解析發生在運行時。

Java 和 Groovy 之間的其他顯著差異是,

預設導入:
Groovy 為常用套件提供了一組預設導入,可以簡化程式碼並減少明確導入語句的需要。例如,Groovy 會自動從 java.util、java.io 和 groovy.lang 等套件中導入類別。相較之下,Java 要求明確指定所有必要的導入。

閉包

閉包是獨立的程式碼區塊,可以接受參數並執行程式碼。它們可以編寫一次並在以後使用。與標準方法或函數不同,閉包可以捕獲並使用周圍上下文中的變數。雖然從技術上講,物件和閉包之間沒有太大區別,但閉包顯著提高了程式碼的可讀性和靈活性。

呼叫閉包

在常規閉包中可以透過兩種方式呼叫。

  • 可以像常規函數一樣呼叫。
  • 使用 Closure 的 .call() 方法。
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

在上面的程式中,updateCounter定義了一個局部變數counter並回傳另一個閉包。由於閉包捕獲其周圍上下文的方式,返回的閉包可以存取計數器變量,這將在本部落格後面進行解釋。

當執行 updateCounter.call() 時,它將 counter 初始化為 0 並傳回一個新的閉包,每次呼叫時 counter 都會加 1。

閉包的特點

為了完全理解 groovy 中的閉包,我們需要了解 this 是什麼以及閉包的所有者。

在groovy中,該關鍵字指的是封閉類別。例如,如果我們在 User 類別中存在的閉包中存取 this,那麼 this 將引用 User 類別。

如果我們在一個嵌套在另一個閉包中的閉包中使用 this,並且這兩個閉包都存在於一個類別中,那麼 this 指的是最近的外部類別。

擁有者

閉包的擁有者與此類似,但owner關鍵字指的是閉包的封閉物件或閉包。

讓我們來看一個例子來清楚地理解閉包的擁有者和 this。

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()

輸出

內部封閉--->範例@6e57e95e
外封口 --->範例@6e57e95e
當前物件--->範例@6e57e95e

在上面宣告的巢狀閉包中,即使涉及多層閉包,當前物件(this)的參考仍然指向外部類別實例。這允許從任何嵌套閉包中存取封閉類別的屬性和方法。

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()

輸出

內封閉所有者 --->範例$_closure1@410954b
外封擁有者--->範例@46cc127b
當前物件--->範例@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 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.

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 ?.

以上是Groovy 相對於 Java 的特性和優勢的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn