«1. Обзор
Иногда мы можем задаться вопросом, можно ли добавить несколько дополнительных удобных методов в скомпилированные классы Java или Groovy, если у нас нет возможности изменять исходный код. Как оказалось, категория Groovy позволяет нам сделать именно это.
Groovy — это динамичный и мощный язык JVM с многочисленными функциями метапрограммирования.
В этом уроке мы рассмотрим концепцию категорий в Groovy.
2. Что такое категория?
Категории — это функция метапрограммирования, вдохновленная Objective-C, которая позволяет нам добавлять дополнительные функции в новый или существующий класс Java или Groovy.
В отличие от расширений, дополнительные функции, предоставляемые категорией, не включены по умолчанию. Таким образом, ключом к включению категории является использование блока кода.
Дополнительные функции, реализуемые категорией, доступны только внутри блока кода использования.
3. Категории в Groovy
Давайте обсудим несколько известных категорий, которые уже доступны в Groovy Development Kit.
3.1. TimeCategory
Класс TimeCategory доступен в пакете groovy.time, который добавляет несколько удобных способов работы с объектами даты и времени.
Эта категория добавляет возможность конвертировать целое число во временную запись, такую как секунды, минуты, дни и месяцы.
Кроме того, класс TimeCategory предоставляет такие методы, как plus и minus, для простого добавления Duration к объектам Date и вычитания Duration из объектов Date соответственно.
Давайте рассмотрим несколько удобных функций, предоставляемых классом 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 создает объект TimeDuration со значением 10 секунд. И оператор плюс (+) добавляет объект TimeDuration к объекту Date.
Аналогично, 1.day создает объект Duration со значением 1 день. И оператор минус (-) вычитает объект Duration из объекта Date.
Кроме того, несколько методов, таких как now, ago и from, доступны через класс TimeCategory, который позволяет создавать относительные даты.
Например, 5.days.from.now создаст объект Date со значением на 5 дней раньше текущей даты. Точно так же 2.hours.ago устанавливает значение на 2 часа раньше текущего времени.
Давайте посмотрим на них в действии. Также мы будем использовать 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)
}
Таким образом, используя класс TimeCategory, мы можем писать простой и более читаемый код, используя уже известные нам классы.
3.2. DOMCategory
Класс DOMCategory доступен в пакете groovy.xml.dom. Он предлагает несколько удобных способов работы с DOM-объектом Java.
В частности, DOMCategory позволяет выполнять операции GPath с элементами DOM, упрощая обход и обработку 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
Здесь корневой объект содержит все дочерние узлы 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. Кроме того, он предоставляет такие методы, как размер и текст, для доступа к информации о любом узле или элементе.
Теперь давайте добавим новый узел к корневому объекту DOM, используя DOMCategory:
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 также содержит несколько методов, таких как appendNode и setValue, для изменения DOM.
4. Создайте категорию
Теперь, когда мы увидели несколько категорий Groovy в действии, давайте рассмотрим, как создать пользовательскую категорию.
4.1. Использование Self Object
Класс категории должен следовать определенным правилам для реализации дополнительных функций.
Во-первых, метод добавления дополнительной функции должен быть статическим. Во-вторых, первым аргументом метода должен быть объект, к которому применима эта новая функция.
Давайте добавим функцию использования заглавных букв в класс String. Это просто изменит первую букву строки на прописную.
«Сначала мы напишем класс BaeldungCategory со статическим методом 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:
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, чтобы объявить категорию как класс в стиле экземпляра. При использовании аннотации мы должны указать имя класса, к которому применима наша категория.
Экземпляр объекта доступен по этому ключевому слову в методе. Следовательно, объект self не обязательно должен быть первым аргументом.
Напишем класс NumberCategory и объявим его как категорию с аннотацией @Category. Кроме того, мы добавим в нашу новую категорию несколько дополнительных функций, таких как куб и 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)
}
}
Здесь функция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. В то же время мы изучили несколько дополнительных удобных способов работы с Date и DOM Java с использованием этих категорий.
Наконец, мы рассмотрели несколько способов создания нашей собственной пользовательской категории.
Как обычно, все реализации кода доступны на GitHub.