ホームページ  >  記事  >  Java  >  Java に対する Groovy の機能と利点

Java に対する Groovy の機能と利点

DDD
DDDオリジナル
2024-09-18 14:32:06406ブラウズ

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 開発に対してより合理化されたアプローチを提供します。 Groovy は、静的型付け機能と動的型付け機能の両方を備えた JVM ベースの言語として、コーディングの多くの側面を簡素化します。

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
グルーヴィーな出力: 1

これが起こる理由は、
Java では、メソッドのオーバーロードはコンパイル時の引数の型に基づいてコンパイル時に解決されます。
ただし、Groovy メソッドでは、解決は実行時に行われます。

Java と Groovy のその他の注目すべき違いは次のとおりです。

デフォルトのインポート:
Groovy は、一般的に使用されるパッケージ用のデフォルトのインポートのセットを提供します。これにより、コードが簡素化され、明示的なインポート ステートメントの必要性が軽減されます。たとえば、Groovy は、java.util、java.io、groovy.lang などのパッケージからクラスを自動的にインポートします。対照的に、Java では、必要なインポートをすべて明示的に指定する必要があります。

クロージャ

クロージャは、パラメータを受け入れてコードを実行できる独立したコード ブロックです。これらは一度書き込んで、後で使用することができます。標準のメソッドや関数とは異なり、クロージャは周囲のコンテキストから変数をキャプチャして使用できます。技術的には、オブジェクトとクロージャの間に大きな違いはありませんが、クロージャはコードの可読性と柔軟性を大幅に向上させます。

クロージャの呼び出し

Groovy では、クロージャは 2 つの方法で呼び出すことができます。

  • 通常の関数と同様に呼び出すことができます。
  • クロージャの .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() が実行されると、カウンターが 0 に初期化され、呼び出されるたびにカウンターを 1 ずつインクリメントする新しいクロージャが返されます。

クロージャーの特徴

Groovy のクロージャを完全に理解するには、これとクロージャの所有者について理解する必要があります。

これ

Groovy では、このキーワードはそれを囲んでいるクラスを指します。たとえば、User クラス内に存在するクロージャ内でこれにアクセスすると、これは User クラスを参照します。

別のクロージャにネストされているクロージャ内で 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()

出力

内部クロージャーの所有者 ---> Example$_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 ?.

以上がJava に対する Groovy の機能と利点の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。