@@ -109,14 +109,14 @@ public <T> ObjectConstructor<T> get(TypeToken<T> typeToken, boolean allowUnsafe)
109109 @ SuppressWarnings ("unchecked" ) // types must agree
110110 InstanceCreator <T > typeCreator = (InstanceCreator <T >) instanceCreators .get (type );
111111 if (typeCreator != null ) {
112- return () -> typeCreator . createInstance ( type );
112+ return new InstanceCreatorConstructor <>( typeCreator , type );
113113 }
114114
115115 // Next try raw type match for instance creators
116116 @ SuppressWarnings ("unchecked" ) // types must agree
117117 InstanceCreator <T > rawTypeCreator = (InstanceCreator <T >) instanceCreators .get (rawType );
118118 if (rawTypeCreator != null ) {
119- return () -> rawTypeCreator . createInstance ( type );
119+ return new InstanceCreatorConstructor <>( rawTypeCreator , type );
120120 }
121121
122122 // First consider special constructors before checking for no-args constructors
@@ -143,19 +143,15 @@ public <T> ObjectConstructor<T> get(TypeToken<T> typeToken, boolean allowUnsafe)
143143 // of adjusting filter suggested below is irrelevant since it would not solve the problem
144144 String exceptionMessage = checkInstantiable (rawType );
145145 if (exceptionMessage != null ) {
146- return () -> {
147- throw new JsonIOException (exceptionMessage );
148- };
146+ return new ThrowingObjectConstructor <>(exceptionMessage );
149147 }
150148
151149 if (!allowUnsafe ) {
152150 String message =
153151 "Unable to create instance of "
154152 + rawType
155153 + "; Register an InstanceCreator or a TypeAdapter for this type." ;
156- return () -> {
157- throw new JsonIOException (message );
158- };
154+ return new ThrowingObjectConstructor <>(message );
159155 }
160156
161157 // Consider usage of Unsafe as reflection, so don't use if BLOCK_ALL
@@ -167,9 +163,7 @@ public <T> ObjectConstructor<T> get(TypeToken<T> typeToken, boolean allowUnsafe)
167163 + "; ReflectionAccessFilter does not permit using reflection or Unsafe. Register an"
168164 + " InstanceCreator or a TypeAdapter for this type or adjust the access filter to"
169165 + " allow using reflection." ;
170- return () -> {
171- throw new JsonIOException (message );
172- };
166+ return new ThrowingObjectConstructor <>(message );
173167 }
174168
175169 // finally try unsafe
@@ -191,10 +185,10 @@ private static <T> ObjectConstructor<T> newSpecialCollectionConstructor(
191185 T set = (T ) EnumSet .noneOf ((Class ) elementType );
192186 return set ;
193187 } else {
194- throw new JsonIOException ("Invalid EnumSet type: " + type . toString () );
188+ throw new JsonIOException ("Invalid EnumSet type: " + type );
195189 }
196190 } else {
197- throw new JsonIOException ("Invalid EnumSet type: " + type . toString () );
191+ throw new JsonIOException ("Invalid EnumSet type: " + type );
198192 }
199193 };
200194 }
@@ -209,10 +203,10 @@ else if (rawType == EnumMap.class) {
209203 T map = (T ) new EnumMap ((Class ) elementType );
210204 return map ;
211205 } else {
212- throw new JsonIOException ("Invalid EnumMap type: " + type . toString () );
206+ throw new JsonIOException ("Invalid EnumMap type: " + type );
213207 }
214208 } else {
215- throw new JsonIOException ("Invalid EnumMap type: " + type . toString () );
209+ throw new JsonIOException ("Invalid EnumMap type: " + type );
216210 }
217211 };
218212 }
@@ -250,30 +244,15 @@ private static <T> ObjectConstructor<T> newDefaultConstructor(
250244 + " constructor is not accessible and ReflectionAccessFilter does not permit making"
251245 + " it accessible. Register an InstanceCreator or a TypeAdapter for this type, change"
252246 + " the visibility of the constructor or adjust the access filter." ;
253- return () -> {
254- throw new JsonIOException (message );
255- };
247+ return new ThrowingObjectConstructor <>(message );
256248 }
257249
258250 // Only try to make accessible if allowed; in all other cases checks above should
259251 // have verified that constructor is accessible
260252 if (filterResult == FilterResult .ALLOW ) {
261253 String exceptionMessage = ReflectionHelper .tryMakeAccessible (constructor );
262254 if (exceptionMessage != null ) {
263- /*
264- * Create ObjectConstructor which throws exception.
265- * This keeps backward compatibility (compared to returning `null` which
266- * would then choose another way of creating object).
267- * And it supports types which are only serialized but not deserialized
268- * (compared to directly throwing exception here), e.g. when runtime type
269- * of object is inaccessible, but compile-time type is accessible.
270- */
271- return () -> {
272- // New exception is created every time to avoid keeping reference
273- // to exception with potentially long stack trace, causing a
274- // memory leak
275- throw new JsonIOException (exceptionMessage );
276- };
255+ return new ThrowingObjectConstructor <>(exceptionMessage );
277256 }
278257 }
279258
@@ -333,24 +312,24 @@ private static <T> ObjectConstructor<T> newDefaultImplementationConstructor(
333312 return null ;
334313 }
335314
336- private static ObjectConstructor <? extends Collection <? extends Object >> newCollectionConstructor (
315+ private static ObjectConstructor <? extends Collection <?>> newCollectionConstructor (
337316 Class <?> rawType ) {
338317
339318 // First try List implementation
340319 if (rawType .isAssignableFrom (ArrayList .class )) {
341- return () -> new ArrayList <>() ;
320+ return ArrayList :: new ;
342321 }
343322 // Then try Set implementation
344323 else if (rawType .isAssignableFrom (LinkedHashSet .class )) {
345- return () -> new LinkedHashSet <>() ;
324+ return LinkedHashSet :: new ;
346325 }
347326 // Then try SortedSet / NavigableSet implementation
348327 else if (rawType .isAssignableFrom (TreeSet .class )) {
349- return () -> new TreeSet <>() ;
328+ return TreeSet :: new ;
350329 }
351330 // Then try Queue implementation
352331 else if (rawType .isAssignableFrom (ArrayDeque .class )) {
353- return () -> new ArrayDeque <>() ;
332+ return ArrayDeque :: new ;
354333 }
355334
356335 // Was unable to create matching Collection constructor
@@ -370,29 +349,32 @@ private static boolean hasStringKeyType(Type mapType) {
370349 return GsonTypes .getRawType (typeArguments [0 ]) == String .class ;
371350 }
372351
373- private static ObjectConstructor <? extends Map <? extends Object , Object >> newMapConstructor (
352+ private static ObjectConstructor <? extends Map <?, Object >> newMapConstructor (
374353 Type type , Class <?> rawType ) {
375354 // First try Map implementation
376355 /*
377356 * Legacy special casing for Map<String, ...> to avoid DoS from colliding String hashCode
378357 * values for older JDKs; use own LinkedTreeMap<String, Object> instead
379358 */
380359 if (rawType .isAssignableFrom (LinkedTreeMap .class ) && hasStringKeyType (type )) {
360+ // Must use lambda instead of method reference (`LinkedTreeMap::new`) here, otherwise this
361+ // causes an exception when Gson is used by a custom system class loader, see
362+ // https://github.com/google/gson/pull/2864#issuecomment-3528623716
381363 return () -> new LinkedTreeMap <>();
382364 } else if (rawType .isAssignableFrom (LinkedHashMap .class )) {
383- return () -> new LinkedHashMap <>() ;
365+ return LinkedHashMap :: new ;
384366 }
385367 // Then try SortedMap / NavigableMap implementation
386368 else if (rawType .isAssignableFrom (TreeMap .class )) {
387- return () -> new TreeMap <>() ;
369+ return TreeMap :: new ;
388370 }
389371 // Then try ConcurrentMap implementation
390372 else if (rawType .isAssignableFrom (ConcurrentHashMap .class )) {
391- return () -> new ConcurrentHashMap <>() ;
373+ return ConcurrentHashMap :: new ;
392374 }
393375 // Then try ConcurrentNavigableMap implementation
394376 else if (rawType .isAssignableFrom (ConcurrentSkipListMap .class )) {
395- return () -> new ConcurrentSkipListMap <>() ;
377+ return ConcurrentSkipListMap :: new ;
396378 }
397379
398380 // Was unable to create matching Map constructor
@@ -431,17 +413,53 @@ private <T> ObjectConstructor<T> newUnsafeAllocator(Class<? super T> rawType) {
431413 " Or adjust your R8 configuration to keep the no-args constructor of the class." ;
432414 }
433415
434- // Separate effectively final variable to allow usage in the lambda below
435- String exceptionMessageF = exceptionMessage ;
436-
437- return () -> {
438- throw new JsonIOException (exceptionMessageF );
439- };
416+ return new ThrowingObjectConstructor <>(exceptionMessage );
440417 }
441418 }
442419
443420 @ Override
444421 public String toString () {
445422 return instanceCreators .toString ();
446423 }
424+
425+ /**
426+ * {@link ObjectConstructor} which always throws an exception.
427+ *
428+ * <p>This keeps backward compatibility, compared to using a {@code null} {@code
429+ * ObjectConstructor}, which would then choose another way of creating the object. And it supports
430+ * types which are only serialized but not deserialized (compared to directly throwing an
431+ * exception when the {@code ObjectConstructor} is requested), e.g. when the runtime type of an
432+ * object is inaccessible, but the compile-time type is accessible.
433+ */
434+ private static final class ThrowingObjectConstructor <T > implements ObjectConstructor <T > {
435+ private final String exceptionMessage ;
436+
437+ ThrowingObjectConstructor (String exceptionMessage ) {
438+ this .exceptionMessage = exceptionMessage ;
439+ }
440+
441+ @ Override
442+ public T construct () {
443+ // New exception is created every time to avoid keeping a reference to an exception with
444+ // potentially long stack trace, causing a memory leak
445+ // (which would happen if the exception was already created when the
446+ // `ExceptionObjectConstructor` is created)
447+ throw new JsonIOException (exceptionMessage );
448+ }
449+ }
450+
451+ private static final class InstanceCreatorConstructor <T > implements ObjectConstructor <T > {
452+ private final InstanceCreator <T > instanceCreator ;
453+ private final Type type ;
454+
455+ InstanceCreatorConstructor (InstanceCreator <T > instanceCreator , Type type ) {
456+ this .instanceCreator = instanceCreator ;
457+ this .type = type ;
458+ }
459+
460+ @ Override
461+ public T construct () {
462+ return instanceCreator .createInstance (type );
463+ }
464+ }
447465}
0 commit comments