0
# Memoization Utilities
1
2
Thread-safe memoization utilities for caching expensive computations with configurable eviction policies and resource management. This package provides lazy evaluation with automatic caching to improve performance of repeated operations.
3
4
## Capabilities
5
6
### Basic Memoization
7
8
Thread-safe memoization for expensive computations with lazy evaluation and automatic caching.
9
10
```java { .api }
11
/**
12
* Thread-safe memoization supplier with lazy evaluation
13
* @param <S> Type of the cached value
14
*/
15
public interface Memoize<S> extends Supplier<S> {
16
/**
17
* Create a memoizing supplier that caches the result of the given supplier
18
* @param supplier The supplier to memoize
19
* @return Memoize instance that caches the supplier's result
20
*/
21
static <T> Memoize<T> supplier(Supplier<? extends T> supplier);
22
23
/**
24
* Create a memoizing function application with a fixed argument
25
* @param function The function to memoize
26
* @param argument The fixed argument to apply
27
* @return Memoize instance that caches the function result
28
*/
29
static <T, R> Memoize<R> supplier(Function<? super T, ? extends R> function, T argument);
30
31
/**
32
* Get the cached value, computing it if necessary
33
* This method is thread-safe and will only compute the value once
34
* @return The cached value
35
*/
36
S get();
37
38
/**
39
* Peek at the cached value without triggering computation
40
* @return The cached value, or null if not yet computed
41
*/
42
S peek();
43
44
/**
45
* Check if the value has been computed and cached
46
* @return true if the value is cached, false if it would need to be computed
47
*/
48
boolean isPresent();
49
50
/**
51
* Transform the cached value using the given function
52
* The transformation is itself memoized
53
* @param mapper Function to transform the cached value
54
* @return new Memoize instance with the transformed value
55
*/
56
<R> Memoize<R> map(Function<? super S, ? extends R> mapper);
57
58
/**
59
* FlatMap operation for chaining memoized computations
60
* @param mapper Function that returns another Supplier
61
* @return new Memoize instance from the flattened result
62
*/
63
<R> Memoize<R> flatMap(Function<? super S, ? extends Supplier<? extends R>> mapper);
64
65
/**
66
* Filter the cached value with a predicate
67
* @param predicate Test to apply to the cached value
68
* @return Memoize that returns the value only if predicate passes, null otherwise
69
*/
70
Memoize<S> filter(Predicate<? super S> predicate);
71
72
/**
73
* Consume the cached value if present
74
* @param consumer Consumer to apply to the cached value
75
* @return this instance for method chaining
76
*/
77
Memoize<S> accept(Consumer<? super S> consumer);
78
79
/**
80
* Execute a consumer on the cached value if it's present
81
* @param consumer Consumer to execute
82
* @return this instance for method chaining
83
*/
84
Memoize<S> ifPresent(Consumer<? super S> consumer);
85
}
86
```
87
88
**Usage Example:**
89
90
```java
91
import aQute.bnd.memoize.Memoize;
92
import java.util.concurrent.TimeUnit;
93
94
// Create a memoized expensive computation
95
Memoize<String> expensiveComputation = Memoize.supplier(() -> {
96
System.out.println("Computing expensive result...");
97
// Simulate expensive work
98
try {
99
Thread.sleep(1000);
100
} catch (InterruptedException e) {
101
Thread.currentThread().interrupt();
102
throw new RuntimeException(e);
103
}
104
return "Expensive Result";
105
});
106
107
// First call: computes and caches
108
String result1 = expensiveComputation.get(); // Prints "Computing..." and returns result
109
System.out.println(result1);
110
111
// Second call: returns cached result immediately
112
String result2 = expensiveComputation.get(); // No computation, immediate return
113
System.out.println(result2);
114
115
// Check if computed without triggering computation
116
if (expensiveComputation.isPresent()) {
117
System.out.println("Value is cached");
118
}
119
120
// Peek at cached value
121
expensiveComputation.peek().ifPresent(System.out::println);
122
```
123
124
### Refreshing Memoization
125
126
Memoization with automatic cache expiration after a specified time period.
127
128
```java { .api }
129
/**
130
* Create a refreshing memoizing supplier that expires cached values after a time period
131
* @param supplier The supplier to memoize
132
* @param time_to_live Duration after which cached value expires
133
* @param unit Time unit for the duration
134
* @return Memoize instance that refreshes after the specified time
135
*/
136
static <T> Memoize<T> refreshingSupplier(Supplier<? extends T> supplier, long time_to_live, TimeUnit unit);
137
```
138
139
**Usage Example:**
140
141
```java
142
// Cache that refreshes every 5 minutes
143
Memoize<String> refreshingCache = Memoize.refreshingSupplier(
144
() -> fetchDataFromAPI(),
145
5, TimeUnit.MINUTES
146
);
147
148
// First call within 5 minutes - computes and caches
149
String data1 = refreshingCache.get();
150
151
// Call within 5 minutes - returns cached data
152
String data2 = refreshingCache.get(); // Same as data1
153
154
// After 5 minutes - recomputes and caches new result
155
Thread.sleep(TimeUnit.MINUTES.toMillis(6));
156
String data3 = refreshingCache.get(); // Fresh computation
157
```
158
159
### Reference-based Memoization
160
161
Memoization using weak or soft references for automatic garbage collection of cached values.
162
163
```java { .api }
164
/**
165
* Create a reference-based memoizing supplier using the specified reference type
166
* @param supplier The supplier to memoize
167
* @param referenceFunction Function to create the reference type (WeakReference, SoftReference, etc.)
168
* @return Memoize instance using reference-based caching
169
*/
170
static <T> Memoize<T> referenceSupplier(
171
Supplier<? extends T> supplier,
172
Function<? super T, ? extends Reference<? extends T>> referenceFunction
173
);
174
```
175
176
**Usage Example:**
177
178
```java
179
import java.lang.ref.WeakReference;
180
import java.lang.ref.SoftReference;
181
182
// Weak reference memoization - value can be GC'd if no strong references exist
183
Memoize<LargeObject> weakMemoize = Memoize.referenceSupplier(
184
() -> new LargeObject(),
185
WeakReference::new
186
);
187
188
// Soft reference memoization - value survives until memory pressure
189
Memoize<LargeObject> softMemoize = Memoize.referenceSupplier(
190
() -> new LargeObject(),
191
SoftReference::new
192
);
193
194
LargeObject obj = softMemoize.get(); // Creates and caches
195
// obj reference keeps it alive even with weak reference
196
LargeObject obj2 = softMemoize.get(); // Returns same cached instance
197
```
198
199
### Predicate-based Memoization
200
201
Memoization that recomputes values when a predicate condition is not met.
202
203
```java { .api }
204
/**
205
* Create a predicate-based memoizing supplier that recomputes when predicate fails
206
* @param supplier The supplier to memoize
207
* @param predicate Predicate to test cached values; if false, value is recomputed
208
* @return Memoize instance that validates cached values with the predicate
209
*/
210
static <T> Memoize<T> predicateSupplier(Supplier<? extends T> supplier, Predicate<? super T> predicate);
211
```
212
213
**Usage Example:**
214
215
```java
216
// Cache that recomputes if cached value is null or empty
217
Memoize<String> validatingCache = Memoize.predicateSupplier(
218
() -> fetchConfigValue(),
219
value -> value != null && !value.isEmpty()
220
);
221
222
String config = validatingCache.get(); // Computes initially
223
String config2 = validatingCache.get(); // Returns cached if valid
224
225
// If fetchConfigValue() later returns null/empty, it will recompute
226
```
227
228
### Closeable Memoization
229
230
Specialized memoization for AutoCloseable resources with proper resource management.
231
232
```java { .api }
233
/**
234
* Memoization for AutoCloseable resources with automatic resource management
235
* @param <S> Type of the cached resource (must extend AutoCloseable)
236
*/
237
public interface CloseableMemoize<S extends AutoCloseable> extends Memoize<S>, AutoCloseable {
238
/**
239
* Create a memoizing supplier for closeable resources
240
* @param supplier Supplier that creates AutoCloseable resources
241
* @return CloseableMemoize instance that manages resource lifecycle
242
*/
243
static <T extends AutoCloseable> CloseableMemoize<T> closeableSupplier(Supplier<? extends T> supplier);
244
245
/**
246
* Check if the cached resource has been closed
247
* @return true if closed, false if open or not yet created
248
*/
249
boolean isClosed();
250
251
/**
252
* Get the cached resource, creating it if necessary
253
* @return The cached AutoCloseable resource
254
* @throws IllegalStateException if this CloseableMemoize has been closed
255
*/
256
S get();
257
258
/**
259
* Consume the cached resource if it exists and is not closed
260
* @param consumer Consumer to apply to the resource
261
*/
262
void accept(Consumer<? super S> consumer);
263
264
/**
265
* Execute a consumer on the cached resource if it exists and is not closed
266
* @param consumer Consumer to execute
267
*/
268
void ifPresent(Consumer<? super S> consumer);
269
270
/**
271
* Close the cached resource if it exists
272
* Subsequent calls to get() will throw IllegalStateException
273
*/
274
void close();
275
}
276
```
277
278
**Usage Example:**
279
280
```java
281
import aQute.bnd.memoize.CloseableMemoize;
282
import java.io.FileInputStream;
283
import java.io.IOException;
284
285
// Memoize a file input stream
286
CloseableMemoize<FileInputStream> fileStreamMemoize = CloseableMemoize.closeableSupplier(
287
() -> {
288
try {
289
return new FileInputStream("config.properties");
290
} catch (IOException e) {
291
throw new RuntimeException(e);
292
}
293
}
294
);
295
296
try (CloseableMemoize<FileInputStream> resource = fileStreamMemoize) {
297
// First access - creates and caches the FileInputStream
298
FileInputStream stream1 = resource.get();
299
// Process stream...
300
301
// Second access - returns the same cached FileInputStream
302
FileInputStream stream2 = resource.get(); // Same instance as stream1
303
304
// Use resource if present
305
resource.ifPresent(stream -> {
306
// Process the stream safely, only if it exists and is not closed
307
});
308
309
} // Automatically closes the cached FileInputStream
310
311
// Subsequent calls will fail
312
try {
313
fileStreamMemoize.get(); // Throws IllegalStateException
314
} catch (IllegalStateException e) {
315
System.out.println("Resource has been closed");
316
}
317
```
318
319
### Functional Operations
320
321
Chain and transform memoized values using functional programming patterns.
322
323
```java { .api }
324
// Map transformations
325
Memoize<String> stringSupplier = Memoize.supplier(() -> "hello");
326
Memoize<String> upperCase = stringSupplier.map(String::toUpperCase);
327
Memoize<Integer> length = stringSupplier.map(String::length);
328
329
// Filter operations
330
Memoize<String> nonEmptyString = Memoize.supplier(() -> getStringValue())
331
.filter(s -> !s.isEmpty());
332
333
Optional<String> value = nonEmptyString.get(); // Optional.empty() if string is empty
334
335
// FlatMap for chaining computations
336
Memoize<String> config = Memoize.supplier(() -> loadConfig());
337
Memoize<Database> database = config.flatMap(cfg -> Memoize.supplier(() -> connectToDatabase(cfg)));
338
```
339
340
## Thread Safety
341
342
All Memoize implementations are thread-safe:
343
344
- Multiple threads can safely call `get()` concurrently
345
- Computation happens exactly once even under concurrent access
346
- Memory visibility guarantees ensure all threads see the cached result
347
- No external synchronization is required
348
349
## Performance Characteristics
350
351
- **First Access**: Full computation cost plus small memoization overhead
352
- **Subsequent Accesses**: Near-zero cost (simple field access)
353
- **Memory Overhead**: One additional object per memoized supplier
354
- **Thread Contention**: Minimal - uses efficient double-checked locking pattern
355
356
## Best Practices
357
358
1. **Use for Expensive Operations**: Memoize computations that are costly in time or resources
359
2. **Consider Memory Usage**: Cached values remain in memory until the Memoize instance is GC'd
360
3. **Use CloseableMemoize**: For resources that need cleanup
361
4. **Reference-based Caching**: For large objects that should be GC-eligible
362
5. **Refreshing Caches**: For data that becomes stale over time