Поскольку это было чертовски простым заданием, я был счастлив выполнить.
WTF это гуава?
Это набор очень простых, базовых классов, которые вы все равно пишете сами. Думайте в терминах Apache Commons, просто Google. Просто чтобы сделать вашу жизнь немного проще.
Есть ранняя (v04) презентация, и другая (на польском языке) на Javarsowia 2010 от Wiktor Gworek .
На момент написания этой статьи, последняя версия v07, она была mavenized и доступна в публичном репозитории Maven.
Вот краткий обзор нескольких интересных вещей. Не ожидайте ничего необычного, Гуава очень бейсик.
@VisibleForTesting
Простая аннотация, которая сообщает вам, почему ограничение доступа к конкретному свойству было смягчено.
Обычный прием при тестировании — ослабить ограничения доступа по умолчанию для определенного свойства, чтобы вы могли использовать его в модульном тесте, который находится в одном пакете (хотя и в другом каталоге). Хорошо это или плохо, не забудьте дать подсказку разработчику.
Рассматривать:
|
1
2
3
4
5
|
public class User { private Long id; private String firstName; private String lastName; String login; |
Почему пакет логина ограничен?
|
1
2
3
4
5
|
public class User { private Long id; private String firstName; private String lastName; @VisibleForTesting String login; |
Ах, вот почему.
Предпосылками
У Guava есть несколько предварительных условий для защитного программирования (Design By Contract), но они не так хороши, как у Apache Commons / Spring Framework. Одна интересная вещь заключается в том, что решение Guava возвращает объект, поэтому может быть встроенным. Рассматривать:
Используя рукописные предварительные условия:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
public User(Long id, String firstName, String lastName, String login) { validateParameters(id, firstName, lastName, login); this.id = id; this.firstName = firstName; this.lastName = lastName; this.login = login.toLowerCase(); } private void validateParameters(Long id, String firstName, String lastName, String login) { if(id == null ) { throw new IllegalArgumentException('id cannot be null'); } if(firstName == null || firstName.length() == 0) { throw new IllegalArgumentException('firstName cannot be empty'); } if(lastName == null || lastName.length() == 0) { throw new IllegalArgumentException('lastName cannot be empty'); } if(login == null || login.length() == 0) { throw new IllegalArgumentException('login cannot be empty'); } } |
Использование предварительных условий гуавы:
|
01
02
03
04
05
06
07
08
09
10
|
public void fullyImplementedGuavaConstructorWouldBe(Long id, String firstName, String lastName, String login) { this.id = checkNotNull(id); this.firstName = checkNotNull(firstName); this.lastName = checkNotNull(lastName); this.login = checkNotNull(login); checkArgument(firstName.length() > 0); checkArgument(lastName.length() > 0); checkArgument(login.length() > 0); } |
(Спасибо, Йом, за то, что заметил, что checkNotNull должен идти перед checkArgument, хотя это делает его немного неинтуитивным)
Использование предварительных условий для общих ресурсов Apache или Spring (использование выглядит одинаково для обеих библиотек):
|
1
2
3
4
5
6
7
|
public void springConstructorWouldBe(Long id, String firstName, String lastName, String login) { notNull(id); hasText(firstName); hasText(lastName); hasText(login); this.id = id; this.firstName = firstName; this.lastName = lastName; this.login = login; } |
CharMatcher
Для людей, которые ненавидят regexp или просто хотят получить простое и красивое решение для сопоставления с шаблоном стиля объекта.
Примеры:
И / или простота использования
|
1
2
3
|
String input = 'This invoice has an id of 192/10/10';CharMatcher charMatcher = CharMatcher.DIGIT.or(CharMatcher.is('/'));String output = charMatcher.retainFrom(input); |
вывод: 192/10/10
Отрицание:
|
1
2
3
|
String input = 'DO NOT scream at me!';CharMatcher charMatcher = CharMatcher.JAVA_LOWER_CASE.or(CharMatcher.WHITESPACE).negate();String output = charMatcher.retainFrom(input); |
Вывод: не надо!
Диапазоны:
|
1
2
3
|
String input = 'DO NOT scream at me!';CharMatcher charMatcher = CharMatcher.inRange('m', 's').or(CharMatcher.is('a').or(CharMatcher.WHITESPACE));String output = charMatcher.retainFrom(input); |
вывод: срам ам
Столяр / Сплиттер
Как следует из названия, это соединение строк / разбиение сделано правильно, хотя я нахожу инверсию вызовов немного … ну, это Java.
|
1
2
|
String[] fantasyGenres = {'Space Opera', 'Horror', 'Magic realism', 'Religion'};String joined = Joiner.on(', ').join(fantasyGenres); |
Выход: Космическая Опера, Ужасы, Магический реализм, Религия
Вы можете пропустить нули:
|
1
2
|
String[] fantasyGenres = {'Space Opera', null, 'Horror', 'Magic realism', null, 'Religion'};String joined = Joiner.on(', ').skipNulls().join(fantasyGenres); |
Выход: Космическая Опера, Ужасы, Магический реализм, Религия
Вы можете заполнить нули:
|
1
2
|
String[] fantasyGenres = {'Space Opera', null, 'Horror', 'Magic realism', null, 'Religion'};String joined = Joiner.on(', ').useForNull('NULL!!!').join(fantasyGenres); |
Выход: Космическая опера, НУЛЬ !!!, Ужас, Волшебный реализм, НУЛЬ !!!, Религия
Вы можете присоединиться к картам
|
1
2
3
4
5
|
Map<Integer, String> map = newHashMap();map.put(1, 'Space Opera');map.put(2, 'Horror');map.put(3, 'Magic realism');String joined = Joiner.on(', ').withKeyValueSeparator(' -> ').join(map); |
Выход: 1? Космическая Опера, 2? Ужас, 3? Волшебный реализм
Split возвращает Iterable вместо массивов JDK:
|
1
2
|
String input = 'Some very stupid data with ids of invoces like 121432, 3436534 and 8989898 inside';Iterable<String> splitted = Splitter.on(' ').split(input); |
Разделение выполняет разделение по фиксированной длине, хотя вы не можете задать разную длину для каждого «столбца», что делает его использование немного ограниченным при анализе некоторых плохо экспортированных исключений.
|
01
02
03
04
05
06
07
08
09
10
|
String input = 'A 1 1 1 1\n' + 'B 1 2 2 2\n' + 'C 1 2 3 3\n' + 'D 1 2 5 3\n' + 'E 3 2 5 4\n' + 'F 3 3 7 5\n' + 'G 3 3 7 5\n' + 'H 3 3 9 7';Iterable<String> splitted = Splitter.fixedLength(3).trimResults().split(input); |
Вы можете использовать CharMatcher при разделении
|
1
2
3
4
5
|
String input = 'Some very stupid data with ids of invoces like 123231/fv/10/2010, 123231/fv/10/2010 and 123231/fv/10/2010';Iterable<String> splitted = Splitter.on(CharMatcher.DIGIT.negate()) .trimResults() .omitEmptyStrings() .split(input); |
Предикаты / Функции
Предикатов не так много, это просто интерфейс с методом, который возвращает true, но если вы объедините предикаты с функциями и Collections2 (класс guava, который упрощает работу с коллекциями), вы получите хороший инструмент в своем наборе инструментов.
Но давайте начнем с базового использования предикатов. Представьте, что мы хотим выяснить, есть ли пользователи, у которых есть логины с цифрами внутри. Инокация будет (возвращает логическое значение):
|
1
|
Predicates.in(users).apply(shouldNotHaveDigitsInLoginPredicate); |
И предикат выглядит так
|
1
2
3
4
5
6
7
|
public class ShouldNotHaveDigitsInLoginPredicate implements Predicate<User> { @Override public boolean apply(User user) { checkNotNull(user); return CharMatcher.DIGIT.retainFrom(user.login).length() == 0; } } |
Теперь давайте добавим функцию, которая преобразует пользователя в его полное имя:
|
1
2
3
4
5
6
7
|
public class FullNameFunction implements Function<User, String> { @Override public String apply(User user) { checkNotNull(user); return user.getFirstName() + ' ' + user.getLastName(); } } |
Вы можете вызвать его, используя статический метод transform:
|
1
2
3
4
|
List<User> users = newArrayList(new User(1L, 'sylwek', 'stall', 'rambo'), new User(2L, 'arnold', 'schwartz', 'commando'));List<String> fullNames = transform(users, new FullNameFunction()); |
А теперь давайте объединим предикаты с функциями для вывода имен пользователей, которые имеют логины, которые не содержат цифр:
|
1
2
3
4
5
6
|
List<User> users = newArrayList(new User(1L, 'sylwek', 'stall', 'rambo'), new User(2L, 'arnold', 'schwartz', 'commando'), new User(3L, 'hans', 'kloss', 'jw23'));Collection<User> usersWithoutDigitsInLogin = filter(users, new ShouldNotHaveDigitsInLoginPredicate());String names = Joiner.on('\n').join( transform(usersWithoutDigitsInLogin, new FullNameFunction()) ); |
Что мы не получаем: сложить (уменьшить) и кортежи . Ну ладно, вы бы все равно обратились к функциональной библиотеке Java , если бы вам нужны функции в Java, верно?
CaseFormat
Вы когда-нибудь хотели превратить эти уродливые имена PHP Pear в красивый стиль java / cpp с одним вкладышем? Нет? Ну, во всяком случае, вы можете:
|
1
2
|
String pearPhpName = 'Really_Fucked_Up_PHP_PearConvention_That_Looks_UGLY_because_of_no_NAMESPACES';String javaAndCPPName = CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL , pearPhpName); |
Вывод: ReallyFuckedUpPhpPearconventionThatLooksUglyBecauseOfNoNamespaces
Но с тех пор как Oracle овладел Sun, вы, возможно, захотите превратить их в стиль sql, верно?
|
1
|
String sqlName = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, javaAndCPPName); |
Выходные данные: действительно_фукованный_up_php_pearconvention_that_looks_ugly_because_of_no_namespaces
Коллекции
У Guava есть расширенный набор библиотек Google collection 1.0 , и это действительно очень хорошая причина для включения этой зависимости в ваши poms. Я даже не буду пытаться описать все функции, а просто укажу на несколько приятных моментов:
- у вас есть неизменяемая версия почти всего
- вы получаете несколько хороших статических и статически типизированных методов для общих типов, таких как списки, наборы, карты, объектные массивы, которые включают в себя:
- простой способ создания на основе типа возвращаемого значения: например, newArrayList
- преобразование (способ применения функций, возвращающих неизменяемую версию)
- раздел (пейджинг)
- обратный
А теперь еще несколько интересных коллекций.
Mutlimaps
Mutlimap — это карта, которая может иметь много значений для одного ключа. Вам когда-нибудь приходилось создавать карту <T1, установить <T2 >> в вашем коде? Тебе больше не нужно.
|
1
2
3
4
5
|
Multimap<Integer, String> multimap = HashMultimap.create(); multimap.put(1, 'a'); multimap.put(2, 'b'); multimap.put(3, 'c'); multimap.put(1, 'a2'); |
Конечно, есть и неизменяемые реализации: ImmutableListMultimap, ImmutableSetMultomap и т. Д.
Вы можете создавать неизменяемые элементы либо по линии (до 5 элементов), либо с помощью компоновщика:
|
1
2
3
4
5
6
7
|
Multimap<Integer, String> multimap = ImmutableSetMultimap.of(1, 'a', 2, 'b', 3, 'c', 1, 'a2'); Multimap<Integer, String> multimap = new ImmutableSetMultimap.Builder<Integer, String>() .put(1, 'a') .put(2, 'b') .put(3, 'c') .put(1, 'a2') .build(); |
BiMap
BiMap — это карта, которая имеет только уникальные значения. Учти это:
|
1
2
3
4
5
6
7
|
@Test(expected = IllegalArgumentException.class)public void biMapShouldOnlyHaveUniqueValues() { BiMap<Integer, String> biMap = HashBiMap.create(); biMap.put(1, 'a'); biMap.put(2, 'b'); biMap.put(3, 'a'); //argh! an exception} |
Это позволяет инвертировать карту, поэтому значения становятся ключевыми, и наоборот:
|
1
2
3
4
5
6
|
BiMap<Integer, String> biMap = HashBiMap.create();biMap.put(1, 'a');biMap.put(2, 'b');biMap.put(3, 'c');BiMap<String, Integer> invertedMap = biMap.inverse(); |
Не уверен, что я на самом деле хотел бы использовать его.
Ограничения
Это позволяет добавить проверку ограничения для коллекции, чтобы можно было добавлять только те значения, которые соответствуют ограничению.
Представьте, что мы хотим собирать пользователей с первой буквой «r» в своих логинах.
|
01
02
03
04
05
06
07
08
09
10
11
12
|
Constraint<User> loginMustStartWithR = new Constraint<User>() { @Override public User checkElement(User user) { checkNotNull(user); if(!user.login.startsWith('r')) { throw new IllegalArgumentException('GTFO, you are not Rrrrrrrrr'); } return user; }}; |
А теперь для теста:
|
1
2
3
4
5
6
7
8
9
|
@Test(expected = IllegalArgumentException.class)public void shouldConstraintCollection() { //given Collection<User> users = newArrayList(new User(1L, 'john', 'rambo', 'rambo')); Collection<User> usersThatStartWithR = constrainedCollection(users, loginMustStartWithR); //when usersThatStartWithR.add(new User(2L, 'arnold', 'schwarz', 'commando'));} |
Вы также получаете ограничение notNull из коробки:
|
01
02
03
04
05
06
07
08
09
10
|
//notice it's not an IllegalArgumentException 🙁 @Test(expected = NullPointerException.class)public void notNullConstraintShouldWork() { //given Collection<Integer> users = newArrayList(1); Collection<Integer> notNullCollection = constrainedCollection(users, notNull()); //when notNullCollection.add(null);} |
Важно помнить: ограничения не проверяют данные, уже имеющиеся в коллекции.
таблицы
Как и ожидалось, таблица представляет собой коллекцию со столбцами, строками и значениями. Нет больше карты <T1, карта <T2, T3 >> Я думаю. Использование простое, и вы можете транспонировать:
|
1
2
3
4
5
6
7
|
Table<Integer, String, String> table = HashBasedTable.create();table.put(1, 'a', '1a');table.put(1, 'b', '1b');table.put(2, 'a', '2a');table.put(2, 'b', '2b');Table transponedTable = Tables.transpose(table); |
Вот и все, ребята. Я не представил пакетов util.concurrent, primitives, io и net, но вы, наверное, уже знаете, чего ожидать.
Приятного кодирования и не забудьте поделиться!
Ссылка: примеры Google Guava v07 от нашего партнера по JCG Якуба Набрдалика в блоге Solid Craft .