Перейти к основному содержимому

Категории в Groovy

· 6 мин. чтения

Задача: Наибольшая подстрока без повторений

Для заданной строки s, найдите длину наибольшей подстроки без повторяющихся символов. Подстрока — это непрерывная непустая последовательность символов внутри строки...

ANDROMEDA 42

1. Обзор

Иногда мы можем задаться вопросом, можем ли мы добавить несколько дополнительных удобных методов в скомпилированные классы Java или Groovy, если у нас нет возможности изменять исходный код. Как оказалось, категория Groovy позволяет нам сделать именно это.

Groovy — это динамичный и мощный язык JVM с многочисленными функциями метапрограммирования .

В этом руководстве мы рассмотрим концепцию категорий в Groovy.

2. Что такое категория?

Категории — это функция метапрограммирования, вдохновленная Objective-C, которая позволяет нам добавлять дополнительные функции в новый или существующий класс Java или Groovy.

В отличие от расширений , дополнительные функции, предоставляемые категорией, не включены по умолчанию. Таким образом, ключом к включению категории является использование блока кода.

Дополнительные функции, реализованные категорией, доступны только внутри блока кода использования .

3. Категории в Groovy

Давайте обсудим несколько известных категорий, которые уже доступны в Groovy Development Kit.

3.1. Категория времени

Класс 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. DOMКатегория

Класс DOMCategory доступен в пакете groovy.xml.dom . Он предлагает несколько удобных способов работы с DOM-объектом Java.

В частности, DOMCategory позволяет выполнять операции GPath с элементами DOM, упрощая обход и обработку XML-файлов .

Сначала напишем простой XML-текст и разберем его с помощью класса DOMBuilder :

def foreachArticlesText = """
<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 foreachArticlesDom = DOMBuilder.newInstance().parseText(foreachArticlesText)
def root = foreachArticlesDom.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. Использование собственного объекта

Класс категории должен следовать определенным правилам для реализации дополнительных функций.

Во-первых, метод добавления дополнительной функции должен быть статическим . Во-вторых, первым аргументом метода должен быть объект, к которому применима эта новая функция.

Давайте добавим функцию использования заглавных букв в класс String . Это просто изменит первую букву строки на прописную.

Сначала мы напишем класс ForEachCategory со статическим методом, использующим заглавные буквы , и типом String в качестве первого аргумента:

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

Затем давайте напишем быстрый тест, чтобы включить ForEachCategory и проверить функцию использования заглавных букв в объекте String :

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

Точно так же давайте напишем функцию для возведения числа в степень другого числа:

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

Наконец, давайте проверим нашу пользовательскую категорию:

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

4.2. @категория аннотации

Мы также можем использовать аннотацию @groovy.lang.Category , чтобы объявить категорию как класс в стиле экземпляра . При использовании аннотации мы должны указать имя класса, к которому применима наша категория.

Экземпляр объекта доступен с помощью этого ключевого слова в методе. Следовательно, объект self не обязательно должен быть первым аргументом.

Давайте напишем класс NumberCategory и объявим его как категорию с аннотацией @Category . Кроме того, мы добавим в нашу новую категорию несколько дополнительных функций, таких как куб и разделять с округлением вверх:

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