📜 ⬆️ ⬇️

Clean Code with Google Guava

Probably, any programmer has seen the code dazzling with a large number of repetitions and implementing low-level actions right in the middle of business logic. For example, in the middle of a method that prints a report, there may be such a fragment of code concatenating strings:

StringBuilder sb = new StringBuilder(); for (Iterator<String> i = debtors.iterator(); i.hasNext();) { if (sb.length() != 0) { sb.append(", "); } sb.append(i.next()); } out.println("Debtors: " + sb.toString()); 

It is clear that this code could be more straightforward, for example, in Java 8 you can write this:

 out.println("Debtors: " + String.join(", ", debtors)); 

In this way, it is much clearer what is happening. Google Guava is a set of open-source libraries for Java that helps get rid of these common code patterns. Since Guava appeared long before Java 8, Guava also has a way to concatenate strings: Joiner.on (",") .join (debtors).

Very basic utility


Let's look at a simple class that implements a standard set of basic Java methods. I suggest not to delve particularly into the implementation of the hashCode, equals, toString and compareTo methods (I just generated the first three of them in Eclipse) so as not to waste time, but just look at the amount of code.
')
 class Person implements Comparable<Person> { 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 + "]"; } } 

Now let's look at similar code using Guava and new methods from Java 8:

 class Person implements Comparable<Person> { 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(); } } 

As you can see, the code has become cleaner and more concise. It uses MoreObjects and ComparisonChain from Guava and the Objects class from Java 8. If you are using Java 7 or an older version, you can use the Objects class from Guava - it has hashCode and equal methods, similar to the used hash and equals methods from the java class. lang.Objects. Previously, toStringHelper was also in the Objects class, but with the advent of Java 8 in Guava 18 in the Objects class, the @Deprecated tag was hung on all methods, and those methods that are not available in Java 8 were transferred to MoreObjects so that there was no name conflict - Guava developing, and its developers are not shy to get rid of outdated code.

I note that this version of the class is slightly different from the original one: I assumed that the patronymic may not be completed, in which case we will not see it as a result of toString, and compareTo will assume that non-patronymic individuals should go after those who have a patronymic (in this case, ordering occurs first by surname and first name, and only then by patronymic name).

Another example of very basic utilities is preconditions. For some reason, Java only has Objects.requireNotNull (since Java 7).

Briefly about preconditions:

Method name in Preconditions classGenerated exception
checkArgument (boolean)IllegalArgumentException
checkNotNull (T)NullPointerException
checkState (boolean)IllegalStateException
checkElementIndex (int index, int size)IndexOutOfBoundException
checkPositionIndex (int index, int size)IndexOutOfBoundException

Why they are needed, you can read on the Oracle site .

New collections


It often happens that you can see this kind of code in the middle of business logic:

 Map<String, Integer> 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); } } 

Or this code:
 List<String> values = map.get(key); if (values == null) { values = new ArrayList<>(); map.put(key, values); } values.add(value); 

(in the last passage, the input is map, key, and value). These two examples demonstrate working with collections when collections contain modified data (in this case, numbers and lists, respectively). In the first case, the map (map) essentially describes a multiset, i.e. set with repeating elements, and in the second case, the display is a multi-display. Such abstractions are in Guava. Let's rewrite examples using these abstractions:

 Multiset<String> counts = HashMultiset.create(); for (String word : words) { counts.add(word); } 

and
 map.put(key, value); 

(here map is Multimap <String, String>). I note that Guava allows you to customize the behavior of such multimaps - for example, we may want sets of values ​​to be stored as sets, or we may want lists, for the display itself we may want a linked list, hash or tree — all the necessary implementations in Guava are available. Table is a collection that eliminates the same duplicate code, but for the case of storing mappings inside mappings. Here are examples of new collections that simplify life:

Multiset“Many” that may have duplicates
Multimap“Display”, which may have duplicates
BimapSupports “reverse mapping”
TableAssociates an ordered key pair with a value
ClassToInstanceMapDisplays a type on an instance of this type (eliminates type conversions)
RangesetSet of ranges
RangemapA set of mappings of non-intersecting ranges to nonzero values


Collection decorators


To create collection decorators — both those already in the Java Collections Framework and those defined in Guava — there are corresponding classes, such as ForwardingList, ForwardingMap, ForwardingMiltiset.

Fixed collections


There are also immutable collections in Guava; They may not be directly related to clean code, but they significantly simplify debugging and interaction between different parts of the application. They:

There are positive differences compared to the Collections.unmodifiableSpecific collection methods that create wrappers, so you can expect the collection to be unchanged only if it is no longer referenced; the collection leaves the overhead of the ability to change both in speed and in memory.

A couple of simple examples:

 public static final ImmutableSet<String> COLOR_NAMES = ImmutableSet.of( "red", "green", "blue"); class Foo { final ImmutableSet<Bar> bars; Foo(Set<Bar> bars) { this.bars = ImmutableSet.copyOf(bars); // defensive copy! } } 


Implementing Iterators


PeekingIteratorJust wraps the iterator, adding a peek () method to it to get the value of the next element. Created by calling Iterators.peekingIterator (Iterator)
AbstractIteratorEliminates the need to implement all the methods of the iterator - just enough to implement protected T computeNext ()
AbstractSequentialIteratorIt is similar to the previous one, but calculates the next element based on the previous one: you need to implement the protected T method computeNext (T previous)


Functional and collection utilities


Guava provides interfaces such as Function <A, R> and Predicate, and utility classes Functions, Predicates, FluentIterable, Iterables, Lists, Sets and others. I remind you that Guava appeared long before Java 8, and therefore Optional and the Function <A, R> and Predicate interfaces inevitably appeared in it, which, however, are useful only in limited cases, because without lambda the function code with predicates and functions is in most cases will be much more cumbersome than the usual imperative, but in some cases it allows you to keep it concise. A simple example:

 Predicate<MyClass> nonDefault = not(equalTo(DEFAULT_VALUE)); Iterable<String> strings1 = transform(filter(iterable, nonDefault), toStringFunction()); Iterable<String> strings2 = from(iterable).filter(nonDefault).transform(toStringFunction()); 

Here static methods from Functions (toStringFunction), Predicates (not, equalTo), Iterables (transform, filter) and FluentIterable (from) are imported. In the first case, the static methods Iterable are used to construct the result, in the second - FluentIterable.

Input Output


To abstract byte and character streams, abstract classes such as ByteSource, ByteSink, CharSoure, and CharSink are defined. They are created, as a rule, with the help of the Resources and Files facades. There is also a considerable set of methods for working with input and output streams, such as conversion, reading, copying and concatenation (see classes CharSource, ByteSource, ByteSink). Examples:

 // Read the lines of a UTF-8 text file ImmutableList<String> lines = Files.asCharSource(file, Charsets.UTF_8).readLines(); // Count distinct word occurrences in a file Multiset<String> 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)); 


About everything little by little


ListsCreating various types of lists, incl. Lists.newCopyOnWriteArrayList (iterable), Lists.reverse (list) / * view! /, Lists.transform (fromList, function) / * lazy view! * /
SetsConversion from Map <SomeClass, Boolean> to Set <SomeClass> (view!), Working with sets in the mathematical sense (intersection, union, difference)
IterablesSimple methods of type any, all, contains, concat, filter, find, limit, isEmpty, size, toArray, transform. For some reason, in Java 8, many of these methods apply only to collections, but not to Iterable in general.
Bytes, Ints, UnsignedInteger, etc.Work with unsigned numbers and arrays of primitive types (there are corresponding utility classes for each primitive type).
ObjectArraysIn fact, only two types of methods are concatenation of arrays (for some reason it is not in the standard Java library) and the creation of masses for a given class or class of an array (for some reason, the Java library has only a similar method for copying).
Joiner splitterFlexible classes for combining or stringing rows from or to Iterable, List, or Map.
Strings, MoreObjectsOf the unmentioned, the most commonly used methods are Strings.emptyToNull (String), Strings.isNullOrEmpty (String), Strings.nullToEmpty (String), and MoreObjects.firstNonNull (T, T)
Closer, ThrowablesTry-with-resources emulation, multi-catch (useful only for Java 6 and older), working with stack tracing and throwing exceptions.
com.google.common.netThe class names speak for themselves: InternetDomainName, InetAddresses, HttpHeaders, MediaType, UrlEscapers
com.google.common.html and com.google.common.xmlHtmlEscapers and XmlEscapers
RangeRange.
EventbusPowerful implementation of the publisher-subscriber pattern. Subscribers are registered in EventBus, whose “responsive” methods are annotated, and when you call an event, EventBus finds subscribers who are able to perceive this kind of event and notifies them of the event.
IntMath, LongMath, BigIntegerMath, DoubleMathMany useful functions for working with numbers.
ClasspathThere is no cross-platform way to browse classes on the classpath in Java And Guava provides an opportunity to go through the classes of a package or project.
TypetokenDue to the erasure of types, we cannot manipulate generic types during program execution. TypeToken allows you to manipulate such types.


More examples


Hashing:

 HashFunction hf = Hashing.md5(); HashCode hc = hf.newHasher() .putLong(id) .putString(name, Charsets.UTF_8) .putObject(person, personFunnel) .hash(); 

Caching:
 LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder() .maximumSize(1000) .expireAfterWrite(10, TimeUnit.MINUTES) .removalListener(MY_LISTENER) .build( new CacheLoader<Key, Graph>() { public Graph load(Key key) throws AnyException { return createExpensiveGraph(key); } }); 

Dynamic proxy:

 Foo foo = Reflection.newProxy(Foo.class, invocationHandler) 

To create a dynamic proxy without Guava, the following code is usually written:

 Foo foo = (Foo) Proxy.newProxyInstance( Foo.class.getClassLoader(), new Class<?>[] {Foo.class}, invocationHandler); 


That's all the code you read.

Source: https://habr.com/ru/post/244347/


All Articles