Proposal: Elvis and Other Null-Safe Operators
Neal Gafter
neal at gafter.com
Sun Mar 1 09:05:20 PST 2009
Stephen-
One small nit: In the aMember example of how things have to be done
today, the variable aMember could be declared as a blank final.
Regards,
Neal
On Sun, Mar 1, 2009 at 8:45 AM, Stephen Colebourne <scolebourne at joda.org> wrote:
> 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