PROPOSAL: Elvis operator
Stephen Colebourne
scolebourne at joda.org
Sat Mar 21 06:17:18 PDT 2009
I'm re-submitting the Elvis operator as a separate proposal to ensure it
is treated as such. So far I've not heard any arguments against this on
this list, and there are lots of positives.
I've also added significantly to the rationale, examples and references,
including the point that many developers and coding standards avoid the
use of the ternary (requiring if/else), making Elvis a significant
saving in verbosity.
Stephen
-------------------------------------------------------------------
Elvis Operator for Java
AUTHOR(S):
Stephen Colebourne
primarily written up by Neal Gafter
(Neal Gafter is responsible for the formal write-up[5] 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[5] 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.
MAJOR ADVANTAGE:
It is a common occurance in most large systems to find code that checks
for and handles null. As null is an awkward value to process, a common
requirement is to provide a default value instead of null, for example
an empty string or an empty list. The current code for providing a
default value is more verbose than it needs to be. This proposal
provides a simple syntax sugar to ease the verboseness.
The second advantage is to reduce the number of NullPointerExceptions.
These frequently occur late in the development cycle, significantly
slowing delivery. Academic analysis [6] showed that 5% of the bugs found
in the release of Eclipse JDT v3.3 were directly due to NPE.
Better null-handling is the most-wanted change in Java based on
developer polls [1].
MAJOR BENEFIT:
The common coding pattern of checking for null and supplying a default
value is greatly simplified.
Developers need to default for null in two main scenarios. The first is
when handling input from APIs that return null. While this may be
considered by some to be a design flaw in the API, the reality is that
it is extremely common. Keeping it hard to handle the null value doesn't
make it any more likely that the API be changed to stop returning null.
The second is when auto-unboxing. The current auto-unboxing feature is
considered dangerous by some coding shops, who have banned its use as a
result. This is because it can produce NPE from unexpected places. The
addition of the Elvis operator provides a simple way for developers to
handle any potential null value, thus greatly enhancing the value of
auto-unboxing. (Currently, a developer has to write an if statement,
which introduces an extra block which entirely defeats the purpose of
the 'convenience' unboxing).
The simplification of the code necessary to default for null is also
likely to have positive side effects. Because the code is simpler to
write, developers will be more likely to include the defaulting of null.
This will have the benefit of reducing the number of NullPointerExceptions.
The JSR-305/208 project is proposing adding nullable annotations to
Java. The Elvis operator would dovetail nicely with this work, as it
would provide a safe way to convert from a @Nullable to a @NotNull variable.
Finally, the proposed operator will, in certain cases, generally be
slightly more performant and correct code than that written by hand.
This is because the LHS of the expression will only be evaluated once
with the proposed change, whereas a developer will normally evaluate it
twice (ie. consider the case where the LHS is a method call not a simple
variable).
MAJOR DISADVANTAGE:
Associated costs in documentation, tutorials and overall language size.
The principle perceived disadvantage, 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 null value, and that is where this
proposal comes in.
ALTERNATIVES:
Use the ternary expression, as today. However, since many developers and
coding standards argue against the ternary [7], it may be necessary to
handle the defaulting of null in an if/else and 2-8 lines of code
(depending on how you like your braces). Whether using the ternary or an
if/else, the important business logic is hidden by the need to handle
the low-level null issue.
It is possible to solve this issue using a library, such as
Utils.defaultValue(value, valueIfNull). This is still verbose and
intrusive, possibly more so than just writing a ternary expression.
*EXAMPLES*
SIMPLE EXAMPLE:
String s = mayBeNull ?: "null";
whereas, today this is written:
String s = (mayBeNull != null ? mayBeNull : "null");
or (since many developers and coding shops disapprove of the ternary [7]):
String s;
if (mayBeNull != null) {
s = mayBeNull;
} else {
s = "null";
}
Auto-unboxing example:
Integer ival = ...; // may be null
int i = ival ?: -1; // no NPE from unboxing
ADVANCED EXAMPLE:
private Map<String, Integer> hitCounts = ...
public synchronized void countPageHit(String pageName) {
int count = hitCounts.get(pageName) ?: 0;
hitCounts.put(pageName, ++count);
}
Without this feature, a developer would currently write **:
public synchronized void countPageHit(String pageName) {
Integer countVal = hitCounts.get(pageName);
int count = (countVal != null ? countVal : 0);
hitCounts.put(pageName, ++count);
}
or:
public synchronized void countPageHit(String pageName) {
Integer countVal = hitCounts.get(pageName);
if (countVal == null) {
hitCounts.put(pageName, 0);
} else {
hitCounts.put(pageName, ++countVal);
}
}
** In fact I suspect that a fair few developers would forget the null
check at first and just assign to int, resulting in a NPE during testing
*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
ConditionalExpression:
ElvisExpression
ElvisExpression:
ConditionalOrExpression ? : ConditionalExpression
Semantics:
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:
No change
Definite Assignment:
JLS section 16.1 (definite assignment and expressions) is augmented with
the following new subsections
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?: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 new expression form,
verifying the correct behavior in erroneous and non-erroneous
situations, with or without null as the value of the left-hand operand,
with or without primitives, and with respect to definite assignment.
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 a new tree form for
this new expression type.
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 a new expression
form, there is no impact on the meaning of existing code.
*REFERENCES*
EXISTING BUGS:
Related, though not exact matches:
6341875: New for loop should treat null as an empty list
6303028: Conditional operator + autoboxing throws NullPointerException
6212662: Boxing/Unboxing detector for == that will always fail
URL FOR PROTOTYPE:
No Java prototype exists at this time. However, Groovy[2] and Fan[3]
(among others) have the Elvis operator.
OTHER REFERENCES
[1] 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
[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] The version of this proposal written by Neal Gafter -
http://docs.google.com/Doc?docid=ddb3zt39_78frdf87dc&hl=en
[6] Academic analysis of nulls,
http://users.encs.concordia.ca/~chalin/papers/2006-003.v3s-pub.pdf
[7] Avoiding or limiting use of the ternary:
http://users.csc.calpoly.edu/~jdalbey/SWE/code_std.html
http://www.coderanch.com/t/408524/Java-General-beginner/java/Ternary-operator-with-if-elseif
http://www.aptana.com/dev/index.php/Java_Coding_Standard
https://jjguidelines.dev.java.net/book/html/apas04.html
http://qpid.apache.org/java-coding-standards.html
More information about the coin-dev
mailing list