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()
В предыдущей статье ForEach Что нового в 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", "ForEach", "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("ForEach"));
assertThat(userNamesMultiset.elementSet(), anyOf(containsInAnyOrder("Alex", "David", "ForEach")));
Вы можете легко определить количество повторяющихся элементов, что намного чище, чем в стандартных коллекциях 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
, потому что он не связан с другими интервалами.
С помощью новых методов, таких как asDescendingSetOfRanges
(для RangeSet
) и asDescendingMapOfRanges
(для RangeSet
), вы можете преобразовать RangeSet
в Set
или Map
.
3.3. Добавлены Lists.cartesianProduct(List…)
и Lists.cartesianProduct(List<List>>)
Декартово произведение возвращает все возможные комбинации двух или более коллекций:
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. Добавлено хеширование.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<HashFunction>)
С помощью методов Hashing.concatenate
вы объединяете результаты серии хеш-функций:
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);
Результирующий concatenatedHashCode
будет «4acf27794acf2779», что совпадает с crc32HashCode
(«4acf2779»), объединенным с самим собой.
В нашем примере для наглядности использовался один алгоритм хеширования. Однако это не особенно полезно. Объединение двух хеш-функций полезно, когда вам нужно сделать ваш хеш более надежным, так как его можно взломать только в том случае, если два ваших хеш-функции будут сломаны. В большинстве случаев используйте две разные хэш-функции.
5. Изменения пакета common.reflect
5.1. Добавлен TypeToken.isSubtypeOf
TypeToken
используется для манипулирования универсальными типами и запросов к ним даже во время выполнения, что позволяет избежать проблем из-за стирания типов .
Java не сохраняет информацию об универсальном типе для объектов во время выполнения, поэтому невозможно узнать, имеет ли данный объект универсальный тип или нет. Но с помощью отражения вы можете обнаружить общие типы методов или классов. TypeToken
использует этот обходной путь, чтобы вы могли работать с универсальными типами и запрашивать их без дополнительного кода.
В нашем примере вы можете видеть, что без метода TypeToken
isAssignableFrom
вернет true
, даже если ArrayList<String>
не может быть назначен из ArrayList<Integer>
:
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
, поэтому результат будет true
, как и ожидалось.
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()
То же, что и для ByteSource,
но с помощью CharSource.lengthIfKnown()
вы можете определить длину вашего файла в символах:
CharSource charSource = Files.asCharSource(file, Charsets.UTF_8);
Optional<Long> length = charSource.lengthIfKnown();
7. Заключение
Guava 19 представила множество полезных дополнений и улучшений в своей растущей библиотеке. Это стоит рассмотреть для использования в вашем следующем проекте.
Примеры кода в этой статье доступны в репозитории GitHub .