Proposal: Elvis and Other Null-Safe Operators

Stephen Colebourne scolebourne at joda.org
Sun Mar 1 08:45:22 PST 2009


Elvis and Other Null-Safe Operators for Java
AUTHOR(S):
Stephen Colebourne
primarily written up by Neal Gafter

(Neal Gafter is responsible for the formal write-up[6] of the proposal 
detailed below. However, in private communication he indicated that he 
did not intend to submit it to Project Coin, as indicated in his 
write-up: "[I do] not specifically advocate adding these features to the 
Java programming language.  Rather, this document is offered as an 
example of a language change proposal in a form suitable for 
consideration in the JDK7 small language changes JSR.  Specifically, it 
is more like a specification than a tutorial or sales job.".

As such, this proposal is submitted by myself, thanks to Neal's 
willingness to allow me to reuse his write-up. For the submission, I 
have reworded the advantages/benefits/disadvantages/alternatives 
sections from Neal's original document and added detail to the examples. 
Please see the original[6] to compare Neal's version to mine.)


*OVERVIEW*

FEATURE SUMMARY:
The ?: binary "Elvis" operator results in the value of the 
left-hand-side if it is not null, avoiding evaluation of the 
right-hand-side.  If the left-hand-side is null, the right-hand-side is 
evaluated and is the result.

The ?. null-safe member selection operator provides the same meaning as 
. (member selection), except when the left-hand-side evaluates to null, 
in which case any subexpressions on the right-hand-side are not 
evaluated and the ?. expression yields null.

The ?[] indexing operator operates on a left-hand-side that is an array 
of object type.  If the value of the left-hand operand is null, that is 
the result.  If the left-hand-operand is not null, the index 
subexpression is evaluated and used to index the array, yielding the result.
These three operators always result in a value, not a variable.

NOTE: The Elvis operator could be added on its own without the other two 
operators if deemed necessary/desirable (Elvis is generally considered 
less controversial as far as I can tell).

MAJOR ADVANTAGE:
It is a common occurance in most large systems to find code that checks 
for null. Common cases are to provide a default value instead of null, 
to obtain the result from a nested JavaBean where any of the accessors 
might be null, or to handle auto-unboxing properly. This proposal 
captures these common coding patterns, and as a result makes the code 
clearer and more expresive. Less boilerplate. Clearer business logic.

The result of /not/ handling null properly is a NullPointerException. 
This is such a common mistake amongst developers, that many regular 
internet users (non-developers) are aware of the term 
"NullPointerException", because of its prevalence in production systems.

MAJOR BENEFIT:
Two common coding patterns are simplified - defaulting the value of 
null, and avoiding a NPE on access. The remaining code is focussed more 
tightly on the business logic rather than on the details of coding.

There are also significant benefits in the handling of auto-unboxing, as 
well as switching on enums and the for-each loop. In all three cases, 
the language change added an ability to create a NPE without providing 
an easy and obvious means to avoid it (you have to add an if statement 
and another level of block which entirely defeats the purpose of the 
'convenience' unboxing).

Finally, the proposed code will generally be slightly more performant 
than the code written by hand. This is because each part of the 
expression will only be evaluated once with the proposed change, whereas 
a developer will normally call each part multiple times while checking 
for null.

MAJOR DISADVANTAGE:
Associated costs in documentation, tutorials and overall language size.

The principle perceived disadvantage, however, is that it encourages, 
rather than discourages, the use of null values in APIs. No one is 
disputing that empty arrays or empty collections should be returned from 
APIs rather than nulls, however that is only a small proportion of the 
returned types in any large system. Many large systems consist of large 
numbers of JavaBean type objects which may have null values for many of 
their fields (representing an absence of information, invalid data, 
etc.). In these cases, null is a suitable and valuable value to hold in 
those fields, and is widely used as such. Accessing the resulting data 
for use often requires defaulting the values or handling nulls, and that 
is where this proposal comes in.

To put that another way, if you write low-level APIs, such as the JDK, 
Apache Commons or Google Collections, then this proposal is of little 
value. If your day job involves integrating code from 50 different 
libraries using hundreds of JavaBean style data structures where fields 
can all be null, then this proposal will have a huge impact. Its a 
matter of perspective.

ALTERNATIVES:
It is possible to solve some of the issues using libraries[1]. These 
solutions are not terribly appealing however and would be unlikely to 
make it into the JDK.

The other alternative is, as with any proposal, to make no change. This 
leaves developers to continue to obscure business logic with 
null-handling clutter despite indicating this as their most-wanted 
change in Java[5].


*EXAMPLES*

SIMPLE EXAMPLE:
Standard example:
  String s = mayBeNull?.toString() ?: "null";

Auto-unboxing example:
  Integer ival = ...;  // may be null
  int i = ival ?: -1;  // no NPE from unboxing

ADVANCED EXAMPLE:
Given a Java class

class Group {
   Person[] members; // null if no members
}
class Person {
    String name; // may be null
}
Group g = ...; // may be null

we can compute the name of a member if the group is non-null and 
non-empty and the first member has a known (non-null) name, otherwise 
the string "nobody":

  final String aMember = g?.members?[0]?.name ?: "nobody";

Without this feature, a developer would currently write:

  String aMember = null;
  if (g != null && g.members != null && g.members[0].name != null) {
    aMember = g.members[0].name;
  } else {
    aMember = "nobody";
  }

The proposed version is a lot shorter, clearer, and can even be 
assigneed to a final variable.


*DETAILS*

SPECIFICATION:
Lexical:

We do not add any tokens to the language.  Rather, we introduce new 
operators that are composed of a sequence of existing tokens.

Syntax:

The folllowing new grammar rules are added to the syntax

PrimaryNoNewArray:

NullSafeFieldAccess
NullSafeMethodInvocation
NullSafeClassInstanceCreationExpression
NullSafeArrayAccess

NullSafeFieldAccess:

PrimaryNoNewArray ? . Identifier

NullSafeMethodInvocation:

PrimaryNoNewArray ? . NonWildTypeArgumentsopt Identifier ( ArgumentListopt )

NullSafeClassInstanceCreationExpression:

PrimaryNoNewArray ? . new TypeArgumentsopt Identifier TypeArgumentsopt ( 
ArgumentListopt ) ClassBodyopt

NullSafeArrayAccess:

PrimaryNoNewArray ? [ Expression ]

ConditionalExpression:

ElvisExpression

ElvisExpression:

ConditionalOrExpression ? : ConditionalExpression

Semantics:

A null-safe field access expression e1?.name first evaluates the 
expression e1.  If the result is null, then the null-safe field access 
expression's result is null.  Otherwise, the result is the same as the 
result of the expression e1.name.  In either case, the type of the 
result is the same as the type of e1.name.  It is an error if this is 
not a reference type.

A null-safe method invocation expression e1?.name(args) first evaluates 
the expression e1.  If the result is null, then the null-safe method 
invocation expression's result is null.  Otherwise the arguments are 
evaluated and the result is the same as the result of the invocation 
expression e1.name(args).  In either case, the type of the result is the 
same as the type of e1.name(args).  It is an error if this is not a 
reference type.

A null-safe class instance creation expression e1?.new name(args) first 
evaluates the expression e1.  If the result is null, then the null-safe 
class instance creation expression's result is null.  Otherwise, the 
arguments are evaluated and the result is the same as the result of the 
class instance creation expression e1.new name(args).  In either case, 
the type of the result is the same as the type of e1.new name(args).

A null-safe array access expression e1?[e2] first evaluates the 
expression e1.  If the result is null, then the null-safe array access 
expression's result is null.  Otherwise, e2 is evaluated and the result 
is the same as the result of e1[e2].  In either case, the type of the 
result is the same as the type of e1[e2].  It is an error if this is not 
a reference type.

An Elvis expression e1?:e2 first evaluates the expression e1.  It is an 
error if this is not a reference type.  If the result is non-null, then 
that is the Elvis expression's result.  Otherwise, e2 is evaluated and 
is the result of the Elvis expression.  In either case, the type of the 
result is the same as the type of (e1!=null)?e1:e2.  [Note: this section 
must mention bringing the operands to a common type, for example by 
unboxing when e2 is a primitive, using the same rules as the ternary 
operator]

Exception Analysis:

JLS section 12.2.1 (exception analysis of expressions) is modified to 
read as follows.  Additions are shown in bold.

A method invocation expression or null-safe method invocation expression 
can throw an exception type E iff either:

     * The method to be invoked is of the form Primary.Identifier or 
Primary?.Identifier and the Primary expression can throw E; or
     * Some expression of the argument list can throw E; or
     * E is listed in the throws clause of the type of method that is 
invoked.

A class instance creation expression or null-safe class instance 
creation expression can throw an exception type E iff either:

     * The expression is a qualified class instance creation expression 
or a null-safe class instance creation expression and the qualifying 
expression can throw E; or
     * Some expression of the argument list can throw E; or
     * E is listed in the throws clause of the type of the constructor 
that is invoked; or
     * The class instance creation expression or null-safe class 
instance creation expression includes a ClassBody, and some instnance 
initializer block or instance variable initializer expression in the 
ClassBody can throw E.

For every other kind of expression, the expression can throw type E iff 
one of its immediate subexpressions can throw E.

Definite Assignment:

JLS section 16.1 (definite assignment and expressions) is augmented with 
the following new subsections

16.1.x Null-safe Method Invocation

     * v is definitely assigned after e1?.name(args) iff v is definitely 
assigned after e1.
     * v is definitely unassigned after e1?.name(args) iff v is 
definitely unassigned after args.
     * in an expression of the form e1?.name(args), v is [un]assigned 
before args iff v is [un]assigned after e1.


16.1.x Null-safe Class Instance Creation Expression

     * v is definitely assigned after e1?.new name(args) iff v is 
definitely assigned after e1.
     * v is definitely unassigned after e1?.new name(args) iff v is 
definitely unassigned after args.
     * in an expression of the form e1?.new name(args), v is 
[un]assigned before args iff v is [un]assigned after e1.


16.1.x Null-safe Array Access

     * v is definitely assigned after e1?[e2] iff v is definitely 
assigned after e1.
     * v is definitely unassigned after e1?[e2] iff v is definitely 
unassigned after e2.
     * in an expression of the form e1?[e2], v is [un]assigned before e2 
iff v is [un]assigned after e1.


16.1.x Elvis Operator

     * v is definitely assigned after e1?:e2 iff v is definitely 
assigned after e1.
     * v is definitely unassigned after e1?:e2 iff v is definitely 
unassigned after e2.
     * in an expression of the form e1?:e2, v is [un]assigned before e2 
iff v is [un]assigned after e1.

COMPILATION:
These new expression forms can be desugared as follows:

     * e1?.name is rewritten as (t != null ? t.name : null)
     * e1?.name(args) is rewriten as (t != null ? t.name(args) : null)
     * e1?.new name(args) is rewritten as (t != null ? t.new name(args) 
: null)
     * e1?[e2] is rewritten as (t != null ? t[e2] : null)
     * e1?:e2 is rewritten as (t != null ? t : e2)


where t is a new temporary that holds the computed value of the 
expression e1.

TESTING:
This feature can be tested by exercising the various new expression 
forms, and verifying their correct behavior in erroneous and 
non-erroneous situations, with or without null as the value of the 
left-hand operand, and with respect to definite assignment and exception 
analysis.

LIBRARY SUPPORT:
No library support is required.

REFLECTIVE APIS:
No reflective APIs require any changes.  However, the not-yet-public 
javac Tree APIs, which describe the syntactic structure of Java 
statements and expressions, should be augmented with new tree forms for 
these new expression types.

OTHER CHANGES:
No other platform changes are required.

MIGRATION:
No migration of existing code is recommended.  These new language 
features are mainly to be used in new code.  However, IDEs should 
provide refactoring advice for taking advantage of these new operators 
when existing code uses the corresponding idiom.


*COMPATIBILITY*

BREAKING CHANGES:
No breaking changes are caused by this proposal.

EXISTING PROGRAMS:
Because the changes are purely the introduction of new expression forms, 
there is no impact on the meaning of existing code.


*REFERENCES*

EXISTING BUGS:
4151957: Proposal: null-safe field access operator

URL FOR PROTOTYPE:
No Java prototype exists at this time.  However, Groovy[2] and Fan[3] 
(among others) have the Elvis and null-safe member operators.

OTHER REFERENCES
[1] Stephan Schmidt's discussion of Better Strategies for Null Handling 
in Java - 
http://www.slideshare.net/Stephan.Schmidt/better-strategies-for-null-handling-in-java
[2] Groovy Operators - http://groovy.codehaus.org/Operators
[3] Fan operators - 
http://fandev.org/doc/docLang/Expressions.html#nullConvenience
[4] Stephen Colebourne's brief on null-safe operators - 
http://docs.google.com/View?docid=dfn5297z_3c73gwb
[5] Summary of three recent language change polls showing better null 
handling as a key developer request - 
http://www.jroller.com/scolebourne/entry/jdk_7_language_changes_everyone
[6] The version of this proposal written by Neal Gafter - 
http://docs.google.com/Doc?docid=ddb3zt39_78frdf87dc&hl=en




More information about the coin-dev mailing list