[Из песочницы] Чистый код с Google Guava27.11.2014 17:34
Наверное, любому программисту доводилось видеть код, пестрящий большим количеством повторов и реализации «низкоуровневых» действий прямо посреди бизнес-логики. Например, посреди метода, печатающего отчёт, может оказаться такой фрагмент кода, конкатерирующий строки:
StringBuilder sb = new StringBuilder ();
for (Iterator i = debtors.iterator (); i.hasNext ();) {
if (sb.length () != 0) {
sb.append (»,»);
}
sb.append (i.next ());
}
out.println («Debtors:» + sb.toString ());
Понятно, что этот код мог бы быть более прямолинейным, например, в Java 8 можно написать так:
out.println («Debtors:» + String.join (»,», debtors));
Вот так сразу гораздо понятнее, что происходит. Google Guava — это набор open-source библиотек для Java, помогающий избавиться от подобных часто встречающихся шаблонов кода. Поскольку Guava появилась задолго до Java 8, в Guava тоже есть способ конкатенации строк: Joiner.on (»,»).join (debtors).Очень базовые полезности
Давайте рассмотрим простой класс, реализующий стандартный набор базовых методов Java. Предлагаю не вникать особо в реализацию методов hashCode, equals, toString и compareTo (первые три из них я просто сгенерировал в Eclipse) дабы не тратить время впустую, а просто посмотреть на объём кода.
class Person implements Comparable {
private String lastName;
private String middleName;
private String firstName;
private int zipCode;
// constructor, getters and setters are omitted
@Override
public int compareTo (Person other) {
int cmp = lastName.compareTo (other.lastName);
if (cmp!= 0) {
return cmp;
}
cmp = middleName.compareTo (other.middleName);
if (cmp!= 0) {
return cmp;
}
cmp = firstName.compareTo (other.firstName);
if (cmp!= 0) {
return cmp;
}
return Integer.compare (zipCode, other.zipCode);
}
@Override
public boolean equals (Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass () != obj.getClass ())
return false;
Person other = (Person) obj;
if (firstName == null) {
if (other.firstName!= null)
return false;
} else if (! firstName.equals (other.firstName))
return false;
if (lastName == null) {
if (other.lastName!= null)
return false;
} else if (! lastName.equals (other.lastName))
return false;
if (middleName == null) {
if (other.middleName!= null)
return false;
} else if (! middleName.equals (other.middleName))
return false;
if (zipCode!= other.zipCode)
return false;
return true;
}
@Override
public int hashCode () {
final int prime = 31;
int result = 1;
result = prime * result + ((firstName == null) ? 0: firstName.hashCode ());
result = prime * result + ((lastName == null) ? 0: lastName.hashCode ());
result = prime * result + ((middleName == null) ? 0: middleName.hashCode ());
result = prime * result + zipCode;
return result;
}
@Override
public String toString () {
return «Person [lastName=» + lastName + », middleName=» + middleName
+ », firstName=» + firstName + », zipCode=» + zipCode + »]»;
}
}
Теперь посмотрим на похожий код, использующий Guava и новые методы из Java 8:
class Person implements Comparable {
private String lastName;
private String middleName;
private String firstName;
private int zipCode;
// constructor, getters and setters are omitted
@Override
public int compareTo (Person other) {
return ComparisonChain.start ()
.compare (lastName, other.lastName)
.compare (firstName, other.firstName)
.compare (middleName, other.middleName, Ordering.natural ().nullsLast ())
.compare (zipCode, other.zipCode)
.result ();
}
@Override
public boolean equals (Object obj) {
if (obj == null || getClass () != obj.getClass ()) {
return false;
}
Person other = (Person) obj;
return Objects.equals (lastName, other.lastName)
&& Objects.equals (middleName, other.middleName)
&& Objects.equals (firstName, other.firstName)
&& zipCode == other.zipCode;
}
@Override
public int hashCode () {
return Objects.hash (lastName, middleName, firstName, zipCode);
}
@Override
public String toString () {
return MoreObjects.toStringHelper (this)
.omitNullValues ()
.add («lastName», lastName)
.add («middleName», middleName)
.add («firstName», firstName)
.add («zipCode», zipCode)
.toString ();
}
}
Как видим, код стал чище и лаконичнее. Здесь используются MoreObjects и ComparisonChain из Guava и класс Objects из Java 8. Если вы используете Java 7 или более старую версию, то можете воспользоваться классом Objects из Guava — в нём есть методы hashCode и equal, аналогичные использованным методам hash и equals из класса java.lang.Objects. Раньше toStringHelper тоже находился в классе Objects, но с появлением Java 8 в Guava 18 в классе Objects навесили меточку @Deprecated на все методы, а те методы, аналогов которым которых нет в Java 8, перенесли в MoreObjects, чтобы не было конфликта имён — Guava развивается, а её разработчики не стесняются избавляться от устаревшего кода.Замечу, что эта версия класса немного отличается от изначальной: я предположил, что отчество может быть не заполнено, в таком случае в результате toString мы его не увидим, а compareTo будет считать, что личности без отчества должны идти после тех, у кого есть отчество (при этом упорядочение происходит сначала по фамили и имени, а только потом по отчеству).
Другим примером весьма базовых полезностей могут служить предусловия. По какой-то причине в Java есть только Objects.requireNotNull (начиная с Java 7).
Кратко о предусловиях:
Имя метода в классе Preconditions
Генерируемое исключение
checkArgument (boolean)
IllegalArgumentException
checkNotNull (T)
NullPointerException
checkState (boolean)
IllegalStateException
checkElementIndex (int index, int size)
IndexOutOfBoundException
checkPositionIndex (int index, int size)
IndexOutOfBoundException
Зачем они нужны, можно прочитать на сайте Oracle.Новые коллекции
Частенько бывает, что можно увидеть подобного рода код посреди бизнес-логики:
Map counts = new HashMap<>();
for (String word: words) {
Integer count = counts.get (word);
if (count == null) {
counts.put (word, 1);
} else {
counts.put (word, count + 1);
}
}
Или такой код:
List values = map.get (key);
if (values == null) {
values = new ArrayList<>();
map.put (key, values);
}
values.add (value);
(в последнем отрывке входными данными являются map, key и value). Эти два примера демонстрируют работу с коллекциями, когда в коллекциях содержатся изменяеные данные (в данном случае числа и списки соответственно). В первом случае отображение (map) по-сути описывает мультимножество, т.е. множество с повторяющимися элементами, а во втором случае отображение является мультиотображением. Такие абстракии есть в Guava. Давайте перепишем примеры с использованием этих абстракций:
Multiset counts = HashMultiset.create ();
for (String word: words) {
counts.add (word);
}
и map.put (key, value);
(здесь map — это Multimap). Замечу, что Guava позволяет настраивать поведение таких мультиотображений — например, мы можем хотеть, чтобы наборы значений хранились как множества, а можем захотеть списки, для самого же отображения мы можем захотеть связанный список, хэш или дерево — все нужные реализации в Guava имеются. Table — коллекция, избавляющая от аналогичного дублированая кода, но уже на случай хранения отображений внутри отображений. Вот примеры новых коллекций, упрощающих жизнь: Multiset
«Множество», которое может иметь дубликаты
Multimap
«Отображение», которое может иметь дубликаты
BiMap
Поддерживает «обратное отображение»
Table
Связывает упорядоченную пару ключей со значением
ClassToInstanceMap
Отображает тип на экземпляр этого типа (избавляет от приведений типов)
RangeSet
Набор диапазонов
RangeMap
Набор отображений непересекающихся диапазонов на ненулевые значения
Декораторы для коллекций
Для создания декораторов к коллекциям — и к тем, что уже есть в Java Collections Framework, и к тем, что определены в Guava — имеются соответствующие классы, например ForwardingList, ForwardingMap, ForwardingMiltiset.Неизменяемые коллекции
Также в Guava имеются неизменяемые коллекции; возможно, они не связаны напрямую с чистым кодом, но заметно упрощают отладку и взаимодействие между разными частями приложения. Они: безопасны для использования в «недружественном коде»;
могут сохранить время и память, поскольку не ориентируются на возможность изменения (анализ показал, что все неизенные коллекции эффективнее своих аналогов);
могут быть использованными как константы, и можно ожидать, что они точно не будут изменены.
Здесь есть положительные отличия по сравнению с методами Collections.unmodifiableКонкретнаяКоллекция, которые создают обёртки, благодаря чему можно ожидать, что коллекция неизменна только если на неё больше нет ссылок; коллекция оставляет накладные расходы на возможность изменения как по скорости, так и по памяти.Пара простых примеров:
public static final ImmutableSet COLOR_NAMES = ImmutableSet.of (
«red»,
«green»,
«blue»);
class Foo {
final ImmutableSet bars;
Foo (Set bars) {
this.bars = ImmutableSet.copyOf (bars); // defensive copy!
}
}
Реализация итераторов
PeekingIterator
Просто оборачивает итератор, добавляя к нему метод peek () для получения значения следующего элемента. Создаётся с помощью вызова Iterators.peekingIterator (Iterator)
AbstractIterator
Избавляет от необходимости реализовывать все методы итератора — достаточно только реализовать protected T computeNext ()
AbstractSequentialIterator
Аналогичен предыдущему, но вычисляет следующий элемент на основе предыдущего: нужно реализовать метод protected T computeNext (T previous)
Функциональные и утилиты для коллекций
Guava предоставляет такие интерфейсы, как Function и Predicate, и утилитные классы Functions, Predicates, FluentIterable, Iterables, Lists, Sets и другие. Напоминаю, что Guava появилась задолго до Java 8, и поэтому в ней неизбежно появились Optional и интерфейсы Function и Predicate, которые, впрочем, полезны только в ограниченных случаях, потому что без лямбд функциональный код с предикатами и функциями в большинстве случаев будет гораздо более грамоздким, нежели обычный императивный, но в некоторых случаях он позволяет сохранить лаконичность. Простой пример:
Predicate nonDefault = not (equalTo (DEFAULT_VALUE));
Iterable strings1 = transform (filter (iterable, nonDefault), toStringFunction ());
Iterable strings2 = from (iterable).filter (nonDefault).transform (toStringFunction ());
Здесь импортированы статические методы из Functions (toStringFunction), Predicates (not, equalTo), Iterables (transform, filter) и FluentIterable (from). В первом случае используются статические методы Iterable, чтобы сконструировать результат, во втором — FluentIterable.Ввод/вывод
Для абстрагирования байтовых и символьных потоков определены такие абстрактные классы, как ByteSource, ByteSink, CharSoure и CharSink. Создаются они как правило с помощью фасадов Resources и Files. Также имеется немалый набор методов для работы с потоками ввода и вывода, такие как преобразование, считывание, копирование и конкатенация (см. классы CharSource, ByteSource, ByteSink). Примеры:
// Read the lines of a UTF-8 text file
ImmutableList lines = Files.asCharSource (file, Charsets.UTF_8).readLines ();
// Count distinct word occurrences in a file
Multiset wordOccurrences = HashMultiset.create (
Splitter.on (CharMatcher.WHITESPACE)
.trimResults ()
.omitEmptyStrings ()
.split (Files.asCharSource (file, Charsets.UTF_8).read ()));
// SHA-1 a file
HashCode hash = Files.asByteSource (file).hash (Hashing.sha1());
// Copy the data from a URL to a file
Resources.asByteSource (url).copyTo (Files.asByteSink (file));
Обо всём помаленьку
Lists
Создание различный видов списков, в т.ч. Lists.newCopyOnWriteArrayList (iterable), Lists.reverse (list) /* view! /, Lists.transform (fromList, function) /* lazy view! */
Sets
Преобразование из Map в Set (view!), работа со множествами в математическом смысле (пересечение, объединение, разность)
Iterables
Простые методы типа any, all, contains, concat, filter, find, limit, isEmpty, size, toArray, transform. По какой-то причине в Java 8 многие подобные методы относятся только к коллекциям, но не к Iterable в общем.
Bytes, Ints, UnsignedInteger и т.д. Работа с беззнаковыми числами и массивами примитивных типов (соответствующие утилитные классы есть для каждого примитивного типа).
ObjectArrays
По-сути только два вида методов — конкатенация массивов (по какой-то причине её нет в стандартной библиотеке Java) и создание массовов по заданному классу или классу массива (почему-то в библиотеке Java есть только аналогичный метод для копирования).
Joiner, Splitter
Гибкие классы для объединения или нарезация строк из или в Iterable, List или Map.
Strings, MoreObjects
Из неупомянутых — крайне частоиспользуемые методы Strings.emptyToNull (String), Strings.isNullOrEmpty (String), Strings.nullToEmpty (String) и MoreObjects.firstNonNull (T, T)
Closer, Throwables
Эмуляция try-with-resources, multi-catch (полезно только для Java 6 и старее), работа с трассировкой стека и перекидывание исключений.
com.google.common.net
Названия классов говорят сами за себя: InternetDomainName, InetAddresses, HttpHeaders, MediaType, UrlEscapers
com.google.common.html и com.google.common.xml
HtmlEscapers и XmlEscapers
Range
Диапазон.
EventBus
Мощная реализация паттерна издатель-подписчик. В EventBus регистрируются подписчики, «реагирующие» методы которых помечены аннотацией, а при вызове какого-либо события EventBus находит подписчиков, способных воспринимать данный вид событий, и уведомляет их о событии.
IntMath, LongMath, BigIntegerMath, DoubleMath
Множество полезных функций для работы с числами.
ClassPath
В Java нет кроссплатформенного способа просматривать классы на classpath. А Guava предоставляет возможность пройтись по классам пакета или проекта.
TypeToken
Благодаря стиранию типов мы не можем манипулировать обобщёнными типами во время исполнения программы. TypeToken позволяет манипулировать такими типами.
Ещё примеры
Хеширование:
HashFunction hf = Hashing.md5();
HashCode hc = hf.newHasher ()
.putLong (id)
.putString (name, Charsets.UTF_8)
.putObject (person, personFunnel)
.hash ();
Кэширование:
LoadingCache graphs = CacheBuilder.newBuilder ()
.maximumSize (1000)
.expireAfterWrite (10, TimeUnit.MINUTES)
.removalListener (MY_LISTENER)
.build (
new CacheLoader() {
public Graph load (Key key) throws AnyException {
return createExpensiveGraph (key);
}
});
Динамический прокси:
Foo foo = Reflection.newProxy (Foo.class, invocationHandler)
Для создания динамического прокси без Guava обычно пишется такой код:
Foo foo = (Foo) Proxy.newProxyInstance (
Foo.class.getClassLoader (),
new Class>[] {Foo.class},
invocationHandler);
На этом всё, читаемого вам кода.
© Habrahabr.ru