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

Введение в Apache Commons Lang 3

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

1. Обзор

Библиотека Apache Commons Lang 3 — это популярный полнофункциональный пакет служебных классов, направленный на расширение функциональности Java API .

Репертуар библиотеки довольно богат: от обработки строк, массивов и чисел, отражения и параллелизма до реализации нескольких упорядоченных структур данных, таких как пары и тройки (известные как кортежи ).

В этом уроке мы подробно рассмотрим наиболее полезные служебные классы библиотеки .

2. Зависимость от Maven

Как обычно, чтобы начать использовать Apache Commons Lang 3, нам сначала нужно добавить зависимость Maven :

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>

3. Класс StringUtils

Первый служебный класс, который мы рассмотрим в этом вводном обзоре, — StringUtils .

Как следует из названия, StringUtils позволяет нам выполнять ряд операций со строками, безопасных для нулей , которые дополняют/расширяют те, которые предоставляет java.lang.String из коробки .

Давайте начнем демонстрировать набор служебных методов, которые выполняют несколько проверок заданной строки , например определяют, является ли строка пустой, пустой, строчной, прописной, буквенно-цифровой и т. д.:

@Test
public void whenCalledisBlank_thenCorrect() {
assertThat(StringUtils.isBlank(" ")).isTrue();
}

@Test
public void whenCalledisEmpty_thenCorrect() {
assertThat(StringUtils.isEmpty("")).isTrue();
}

@Test
public void whenCalledisAllLowerCase_thenCorrect() {
assertThat(StringUtils.isAllLowerCase("abd")).isTrue();
}

@Test
public void whenCalledisAllUpperCase_thenCorrect() {
assertThat(StringUtils.isAllUpperCase("ABC")).isTrue();
}

@Test
public void whenCalledisMixedCase_thenCorrect() {
assertThat(StringUtils.isMixedCase("abC")).isTrue();
}

@Test
public void whenCalledisAlpha_thenCorrect() {
assertThat(StringUtils.isAlpha("abc")).isTrue();
}

@Test
public void whenCalledisAlphanumeric_thenCorrect() {
assertThat(StringUtils.isAlphanumeric("abc123")).isTrue();
}

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

Чтобы узнать о некоторых других дополнительных методах, которые проверяют или применяют какой-либо алгоритм преобразования к заданной строке , ознакомьтесь с этим руководством .

Те, что мы рассмотрели выше, действительно просты, поэтому модульные тесты не должны говорить сами за себя.

4. Класс ArrayUtils

Класс ArrayUtils реализует набор служебных методов, позволяющих нам обрабатывать и проверять массивы самых разных форм и форм .

Начнем с двух перегруженных реализаций метода toString() , который возвращает строковое представление заданного массива и конкретную строку , когда массив равен нулю:

@Test
public void whenCalledtoString_thenCorrect() {
String[] array = {"a", "b", "c"};
assertThat(ArrayUtils.toString(array))
.isEqualTo("{a,b,c}");
}

@Test
public void whenCalledtoStringIfArrayisNull_thenCorrect() {
assertThat(ArrayUtils.toString(null, "Array is null"))
.isEqualTo("Array is null");
}

Далее у нас есть методы hasCode() и toMap() .

Первый генерирует пользовательскую реализацию hashCode для массива, а второй преобразует массив в Map :

@Test
public void whenCalledhashCode_thenCorrect() {
String[] array = {"a", "b", "c"};
assertThat(ArrayUtils.hashCode(array))
.isEqualTo(997619);
}

@Test
public void whenCalledtoMap_thenCorrect() {
String[][] array = {{"1", "one", }, {"2", "two", }, {"3", "three"}};
Map map = new HashMap();
map.put("1", "one");
map.put("2", "two");
map.put("3", "three");
assertThat(ArrayUtils.toMap(array))
.isEqualTo(map);
}

Наконец, давайте рассмотрим методы isSameLength() и indexOf() .

Первый используется для проверки того, имеют ли два массива одинаковую длину, а второй — для получения индекса данного элемента:

@Test
public void whenCalledisSameLength_thenCorrect() {
int[] array1 = {1, 2, 3};
int[] array2 = {1, 2, 3};
assertThat(ArrayUtils.isSameLength(array1, array2))
.isTrue();
}

@Test
public void whenCalledIndexOf_thenCorrect() {
int[] array = {1, 2, 3};
assertThat(ArrayUtils.indexOf(array, 1, 0))
.isEqualTo(0);
}

Как и в случае с классом StringUtils , ArrayUtils реализует гораздо больше дополнительных методов. Вы можете узнать о них больше в этом уроке .

В этом случае мы продемонстрировали только самые представительные из них.

5. Класс NumberUtils

Еще одним ключевым компонентом Apache Commons Lang 3 является класс NumberUtils .

Как и ожидалось, класс предоставляет большое количество служебных методов, предназначенных для обработки и управления числовыми типами .

Давайте посмотрим на перегруженные реализации метода compare() , который сравнивает равенство различных примитивов, таких как int и long :

@Test
public void whenCalledcompareWithIntegers_thenCorrect() {
assertThat(NumberUtils.compare(1, 1))
.isEqualTo(0);
}

@Test
public void whenCalledcompareWithLongs_thenCorrect() {
assertThat(NumberUtils.compare(1L, 1L))
.isEqualTo(0);
}

Кроме того, существуют реализации compare() , которые работают с byte и short и работают очень похоже на приведенные выше примеры.

Следующими в этом обзоре являются методы createNumber() и isDigit() .

Первый позволяет нам создать числовое представление строки , а второй проверяет, состоит ли строка только из цифр:

@Test
public void whenCalledcreateNumber_thenCorrect() {
assertThat(NumberUtils.createNumber("123456"))
.isEqualTo(123456);
}

@Test
public void whenCalledisDigits_thenCorrect() {
assertThat(NumberUtils.isDigits("123456")).isTrue();
}

Когда дело доходит до нахождения комбинированных и максимальных значений предоставленного массива, класс NumberUtils обеспечивает мощную поддержку этих операций посредством перегруженных реализаций методов min() и max() :

@Test
public void whenCalledmaxwithIntegerArray_thenCorrect() {
int[] array = {1, 2, 3, 4, 5, 6};
assertThat(NumberUtils.max(array))
.isEqualTo(6);
}

@Test
public void whenCalledminwithIntegerArray_thenCorrect() {
int[] array = {1, 2, 3, 4, 5, 6};
assertThat(NumberUtils.min(array)).isEqualTo(1);
}

@Test
public void whenCalledminwithByteArray_thenCorrect() {
byte[] array = {1, 2, 3, 4, 5, 6};
assertThat(NumberUtils.min(array))
.isEqualTo((byte) 1);
}

6. Класс фракций _

Работа с дробями проходит хорошо, когда мы пользуемся ручкой и листом бумаги. Но должны ли мы проходить через сложности этого процесса при написании кода? Не совсем.

Класс Fraction упрощает сложение, вычитание и умножение дробей :

@Test
public void whenCalledgetFraction_thenCorrect() {
assertThat(Fraction.getFraction(5, 6)).isInstanceOf(Fraction.class);
}

@Test
public void givenTwoFractionInstances_whenCalledadd_thenCorrect() {
Fraction fraction1 = Fraction.getFraction(1, 4);
Fraction fraction2 = Fraction.getFraction(3, 4);
assertThat(fraction1.add(fraction2).toString()).isEqualTo("1/1");
}

@Test
public void givenTwoFractionInstances_whenCalledsubstract_thenCorrect() {
Fraction fraction1 = Fraction.getFraction(3, 4);
Fraction fraction2 = Fraction.getFraction(1, 4);
assertThat(fraction1.subtract(fraction2).toString()).isEqualTo("1/2");
}

@Test
public void givenTwoFractionInstances_whenCalledmultiply_thenCorrect() {
Fraction fraction1 = Fraction.getFraction(3, 4);
Fraction fraction2 = Fraction.getFraction(1, 4);
assertThat(fraction1.multiplyBy(fraction2).toString()).isEqualTo("3/16");
}

Хотя операции с дробями, безусловно, не самая частая задача, которую нам придется решать в нашей повседневной работе по разработке, класс Fraction обеспечивает ценную поддержку для выполнения этих операций простым способом.

7. Класс SystemUtils

Иногда нам нужно получить некоторую динамическую информацию о различных свойствах и переменных базовой платформы Java или операционной системы.

Apache Commons Lang 3 предоставляет класс SystemUtils для безболезненного выполнения этой задачи .

Рассмотрим, например, методы getJavaHome() , getUserHome() и isJavaVersionAtLeast() :

@Test
public void whenCalledgetJavaHome_thenCorrect() {
assertThat(SystemUtils.getJavaHome())
.isEqualTo(new File("path/to/java/jdk"));
}

@Test
public void whenCalledgetUserHome_thenCorrect() {
assertThat(SystemUtils.getUserHome())
.isEqualTo(new File("path/to/user/home"));
}

@Test
public void whenCalledisJavaVersionAtLeast_thenCorrect() {
assertThat(SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_RECENT)).isTrue();
}

Класс SystemUtils реализует несколько дополнительных служебных методов . Мы опустили их, чтобы примеры были короткими.

8. Ленивая инициализация и классы-строители

Одним из наиболее привлекательных аспектов Apache Commons Lang 3 является реализация некоторых хорошо известных шаблонов проектирования, включая шаблоны отложенной инициализации и построения .

Например, предположим, что мы создали дорогостоящий класс User (не показан для краткости) и хотим отложить его создание до тех пор, пока он действительно не понадобится.

В таком случае все, что нам нужно сделать, это расширить параметризованный абстрактный класс LazyInitializer и переопределить его метод initialize() :

public class UserInitializer extends LazyInitializer<User> {

@Override
protected User initialize() {
return new User("John", "john@domain.com");
}
}

Теперь, если мы хотим получить наш дорогостоящий объект User , когда это потребуется, мы просто вызываем метод get() UserInitializer :

@Test 
public void whenCalledget_thenCorrect()
throws ConcurrentException {
UserInitializer userInitializer = new UserInitializer();
assertThat(userInitializer.get()).isInstanceOf(User.class);
}

Метод get() является реализацией идиомы двойной проверки (поточно-ориентированной) для поля экземпляра, как указано в статье Джошуа Блоха «Эффективная Java», пункт 71 :

private volatile User instance;

User get() {
if (instance == null) {
synchronized(this) {
if (instance == null)
instance = new User("John", "john@domain.com");
}
}
}
return instance;
}

Кроме того, Apache Commons Lang 3 реализует класс HashCodeBuilder , который позволяет нам генерировать реализации hashCode() , предоставляя построителю различные параметры на основе типичного плавного API:

@Test
public void whenCalledtoHashCode_thenCorrect() {
int hashcode = new HashCodeBuilder(17, 37)
.append("John")
.append("john@domain.com")
.toHashCode();
assertThat(hashcode).isEqualTo(1269178828);
}

Мы можем сделать что-то подобное с классом BasicThreadFactory и создать потоки демона с шаблоном именования и приоритетом:

@Test
public void whenCalledBuilder_thenCorrect() {
BasicThreadFactory factory = new BasicThreadFactory.Builder()
.namingPattern("workerthread-%d")
.daemon(true)
.priority(Thread.MAX_PRIORITY)
.build();
assertThat(factory).isInstanceOf(BasicThreadFactory.class);
}

9. Класс ConstructorUtils

Reflection является первоклассным гражданином в Apache Commons Lang 3.

Библиотека включает в себя несколько классов отражения, что позволяет нам получать доступ к полям и методам классов и манипулировать ими.

Например, предположим, что мы реализовали наивный класс домена пользователя :

public class User {

private String name;
private String email;

// standard constructors / getters / setters / toString
}

Предполагая, что его параметризованный конструктор является общедоступным , мы можем легко получить к нему доступ с помощью класса ConstructorUtils :

@Test
public void whenCalledgetAccessibleConstructor_thenCorrect() {
assertThat(ConstructorUtils
.getAccessibleConstructor(User.class, String.class, String.class))
.isInstanceOf(Constructor.class);
}

В качестве альтернативы стандартному созданию экземпляров класса с помощью конструкторов мы можем рефлективно создавать экземпляры User , просто вызывая методы invokeConstructor() и invokeExactConstructor() :

@Test
public void whenCalledinvokeConstructor_thenCorrect()
throws Exception {
assertThat(ConstructorUtils.invokeConstructor(User.class, "name", "email"))
.isInstanceOf(User.class);
}

@Test
public void whenCalledinvokeExactConstructor_thenCorrect()
throws Exception {
String[] args = {"name", "email"};
Class[] parameterTypes= {String.class, String.class};
assertThat(ConstructorUtils.invokeExactConstructor(User.class, args, parameterTypes))
.isInstanceOf(User.class);
}

10. Класс FieldUtils

Точно так же мы можем использовать методы класса FieldUtils для рефлексивного чтения/записи полей класса .

Предположим, что мы хотим получить поле класса User или поле, которое класс наследует от суперкласса.

В таком случае мы можем вызвать метод getField() :

@Test
public void whenCalledgetField_thenCorrect() {
assertThat(FieldUtils.getField(User.class, "name", true).getName())
.isEqualTo("name");
}

В качестве альтернативы, если мы хотим использовать более ограничительную область отражения и получить только поле, объявленное в классе User , а не унаследованное от суперкласса , мы просто используем метод getDeclaredField() :

@Test
public void whenCalledgetDeclaredFieldForceAccess_thenCorrect() {
assertThat(FieldUtils.getDeclaredField(User.class, "name", true).getName())
.isEqualTo("name");
}

Кроме того, мы можем использовать метод getAllFields() для получения количества полей отраженного класса и записать значение в объявленное поле или поле, определенное в иерархии с помощью методов writeField() и writeDeclaredField() :

@Test
public void whenCalledgetAllFields_thenCorrect() {
assertThat(FieldUtils.getAllFields(User.class).length)
.isEqualTo(2);
}

@Test
public void whenCalledwriteField_thenCorrect()
throws IllegalAccessException {
FieldUtils.writeField(user, "name", "Julie", true);
assertThat(FieldUtils.readField(user, "name", true))
.isEqualTo("Julie");
}

@Test
public void givenFieldUtilsClass_whenCalledwriteDeclaredField_thenCorrect() throws IllegalAccessException {
FieldUtils.writeDeclaredField(user, "name", "Julie", true);
assertThat(FieldUtils.readField(user, "name", true))
.isEqualTo("Julie");
}

11. Класс MethodUtils

В том же духе мы можем использовать отражение методов класса с классом MethodUtils .

В этом случае видимость метода getName() класса User является общедоступной . Итак, мы можем получить к нему доступ с помощью метода getAccessibleMethod() : ``

@Test
public void whenCalledgetAccessibleMethod_thenCorrect() {
assertThat(MethodUtils.getAccessibleMethod(User.class, "getName"))
.isInstanceOf(Method.class);
}

Когда дело доходит до рефлективного вызова методов, мы можем использовать методы invokeExactMethod() и invokeMethod() :

@Test
public
void whenCalledinvokeExactMethod_thenCorrect()
throws Exception {
assertThat(MethodUtils.invokeExactMethod(new User("John", "john@domain.com"), "getName"))
.isEqualTo("John");
}

@Test
public void whenCalledinvokeMethod_thenCorrect()
throws Exception {
User user = new User("John", "john@domain.com");
Object method = MethodUtils.invokeMethod(user, true, "setName", "John");
assertThat(user.getName()).isEqualTo("John");
}

12. Класс изменяемого объекта

Хотя неизменяемость является ключевой особенностью хорошего объектно-ориентированного программного обеспечения, которую мы должны использовать по умолчанию в каждом возможном случае , к сожалению, иногда нам приходится иметь дело с изменяемыми объектами.

Кроме того, для создания изменяемых классов требуется много шаблонного кода, который может быть сгенерирован большинством IDE с помощью автоматически генерируемых сеттеров.

С этой целью Apache Commons Lang 3 предоставляет класс MutableObject , простой класс-оболочку для создания изменяемых объектов с минимальными усилиями:

@BeforeClass
public static void setUpMutableObject() {
mutableObject = new MutableObject("Initial value");
}

@Test
public void whenCalledgetValue_thenCorrect() {
assertThat(mutableObject.getValue()).isInstanceOf(String.class);
}

@Test
public void whenCalledsetValue_thenCorrect() {
mutableObject.setValue("Another value");
assertThat(mutableObject.getValue()).isEqualTo("Another value");
}

@Test
public void whenCalledtoString_thenCorrect() {
assertThat(mutableObject.toString()).isEqualTo("Another value");
}

Конечно, это всего лишь пример использования класса MutableObject .

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

13. Класс MutablePair

Интересно, что Apache Commons Lang 3 обеспечивает мощную поддержку кортежей в виде пар и троек.

Итак, давайте предположим, что нам нужно создать изменяемую пару упорядоченных элементов.

В таком случае мы бы использовали класс MutablePair :

private static MutablePair<String, String> mutablePair;

@BeforeClass
public static void setUpMutablePairInstance() {
mutablePair = new MutablePair<>("leftElement", "rightElement");
}

@Test
public void whenCalledgetLeft_thenCorrect() {
assertThat(mutablePair.getLeft()).isEqualTo("leftElement");
}

@Test
public void whenCalledgetRight_thenCorrect() {
assertThat(mutablePair.getRight()).isEqualTo("rightElement");
}

@Test
public void whenCalledsetLeft_thenCorrect() {
mutablePair.setLeft("newLeftElement");
assertThat(mutablePair.getLeft()).isEqualTo("newLeftElement");
}

Наиболее важной деталью, на которую стоит обратить внимание, является чистый API класса.

Это позволяет нам устанавливать и получать доступ к левому и правому объектам, обернутым парой, через стандартные сеттеры/геттеры.

14. Класс ImmutablePair

Неудивительно, что существует также неизменная реализация класса MutablePair , называемая ImmutablePair :

private static ImmutablePair<String, String> immutablePair = new ImmutablePair<>("leftElement", "rightElement");

@Test
public void whenCalledgetLeft_thenCorrect() {
assertThat(immutablePair.getLeft()).isEqualTo("leftElement");
}

@Test
public void whenCalledgetRight_thenCorrect() {
assertThat(immutablePair.getRight()).isEqualTo("rightElement");
}

@Test
public void whenCalledof_thenCorrect() {
assertThat(ImmutablePair.of("leftElement", "rightElement"))
.isInstanceOf(ImmutablePair.class);
}

@Test(expected = UnsupportedOperationException.class)
public void whenCalledSetValue_thenThrowUnsupportedOperationException() {
immutablePair.setValue("newValue");
}

Как и следовало ожидать от неизменяемого класса, любая попытка изменить внутреннее состояние пары с помощью метода setValue() приведет к возникновению исключения UnsupportedOperationException .

15. Тройной класс _ ``

Последний служебный класс, который мы здесь рассмотрим, — Triple .

Поскольку класс является абстрактным, мы можем создавать экземпляры Triple с помощью статического фабричного метода of() :

@BeforeClass
public static void setUpTripleInstance() {
triple = Triple.of("leftElement", "middleElement", "rightElement");
}

@Test
public void whenCalledgetLeft_thenCorrect() {
assertThat(triple.getLeft()).isEqualTo("leftElement");
}

@Test
public void whenCalledgetMiddle_thenCorrect() {
assertThat(triple.getMiddle()).isEqualTo("middleElement");
}

@Test
public void whenCalledgetRight_thenCorrect() {
assertThat(triple.getRight()).isEqualTo("rightElement");
}

Существуют также конкретные реализации как для изменяемых, так и для неизменяемых троек с помощью классов MutableTriple и ImmutableTriple .

Мы можем создавать их экземпляры с помощью параметризованных конструкторов, а не с помощью статического фабричного метода.

В этом случае мы их просто пропустим, так как их API очень похожи на интерфейсы классов MutablePair и ImmutablePair .

16. Заключение

В этом руководстве мы подробно рассмотрели некоторые из наиболее полезных служебных классов, которые Apache Commons Lang 3 предоставляет в готовом виде .

Библиотека реализует множество других служебных классов, на которые стоит обратить внимание . Здесь мы только что продемонстрировали самые полезные из них, основанные на довольно самоуверенном критерии.

Полный API библиотеки см. в официальной документации Javadocs .

Как обычно, все примеры кода, показанные в этом руководстве, доступны на GitHub .