Proposal: Elvis and Other Null-Safe Operators (original) (raw)

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=dfn5297z3c73gwb [5] Summary of three recent language change polls showing better null handling as a key developer request - http://www.jroller.com/scolebourne/entry/jdk7languagechangeseveryone [6] The version of this proposal written by Neal Gafter - http://docs.google.com/Doc?docid=ddb3zt3978frdf87dc&hl=en



More information about the coin-dev mailing list