Generated immutable value classes for Java 8+ using annotation processing
—
The @Memoized annotation provides thread-safe caching of method results using double-checked locking, ideal for expensive computations in AutoValue classes.
@AutoValue
public abstract class Person {
public abstract String firstName();
public abstract String lastName();
@Memoized
public String fullName() {
return firstName() + " " + lastName();
}
public static Person create(String firstName, String lastName) {
return new AutoValue_Person(firstName, lastName);
}
}Person person = Person.create("John", "Doe");
// First call computes the result
String name1 = person.fullName(); // Computes "John Doe"
// Subsequent calls return cached result
String name2 = person.fullName(); // Returns cached "John Doe"
System.out.println(name1 == name2); // true (same object reference)@AutoValue
public abstract class DataSet {
public abstract List<Double> values();
@Memoized
public Statistics computeStatistics() {
// Expensive computation that we want to cache
List<Double> vals = values();
double sum = vals.stream().mapToDouble(Double::doubleValue).sum();
double mean = sum / vals.size();
double variance = vals.stream()
.mapToDouble(v -> Math.pow(v - mean, 2))
.sum() / vals.size();
return Statistics.create(mean, Math.sqrt(variance), vals.size());
}
@Memoized
public List<Double> sortedValues() {
return values().stream()
.sorted()
.collect(Collectors.toList());
}
public static DataSet create(List<Double> values) {
return new AutoValue_DataSet(ImmutableList.copyOf(values));
}
}
@AutoValue
abstract class Statistics {
abstract double mean();
abstract double standardDeviation();
abstract int count();
static Statistics create(double mean, double stdDev, int count) {
return new AutoValue_Statistics(mean, stdDev, count);
}
}Usage:
List<Double> data = Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0);
DataSet dataSet = DataSet.create(data);
// First call performs expensive computation
Statistics stats1 = dataSet.computeStatistics(); // Computes
// Second call returns cached result instantly
Statistics stats2 = dataSet.computeStatistics(); // Cached
System.out.println(stats1 == stats2); // trueYou can memoize Object methods for performance:
@AutoValue
public abstract class ComplexObject {
public abstract List<String> items();
public abstract Map<String, Object> properties();
public abstract Set<Integer> values();
@Memoized
@Override
public abstract int hashCode();
@Memoized
@Override
public abstract String toString();
public static ComplexObject create(
List<String> items,
Map<String, Object> properties,
Set<Integer> values) {
return new AutoValue_ComplexObject(
ImmutableList.copyOf(items),
ImmutableMap.copyOf(properties),
ImmutableSet.copyOf(values));
}
}Usage:
ComplexObject obj = ComplexObject.create(
Arrays.asList("a", "b", "c"),
Map.of("key1", "value1", "key2", "value2"),
Set.of(1, 2, 3, 4, 5));
// First calls compute and cache results
int hash1 = obj.hashCode(); // Computes
String str1 = obj.toString(); // Computes
// Subsequent calls use cached values
int hash2 = obj.hashCode(); // Cached
String str2 = obj.toString(); // CachedMethods returning nullable values can be memoized if annotated with @Nullable:
@AutoValue
public abstract class Document {
public abstract String content();
@Memoized
@Nullable
public String extractTitle() {
// Expensive regex or parsing operation
Pattern titlePattern = Pattern.compile("<title>(.*?)</title>", Pattern.CASE_INSENSITIVE);
Matcher matcher = titlePattern.matcher(content());
return matcher.find() ? matcher.group(1) : null;
}
@Memoized
public Optional<String> extractTitleOptional() {
String title = extractTitle();
return Optional.ofNullable(title);
}
public static Document create(String content) {
return new AutoValue_Document(content);
}
}Usage:
Document doc = Document.create("<html><title>My Page</title><body>Content</body></html>");
String title1 = doc.extractTitle(); // Computes "My Page"
String title2 = doc.extractTitle(); // Returns cached "My Page"
Optional<String> titleOpt = doc.extractTitleOptional(); // Uses cached resultMemoized methods cannot have parameters. Use property-based memoization instead:
@AutoValue
public abstract class Calculator {
public abstract double base();
public abstract int exponent();
@Memoized
public double result() {
// Expensive computation based on properties
return Math.pow(base(), exponent());
}
// Cannot memoize methods with parameters
// @Memoized // ERROR: Methods with parameters cannot be memoized
// public double power(double base, int exp) { ... }
public static Calculator create(double base, int exponent) {
return new AutoValue_Calculator(base, exponent);
}
}Memoized methods are thread-safe using double-checked locking:
// Generated code is equivalent to:
private volatile String memoizedFullName;
@Override
public String fullName() {
if (memoizedFullName == null) {
synchronized (this) {
if (memoizedFullName == null) {
memoizedFullName = super.fullName();
}
}
}
return memoizedFullName;
}This ensures:
Memoized methods work seamlessly with builders:
@AutoValue
public abstract class Configuration {
public abstract String host();
public abstract int port();
public abstract boolean ssl();
@Memoized
public String connectionString() {
String protocol = ssl() ? "https" : "http";
return protocol + "://" + host() + ":" + port();
}
@Memoized
public URL url() {
try {
return new URL(connectionString());
} catch (MalformedURLException e) {
throw new IllegalStateException("Invalid URL: " + connectionString(), e);
}
}
public static Builder builder() {
return new AutoValue_Configuration.Builder()
.ssl(false)
.port(80);
}
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder host(String host);
public abstract Builder port(int port);
public abstract Builder ssl(boolean ssl);
public abstract Configuration build();
}
}Usage:
Configuration config = Configuration.builder()
.host("api.example.com")
.port(443)
.ssl(true)
.build();
String conn1 = config.connectionString(); // Computes "https://api.example.com:443"
URL url1 = config.url(); // Computes URL object
URL url2 = config.url(); // Returns cached URL objectExceptions thrown by memoized methods are not cached:
@AutoValue
public abstract class Parser {
public abstract String input();
@Memoized
public JsonNode parseJson() {
try {
return objectMapper.readTree(input());
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("Invalid JSON: " + input(), e);
}
}
public static Parser create(String input) {
return new AutoValue_Parser(input);
}
}Parser parser = Parser.create("invalid json");
try {
parser.parseJson(); // Throws exception
} catch (IllegalArgumentException e) {
// Exception not cached
}
try {
parser.parseJson(); // Throws exception again (not cached)
} catch (IllegalArgumentException e) {
// Same exception thrown, method re-executed
}Memoized methods cannot be:
abstract (except for hashCode() and toString())privatefinalstaticvoidValid memoized method signature:
@Memoized
public ReturnType methodName() {
// computation
return result;
}Install with Tessl CLI
npx tessl i tessl/maven-com-google-auto-value--auto-value