Статьи

Google Guava v07 примеры

У нас есть что-то под названием Еженедельные технологические семинары в TouK, то есть каждую пятницу в 16:00 кто-то готовит презентацию для всех желающих прийти. Мы представляем материал, который мы изучаем и работаем дома, но у нас также есть доска объявлений с темами, которые люди хотели бы слушать. На прошлой неделе Мацей Прочняк поговорил о Clojure , на этот раз несколько человек попросили представить библиотеки Google Guava .

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

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 .