Skip to content

Commit 8601577

Browse files
committed
[LANG-1784] New ObjectUtils.applyIfNotNull methods
These new methods add support for chaining calls to potentially null values, e.g. applyIfNull(person.getName(), String::trim).
1 parent d75fb0b commit 8601577

File tree

2 files changed

+227
-0
lines changed

2 files changed

+227
-0
lines changed

src/main/java/org/apache/commons/lang3/ObjectUtils.java

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.util.Objects;
3131
import java.util.Optional;
3232
import java.util.TreeSet;
33+
import java.util.function.Function;
3334
import java.util.function.Supplier;
3435
import java.util.stream.Stream;
3536

@@ -215,6 +216,199 @@ public static boolean anyNull(final Object... values) {
215216
return !allNotNull(values);
216217
}
217218

219+
/**
220+
* Applies a function to a value if it's not {@code null}. The
221+
* function is only applied if the value is not {@code null},
222+
* otherwise the method returns {@code null} immediately. If the
223+
* value is not {@code null} then the result of the function is
224+
* returned.
225+
*
226+
* <pre>
227+
* ObjectUtils.applyIfNotNull("a", String::toUpperCase) = "A"
228+
* ObjectUtils.applyIfNotNull(null, String::toUpperCase) = null
229+
* ObjectUtils.applyIfNotNull("a", s -&gt; null) = null
230+
* </pre>
231+
*
232+
* Useful when working with expressions that may return {@code null}
233+
* as it allows a single-line expression without making temporary
234+
* local variables or evaluating expressions twice. Provides an
235+
* alternative to using {@link Optional} that is shorter and has
236+
* less allocation.
237+
*
238+
* <pre>
239+
* String name = applyIfNotNull(peopleMap.get(key), Person::getName);
240+
*
241+
* // Alternative - requires local to avoid calling Map.get twice
242+
* Person person = peopleMap.get(key);
243+
* String name = (person != null) ? person.getName() : null;
244+
*
245+
* // Alternative with Optional - idiomatic, but longer and requires
246+
* // allocation
247+
* String name = Optional.ofNullable(peopleMap.get(key))
248+
* .map(Person::getName)
249+
* .orElse(null);
250+
* </pre>
251+
*
252+
* @param <T> The type of the input value.
253+
* @param <R> The type of the returned value.
254+
* @param value The value to apply the function to, may be
255+
* {@code null}.
256+
* @param mapper The function to apply, must not be {@code null}.
257+
* @return The result of the function (which may be {@code null})
258+
* or {@code null} if the input value is {@code null}.
259+
* @since 3.19.0
260+
*/
261+
public static <T, R> R applyIfNotNull(
262+
final T value,
263+
final Function<? super T, ? extends R> mapper) {
264+
Objects.requireNonNull(mapper, "mapper");
265+
if (value == null) {
266+
return null;
267+
}
268+
return mapper.apply(value);
269+
}
270+
271+
/**
272+
* Applies two functions to a value, handling {@code null} at each
273+
* step. The functions are only applied if the previous value is not
274+
* {@code null}, otherwise the method exits early and returns
275+
* {@code null}.
276+
*
277+
* <pre>
278+
* ObjectUtils.applyIfNotNull(" a ", String::toUpperCase, String::trim) = "A"
279+
* ObjectUtils.applyIfNotNull(null, String::toUpperCase, String::trim) = null
280+
* ObjectUtils.applyIfNotNull(" a ", s -&gt; null, String::trim) = null
281+
* ObjectUtils.applyIfNotNull(" a ", String::toUpperCase, s -&gt; null) = null
282+
* </pre>
283+
*
284+
* Useful when working with expressions that may return {@code null}
285+
* as it allows a single-line expression without making temporary
286+
* local variables or evaluating expressions twice. Provides an
287+
* alternative to using {@link Optional} that is shorter and has
288+
* less allocation.
289+
*
290+
* <pre>
291+
* String petName = applyIfNotNull(peopleMap.get(key),
292+
* Person::getPet,
293+
* Pet::getName);
294+
*
295+
* // Alternative - requires locals to avoid calling Map.get or
296+
* // Person.getPet twice
297+
* Person person = peopleMap.get(key);
298+
* Pet pet = (person != null) ? person.getPet() : null;
299+
* String petName = (pet != null) ? pet.getName() : null;
300+
*
301+
* // Alternative with Optional - idiomatic, but longer and requires
302+
* // allocation
303+
* String petName = Optional.ofNullable(peopleMap.get(key))
304+
* .map(Person::getPet)
305+
* .map(Pet::getName)
306+
* .orElse(null);
307+
* </pre>
308+
*
309+
* @param <T> The type of the input value.
310+
* @param <U> The type of the intermediate value.
311+
* @param <R> The type of the returned value.
312+
* @param value The value to apply the functions to, may be {@code null}.
313+
* @param mapper1 The first function to apply, must not be {@code null}.
314+
* @param mapper2 The second function to apply, must not be {@code null}.
315+
* @return The result of the final function (which may be {@code null})
316+
* or {@code null} if the input value or any intermediate value is
317+
* {@code null}.
318+
* @since 3.19.0
319+
*/
320+
public static <T, U, R> R applyIfNotNull(
321+
final T value,
322+
final Function<? super T, ? extends U> mapper1,
323+
final Function<? super U, ? extends R> mapper2) {
324+
Objects.requireNonNull(mapper1, "mapper1");
325+
Objects.requireNonNull(mapper2, "mapper2");
326+
if (value == null) {
327+
return null;
328+
}
329+
final U value1 = mapper1.apply(value);
330+
if (value1 == null) {
331+
return null;
332+
}
333+
return mapper2.apply(value1);
334+
}
335+
336+
/**
337+
* Applies three functions to a value, handling {@code null} at each
338+
* step. The functions are only applied if the previous value is not
339+
* {@code null}, otherwise the method exits early and returns
340+
* {@code null}.
341+
*
342+
* <pre>
343+
* ObjectUtils.applyIfNotNull(" abc ", String::toUpperCase, String::trim, StringUtils::reverse) = "CBA"
344+
* ObjectUtils.applyIfNotNull(null, String::toUpperCase, String::trim, StringUtils::reverse) = null
345+
* ObjectUtils.applyIfNotNull(" abc ", s -&gt; null, String::trim, StringUtils::reverse) = null
346+
* ObjectUtils.applyIfNotNull(" abc ", String::toUpperCase, s -&gt; null, StringUtils::reverse) = null
347+
* ObjectUtils.applyIfNotNull(" abc ", String::toUpperCase, String::trim, s -&gt; null) = null
348+
* </pre>
349+
*
350+
* Useful when working with expressions that may return {@code null}
351+
* as it allows a single-line expression without making temporary
352+
* local variables or evaluating expressions twice. Provides an
353+
* alternative to using {@link Optional} that is shorter and has
354+
* less allocation.
355+
*
356+
* <pre>
357+
* String grandChildName = applyIfNotNull(peopleMap.get(key),
358+
* Person::getChild,
359+
* Person::getChild,
360+
* Person::getName);
361+
*
362+
* // Alternative - requires locals to avoid multiple lookups
363+
* Person person = peopleMap.get(key);
364+
* Person child = (person != null) ? person.getChild() : null;
365+
* Person grandChild = (child != null) ? child.getChild() : null;
366+
* String grandChildName = (grandChild != null) ? grandChild.getName() : null;
367+
*
368+
* // Alternative with Optional - idiomatic, but longer and requires
369+
* // allocation
370+
* String grandChildName = Optional.ofNullable(peopleMap.get(key))
371+
* .map(Person::getChild)
372+
* .map(Person::getChild)
373+
* .map(Person::getName)
374+
* .orElse(null);
375+
* </pre>
376+
*
377+
* @param <T> The type of the input value.
378+
* @param <U> The type of the first intermediate value.
379+
* @param <V> The type of the second intermediate value.
380+
* @param <R> The type of the returned valueg.
381+
* @param value The value to apply the functions to, may be {@code null}.
382+
* @param mapper1 The first function to apply, must not be {@code null}.
383+
* @param mapper2 The second function to apply, must not be {@code null}.
384+
* @param mapper3 The third function to apply, must not be {@code null}.
385+
* @return The result of the final function (which may be {@code null})
386+
* or {@code null} if the input value or any intermediate value is
387+
* {@code null}.
388+
* @since 3.19.0
389+
*/
390+
public static <T, U, V, R> R applyIfNotNull(
391+
final T value,
392+
final Function<? super T, ? extends U> mapper1,
393+
final Function<? super U, ? extends V> mapper2,
394+
final Function<? super V, ? extends R> mapper3) {
395+
Objects.requireNonNull(mapper1, "mapper1");
396+
Objects.requireNonNull(mapper2, "mapper2");
397+
Objects.requireNonNull(mapper3, "mapper3");
398+
if (value == null) {
399+
return null;
400+
}
401+
final U value1 = mapper1.apply(value);
402+
if (value1 == null) {
403+
return null;
404+
}
405+
final V value2 = mapper2.apply(value1);
406+
if (value2 == null) {
407+
return null;
408+
}
409+
return mapper3.apply(value2);
410+
}
411+
218412
/**
219413
* Clones an object.
220414
*

src/test/java/org/apache/commons/lang3/ObjectUtilsTest.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,39 @@ void testAnyNull() {
209209
assertFalse(ObjectUtils.anyNull(FOO, BAR, 1, Boolean.TRUE, new Object(), new Object[]{}));
210210
}
211211

212+
@Test
213+
void testApplyIfNotNull() {
214+
assertEquals("A", ObjectUtils.applyIfNotNull("a", String::toUpperCase));
215+
assertNull(ObjectUtils.applyIfNotNull((String) null, String::toUpperCase));
216+
assertNull(ObjectUtils.applyIfNotNull("a", s -> null));
217+
218+
assertThrows(NullPointerException.class, () -> ObjectUtils.applyIfNotNull("a", null));
219+
}
220+
221+
@Test
222+
void testApplyIfNotNull2() {
223+
assertEquals("A", ObjectUtils.applyIfNotNull(" a ", String::toUpperCase, String::trim));
224+
assertNull(ObjectUtils.applyIfNotNull((String) null, String::toUpperCase, String::trim));
225+
assertNull(ObjectUtils.applyIfNotNull(" a ", s -> null, String::trim));
226+
assertNull(ObjectUtils.applyIfNotNull(" a ", String::toUpperCase, s -> null));
227+
228+
assertThrows(NullPointerException.class, () -> ObjectUtils.applyIfNotNull(" a ", null, String::trim));
229+
assertThrows(NullPointerException.class, () -> ObjectUtils.applyIfNotNull(" a ", String::toUpperCase, null));
230+
}
231+
232+
@Test
233+
void testApplyIfNotNull3() {
234+
assertEquals("CBA", ObjectUtils.applyIfNotNull(" abc ", String::toUpperCase, String::trim, StringUtils::reverse));
235+
assertNull(ObjectUtils.applyIfNotNull((String) null, String::toUpperCase, String::trim, StringUtils::reverse));
236+
assertNull(ObjectUtils.applyIfNotNull(" abc ", s -> null, String::trim, StringUtils::reverse));
237+
assertNull(ObjectUtils.applyIfNotNull(" abc ", String::toUpperCase, s -> null, StringUtils::reverse));
238+
assertNull(ObjectUtils.applyIfNotNull(" abc ", String::toUpperCase, String::trim, s -> null));
239+
240+
assertThrows(NullPointerException.class, () -> ObjectUtils.applyIfNotNull(" abc ", null, String::trim, StringUtils::reverse));
241+
assertThrows(NullPointerException.class, () -> ObjectUtils.applyIfNotNull(" abc ", String::toUpperCase, null, StringUtils::reverse));
242+
assertThrows(NullPointerException.class, () -> ObjectUtils.applyIfNotNull(" abc ", String::toUpperCase, String::trim, null));
243+
}
244+
212245
/**
213246
* Test for {@link ObjectUtils#isArray(Object)}.
214247
*/

0 commit comments

Comments
 (0)