CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-google-auto-value--auto-value

Generated immutable value classes for Java 8+ using annotation processing

Pending
Overview
Eval results
Files

memoization.mddocs/

Memoization

The @Memoized annotation provides thread-safe caching of method results using double-checked locking, ideal for expensive computations in AutoValue classes.

Basic Memoization

@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);
  }
}

Usage Example

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)

Expensive Computation Memoization

@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); // true

Memoized hashCode() and toString()

You 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(); // Cached

Nullable Memoization

Methods 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 result

Memoization with Parameters

Memoized 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);
  }
}

Thread Safety

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:

  • Only one thread computes the value
  • No race conditions
  • Efficient access after first computation
  • Proper memory visibility guarantees

Memoization with Builders

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 object

Error Handling in Memoized Methods

Exceptions 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
}

Performance Characteristics

  • First Call: Synchronization overhead + computation time
  • Subsequent Calls: Single volatile read (very fast)
  • Memory: One field per memoized method storing cached result
  • Thread Safety: Uses double-checked locking pattern
  • Null Values: Properly cached if method annotated with @Nullable

Restrictions

Memoized methods cannot be:

  • abstract (except for hashCode() and toString())
  • private
  • final
  • static
  • Have parameters
  • Return void

Valid 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

docs

annotation-generation.md

builders.md

extensions.md

index.md

memoization.md

pretty-strings.md

serialization.md

standalone-builders.md

tagged-unions.md

value-classes.md

tile.json