0
# Attribute Customization
1
2
Advanced attribute behavior annotations for flexible object modeling including default values, lazy computation, derived values, parameter configuration, and auxiliary attributes.
3
4
## Capabilities
5
6
### Default Values
7
8
Specify default values for attributes that are optional during construction.
9
10
```java { .api }
11
/**
12
* Annotates accessor that should be turned into settable generated attribute
13
* that is non-mandatory to set via builder. Default value obtained by calling
14
* the annotated method.
15
*/
16
@interface Value.Default { }
17
```
18
19
**Usage Examples:**
20
21
```java
22
@Value.Immutable
23
public interface Server {
24
String host();
25
26
@Value.Default
27
default int port() { return 8080; }
28
29
@Value.Default
30
default boolean ssl() { return false; }
31
32
@Value.Default
33
default List<String> tags() { return List.of(); }
34
}
35
36
// Builder usage - default values are optional
37
Server server1 = ImmutableServer.builder()
38
.host("localhost")
39
.build(); // port=8080, ssl=false, tags=[]
40
41
Server server2 = ImmutableServer.builder()
42
.host("example.com")
43
.port(443)
44
.ssl(true)
45
.addTags("production", "web")
46
.build();
47
```
48
49
### Lazy Attributes
50
51
Thread-safe lazy computation of expensive attribute values.
52
53
```java { .api }
54
/**
55
* Lazy attributes cannot be set during building but are computed lazily
56
* once and only once in a thread-safe manner. Computed from other attributes.
57
* Always act as auxiliary (excluded from equals, hashCode, toString).
58
*/
59
@interface Value.Lazy { }
60
```
61
62
**Usage Examples:**
63
64
```java
65
@Value.Immutable
66
public interface Order {
67
List<Item> items();
68
69
@Value.Lazy
70
default BigDecimal totalCost() {
71
return items().stream()
72
.map(item -> item.price().multiply(BigDecimal.valueOf(item.quantity())))
73
.reduce(BigDecimal.ZERO, BigDecimal::add);
74
}
75
76
@Value.Lazy
77
default String summary() {
78
return String.format("Order with %d items, total: $%.2f",
79
items().size(), totalCost());
80
}
81
}
82
83
// Usage - lazy values computed on first access
84
Order order = ImmutableOrder.builder()
85
.addItems(item1, item2, item3)
86
.build();
87
88
// First access triggers computation and caches result
89
BigDecimal cost = order.totalCost(); // Computes and caches
90
BigDecimal sameCost = order.totalCost(); // Returns cached value
91
```
92
93
### Derived Attributes
94
95
Eagerly computed attributes that are calculated from other attributes during construction.
96
97
```java { .api }
98
/**
99
* Derived attributes cannot be set during building but are eagerly computed
100
* from other attributes and stored in field. Should be applied to non-abstract
101
* method that serves as the attribute value initializer.
102
*/
103
@interface Value.Derived { }
104
```
105
106
**Usage Examples:**
107
108
```java
109
@Value.Immutable
110
public interface Rectangle {
111
double width();
112
double height();
113
114
@Value.Derived
115
default double area() {
116
return width() * height();
117
}
118
119
@Value.Derived
120
default double perimeter() {
121
return 2 * (width() + height());
122
}
123
124
@Value.Derived
125
default String dimensions() {
126
return width() + "x" + height();
127
}
128
}
129
130
// Derived values are computed during construction
131
Rectangle rect = ImmutableRectangle.builder()
132
.width(10.0)
133
.height(5.0)
134
.build();
135
136
// Values are already computed and stored
137
double area = rect.area(); // Returns pre-computed 50.0
138
String dims = rect.dimensions(); // Returns pre-computed "10.0x5.0"
139
```
140
141
### Parameter Configuration
142
143
Control which attributes become constructor parameters and their ordering.
144
145
```java { .api }
146
/**
147
* Mark abstract accessor method to be included as constructor parameter.
148
* For constructable objects, all non-default and non-derived attributes
149
* should be annotated with @Value.Parameter.
150
*/
151
@interface Value.Parameter {
152
/**
153
* Specify order of constructor argument. Defaults to -1 (unspecified).
154
* Arguments are sorted ascending by this order value.
155
*/
156
int order() default -1;
157
158
/**
159
* Whether to include as parameter. Set false to cancel out parameter
160
* (useful with Style.allParameters flag).
161
*/
162
boolean value() default true;
163
}
164
```
165
166
**Usage Examples:**
167
168
```java
169
@Value.Immutable
170
public interface Person {
171
@Value.Parameter(order = 1)
172
String firstName();
173
174
@Value.Parameter(order = 2)
175
String lastName();
176
177
@Value.Parameter(order = 3)
178
int age();
179
180
@Value.Default
181
default String email() { return ""; }
182
}
183
184
// Generated constructor: ImmutablePerson.of(firstName, lastName, age)
185
Person person = ImmutablePerson.of("John", "Doe", 30);
186
187
// Builder still available
188
Person person2 = ImmutablePerson.builder()
189
.firstName("Jane")
190
.lastName("Smith")
191
.age(25)
192
.email("jane@example.com")
193
.build();
194
195
// Exclude from parameters while using allParameters style
196
@Value.Immutable
197
@Value.Style(allParameters = true)
198
public interface Coordinate {
199
double x();
200
double y();
201
202
@Value.Parameter(false) // Exclude from constructor
203
@Value.Default
204
default String label() { return ""; }
205
}
206
207
// Constructor: ImmutableCoordinate.of(x, y) - label excluded
208
```
209
210
### Auxiliary Attributes
211
212
Attributes that are stored and accessible but excluded from equals, hashCode, and toString methods.
213
214
```java { .api }
215
/**
216
* Annotate attribute as auxiliary - will be stored and accessible but
217
* excluded from generated equals(), hashCode() and toString() methods.
218
* Lazy attributes are always auxiliary.
219
*/
220
@interface Value.Auxiliary { }
221
```
222
223
**Usage Examples:**
224
225
```java
226
@Value.Immutable
227
public interface CacheEntry {
228
String key();
229
Object value();
230
231
@Value.Auxiliary
232
long createdTimestamp();
233
234
@Value.Auxiliary
235
int accessCount();
236
237
@Value.Auxiliary
238
@Value.Default
239
default String debugInfo() { return ""; }
240
}
241
242
// Auxiliary attributes don't affect equality
243
CacheEntry entry1 = ImmutableCacheEntry.builder()
244
.key("user:123")
245
.value(userData)
246
.createdTimestamp(System.currentTimeMillis())
247
.accessCount(0)
248
.build();
249
250
CacheEntry entry2 = ImmutableCacheEntry.builder()
251
.key("user:123")
252
.value(userData)
253
.createdTimestamp(System.currentTimeMillis() + 1000) // Different timestamp
254
.accessCount(5) // Different access count
255
.build();
256
257
// These are considered equal (auxiliary attributes ignored)
258
assert entry1.equals(entry2); // true
259
assert entry1.hashCode() == entry2.hashCode(); // true
260
261
// But auxiliary values are still accessible
262
long timestamp = entry1.createdTimestamp();
263
int count = entry1.accessCount();
264
```
265
266
### Attribute Validation
267
268
Mark methods as non-attributes to prevent them from becoming generated attributes.
269
270
```java { .api }
271
/**
272
* Mark some abstract no-argument methods in supertypes as regular,
273
* non-attribute methods. Prevents annotation processor from generating
274
* field, accessor, and builder initializer.
275
*/
276
@interface Value.NonAttribute { }
277
```
278
279
**Usage Examples:**
280
281
```java
282
// Base interface with methods that shouldn't be attributes
283
public interface Validatable {
284
@Value.NonAttribute
285
default boolean isValid() {
286
// Custom validation logic
287
return true;
288
}
289
290
@Value.NonAttribute
291
default List<String> validate() {
292
// Return validation errors
293
return List.of();
294
}
295
}
296
297
@Value.Immutable
298
public interface User extends Validatable {
299
String name();
300
String email();
301
302
// Override validation methods
303
@Override
304
default boolean isValid() {
305
return !name().isEmpty() && email().contains("@");
306
}
307
308
@Override
309
default List<String> validate() {
310
List<String> errors = new ArrayList<>();
311
if (name().isEmpty()) errors.add("Name is required");
312
if (!email().contains("@")) errors.add("Invalid email format");
313
return errors;
314
}
315
}
316
317
// isValid() and validate() are regular methods, not attributes
318
User user = ImmutableUser.builder()
319
.name("John")
320
.email("john@example.com")
321
.build();
322
323
boolean valid = user.isValid(); // Method call, not attribute access
324
List<String> errors = user.validate(); // Method call, not attribute access
325
```