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.