@@ -108,42 +108,53 @@ public class Data {
108
108
* @return magic object instance that represents the "null" value (not Java {@code null})
109
109
* @throws IllegalArgumentException if unable to create a new instance
110
110
*/
111
- public static <T > T nullOf (Class <T > objClass ) {
111
+ public static <T > T nullOf (Class <?> objClass ) {
112
+ // ConcurrentMap.computeIfAbsent is explicitly NOT used in the following logic. The
113
+ // ConcurrentHashMap implementation of that method BLOCKS if the mappingFunction triggers
114
+ // modification of the map which createNullInstance can do depending on the state of class
115
+ // loading.
112
116
Object result = NULL_CACHE .get (objClass );
113
117
if (result == null ) {
114
- synchronized (NULL_CACHE ) {
115
- result = NULL_CACHE .get (objClass );
116
- if (result == null ) {
117
- if (objClass .isArray ()) {
118
- // arrays are special because we need to compute both the dimension and component type
119
- int dims = 0 ;
120
- Class <?> componentType = objClass ;
121
- do {
122
- componentType = componentType .getComponentType ();
123
- dims ++;
124
- } while (componentType .isArray ());
125
- result = Array .newInstance (componentType , new int [dims ]);
126
- } else if (objClass .isEnum ()) {
127
- // enum requires look for constant with @NullValue
128
- FieldInfo fieldInfo = ClassInfo .of (objClass ).getFieldInfo (null );
129
- Preconditions .checkNotNull (
130
- fieldInfo , "enum missing constant with @NullValue annotation: %s" , objClass );
131
- @ SuppressWarnings ({"unchecked" , "rawtypes" })
132
- Enum e = fieldInfo .<Enum >enumValue ();
133
- result = e ;
134
- } else {
135
- // other classes are simpler
136
- result = Types .newInstance (objClass );
137
- }
138
- NULL_CACHE .put (objClass , result );
139
- }
118
+ // If nullOf is called concurrently for the same class createNullInstance may be executed
119
+ // multiple times. However putIfAbsent ensures that no matter what the concurrent access
120
+ // pattern looks like callers always get a singleton instance returned. Since
121
+ // createNullInstance has no side-effects beyond triggering class loading this multiple-call
122
+ // pattern is safe.
123
+ Object newValue = createNullInstance (objClass );
124
+ result = NULL_CACHE .putIfAbsent (objClass , newValue );
125
+ if (result == null ) {
126
+ result = newValue ;
140
127
}
141
128
}
142
129
@ SuppressWarnings ("unchecked" )
143
130
T tResult = (T ) result ;
144
131
return tResult ;
145
132
}
146
133
134
+ private static Object createNullInstance (Class <?> objClass ) {
135
+ if (objClass .isArray ()) {
136
+ // arrays are special because we need to compute both the dimension and component type
137
+ int dims = 0 ;
138
+ Class <?> componentType = objClass ;
139
+ do {
140
+ componentType = componentType .getComponentType ();
141
+ dims ++;
142
+ } while (componentType .isArray ());
143
+ return Array .newInstance (componentType , new int [dims ]);
144
+ }
145
+ if (objClass .isEnum ()) {
146
+ // enum requires look for constant with @NullValue
147
+ FieldInfo fieldInfo = ClassInfo .of (objClass ).getFieldInfo (null );
148
+ Preconditions .checkNotNull (
149
+ fieldInfo , "enum missing constant with @NullValue annotation: %s" , objClass );
150
+ @ SuppressWarnings ({"unchecked" , "rawtypes" })
151
+ Enum e = fieldInfo .<Enum >enumValue ();
152
+ return e ;
153
+ }
154
+ // other classes are simpler
155
+ return Types .newInstance (objClass );
156
+ }
157
+
147
158
/**
148
159
* Returns whether the given object is the magic object that represents the null value of its
149
160
* class.
0 commit comments