1. Overview
1.概述
We may sometimes wonder if we can add a few additional handy methods to compiled Java or Groovy classes where we don’t have the ability to modify the source code. As it turns out, a Groovy category lets us do just that.
我们有时会想,在我们没有能力修改源代码的情况下,是否可以给编译好的Java或Groovy类增加一些额外的方便方法。事实证明,一个Groovy类可以让我们做到这一点。
Groovy is a dynamic and powerful JVM language that has numerous Metaprogramming features.
Groovy是一种动态的、强大的JVM语言,具有众多Metaprogramming特性。
In this tutorial, we’ll explore the concept of categories in Groovy.
在本教程中,我们将探讨Groovy中类别的概念。
2. What Is a Category?
2.什么是类别?
Categories are a metaprogramming feature, inspired by Objective-C, that allows us to add extra functionality to a new or existing Java or Groovy class.
分类是一种元编程特性,受到Objective-C的启发,它允许我们为新的或现有的Java或Groovy类添加额外的功能。
Unlike extensions, the additional features provided by a category aren’t enabled by default. Therefore, the key to enabling a category is the use code block.
与extensions不同,类别所提供的额外功能并不是默认启用的。因此,启用一个类别的关键是use代码块。
The extra features implemented by a category are only accessible inside the use code block.
一个类别所实现的额外功能只能在use代码块内访问。。
3. Categories in Groovy
3.Groovy中的类别
Let’s discuss a few prominent categories that are already available in the Groovy Development Kit.
让我们来讨论一下Groovy开发工具包中已有的几个突出的类别。
3.1. TimeCategory
3.1 时间类别
The TimeCategory class is available in the groovy.time package that adds a few handy ways to work with Date and Time objects.
TimeCategory类在groovy.time包中可用,它增加了一些方便的方法来处理Date和Time对象。
This category adds the capability to convert an Integer into a time notation like seconds, minutes, days, and months.
该类别增加了将整数转换为秒、分、日、月等时间符号的功能。
Also, the TimeCategory class provides methods like plus and minus for easily adding Duration to Date objects and subtracting Duration from Date objects, respectively.
另外,TimeCategory类提供了plus和minus等方法,分别用于轻松地向Date对象添加Duration和从Date对象减去Duration。
Let’s examine a few handy features provided by the TimeCategory class. For these examples, we’ll first create a Date object and then perform a few operations using TimeCategory:
让我们研究一下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")
}
Let’s discuss the code in detail.
让我们详细讨论一下这段代码。
Here, 10.seconds creates the TimeDuration object with the value of 10 seconds. And, the plus (+) operator adds the TimeDuration object to the Date object.
这里,10.seconds创建了TimeDuration对象,值为10秒。而且,加号(+)运算符将TimeDuration对象添加到Date对象。
Similarly, 1.day creates the Duration object with the value of 1 day. And, the minus (-) operator subtracts the Duration object from the Date object.
同样,1.day创建Duration对象,值为1天。而且,减(-)运算符将Duration对象从Date对象中减去。
Also, a few methods like now, ago, and from are available through the TimeCategory class, which allows creating relative dates.
另外,一些方法如now、ago,和f可以通过TimeCategory类获得,它允许创建相对日期。
For instance, 5.days.from.now will create a Date object with the value of 5 days ahead of the current date. Similarly, 2.hours.ago sets the value of 2 hours before the current time.
例如,5.days.from.now将创建一个Date对象,其值为比当前日期提前5天。类似地,2.hours.against设置的值是比当前时间早2小时。
Let’s look at them in action. Also, we’ll use SimpleDateFormat to ignore the boundaries of time while comparing two similar Date objects:
让我们来看看它们的运作情况。另外,我们将使用SimpleDateFormat来忽略时间的界限,同时比较两个类似的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)
}
Therefore, using the TimeCategory class, we can write simple and more readable code using the classes we already know.
因此,使用TimeCategory类,我们可以使用我们已经知道的类来编写简单且更易读的代码。
3.2. DOMCategory
3.2. DOMCategory
The DOMCategory class is available in the groovy.xml.dom package. It offers a few handy ways to work with Java’s DOM object.
DOMCategory类在groovy.xml.dom包中可用。它提供了一些方便的方法来处理Java的DOM对象。
More specifically, DOMCategory allows GPath operations on DOM elements, allowing easier traversal and processing of XMLs.
更具体地说,DOMCategory允许对DOM元素进行GPath操作,使XML的遍历和处理更加容易。
At first, let’s write a simple XML text and parse it using the DOMBuilder class:
首先,让我们写一个简单的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
Here, the root object contains all the child-nodes of the DOM. Let’s traverse these nodes using the DOMCategory class:
这里,根对象包含了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."
}
Here, the DOMCategory class allows easy access of nodes and elements using dot operations provided by GPath. Also, it provides methods like size and text to access the information of any node or element.
在这里,DOMCategory类允许使用GPath提供的点操作轻松访问节点和元素。此外,它还提供了像size和text这样的方法来访问任何节点或元素的信息。
Now, let’s append a new node to the root DOM object using DOMCategory:
现在,让我们使用DOMCategory向root DOM对象追加一个新节点。
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"
}
Similarly, the DOMCategory class also contains a few methods like appendNode and setValue to modify the DOM.
同样,DOMCategory 类也包含一些方法,如appendNode 和setValue来修改DOM。
4. Create a Category
4.创建一个类别
Now that we’ve seen a couple of Groovy categories in action, let’s explore how to create a custom category.
现在我们已经看到了几个Groovy类别的应用,让我们来探讨一下如何创建一个自定义类别。
4.1. Using Self Object
4.1.使用自我对象
A category class must follow certain practices to implement additional features.
一个类别类必须遵循某些惯例,以实现额外的功能。
First, the method adding an additional feature should be static. Second, the first argument of the method should be the object to which this new feature is applicable.
首先,添加额外特性的方法应该是static。其次,该方法的第一个参数应该是该新特性所适用的对象。
Let’s add the capitalize feature to the String class. This will simply change the first letter of the String to upper-case.
让我们将capitalize特性添加到String类。这将简单地把String的第一个字母改为大写。
At first, we’ll write the BaeldungCategory class with a static method capitalize and String type as its first argument:
首先,我们要写一个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
}
}
Next, let’s write a quick test to enable the BaeldungCategory and verify the capitalize feature on the String object:
接下来,让我们写一个快速测试,启用BaeldungCategory,并验证String对象的capitalize功能。
use (BaeldungCategory) {
assert "norman".capitalize() == "Norman"
}
Similarly, let’s write a feature to raise a number to the power of another number:
同样地,让我们写一个功能,把一个数字提高到另一个数字的幂。
public static double toThePower(Number self, Number exponent) {
return Math.pow(self, exponent);
}
Finally, let’s test our custom category:
最后,让我们测试一下我们的自定义类别。
use (BaeldungCategory) {
assert 50.toThePower(2) == 2500
assert 2.4.toThePower(4) == 33.1776
}
4.2. @Category Annotation
4.2.@Category注释
We can also use @groovy.lang.Category annotation to declare a category as an instance-style class. When using the annotation, we must supply the class name to which our category is applicable.
我们也可以使用@groovy.lang.Category注解来将一个类别声明为一个实例式类。在使用该注解时,我们必须提供我们的类别所适用的类名。
The instance of the object is accessible using this keyword in a method. Hence, the self object is not required to be the first argument.
对象的实例可以通过方法中的this关键字访问。因此,self对象不需要作为第一个参数。
Let’s write a NumberCategory class and declare it as a category with the @Category annotation. Also, we’ll add a few additional features like cube and divideWithRoundUp to our new category:
让我们编写一个NumberCategory类,并用@Category注解将其声明为一个类别。另外,我们将为我们的新类别添加一些额外的功能,如cube和divideWithRoundUp。
@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)
}
}
Here, the divideWithRoundUp feature divides a number by the divisor and rounds up/down the result to the next or previous integer based on the isRoundUp parameter.
这里,divideWithRoundUp特性将一个数字除以除数,并根据isRoundUp参数将结果向上/向下舍入到下一个或前一个整数。
Let’s test our new category:
让我们测试一下我们的新类别。
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. Conclusion
5.总结
In this article, we’ve explored the concept of Categories in Groovy — a metaprogramming feature that can enable additional features on Java and Groovy classes.
在这篇文章中,我们已经探讨了Groovy中Category的概念–这是一种元编程功能,可以在Java和Groovy类上实现额外的功能。
We’ve examined a few categories like TimeCategory and DOMCategory, which are already available in Groovy. At the same time, we’ve explored a few additional handy ways to work with the Date and Java’s DOM using these categories.
我们研究了一些类别,如TimeCategory和DOMCategory,它们在Groovy中已经可用。同时,我们还探索了一些额外的便捷方式,使用这些类别来处理Date和Java的DOM。
Last, we’ve explored a couple of ways to create our own custom category.
最后,我们已经探索了几种创建我们自己的自定义类别的方法。
As usual, all the code implementations are available over on GitHub.
像往常一样,所有的代码实现都可以在GitHub上找到,。