«1. Обзор

Google Guava предоставляет библиотеки с утилитами, облегчающими разработку Java. В этом руководстве мы рассмотрим новые функции, представленные в выпуске Guava 19.

2. Изменения пакета common.base

2.1. Добавлены статические методы CharMatcher

CharMatcher, как следует из названия, используется для проверки соответствия строки набору требований.

String inputString = "someString789";
boolean result = CharMatcher.javaLetterOrDigit().matchesAllOf(inputString);

В приведенном выше примере результат будет истинным.

CharMatcher также можно использовать, когда вам нужно преобразовать строки.

String number = "8 123 456 123";
String result = CharMatcher.whitespace().collapseFrom(number, '-');

В приведенном выше примере результатом будет «8-123-456-123».

С помощью CharMatcher вы можете подсчитать количество вхождений символа в заданную строку:

String number = "8 123 456 123";
int result = CharMatcher.digit().countIn(number);

В приведенном выше примере результатом будет 10.

Предыдущие версии Guava имеют константы сопоставления например, CharMatcher.WHITESPACE и CharMatcher.JAVA_LETTER_OR_DIGIT.

В Guava 19 они были заменены эквивалентными методами (CharMatcher.whitespace() и CharMatcher.javaLetterOrDigit() соответственно). Это было изменено, чтобы уменьшить количество классов, создаваемых при использовании CharMatcher.

Использование статических фабричных методов позволяет создавать классы только по мере необходимости. В будущих выпусках константы сопоставления будут объявлены устаревшими и удалены.

2.2. Метод lazyStackTrace в Throwables

Этот метод возвращает список элементов трассировки стека (строк) предоставленного Throwable. Это может быть быстрее, чем повторение всей трассировки стека (Throwable.getStackTrace()), если нужна только часть, но может быть медленнее, если вы будете перебирать всю трассировку стека.

IllegalArgumentException e = new IllegalArgumentException("Some argument is incorrect");
List<StackTraceElement> stackTraceElements = Throwables.lazyStackTrace(e);

3. Изменения пакета common.collect

3.1. Добавлен FluentIterable.toMultiset()

В предыдущей статье Baeldung Что нового в Guava 18 мы рассмотрели FluentIterable. Метод toMultiset() используется, когда вам нужно преобразовать FluentIterable в ImmutableMultiSet.

User[] usersArray = {new User(1L, "John", 45), new User(2L, "Max", 15)};
ImmutableMultiset<User> users = FluentIterable.of(usersArray).toMultiset();

Multiset — это коллекция, такая как Set, которая поддерживает независимое от порядка равенство. Основное различие между набором и мультинабором заключается в том, что мультинабор может содержать повторяющиеся элементы. Multiset хранит одинаковые элементы как вхождения одного и того же элемента, поэтому вы можете вызвать Multiset.count(java.lang.Object), чтобы получить общее количество вхождений данного объекта.

Давайте рассмотрим несколько примеров:

List<String> userNames = Arrays.asList("David", "Eugen", "Alex", "Alex", "David", "David", "David");

Multiset<String> userNamesMultiset = HashMultiset.create(userNames);

assertEquals(7, userNamesMultiset.size());
assertEquals(4, userNamesMultiset.count("David"));
assertEquals(2, userNamesMultiset.count("Alex"));
assertEquals(1, userNamesMultiset.count("Eugen"));
assertThat(userNamesMultiset.elementSet(), anyOf(containsInAnyOrder("Alex", "David", "Eugen")));

Вы можете легко определить количество повторяющихся элементов, что намного проще, чем в стандартных коллекциях Java.

3.2. Добавлен RangeSet.asDescendingSetOfRanges() и asDescendingMapOfRanges()

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

Давайте рассмотрим некоторые методы, которые мы можем использовать для создания новых диапазонов: Range.closed(), Range. openClosed(), Range.closedOpen(), Range.open().

Разница между ними в том, что открытые диапазоны не включают свои конечные точки. Они имеют разное обозначение в математике. Открытые интервалы обозначаются «(» или «)», а закрытые диапазоны обозначаются «[» или «]».

Например, (0,5) означает «любое значение больше 0 и меньше 5», а (0,5] означает «любое значение больше 0 и меньше или равно 5»:

RangeSet<Integer> rangeSet = TreeRangeSet.create();
rangeSet.add(Range.closed(1, 10));

Здесь мы добавили диапазон [1, 10] к нашему RangeSet, и теперь мы хотим расширить его, добавив новый диапазон:

rangeSet.add(Range.closed(5, 15));

Вы можете видеть, что эти два диапазона соединены в 5, так что RangeSet объединится их в новый единый диапазон, [1, 15]:

rangeSet.add(Range.closedOpen(10, 17));

Эти диапазоны соединены в 10, поэтому они будут объединены, в результате чего получится закрытый-открытый диапазон, [1, 17). Вы можете проверить, входит ли значение в диапазон или нет, используя метод contains:

rangeSet.contains(15);

Это вернет true, потому что диапазон [1,17) содержит 15. Давайте попробуем другое значение:

rangeSet.contains(17);

Это вернет false, потому что диапазон [1,17) не содержит своей верхней конечной точки, 17. Вы также можете проверить, охватывает ли диапазон какой-либо другой диапазон, используя метод encloses:

rangeSet.encloses(Range.closed(2, 3));

Это вернет true, потому что диапазон [ 2,3] полностью попадает в наш диапазон [1,17].

«Есть еще несколько методов, которые могут помочь вам работать с интервалами, например Range.greaterThan(), Range.lessThan(), Range.atLeast(), Range.atMost(). Первые два добавят открытые интервалы, последние два добавят закрытые интервалы. Например:

rangeSet.add(Range.greaterThan(22));

Это добавит новый интервал (22, +~ž) в ​​ваш RangeSet, потому что он не связан с другими интервалами.

С помощью новых методов, таких как DescendingSetOfRanges (для RangeSet) и asDescendingMapOfRanges (для RangeSet), вы можете преобразовать RangeSet в Set или Map.

3.3. Добавлены Lists.cartesianProduct(List…) и Lists.cartesianProduct(List\u003cList\u003e\u003e)

Декартово произведение возвращает все возможные комбинации двух или более коллекций:

List<String> first = Lists.newArrayList("value1", "value2");
List<String> second = Lists.newArrayList("value3", "value4");

List<List<String>> cartesianProduct = Lists.cartesianProduct(first, second);

List<String> pair1 = Lists.newArrayList("value2", "value3");
List<String> pair2 = Lists.newArrayList("value2", "value4");
List<String> pair3 = Lists.newArrayList("value1", "value3");
List<String> pair4 = Lists.newArrayList("value1", "value4");

assertThat(cartesianProduct, anyOf(containsInAnyOrder(pair1, pair2, pair3, pair4)));

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

3.4. Добавлено Maps.newLinkedHashMapWithExpectedSize(int)

Начальный размер стандартного LinkedHashMap равен 16 (вы можете проверить это в источнике LinkedHashMap). Когда он достигает коэффициента загрузки HashMap (по умолчанию 0,75), HashMap повторно хеширует и удваивает свой размер. Но если вы знаете, что ваш HashMap будет обрабатывать множество пар ключ-значение, вы можете указать начальный размер больше 16, что позволит вам избежать повторного перефразирования:

LinkedHashMap<Object, Object> someLinkedMap = Maps.newLinkedHashMapWithExpectedSize(512);

3.5. Повторно добавлен Multisets.removeOccurrences(Multiset, Multiset)

Этот метод используется для удаления указанных вхождений в Multiset:

Multiset<String> multisetToModify = HashMultiset.create();
Multiset<String> occurrencesToRemove = HashMultiset.create();

multisetToModify.add("John");
multisetToModify.add("Max");
multisetToModify.add("Alex");

occurrencesToRemove.add("Alex");
occurrencesToRemove.add("John");

Multisets.removeOccurrences(multisetToModify, occurrencesToRemove);

После этой операции в multisetToModify останется только «Max».

Обратите внимание, что если multisetToModify содержит несколько экземпляров данного элемента, а instancesToRemove содержит только один экземпляр этого элемента, removeOccurrences удалит только один экземпляр.

4. Изменения пакета common.hash

4.1. Добавлен Hashing.sha384()

Метод Hashing.sha384() возвращает хэш-функцию, реализующую алгоритм SHA-384:

int inputData = 15;
        
HashFunction hashFunction = Hashing.sha384();
HashCode hashCode = hashFunction.hashInt(inputData);

SHA-384 имеет значение 15: «0904b6277381dcfbddd…2240a621b2b5e3cda8».

4.2. Добавлено Hashing.concatenating(HashFunction, HashFunction, HashFunction…) и Hashing.concatenating(Iterable\u003cHashFunction\u003e)

С помощью методов Hashing.concatenating вы объединяете результаты серии хеш-функций:

int inputData = 15;

HashFunction crc32Function = Hashing.crc32();
HashCode crc32HashCode = crc32Function.hashInt(inputData);

HashFunction hashFunction = Hashing.concatenating(Hashing.crc32(), Hashing.crc32());
HashCode concatenatedHashCode = hashFunction.hashInt(inputData);

~ ~~ Результирующий конкатенированныйHashCode будет «4acf27794acf2779», что совпадает с crc32HashCode («4acf2779»), объединенным с самим собой.

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

5. Изменения пакета common.reflect

5.1. Добавлен TypeToken.isSubtypeOf

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

Java не сохраняет информацию об универсальном типе для объектов во время выполнения, поэтому невозможно узнать, имеет ли данный объект универсальный тип или нет. Но с помощью отражения вы можете обнаружить общие типы методов или классов. TypeToken использует этот обходной путь, чтобы вы могли работать с универсальными типами и запрашивать их без дополнительного кода.

В нашем примере вы можете видеть, что метод isAssignableFrom без TypeToken вернет true, даже если ArrayList\u003cString\u003e не может быть назначен из ArrayList\u003cInteger\u003e:

ArrayList<String> stringList = new ArrayList<>();
ArrayList<Integer> intList = new ArrayList<>();
boolean isAssignableFrom = stringList.getClass().isAssignableFrom(intList.getClass());

Чтобы решить эту проблему, мы можем проверить это с помощью TypeToken.

TypeToken<ArrayList<String>> listString = new TypeToken<ArrayList<String>>() { };
TypeToken<ArrayList<Integer>> integerString = new TypeToken<ArrayList<Integer>>() { };

boolean isSupertypeOf = listString.isSupertypeOf(integerString);

В этом примере isSupertypeOf вернет false.

В предыдущих версиях Guava для этих целей был метод isAssignableFrom, но начиная с Guava 19 он устарел в пользу isSupertypeOf. Кроме того, метод isSubtypeOf(TypeToken) можно использовать для определения того, является ли класс подтипом другого класса:

TypeToken<ArrayList<String>> stringList = new TypeToken<ArrayList<String>>() { };
TypeToken<List> list = new TypeToken<List>() { };

boolean isSubtypeOf = stringList.isSubtypeOf(list);

ArrayList является подтипом List, поэтому результат будет верным, как и ожидалось.

6. Изменения пакета common.io

6.1. Добавлен ByteSource.sizeIfKnown()

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

ByteSource charSource = Files.asByteSource(file);
Optional<Long> size = charSource.sizeIfKnown();

«

«6.2. Добавлен CharSource.length()

В предыдущей версии Guava не было метода определения длины CharSource. Теперь вы можете использовать CharSource.length() для этой цели.

6.3. Добавлен CharSource.lengthIfKnown()

CharSource charSource = Files.asCharSource(file, Charsets.UTF_8);
Optional<Long> length = charSource.lengthIfKnown();

То же, что и для ByteSource, но с помощью CharSource.lengthIfKnown() вы можете определить длину вашего файла в символах:

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

В Guava 19 появилось много полезных дополнений и улучшения в его растущей библиотеке. Это стоит рассмотреть для использования в вашем следующем проекте.