or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

annotation-generation.mdbuilders.mdextensions.mdindex.mdmemoization.mdpretty-strings.mdserialization.mdstandalone-builders.mdtagged-unions.mdvalue-classes.md

memoization.mddocs/

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

```