Google Guava is a Java utility library that simplifies many aspects of development. It offers many useful classes and methods for working with collections, caching, functional programming, string handling, and more.
Key features of Google Guava:
Collections:
Guava significantly extends the standard Java collections by providing new data structures and utilities for working with them.
Multimap:
A collection that allows storing multiple values for a single key.
Examples: ArrayListMultimap, HashMultimap, LinkedListMultimap.
import com.google.common.collect.ArrayListMultimap import com.google.common.collect.Multimap fun multimapExample() { val multimap: Multimap<String, String> = ArrayListMultimap.create() multimap.put("fruit", "apple") multimap.put("fruit", "banana") multimap.put("vegetable", "carrot") println(multimap.get("fruit")) // [apple, banana] println(multimap.get("vegetable")) // [carrot] }
Multiset:
A collection that allows you to store multiple copies of the same element.
Examples: HashMultiset, TreeMultiset.
import com.google.common.collect.HashMultiset import com.google.common.collect.Multiset fun multisetExample() { val multiset: Multiset<String> = HashMultiset.create() multiset.add("apple") multiset.add("apple") multiset.add("banana") println(multiset.count("apple")) // 2 println(multiset.count("banana")) // 1 }
BiMap:
Bidirectional Map, where each key has a unique value and vice versa.
Example: HashBiMap.
import com.google.common.collect.HashBiMap fun bimapExample() { val biMap = HashBiMap.create<String, String>() biMap["one"] = "1" biMap["two"] = "2" // Accessing a value by key println(biMap["one"]) // 1 // Reverse key-by-value access println(biMap.inverse()["1"]) // one }
Table:
A two-dimensional Map equivalent to Map>.
Example: HashBasedTable.
import com.google.common.collect.HashBasedTable import com.google.common.collect.Table fun tableExample() { val table: Table<String, String, Int> = HashBasedTable.create() table.put("row1", "column1", 1) table.put("row1", "column2", 2) table.put("row2", "column1", 3) println(table.get("row1", "column1")) // 1 println(table.get("row1", "column2")) // 2 println(table.row("row1")) // {column1=1, column2=2} println(table.column("column1")) // {row1=1, row2=3} }
ClassToInstanceMap:
Map, where keys are classes and values are instances of these classes.
Example: MutableClassToInstanceMap.
import com.google.common.collect.MutableClassToInstanceMap import com.google.common.collect.ClassToInstanceMap fun classToInstanceMapExample() { val classToInstanceMap: ClassToInstanceMap<Any> = MutableClassToInstanceMap.create() classToInstanceMap.putInstance(String::class.java, "Hello World") classToInstanceMap.putInstance(Int::class.java, 42) val strValue = classToInstanceMap.getInstance(String::class.java) val intValue = classToInstanceMap.getInstance(Int::class.java) println(strValue) // Hello World println(intValue) // 42 }
RangeSet:
A set of ranges of numbers that provide operations for testing intersection and inclusion.
Example: TreeRangeSet.
import com.google.common.collect.Range import com.google.common.collect.TreeRangeSet fun rangeSetExample() { val rangeSet = TreeRangeSet.create<Int>() rangeSet.add(Range.closed(1, 10)) rangeSet.add(Range.closed(20, 30)) println(rangeSet.contains(5)) // true println(rangeSet.contains(15)) // false println(rangeSet.contains(25)) // true }
RangeMap:
Map whose keys are ranges and whose values are objects associated with these ranges.
Example: TreeRangeMap.
import com.google.common.collect.Range import com.google.common.collect.TreeRangeMap fun rangeMapExample() { val rangeMap = TreeRangeMap.create<Int, String>() rangeMap.put(Range.closed(1, 10), "Low") rangeMap.put(Range.closed(20, 30), "Medium") rangeMap.put(Range.closed(31, 40), "High") println(rangeMap.get(5)) // Low println(rangeMap.get(25)) // Medium println(rangeMap.get(35)) // High }
Immutable Collections:
Immutable versions of standard collections for improved security and optimization.
Examples: ImmutableList, ImmutableSet, ImmutableMap, ImmutableMultimap, ImmutableTable.
import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableSet fun immutableCollectionsExample() { val immutableList: List<String> = ImmutableList.of("apple", "banana", "cherry") val immutableSet: Set<String> = ImmutableSet.of("apple", "banana", "cherry") println(immutableList) // [apple, banana, cherry] println(immutableSet) // [apple, banana, cherry] // These collections are immutable, attempting to add an element will cause an error. // immutableList.add("pear") // UnsupportedOperationException }
Working with strings:
Splitter:
Utility for splitting a string by a delimiter with various options (skipping empty lines, trimming spaces, etc.).
import com.google.common.base.Splitter fun splitterExample() { val input = "apple, banana, , orange,," val result = Splitter.on(',') .trimResults() // Trim spaces .omitEmptyStrings() // Skipping empty lines .splitToList(input) println(result) // [apple, banana, orange] }
Joiner:
A utility for concatenating a collection of strings or other objects into a string with a specified separator.
import com.google.common.base.Joiner fun joinerExample() { val fruits = listOf("apple", null, "banana", "orange") // Joiner will skip null values val result = Joiner.on(", ") .skipNulls() .join(fruits) println(result) // apple, banana, orange }
CharMatcher:
A utility for searching and manipulating characters in a string.
import com.google.common.base.CharMatcher fun charMatcherExample() { val input = "Hello123 World456" // We delete all numbers val onlyLetters = CharMatcher.javaDigit().removeFrom(input) println(onlyLetters) // Hello World // We leave only numbers val onlyDigits = CharMatcher.javaDigit().retainFrom(input) println(onlyDigits) // 123456 // We trim the spaces and replace them with commas val trimmed = CharMatcher.whitespace().trimAndCollapseFrom(input, ',') println(trimmed) // Hello123,World456 }
CaseFormat:
Convert between different string case formats (e.g. snake_case, camelCase, UPPER_UNDERSCORE, etc.).
import com.google.common.base.CaseFormat fun caseFormatExample() { val input = "my_variable_name" // Convert from snake_case to camelCase val camelCase = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, input) println(camelCase) // myVariableName // Convert from snake_case to UPPER_UNDERSCORE val upperUnderscore = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_UNDERSCORE, input) println(upperUnderscore) // MY_VARIABLE_NAME }
Caching:
Guava provides a powerful caching mechanism through the Cache and LoadingCache classes, which supports various data removal strategies such as lifetime-based or cardinality-based removal.
Cache:
Built-in cache implementation with support for automatic data removal based on lifetime or volume.
import com.google.common.cache.CacheBuilder import com.google.common.cache.Cache fun simpleCacheExample() { // Create a cache with a maximum of 100 elements and a lifetime of 10 minutes val cache: Cache<String, String> = CacheBuilder.newBuilder() .maximumSize(100) .expireAfterWrite(10, java.util.concurrent.TimeUnit.MINUTES) .build() // Adding data to the cache cache.put("key1", "value1") // Getting data from cache val value = cache.getIfPresent("key1") println(value) // value1 // If the key is missing, null is returned. val missingValue = cache.getIfPresent("key2") println(missingValue) // null }
LoadingCache:
A cache that automatically downloads data if it is not there using the download function.
import com.google.common.cache.CacheBuilder import com.google.common.cache.CacheLoader import com.google.common.cache.LoadingCache import java.util.concurrent.TimeUnit fun loadingCacheExample() { // Create a cache that automatically downloads data if it is not there val loadingCache: LoadingCache<String, String> = CacheBuilder.newBuilder() .maximumSize(100) .expireAfterWrite(10, TimeUnit.MINUTES) .build(object : CacheLoader<String, String>() { override fun load(key: String): String { return "Generated value for $key" } }) // We get data from the cache, if the key is missing, the data is generated automatically println(loadingCache.get("key1")) // Generated value for key1 // Adding an element manually to the cache loadingCache.put("key2", "Manually added value") println(loadingCache.get("key2")) // Manually added value }
RemovalListener:
Handler for events of removing items from the cache.
import com.google.common.cache.CacheBuilder import com.google.common.cache.RemovalListener import com.google.common.cache.RemovalNotification import java.util.concurrent.TimeUnit fun cacheWithRemovalListenerExample() { // Create a cache with a handler for deleting elements val cache = CacheBuilder.newBuilder() .maximumSize(100) .expireAfterWrite(10, TimeUnit.MINUTES) .removalListener(object : RemovalListener<String, String> { override fun onRemoval(notification: RemovalNotification<String, String>) { println("Removed key: ${notification.key}, value: ${notification.value}, cause: ${notification.cause}") } }) .build<String, String>() // Adding and removing items from the cache cache.put("key1", "value1") cache.invalidate("key1") // Force deletion of an element }
CacheBuilder:
Allows flexible configuration of the cache with restrictions on the lifetime of elements, quantity, handling of competition, etc.
import com.google.common.cache.CacheBuilder import com.google.common.cache.Cache fun cacheBuilderExample() { // Create a cache with the maximum number of elements and a lifetime of 5 minutes val cache: Cache<String, String> = CacheBuilder.newBuilder() .maximumSize(200) // Limit on the number of elements .expireAfterWrite(5, TimeUnit.MINUTES) // Elements live for 5 minutes .build() // Adding an element cache.put("key1", "value1") // We get the element println(cache.getIfPresent("key1")) // value1 // After 5 minutes the item will be deleted automatically. }
Functional programming:
Predicate:
Interface for defining logical condition for objects.
import com.google.common.base.Predicate import com.google.common.collect.FluentIterable fun predicateExample() { val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) // Define a predicate to filter even numbers val isEven: Predicate<Int> = Predicate { it % 2 == 0 } // Using FluentIterable to Filter Numbers val evenNumbers = FluentIterable.from(numbers) .filter(isEven) .toList() println(evenNumbers) // [2, 4, 6, 8, 10] }
Function:
An interface for converting one object to another.
import com.google.common.base.Function import com.google.common.collect.FluentIterable fun functionExample() { val numbers = listOf(1, 2, 3, 4, 5) // We define a function for squaring a number val squareFunction: Function<Int, Int> = Function { it * it } // Transform a list of numbers val squaredNumbers = FluentIterable.from(numbers) .transform(squareFunction) .toList() println(squaredNumbers) // [1, 4, 9, 16, 25] }
Suppliers:
A utility for lazy and memoized value delivery.
import com.google.common.base.Suppliers import java.util.concurrent.TimeUnit fun supplierExample() { // Lazy string evaluator with memoization for 10 seconds val supplier = Suppliers.memoizeWithExpiration( Suppliers.ofInstance("Hello, world!"), 10, TimeUnit.SECONDS ) // We get the value println(supplier.get()) // Hello, world! // The value is cached for 10 seconds, then will be recalculated. }
Optional:
A container for objects that can either contain a value or be empty (an alternative to null).
import com.google.common.base.Optional fun optionalExample() { val optionalPresent: Optional<String> = Optional.of("Hello") val optionalAbsent: Optional<String> = Optional.absent() // Checking for the presence of a value if (optionalPresent.isPresent) { println(optionalPresent.get()) // Hello } // Return default value if none exists println(optionalAbsent.or("Default Value")) // Default Value }
FluentIterable:
A utility for processing collections with support for functional composition methods (filtering, transformation, etc.).
import com.google.common.collect.FluentIterable fun fluentIterableExample() { val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) // Combining Filtering and Transformation with FluentIterable val processedNumbers = FluentIterable.from(numbers) .filter { it % 2 == 0 } // Filter only even numbers .transform { it * 2 } // We multiply each by 2 .toList() println(processedNumbers) // [4, 8, 12, 16, 20] }
Working with exceptions:
Throwables:
Utilities for working with exceptions (for example, casting checked exceptions to runtime exceptions).
import com.google.common.base.Throwables fun throwablesExample() { try { // We artificially create an exception throw IllegalArgumentException("Illegal argument!") } catch (e: Exception) { // We display full information about the exception println(Throwables.getStackTraceAsString(e)) // Check and rethrow an exception if it is of a certain type if (Throwables.getRootCause(e) is IllegalArgumentException) { throw Throwables.propagate(e) } } }
Retryer:
A utility for retrying operations when exceptions occur, with support for wait and stop strategies. Google Guava does not have built-in support for Retryer, but there are popular libraries such as Failsafe or Guava-Retrying that can be used for retries. Here is an example of using the Guava-Retrying library to retry an operation:
import com.github.rholder.retry.Retryer import com.github.rholder.retry.RetryerBuilder import com.github.rholder.retry.StopStrategies import com.github.rholder.retry.WaitStrategies import java.util.concurrent.TimeUnit fun retryerExample() { // Create a Retryer with a maximum of 3 attempts and a 2 second wait between attempts val retryer: Retryer<String> = RetryerBuilder.newBuilder<String>() .retryIfException() // Retry if any exception occurs .withWaitStrategy(WaitStrategies.fixedWait(2, TimeUnit.SECONDS)) // Wait 2 seconds between attempts .withStopStrategy(StopStrategies.stopAfterAttempt(3)) // We stop after 3 attempts .build() try { // Performing an operation with repeated attempts val result = retryer.call { // Logic that can throw an exception if (Math.random() > 0.7) { "Success" } else { throw RuntimeException("Failed attempt") } } println("Operation succeeded: $result") } catch (e: Exception) { println("Operation failed after retries: ${e.message}") } }
Checked exceptions to Unchecked exceptions:
Guava helps in converting checked exceptions to unchecked via Throwables.propagate():
import com.google.common.base.Throwables fun propagateExample() { try { throwCheckedException() } catch (e: Exception) { // Convert a checked exception to an unchecked exception throw Throwables.propagate(e) } } fun throwCheckedException() { throw Exception("Checked exception!") }
Working with primitives:
Google Guava offers convenient utilities for working with primitives such as Ints, Longs, Doubles, Booleans, Bytes, as well as support for unsigned integers and immutable collections for primitives. These utilities allow you to avoid working with primitive type wrappers and perform various operations more efficiently.
Ints, Longs, Doubles, Booleans, Floats, Bytes:
Utilities for converting and working with primitive types.
import com.google.common.primitives.Ints fun intsExample() { val numbers = listOf(1, 2, 3, 4, 5) // Converting a list to an int array val intArray = Ints.toArray(numbers) println(intArray.contentToString()) // [1, 2, 3, 4, 5] // Finding the maximum and minimum value println(Ints.max(1, 3, 5, 2)) // 5 println(Ints.min(1, 3, 5, 2)) // 1 // Checking if a number is in an array println(Ints.contains(intArray, 3)) // true // Comparing two int println(Ints.compare(5, 10)) // -1 (the first is less than the second) }
import com.google.common.primitives.Longs fun longsExample() { val longArray = longArrayOf(10L, 20L, 30L) // Converting a long array to a list val longList = Longs.asList(*longArray) println(longList) // [10, 20, 30] // Converting a List Back to an Array val array = Longs.toArray(longList) println(array.contentToString()) // [10, 20, 30] }
import com.google.common.primitives.Doubles fun doublesExample() { val doubleArray = doubleArrayOf(1.1, 2.2, 3.3) // Converting a double array to a list val doubleList = Doubles.asList(*doubleArray) println(doubleList) // [1.1, 2.2, 3.3] // Converting a List Back to an Array val array = Doubles.toArray(doubleList) println(array.contentToString()) // [1.1, 2.2, 3.3] // Comparison of two doubles println(Doubles.compare(3.14, 2.71)) // 1 (the first is greater than the second) }
import com.google.common.primitives.Booleans fun booleansExample() { val booleanArray = booleanArrayOf(true, false, true) // Convert boolean array to list val booleanList = Booleans.asList(*booleanArray) println(booleanList) // [true, false, true] // Converting a List Back to an Array val array = Booleans.toArray(booleanList) println(array.contentToString()) // [true, false, true] }
import com.google.common.primitives.Bytes fun bytesExample() { val byteArray = byteArrayOf(1, 2, 3) // Convert byte array to list val byteList = Bytes.asList(*byteArray) println(byteList) // [1, 2, 3] // Converting a List Back to an Array val array = Bytes.toArray(byteList) println(array.contentToString()) // [1, 2, 3] }
UnsignedInts, UnsignedLongs:
Support for unsigned integers.
import com.google.common.primitives.UnsignedInts import com.google.common.primitives.UnsignedLongs fun unsignedIntsExample() { val unsignedInt = UnsignedInts.parseUnsignedInt("4294967295") // Maximum value of unsigned int println(unsignedInt) // 4294967295 (but stored as a normal int) val compared = UnsignedInts.compare(4000000000.toInt(), 3000000000.toInt()) println(compared) // 1 (the first is greater than the second) } fun unsignedLongsExample() { val unsignedLong = UnsignedLongs.parseUnsignedLong("18446744073709551615") // Maximum value of unsigned long println(unsignedLong) // 18446744073709551615 (but stored as a regular long) val compared = UnsignedLongs.compare(18000000000000000000L, 17000000000000000000L) println(compared) // 1 (the first is greater than the second) }
Immutable collections for primitives:
For example, ImmutableIntArray.
import com.google.common.primitives.ImmutableIntArray fun immutableIntArrayExample() { // Creating an immutable array val immutableIntArray = ImmutableIntArray.of(1, 2, 3, 4, 5) println(immutableIntArray) // [1, 2, 3, 4, 5] // Getting an element println(immutableIntArray.get(2)) // 3 // Trying to modify the array will cause an error. // immutableIntArray.set(0, 10) // UnsupportedOperationException }
Iterators and generators:
Google Guava provides powerful utilities for working with iterators and data generators. These utilities make it easy to filter, transform, and combine collections, providing a flexible and convenient API. Let's look at some examples of using iterators and data generators in Kotlin.
Iterators:
Utilities for working with Java Iterator, such as filtering, transforming, merging, etc.
Example of iterator filtering:
import com.google.common.collect.Iterators fun iteratorFilterExample() { val numbers = listOf(1, 2, 3, 4, 5).iterator() // Filter the iterator to contain only even numbers val evenNumbers = Iterators.filter(numbers) { it % 2 == 0 } while (evenNumbers.hasNext()) { println(evenNumbers.next()) // 2, 4 } }
Example of iterator transformation:
import com.google.common.collect.Iterators import com.google.common.base.Function fun iteratorTransformExample() { val numbers = listOf(1, 2, 3, 4, 5).iterator() // Transform the iterator by squaring numbers val squaredNumbers = Iterators.transform(numbers, Function { it * it }) while (squaredNumbers.hasNext()) { println(squaredNumbers.next()) // 1, 4, 9, 16, 25 } }
PeekingIterator:
An iterator that allows you to "look" at the next element without extracting it.
import com.google.common.collect.Iterators import com.google.common.collect.PeekingIterator fun peekingIteratorExample() { val numbers = listOf(1, 2, 3, 4, 5).iterator() val peekingIterator: PeekingIterator<Int> = Iterators.peekingIterator(numbers) while (peekingIterator.hasNext()) { println("Current: ${peekingIterator.next()}") if (peekingIterator.hasNext()) { println("Next (peek): ${peekingIterator.peek()}") } } }
AbstractIterator:
A template to simplify the creation of custom iterators.
import com.google.common.collect.AbstractIterator fun abstractIteratorExample() { val customIterator = object : AbstractIterator<Int>() { var count = 0 override fun computeNext(): Int? { count++ return if (count <= 5) count else endOfData() // We finish when count > 5 } } while (customIterator.hasNext()) { println(customIterator.next()) // 1, 2, 3, 4, 5 } }
UnmodifiableIterator:
An iterator that does not allow modification. UnmodifiableIterator ensures that elements cannot be modified during iteration.
import com.google.common.collect.UnmodifiableIterator fun unmodifiableIteratorExample() { val numbers = listOf(1, 2, 3, 4, 5) // Create an immutable iterator val unmodifiableIterator: UnmodifiableIterator<Int> = object : UnmodifiableIterator<Int>() { private val iter = numbers.iterator() override fun hasNext(): Boolean = iter.hasNext() override fun next(): Int = iter.next() } while (unmodifiableIterator.hasNext()) { println(unmodifiableIterator.next()) // 1, 2, 3, 4, 5 } // unmodifiableIterator.remove() // UnsupportedOperationException }
FluentIterable:
Functional work with collections. Although FluentIterable works with both collections and iterators, it provides a functional style of working with them, combining filtering, transformation, and other operations.
import com.google.common.collect.FluentIterable fun fluentIterableExample() { val numbers = listOf(1, 2, 3, 4, 5) // Combining Filtering and Transformation with FluentIterable val result = FluentIterable.from(numbers) .filter { it % 2 == 0 } // Filtering even numbers .transform { it * 2 } // We multiply each by 2 .toList() println(result) // [4, 8] }
Type conversions:
Google Guava provides powerful type conversion utilities such as TypeToken and Converter. They allow you to work with generics, convert objects between different types, and manage type safety at runtime.
TypeToken:
A utility for working with types that supports generics. TypeToken allows you to work with types, especially generics, and extract information about them at runtime. In standard Java/Kotlin, the type system erases information about generics at runtime, but with TypeToken, you can preserve type information.
import com.google.common.reflect.TypeToken fun typeTokenExample() { // Create a TypeToken for List<String> val typeToken = object : TypeToken<List<String>>() {} // Getting information about the type val type = typeToken.type println(type) // java.util.List<java.lang.String> // Checking the type at runtime val listOfStrings: Any = listOf("a", "b", "c") if (typeToken.rawType.isAssignableFrom(listOfStrings.javaClass)) { println("listOfStrings is a List<String>") } }
Converter:
An abstraction for converting one type to another, with support for the reverse conversion. Converter allows you to easily convert objects from one type to another. This is useful when you need to define a conversion in both directions – back and forth.
import com.google.common.base.Converter fun converterExample() { // Defining a converter between String and Integer val stringToIntegerConverter = object : Converter<String, Int>() { override fun doForward(string: String): Int { return string.toInt() } override fun doBackward(integer: Int): String { return integer.toString() } } // Convert a string to a number val intValue = stringToIntegerConverter.convert("123") println(intValue) // 123 // Convert the number back to a string val stringValue = stringToIntegerConverter.reverse().convert(456) println(stringValue) // "456" }
Enums:
Converting Strings to Enums (and Back) Guava also makes it easy to work with enums by providing utilities to convert them from strings and back.
import com.google.common.base.Enums enum class Color { RED, GREEN, BLUE } fun enumExample() { // Convert a string to an enumeration value val color = Enums.getIfPresent(Color::class.java, "RED").orNull() println(color) // RED // Get a string representation of the enumeration val colorName = Enums.stringConverter(Color::class.java).convert(Color.GREEN) println(colorName) // GREEN }
Working with time:
Stopwatch:
Timer for measuring the execution time of operations.
import com.google.common.base.Stopwatch import java.util.concurrent.TimeUnit fun stopwatchExample() { // Create a timer val stopwatch = Stopwatch.createStarted() // We perform some operation Thread.sleep(1000) // Stop the timer stopwatch.stop() // We get the result in milliseconds println("Elapsed time: ${stopwatch.elapsed(TimeUnit.MILLISECONDS)} ms") // Example: Elapsed time: 1001 ms // You can restart the timer stopwatch.reset().start() Thread.sleep(500) stopwatch.stop() println("Elapsed time after reset: ${stopwatch.elapsed(TimeUnit.MILLISECONDS)} ms") // Example: Elapsed time after reset: 500 ms }
Timestamps:
Timestamp utilities. Guava also offers utilities for working with timestamps and time intervals. However, there is no standard Timestamps utility in Guava. Instead, classes from the java.time package are typically used (since Java 8). However, Stopwatch is Guava's primary time tool.
An example of working with Java 8 time classes (for working with timestamps):
Although it is not part of Google Guava, since Java 8 we can use java.time to work with timestamps, and this is often used in conjunction with Guava utilities.
import java.time.LocalDateTime import java.time.format.DateTimeFormatter fun timestampExample() { // Get the current timestamp val currentTime = LocalDateTime.now() println("Current timestamp: $currentTime") // Example: Current timestamp: 2023-10-01T15:45:32.123 // Formatting a timestamp val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") val formattedTime = currentTime.format(formatter) println("Formatted timestamp: $formattedTime") // Example: Formatted timestamp: 2023-10-01 15:45:32 }
Working with processes:
Google Guava provides abstractions for managing services and processes through the com.google.common.util.concurrent packages and the Service class. These utilities are particularly useful for managing the lifecycle of processes or services that have startup, execution, and shutdown stages, making them suitable for long-lived processes such as servers, background task processing, and other long-running operations.
Service:
Abstraction for representing services with lifecycle support (initialization, stop, etc.). Provides methods for managing the lifecycle of processes: starting, stopping, monitoring status.
AbstractExecutionThreadService:
AbstractExecutionThreadService provides a template for creating processes that should run on separate threads. This class makes it easy to run a service on a background thread and handle its termination.
import com.google.common.util.concurrent.AbstractExecutionThreadService fun abstractExecutionThreadServiceExample() { // Create a service that runs in a separate thread val backgroundService: Service = object : AbstractExecutionThreadService() { override fun run() { println("Background service is running") // Emulate a long operation Thread.sleep(5000) println("Background service has completed") } override fun startUp() { println("Background service is starting up") } override fun shutDown() { println("Background service is shutting down") } } // Launching the service backgroundService.startAsync() backgroundService.awaitRunning() // We stop the service after work backgroundService.stopAsync() backgroundService.awaitTerminated() }
ServiceManager:
Provides the ability to manage multiple services simultaneously, monitor their status, and terminate them gracefully.
import com.google.common.util.concurrent.AbstractExecutionThreadService import com.google.common.util.concurrent.Service import com.google.common.util.concurrent.ServiceManager fun serviceManagerExample() { // We create two services val service1: Service = object : AbstractExecutionThreadService() { override fun run() { println("Service 1 is running...") Thread.sleep(2000) } override fun shutDown() { println("Service 1 is shutting down...") } } val service2: Service = object : AbstractExecutionThreadService() { override fun run() { println("Service 2 is running...") Thread.sleep(3000) } override fun shutDown() { println("Service 2 is shutting down...") } } // Using ServiceManager to manage two services val serviceManager = ServiceManager(listOf(service1, service2)) // Add a handler to notify about the completion of all services serviceManager.addListener(object : ServiceManager.Listener() { override fun stopped() { println("All services have stopped.") } override fun failure(service: Service) { println("Service failed: ${service.javaClass.name}") } }, Runnable::run) // We launch all services serviceManager.startAsync() serviceManager.awaitHealthy() // Waiting for all services to start // We stop all services serviceManager.stopAsync() serviceManager.awaitStopped() // Waiting for all services to stop }
Concurrent programming:
ListenableFuture:
An extension of the standard Future that supports adding callbacks.
import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.MoreExecutors import com.google.common.util.concurrent.ListenableFutureTask import java.util.concurrent.Executors fun listenableFutureExample() { // Create an executor to perform tasks val executor = Executors.newSingleThreadExecutor() // Create a ListenableFutureTask for an asynchronous operation val futureTask: ListenableFuture<String> = ListenableFutureTask.create<String> { Thread.sleep(2000) // Emulate a long running task "Task result" } // Add a callback to process the result futureTask.addListener({ try { println("Task completed with result: ${futureTask.get()}") } catch (e: Exception) { println("Error during task execution: ${e.message}") } }, MoreExecutors.directExecutor()) // Launch the task executor.submit(futureTask) }
Futures:
Utilities for working with ListenableFuture, including composition and combination of multiple future values.
import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.ListenableFutureTask import java.util.concurrent.Executors fun futuresExample() { // Create an executor to perform tasks val executor = Executors.newFixedThreadPool(2) // Create two asynchronous tasks val futureTask1 = ListenableFutureTask.create<String> { Thread.sleep(1000) "Result from Task 1" } val futureTask2 = ListenableFutureTask.create<String> { Thread.sleep(2000) "Result from Task 2" } // Launching tasks executor.submit(futureTask1) executor.submit(futureTask2) // We combine the results of two tasks val combinedFuture = Futures.allAsList(futureTask1, futureTask2) // Add a callback to process the result combinedFuture.addListener({ try { val results = combinedFuture.get() println("All tasks completed with results: $results") } catch (e: Exception) { println("Error: ${e.message}") } }, MoreExecutors.directExecutor()) }
MoreExecutors:
Advanced utilities for working with executors (for example, converting an executor to a ListenableFuture).
import com.google.common.util.concurrent.MoreExecutors import java.util.concurrent.Executors fun moreExecutorsExample() { // Create an executor that automatically terminates when all tasks are completed val executor = MoreExecutors.getExitingExecutorService(Executors.newSingleThreadExecutor()) // Launch the task executor.submit { println("Task is running in exiting executor") } // The executor will automatically terminate its work when all tasks are completed. }
ListeningExecutorService:
Extends ExecutorService and supports creating tasks that return a ListenableFuture. This is useful for managing asynchronous tasks with callbacks.
import com.google.common.util.concurrent.ListeningExecutorService import com.google.common.util.concurrent.MoreExecutors import java.util.concurrent.Executors fun listeningExecutorServiceExample() { // Create ListeningExecutorService val executorService: ListeningExecutorService = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(2)) // Launching an asynchronous task val future = executorService.submit<String> { Thread.sleep(1000) "Async result" } // Add a callback to process the result future.addListener({ try { println("Task completed with result: ${future.get()}") } catch (e: Exception) { println("Error: ${e.message}") } }, MoreExecutors.directExecutor()) }
RateLimiter:
Used to limit the speed at which operations are performed, such as to control the number of requests per second.
import com.google.common.util.concurrent.RateLimiter fun rateLimiterExample() { // Create a RateLimiter with a limit of 2 operations per second val rateLimiter = RateLimiter.create(2.0) // We perform operations with speed limits for (i in 1..5) { rateLimiter.acquire() // Block execution until the limit allows us to continue println("Executing task $i at ${System.currentTimeMillis()}") } }
Utilities for working with IO:
Files:
Utilities for working with files (copying, moving, reading, writing, etc.).
Example of reading and writing files:
import com.google.common.io.Files import java.io.File import java.nio.charset.StandardCharsets fun filesExample() { val file = File("example.txt") // Write data to a file Files.asCharSink(file, StandardCharsets.UTF_8).write("Hello, world!") // Reading data from a file val content = Files.asCharSource(file, StandardCharsets.UTF_8).read() println("File content: $content") // Hello, world! }
Example of copying a file:
import com.google.common.io.Files import java.io.File fun fileCopyExample() { val sourceFile = File("source.txt") val targetFile = File("target.txt") // Copy the file Files.copy(sourceFile, targetFile) println("File copied from ${sourceFile.name} to ${targetFile.name}") }
Example of moving a file:
import com.google.common.io.Files import java.io.File fun fileMoveExample() { val sourceFile = File("source.txt") val targetFile = File("moved.txt") // Moving the file Files.move(sourceFile, targetFile) println("File moved from ${sourceFile.name} to ${targetFile.name}") }
CharStreams:
Utilities for working with character streams.
Example of reading data from Reader:
import com.google.common.io.CharStreams import java.io.StringReader fun charStreamsExample() { val reader = StringReader("Hello from StringReader!") // Read all data from Reader val content = CharStreams.toString(reader) println(content) // Hello from StringReader! }
Example of writing data to Writer:
import com.google.common.io.CharStreams import java.io.StringWriter fun charStreamsWriteExample() { val writer = StringWriter() // Writing data to Writer CharStreams.asWriter(writer).write("Hello to StringWriter!") println(writer.toString()) // Hello to StringWriter! }
ByteStreams:
Utilities for working with byte streams.
Example of copying data from one stream to another:
import com.google.common.io.ByteStreams import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream fun byteStreamsExample() { val input = ByteArrayInputStream("Hello in bytes!".toByteArray()) val output = ByteArrayOutputStream() // Copy data from InputStream to OutputStream ByteStreams.copy(input, output) println(output.toString()) // Hello in bytes! }
Example of reading all data from InputStream:
import com.google.common.io.ByteStreams import java.io.ByteArrayInputStream fun byteStreamsReadExample() { val input = ByteArrayInputStream("Some byte data".toByteArray()) // Read all data from InputStream val byteData = ByteStreams.toByteArray(input) println(String(byteData)) // Some byte data }
Closer:
Simplifies the management of resources such as files and streams by ensuring that they are automatically closed in the event of an error or successful completion.
import com.google.common.io.Closer import java.io.FileReader import java.io.FileWriter fun closerExample() { val closer = Closer.create() try { // Opening resources val reader = closer.register(FileReader("input.txt")) val writer = closer.register(FileWriter("output.txt")) // We read data and write it val data = reader.readText() writer.write(data) } catch (e: Exception) { println("Error occurred: ${e.message}") throw e } finally { closer.close() // Automatically closes all resources } }
Resources:
Guava also provides utilities for working with resources as files that reside on the classpath.
import com.google.common.io.Resources import java.nio.charset.StandardCharsets fun resourcesExample() { // Reading resource from classpath val url = Resources.getResource("config.txt") val content = Resources.toString(url, StandardCharsets.UTF_8) println(content) }
Utilities for working with streams:
Stream:
Advanced utilities for working with data streams.
import com.google.common.collect.Streams fun streamsExample() { val list1 = listOf(1, 2, 3) val list2 = listOf(4, 5, 6) // Merge two lists into one stream val combinedStream = Streams.concat(list1.stream(), list2.stream()) // Convert a stream to a list val combinedList = combinedStream.toList() println(combinedList) // [1, 2, 3, 4, 5, 6] }
The Streams.zip utility allows you to merge two streams into one using the function:
import com.google.common.collect.Streams import java.util.stream.Stream fun streamsZipExample() { val stream1 = Stream.of(1, 2, 3) val stream2 = Stream.of("a", "b", "c") // We combine two streams by creating pairs (number, string) val zippedStream = Streams.zip(stream1, stream2) { number, letter -> "$number$letter" } // Convert a stream to a list val result = zippedStream.toList() println(result) // [1a, 2b, 3c] }
Hashing:
Hashing:
Support for various hash functions (MD5, SHA-256, MurmurHash, etc.).
import com.google.common.hash.Hashing import java.nio.charset.StandardCharsets fun hashingExample() { val input = "Hello, World!" // Using SHA-256 to hash a string val hash = Hashing.sha256() .hashString(input, StandardCharsets.UTF_8) .toString() println("SHA-256 hash: $hash") // Example output: SHA-256 hash: c0535e4be2b79ffd93291305436bf889314e4a3d9a91fb217b0b14cba2999e66 }
import com.google.common.hash.Hashing import java.nio.charset.StandardCharsets fun murmurHashExample() { val input = "Guava Hashing Example" // Using MurmurHash3 to hash a string val hash = Hashing.murmur3_128() .hashString(input, StandardCharsets.UTF_8) .toString() println("MurmurHash3 (128-bit) hash: $hash") // Example output: MurmurHash3 (128-bit) hash: 2f17b911e9d9b22fa10f0ab091d02e17 }
Hasher:
Allows you to add data to hash incrementally. This is useful if you need to hash data coming from multiple sources, or data that needs to be hashed sequentially.
import com.google.common.hash.Hashing import java.nio.charset.StandardCharsets fun hasherExample() { val hasher = Hashing.sha256().newHasher() // Adding data for hashing in parts hasher.putString("Hello", StandardCharsets.UTF_8) hasher.putInt(12345) hasher.putLong(9876543210L) // We get the final hash val hash = hasher.hash().toString() println("Combined SHA-256 hash: $hash") // Sample output: Combined SHA-256 hash: 3627904d36e33402e1dba8e132ea9d222f3baaf3dcb9bfb144f50c3cf555160b }
HashCode:
Represents the hash result, which can be converted to a string, a byte array, or used for comparison.
import com.google.common.hash.Hashing import java.nio.charset.StandardCharsets fun hashCodeExample() { val hashCode = Hashing.sha256() .hashString("Guava HashCode Example", StandardCharsets.UTF_8) // Convert the hash to a string println("Hash as string: ${hashCode.toString()}") // Convert the hash to a byte array val byteArray = hashCode.asBytes() println("Hash as byte array: ${byteArray.contentToString()}") }
BloomFilter:
a probabilistic data structure used to test whether an element belongs to a set with a small probability of false positives. This tool is useful when memory is a concern and a small probability of false positives can be tolerated.
import com.google.common.hash.BloomFilter import com.google.common.hash.Funnels fun bloomFilterExample() { // Create a Bloom filter for rows with an expected throughput of 1000 and a false positive rate of 0.01 val bloomFilter = BloomFilter.create(Funnels.stringFunnel(StandardCharsets.UTF_8), 1000, 0.01) // Add data to the filter bloomFilter.put("Guava") bloomFilter.put("BloomFilter") bloomFilter.put("Hashing") // Checking the presence of elements println("Might contain 'Guava': ${bloomFilter.mightContain("Guava")}") // true println("Might contain 'Hashing': ${bloomFilter.mightContain("Hashing")}") // true println("Might contain 'Unknown': ${bloomFilter.mightContain("Unknown")}") // false (false positive probability 0.01) }
Custom Hash Functions:
Creating your own hash function. If the built-in hashing algorithms are not suitable, you can create your own hash function by implementing the HashFunction interface.
import com.google.common.hash.AbstractHasher import com.google.common.hash.HashFunction import com.google.common.hash.HashCode fun customHashFunctionExample() { val customHashFunction = object : HashFunction { override fun newHasher() = object : AbstractHasher() { private var sum = 0 override fun putInt(i: Int): AbstractHasher { sum += i return this } override fun putString(input: CharSequence, charset: java.nio.charset.Charset): AbstractHasher { sum += input.length return this } override fun hash(): HashCode { return HashCode.fromInt(sum) } } override fun bits(): Int = 32 } val hasher = customHashFunction.newHasher() hasher.putInt(123) hasher.putString("custom hash function", StandardCharsets.UTF_8) val hash = hasher.hash() println("Custom hash code: ${hash.asInt()}") // Output example: Custom hash code: 143 }
Mathematical utilities:
Ints, Longs, Doubles, BigIntegerMath, DoubleMath:
Utilities for mathematical operations with numbers of various types.
RoundingMode:
Utilities for rounding numbers.
Working with annotations:
@Beta:
Unstable API flag. Indicates that the API may change in the future. The @Beta annotation is used to flag classes, methods, and APIs that may change in future versions. This API is not considered stable and should be used with caution in production as it may change without backward compatibility.
import com.google.common.annotations.Beta @Beta class BetaFeature { fun newFeature() { println("This feature is in beta and may change!") } } fun betaFeatureExample() { val feature = BetaFeature() feature.newFeature() }
@VisibleForTesting:
Visible for testing. Indicates that a method or class is intended to be used in tests. The @VisibleForTesting annotation is used to indicate that a particular method or field is only accessible to tests. This can be useful for identifying methods that are not intended to be used outside of tests, but are open to testing.
import com.google.common.annotations.VisibleForTesting class ProductionClass { // This method is only available for tests. @VisibleForTesting internal fun internalLogicForTests(): Int { return 42 } fun publicLogic(): String { return "Public logic result" } } fun testVisibilityExample() { val productionClass = ProductionClass() // You can call the method in tests val testResult = productionClass.internalLogicForTests() println("Test logic result: $testResult") // Test logic result: 42 }
@Nullable:
Annotation to indicate nullability. The @Nullable annotation indicates that a method can return null or that a method parameter can be null. This helps improve code safety and transparency by showing where null can occur.
import javax.annotation.Nullable class NullableExample { // The method may return null @Nullable fun findValue(key: String): String? { return if (key == "exists") "Found value" else null } } fun nullableExample() { val example = NullableExample() val value = example.findValue("not_exists") println("Value: $value") // Value: null }
@CheckReturnValue:
Mandatory check of return value. The @CheckReturnValue annotation indicates that the return value of a method must be checked, and ignoring it may result in an error. This annotation is useful for methods whose result is important to handle.
import com.google.errorprone.annotations.CheckReturnValue class CheckReturnValueExample { @CheckReturnValue fun computeValue(): Int { return 42 } } fun checkReturnValueExample() { val example = CheckReturnValueExample() // It is assumed that this value should be used. val result = example.computeValue() println("Computed value: $result") // Computed value: 42 }
@CanIgnoreReturnValue:
Allowing the return value to be ignored. The @CanIgnoreReturnValue annotation allows the return value of a method to be ignored. This is useful in situations where the return value does not necessarily need to be used.
import com.google.errorprone.annotations.CanIgnoreReturnValue class IgnoreReturnValueExample { @CanIgnoreReturnValue fun performAction(): String { return "Action performed" } } fun ignoreReturnValueExample() { val example = IgnoreReturnValueExample() // The return value can be ignored. example.performAction() // The value does not have to be used }
Other utilities:
Preconditions:
Preconditions utilities used to validate method arguments. Google Guava provides a Preconditions utility that helps perform checks on method arguments and program states. These checks improve the readability and reliability of code by throwing exceptions with informative messages in case of invalid data.
The Preconditions utility provides the following main methods:
checkArgument:
is used to check conditions regarding the arguments passed. If the condition is not met, an IllegalArgumentException is thrown.
import com.google.common.base.Preconditions fun checkArgumentExample(age: Int) { // Check if age is greater than or equal to 18 Preconditions.checkArgument(age >= 18, "Age must be 18 or older, but was %s", age) println("Valid age: $age") } fun main() { checkArgumentExample(20) // Valid age: 20 // checkArgumentExample(15) // IllegalArgumentException: Age must be 18 or older, but was 15 }
checkNotNull:
checks that the passed argument is not null. If the argument is null, a NullPointerException is thrown.
import com.google.common.base.Preconditions fun checkNotNullExample(name: String?) { // Checking if a name is not null val validName = Preconditions.checkNotNull(name, "Name cannot be null") println("Valid name: $validName") } fun main() { checkNotNullExample("Alice") // Valid name: Alice // checkNotNullExample(null) // NullPointerException: Name cannot be null }
checkState:
is used to check the state of an object. If the condition is not met, an IllegalStateException is thrown.
import com.google.common.base.Preconditions class StateExample(private var isInitialized: Boolean) { fun doSomething() { // Checking if an object is initialized Preconditions.checkState(isInitialized, "Object must be initialized before use") println("Doing something") } fun initialize() { isInitialized = true } } fun main() { val example = StateExample(false) // example.doSomething() // IllegalStateException: Object must be initialized before use example.initialize() example.doSomething() // Doing something }
checkElementIndex:
checks that the element index is within the allowed range (from 0 to the size of the list/array). If the index is out of range, an IndexOutOfBoundsException is thrown.
import com.google.common.base.Preconditions fun checkElementIndexExample(index: Int, list: List<String>) { // Checking if an index is within a list Preconditions.checkElementIndex(index, list.size, "Invalid index") println("Element at index $index: ${list[index]}") } fun main() { val list = listOf("apple", "banana", "cherry") checkElementIndexExample(1, list) // Element at index 1: banana // checkElementIndexExample(5, list) // IndexOutOfBoundsException: Invalid index (5) must be less than size (3) }
checkPositionIndex:
checks the position in a collection or array. The position is an index that can point to the beginning or end of the collection. If the position is invalid, an IndexOutOfBoundsException is thrown.
import com.google.common.base.Preconditions fun checkPositionIndexExample(position: Int, list: List<String>) { // Checking the position Preconditions.checkPositionIndex(position, list.size, "Invalid position") println("Valid position: $position") } fun main() { val list = listOf("apple", "banana", "cherry") checkPositionIndexExample(3, list) // Valid position: 3 (points to the end of the list) // checkPositionIndexExample(5, list) // IndexOutOfBoundsException: Invalid position (5) must be less than size (3) }
checkPositionIndexes:
checks that the start and end indices are in the allowed range and that the start index is not greater than the end index.
import com.google.common.base.Preconditions fun checkPositionIndexesExample(start: Int, end: Int, list: List<String>) { // Checking index range Preconditions.checkPositionIndexes(start, end, list.size) println("Valid range: from $start to $end") } fun main() { val list = listOf("apple", "banana", "cherry", "date") checkPositionIndexesExample(0, 2, list) // Valid range: 0 to 2 // checkPositionIndexesExample(2, 5, list) // IndexOutOfBoundsException: End index (5) must not be greater than size (4) }
Objects:
Utilities for working with objects (comparison, hashCode generation, etc.).
MoreObjects:
Additional utilities for working with objects, including toStringHelper.
ComparisonChain:
A utility for creating chains of object comparisons that simplifies the implementation of compareTo methods.