1. 概要

ソースコードを変更する機能がないコンパイル済みのJavaクラスまたはGroovyクラスに、いくつかの便利なメソッドを追加できるかどうか疑問に思うことがあります。 結局のところ、Groovyカテゴリではそれが可能です。

Groovy は、動的で強力なJVM言語であり、多数のメタプログラミング機能を備えています。

このチュートリアルでは、Groovyのカテゴリの概念について説明します。

2. カテゴリとは何ですか?

カテゴリは、Objective-Cに触発されたメタプログラミング機能であり、新規または既存のJavaまたはGroovyクラスに機能を追加できます。

拡張機能とは異なり、カテゴリによって提供される追加機能はデフォルトでは有効になっていません。 したがって、カテゴリを有効にするための鍵は、useコードブロックです。

カテゴリによって実装される追加機能は、useコードブロック内でのみアクセスできます。

3. Groovyのカテゴリ

Groovy開発キットですでに利用可能ないくつかの著名なカテゴリについて説明しましょう。

3.1. TimeCategory

TimeCategory クラスは、 groovy.time パッケージで利用でき、DateおよびTimeオブジェクトを操作するためのいくつかの便利な方法を追加します。

このカテゴリは、整数を秒、分、日、月などの時間表記に変換する機能を追加します

また、 TimeCategory クラスは、plusminusなどのメソッドを提供し、 DateオブジェクトにDurationを追加し、DateオブジェクトからDurationを減算します。 。

TimeCategoryクラスによって提供されるいくつかの便利な機能を調べてみましょう。 これらの例では、最初に Date オブジェクトを作成してから、TimeCategoryを使用していくつかの操作を実行します。

def jan_1_2019 = new Date("01/01/2019")
use (TimeCategory) {
    assert jan_1_2019 + 10.seconds == new Date("01/01/2019 00:00:10")
    assert jan_1_2019 + 20.minutes == new Date("01/01/2019 00:20:00")
    assert jan_1_2019 - 1.day == new Date("12/31/2018")
    assert jan_1_2019 - 2.months == new Date("11/01/2018")
}

コードについて詳しく説明しましょう。

ここで、 10.seconds は、値が10秒のTimeDurationオブジェクトを作成します。 また、プラス(+)演算子は、TimeDurationオブジェクトをDateオブジェクトに追加します。

同様に、1.dayは1日の値でDurationオブジェクトを作成します。 また、マイナス(-)演算子は、DateオブジェクトからDurationオブジェクトを減算します。

また、 now ago、 from などのいくつかのメソッドは、 TimeCategory クラスを介して利用でき、相対の作成が可能です。日付

たとえば、 5.days.from.now は、現在の日付より5日前の値でDateオブジェクトを作成します。 同様に、 2.hours.ago は、現在時刻の2時間前の値を設定します。

それらの動作を見てみましょう。 また、 SimpleDateFormat を使用して、2つの類似した Date オブジェクトを比較するときに、時間の境界を無視します。

SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy")
use (TimeCategory) {
    assert sdf.format(5.days.from.now) == sdf.format(new Date() + 5.days)

    sdf = new SimpleDateFormat("dd/MM/yyyy hh:mm:ss")
    assert sdf.format(10.minutes.from.now) == sdf.format(new Date() + 10.minutes)
    assert sdf.format(2.hours.ago) == sdf.format(new Date() - 2.hours)
}

したがって、 TimeCategory クラスを使用すると、既知のクラスを使用して、単純で読みやすいコードを記述できます。

3.2. DOMCategory

DOMCategory クラスは、groovy.xml.domパッケージで利用できます。 JavaのDOMオブジェクトを操作するためのいくつかの便利な方法を提供します。

より具体的には、 DOMCategoryはDOM要素に対するGPath操作を可能にし、XMLのトラバースと処理を容易にします。

最初に、簡単なXMLテキストを記述し、DOMBuilderクラスを使用して解析します。

def baeldungArticlesText = """
<articles>
    <article core-java="true">
        <title>An Intro to the Java Debug Interface (JDI)</title>
        <desc>A quick and practical overview of Java Debug Interface.</desc>
    </article>
    <article core-java="false">
        <title>A Quick Guide to Working with Web Services in Groovy</title>
        <desc>Learn how to work with Web Services in Groovy.</desc>
    </article>
</articles>
"""

def baeldungArticlesDom = DOMBuilder.newInstance().parseText(baeldungArticlesText)
def root = baeldungArticlesDom.documentElement

ここで、 root オブジェクトには、DOMのすべての子ノードが含まれています。 DOMCategory クラスを使用して、これらのノードをトラバースしてみましょう。

use (DOMCategory) {
    assert root.article.size() == 2

    def articles = root.article
    assert articles[0].title.text() == "An Intro to the Java Debug Interface (JDI)"
    assert articles[1].desc.text() == "Learn how to work with Web Services in Groovy."
}

ここで、 DOMCategory クラスは、 GPath によって提供されるドット操作を使用して、ノードおよび要素に簡単にアクセスできるようにします。 また、任意のノードまたは要素の情報にアクセスするためのsizeやtextなどのメソッドを提供します。

次に、 DOMCategory を使用して、 rootDOMオブジェクトに新しいノードを追加しましょう。

use (DOMCategory) {
    def articleNode3 = root.appendNode(new QName("article"), ["core-java": "false"])
    articleNode3.appendNode("title", "Metaprogramming in Groovy")
    articleNode3.appendNode("desc", "Explore the concept of metaprogramming in Groovy")

    assert root.article.size() == 3
    assert root.article[2].title.text() == "Metaprogramming in Groovy"
}

同様に、 DOMCategory クラスには、DOMを変更するためのappendNodeやsetValueなどのメソッドもいくつか含まれています。

4. カテゴリを作成する

いくつかのGroovyカテゴリの動作を確認したので、カスタムカテゴリを作成する方法を見てみましょう。

4.1. セルフオブジェクトの使用

カテゴリクラスは、追加機能を実装するために特定の慣行に従う必要があります。

まず、機能を追加する方法はstaticである必要があります。 次に、メソッドの最初の引数は、この新機能を適用できるオブジェクトである必要があります。

Capitalize機能をStringクラスに追加しましょう。 これにより、Stringの最初の文字が大文字に変更されます。

最初に、 BaeldungCategory クラスを、staticメソッドcapitalizeおよびString型を最初の引数として記述します。

class BaeldungCategory {
    public static String capitalize(String self) {
        String capitalizedStr = self;
        if (self.size() > 0) {
            capitalizedStr = self.substring(0, 1).toUpperCase() + self.substring(1);
        }
        return capitalizedStr
    }
}

次に、 BaeldungCategory を有効にして、Stringオブジェクトのcapitalize機能を検証する簡単なテストを作成しましょう。

use (BaeldungCategory) {
    assert "norman".capitalize() == "Norman"
}

同様に、ある数を別の数の累乗にする機能を書いてみましょう。

public static double toThePower(Number self, Number exponent) {
    return Math.pow(self, exponent);
}

最後に、カスタムカテゴリをテストしましょう。

use (BaeldungCategory) {
    assert 50.toThePower(2) == 2500
    assert 2.4.toThePower(4) == 33.1776
}

4.2. @Categoryアノテーション

@ groovy.lang.Category アノテーションを使用して、カテゴリをインスタンススタイルのクラスとして宣言することもできます。 アノテーションを使用する場合は、カテゴリが適用されるクラス名を指定する必要があります。

オブジェクトのインスタンスには、メソッドでthisキーワードを使用してアクセスできます。 したがって、selfオブジェクトを最初の引数にする必要はありません。

NumberCategory クラスを作成し、@Categoryアノテーションを付けてカテゴリとして宣言しましょう。 また、cubedivideWithRoundUpなどのいくつかの機能を新しいカテゴリに追加します。

@Category(Number)
class NumberCategory {
    public Number cube() {
        return this*this*this
    }
    
    public int divideWithRoundUp(BigDecimal divisor, boolean isRoundUp) {
        def mathRound = isRoundUp ? BigDecimal.ROUND_UP : BigDecimal.ROUND_DOWN
        return (int)new BigDecimal(this).divide(divisor, 0, mathRound)
    }
}

ここで、 divideWithRoundUp 機能は、数値を除数で除算し、 isRoundUp パラメーターに基づいて、結果を次または前の整数に切り上げ/切り下げます。

新しいカテゴリをテストしてみましょう。

use (NumberCategory) {
    assert 3.cube() == 27
    assert 25.divideWithRoundUp(6, true) == 5
    assert 120.23.divideWithRoundUp(6.1, true) == 20
    assert 150.9.divideWithRoundUp(12.1, false) == 12
}

5. 結論

この記事では、Groovyのカテゴリーの概念について説明しました。これは、JavaクラスとGroovyクラスで追加機能を有効にできるメタプログラミング機能です。

私たちは次のようないくつかのカテゴリを調べました TimeCategory DOMCategory、 Groovyですでに利用可能です同時に、を使用するためのいくつかの追加の便利な方法を検討しました日にちおよびJavaのDOMはこれらのカテゴリを使用します。

最後に、独自のカスタムカテゴリを作成するためのいくつかの方法を検討しました。

いつものように、すべてのコード実装はGitHub利用できます。