Null-Safety Operators
Expresso provides powerful null-safety operators that help you write more robust code with elegant handling of null values.
The Problem with Null Values
In traditional Java code, handling null values often requires verbose null checks:
// Traditional null handling in Java
String cityName = null;
if (person != null) {
Address address = person.getAddress();
if (address != null) {
City city = address.getCity();
if (city != null) {
cityName = city.getName();
}
}
}
// Use a default value if null
String displayCity = cityName != null ? cityName : "Unknown";
This leads to deeply nested if-statements and repetitive code.
Null-Safe Property Access Operator (?.
)
Expresso's null-safe property access operator makes null handling simple and concise:
// Null-safe property access
$person?.address?.city?.name
// If any part of the chain is null, the result is null
// instead of throwing a PropertyNotFoundException
The null-safe property access operator:
- Returns null if the target object is null rather than throwing an exception
- Can be chained for multiple property accesses
- Works with any object property
Null-Safe Array Access Operator (?[]
)
For arrays and collections, Expresso provides a null-safe array access operator:
// Null-safe array access
$person?.hobbies?[0]
// Returns null if:
// - The person object is null
// - The hobbies collection/array is null
// - The index is out of bounds
The null-safe array access operator:
- Returns null if the array/list is null rather than throwing an exception
- Returns null if the index is out of bounds
- Can be combined with property access (
$person?.hobbies?[0]?.name
)
Working with Complex Property Paths
Expresso supports complex property paths including array access with subsequent property access:
// Complex property paths with array access work seamlessly
$company.departments[0].name // Access the name of the first department
$company.departments[1].employees[0].title // Access the title of the first employee in the second department
// You can also use null-safe operators for complex paths
$company?.departments?[0]?.employees?[0]?.skills?[0] ?? 'No skill'
When working with complex property paths, particularly those that involve arrays and deeply nested structures:
- Use null-safe operators (
?.
and?[]
) when there's any chance that a value in the path might be null - Provide default values using the null coalescing operator (
??
) for important values - Consider breaking very complex paths into multiple expressions for better readability and error handling
Null Coalescing Operator (??
)
The null coalescing operator allows you to provide a default value when an expression evaluates to null:
// Null coalescing operator
$person?.name ?? 'Unnamed'
// If $person?.name is null, 'Unnamed' is returned
The null coalescing operator:
- Takes a left expression and a right expression
- Returns the left expression's value if it's not null
- Returns the right expression's value if the left expression is null
Null Handling in Logical Operations
Expresso treats null values as falsy in logical operations, making it easy to write clean conditional expressions:
// Null values are treated as false in logical operations
$nullVariable && $trueValue // => false
$nullVariable || $trueValue // => true
$nullVariable || $falseValue // => false
!$nullVariable // => true
// This works with properties too
$person?.address?.isValid && $otherCondition
$person?.manager?.isActive || $fallbackCondition
// And with null-safe property access
$data?.user?.profile?.isActive ?? false
Expresso follows these "truthiness" rules:
null
is falsy (equivalent tofalse
in logical operations)Boolean
values behave as expected (true
is truthy,false
is falsy)- All other non-null values are truthy
This allows for concise expressions that gracefully handle null values without throwing exceptions.
Combined Usage
These operators are particularly powerful when combined:
// Combining null-safe property access, array access, and null coalescing
$person?.address?.city ?? 'Unknown City'
$person?.hobbies?[0] ?? 'No Hobby'
// With complex paths including array access
$orders?[0]?.items?[0]?.productName ?? 'No Product'
// Combining with logical operations
$user?.profile?.isVerified && $user?.subscription?.isActive
$product?.inStock || $product?.backorderAvailable
Benefits
Using these null-safety operators offers several advantages:
- Concise code: Less boilerplate and fewer lines of code
- More readable: Clear intention to handle null values safely
- Less error-prone: Eliminates common errors like NullPointerException
- Expressive: Makes the code's intent clear and direct
Examples
Basic Property Access
// Person with null address
Person personWithNullAddress = new Person("Alice", 25, null, Arrays.asList("reading"));
context.setVariable("person", personWithNullAddress);
// This returns null instead of throwing an exception
Object result = evaluator.evaluate("$person?.address?.city", context);
// result == null
// With a default value
String city = (String) evaluator.evaluate("$person?.address?.city ?? 'Unknown'", context);
// city == "Unknown"
Array Access
// Person with null hobbies
Person personWithNullHobbies = new Person("Charlie", 35,
new Address("London", "UK", 10000), null);
context.setVariable("person", personWithNullHobbies);
// This returns null instead of throwing an exception
Object hobby = evaluator.evaluate("$person?.hobbies?[0]", context);
// hobby == null
// With a default value
String firstHobby = (String) evaluator.evaluate("$person?.hobbies?[0] ?? 'Unknown'", context);
// firstHobby == "Unknown"
Chained Operations
// Complex expressions with array access work reliably
String result = evaluator.evaluate("$user?.orders?[0]?.items?[0]?.product?.name ?? 'No Product'", context);
// You can also break complex expressions into steps for clarity
context.setVariable("firstOrder", evaluator.evaluate("$user?.orders?[0]", context));
context.setVariable("firstItem", evaluator.evaluate("$firstOrder?.items?[0]", context));
String productName = (String) evaluator.evaluate("$firstItem?.product?.name ?? 'No Product'", context);
Logical Operations with Null
// Using null in logical operations
context.setVariable("user", null);
context.setVariable("hasAccess", true);
// Returns true because null || true => true
Boolean result1 = (Boolean) evaluator.evaluate("$user?.isAdmin || $hasAccess", context);
// Returns false because null && true => false
Boolean result2 = (Boolean) evaluator.evaluate("$user?.isActive && $hasAccess", context);