0
# Memoization
1
2
The @Memoized annotation provides thread-safe caching of method results using double-checked locking, ideal for expensive computations in AutoValue classes.
3
4
## Basic Memoization
5
6
```java { .api }
7
@AutoValue
8
public abstract class Person {
9
public abstract String firstName();
10
public abstract String lastName();
11
12
@Memoized
13
public String fullName() {
14
return firstName() + " " + lastName();
15
}
16
17
public static Person create(String firstName, String lastName) {
18
return new AutoValue_Person(firstName, lastName);
19
}
20
}
21
```
22
23
## Usage Example
24
25
```java
26
Person person = Person.create("John", "Doe");
27
28
// First call computes the result
29
String name1 = person.fullName(); // Computes "John Doe"
30
31
// Subsequent calls return cached result
32
String name2 = person.fullName(); // Returns cached "John Doe"
33
34
System.out.println(name1 == name2); // true (same object reference)
35
```
36
37
## Expensive Computation Memoization
38
39
```java { .api }
40
@AutoValue
41
public abstract class DataSet {
42
public abstract List<Double> values();
43
44
@Memoized
45
public Statistics computeStatistics() {
46
// Expensive computation that we want to cache
47
List<Double> vals = values();
48
double sum = vals.stream().mapToDouble(Double::doubleValue).sum();
49
double mean = sum / vals.size();
50
51
double variance = vals.stream()
52
.mapToDouble(v -> Math.pow(v - mean, 2))
53
.sum() / vals.size();
54
55
return Statistics.create(mean, Math.sqrt(variance), vals.size());
56
}
57
58
@Memoized
59
public List<Double> sortedValues() {
60
return values().stream()
61
.sorted()
62
.collect(Collectors.toList());
63
}
64
65
public static DataSet create(List<Double> values) {
66
return new AutoValue_DataSet(ImmutableList.copyOf(values));
67
}
68
}
69
70
@AutoValue
71
abstract class Statistics {
72
abstract double mean();
73
abstract double standardDeviation();
74
abstract int count();
75
76
static Statistics create(double mean, double stdDev, int count) {
77
return new AutoValue_Statistics(mean, stdDev, count);
78
}
79
}
80
```
81
82
Usage:
83
84
```java
85
List<Double> data = Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0);
86
DataSet dataSet = DataSet.create(data);
87
88
// First call performs expensive computation
89
Statistics stats1 = dataSet.computeStatistics(); // Computes
90
91
// Second call returns cached result instantly
92
Statistics stats2 = dataSet.computeStatistics(); // Cached
93
94
System.out.println(stats1 == stats2); // true
95
```
96
97
## Memoized hashCode() and toString()
98
99
You can memoize Object methods for performance:
100
101
```java { .api }
102
@AutoValue
103
public abstract class ComplexObject {
104
public abstract List<String> items();
105
public abstract Map<String, Object> properties();
106
public abstract Set<Integer> values();
107
108
@Memoized
109
@Override
110
public abstract int hashCode();
111
112
@Memoized
113
@Override
114
public abstract String toString();
115
116
public static ComplexObject create(
117
List<String> items,
118
Map<String, Object> properties,
119
Set<Integer> values) {
120
return new AutoValue_ComplexObject(
121
ImmutableList.copyOf(items),
122
ImmutableMap.copyOf(properties),
123
ImmutableSet.copyOf(values));
124
}
125
}
126
```
127
128
Usage:
129
130
```java
131
ComplexObject obj = ComplexObject.create(
132
Arrays.asList("a", "b", "c"),
133
Map.of("key1", "value1", "key2", "value2"),
134
Set.of(1, 2, 3, 4, 5));
135
136
// First calls compute and cache results
137
int hash1 = obj.hashCode(); // Computes
138
String str1 = obj.toString(); // Computes
139
140
// Subsequent calls use cached values
141
int hash2 = obj.hashCode(); // Cached
142
String str2 = obj.toString(); // Cached
143
```
144
145
## Nullable Memoization
146
147
Methods returning nullable values can be memoized if annotated with @Nullable:
148
149
```java { .api }
150
@AutoValue
151
public abstract class Document {
152
public abstract String content();
153
154
@Memoized
155
@Nullable
156
public String extractTitle() {
157
// Expensive regex or parsing operation
158
Pattern titlePattern = Pattern.compile("<title>(.*?)</title>", Pattern.CASE_INSENSITIVE);
159
Matcher matcher = titlePattern.matcher(content());
160
return matcher.find() ? matcher.group(1) : null;
161
}
162
163
@Memoized
164
public Optional<String> extractTitleOptional() {
165
String title = extractTitle();
166
return Optional.ofNullable(title);
167
}
168
169
public static Document create(String content) {
170
return new AutoValue_Document(content);
171
}
172
}
173
```
174
175
Usage:
176
177
```java
178
Document doc = Document.create("<html><title>My Page</title><body>Content</body></html>");
179
180
String title1 = doc.extractTitle(); // Computes "My Page"
181
String title2 = doc.extractTitle(); // Returns cached "My Page"
182
183
Optional<String> titleOpt = doc.extractTitleOptional(); // Uses cached result
184
```
185
186
## Memoization with Parameters
187
188
Memoized methods cannot have parameters. Use property-based memoization instead:
189
190
```java { .api }
191
@AutoValue
192
public abstract class Calculator {
193
public abstract double base();
194
public abstract int exponent();
195
196
@Memoized
197
public double result() {
198
// Expensive computation based on properties
199
return Math.pow(base(), exponent());
200
}
201
202
// Cannot memoize methods with parameters
203
// @Memoized // ERROR: Methods with parameters cannot be memoized
204
// public double power(double base, int exp) { ... }
205
206
public static Calculator create(double base, int exponent) {
207
return new AutoValue_Calculator(base, exponent);
208
}
209
}
210
```
211
212
## Thread Safety
213
214
Memoized methods are thread-safe using double-checked locking:
215
216
```java
217
// Generated code is equivalent to:
218
private volatile String memoizedFullName;
219
220
@Override
221
public String fullName() {
222
if (memoizedFullName == null) {
223
synchronized (this) {
224
if (memoizedFullName == null) {
225
memoizedFullName = super.fullName();
226
}
227
}
228
}
229
return memoizedFullName;
230
}
231
```
232
233
This ensures:
234
- Only one thread computes the value
235
- No race conditions
236
- Efficient access after first computation
237
- Proper memory visibility guarantees
238
239
## Memoization with Builders
240
241
Memoized methods work seamlessly with builders:
242
243
```java { .api }
244
@AutoValue
245
public abstract class Configuration {
246
public abstract String host();
247
public abstract int port();
248
public abstract boolean ssl();
249
250
@Memoized
251
public String connectionString() {
252
String protocol = ssl() ? "https" : "http";
253
return protocol + "://" + host() + ":" + port();
254
}
255
256
@Memoized
257
public URL url() {
258
try {
259
return new URL(connectionString());
260
} catch (MalformedURLException e) {
261
throw new IllegalStateException("Invalid URL: " + connectionString(), e);
262
}
263
}
264
265
public static Builder builder() {
266
return new AutoValue_Configuration.Builder()
267
.ssl(false)
268
.port(80);
269
}
270
271
@AutoValue.Builder
272
public abstract static class Builder {
273
public abstract Builder host(String host);
274
public abstract Builder port(int port);
275
public abstract Builder ssl(boolean ssl);
276
public abstract Configuration build();
277
}
278
}
279
```
280
281
Usage:
282
283
```java
284
Configuration config = Configuration.builder()
285
.host("api.example.com")
286
.port(443)
287
.ssl(true)
288
.build();
289
290
String conn1 = config.connectionString(); // Computes "https://api.example.com:443"
291
URL url1 = config.url(); // Computes URL object
292
URL url2 = config.url(); // Returns cached URL object
293
```
294
295
## Error Handling in Memoized Methods
296
297
Exceptions thrown by memoized methods are not cached:
298
299
```java { .api }
300
@AutoValue
301
public abstract class Parser {
302
public abstract String input();
303
304
@Memoized
305
public JsonNode parseJson() {
306
try {
307
return objectMapper.readTree(input());
308
} catch (JsonProcessingException e) {
309
throw new IllegalArgumentException("Invalid JSON: " + input(), e);
310
}
311
}
312
313
public static Parser create(String input) {
314
return new AutoValue_Parser(input);
315
}
316
}
317
```
318
319
```java
320
Parser parser = Parser.create("invalid json");
321
322
try {
323
parser.parseJson(); // Throws exception
324
} catch (IllegalArgumentException e) {
325
// Exception not cached
326
}
327
328
try {
329
parser.parseJson(); // Throws exception again (not cached)
330
} catch (IllegalArgumentException e) {
331
// Same exception thrown, method re-executed
332
}
333
```
334
335
## Performance Characteristics
336
337
- **First Call**: Synchronization overhead + computation time
338
- **Subsequent Calls**: Single volatile read (very fast)
339
- **Memory**: One field per memoized method storing cached result
340
- **Thread Safety**: Uses double-checked locking pattern
341
- **Null Values**: Properly cached if method annotated with @Nullable
342
343
## Restrictions
344
345
Memoized methods cannot be:
346
- `abstract` (except for `hashCode()` and `toString()`)
347
- `private`
348
- `final`
349
- `static`
350
- Have parameters
351
- Return `void`
352
353
Valid memoized method signature:
354
```java { .api }
355
@Memoized
356
public ReturnType methodName() {
357
// computation
358
return result;
359
}
360
```