Skip to content

Commit 78d8574

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 78d8574

2 files changed

Lines changed: 226 additions & 0 deletions

File tree

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

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

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)