ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


Readable Java 1.5

by Stephen Jungels
09/24/2003

The Java 1.5 proposal offers programmers a false choice between desirable new features and readability. In fact, all of the proposed new features for 1.5 can be represented by clear, unambiguous, and readable constructs without breaking backwards compatibility. In the rest of this article I will illustrate this point by describing three alternative syntaxes I am proposing.

New for Loop

Because of frequent requests for a more compact loop construct that works with collections and arrays, Sun proposes a new for loop that has foreach semantics. Using an example from the Joshua Bloch interview, Sun wants to replace this:

void cancelAll(Collection c) { 
    for (Iterator i = c.iterator(); i.hasNext(); ) {
        TimerTask tt = (TimerTask) i.next();
        tt.cancel();
    }
}

with this:

void cancelAll(Collection c) {
    for (Object o : c)
        ((TimerTask)o).cancel();
}

The collection-iterating syntax is more compact, but the use of a colon to indicate foreach semantics is cryptic. (Is it supposed to resemble a squashed equals sign? The ale must have flowed like water when they came up with that!) There is a belief at Sun that foreach is so commonly used (it is a keyword in Perl, VBScript, Ruby, PHP, and Tcl) that adding a foreach keyword now would break a lot of code that uses it as a method or variable name. This is a strong argument, and I won't question it, since I am proposing a backwards-compatible syntax, but that doesn't mean that all other options are exhausted and we should resort to punctuation.

For example, why not simply add an eachof keyword that turns standard for loops into foreach loops, resulting in code like this?

void cancelAll(Collection c) {
    for (Object o = eachof c)
        ((TimerTask)o).cancel();
}

Unlike foreach, eachof is not used in any of the major scripting languages. For the very small group of people who might have used "eachof" as a method or variable name, there is the javac -source option.

Besides resolving the backwards-compatible keyword issue, this syntax has several other advantages:

This is my proposal. Other people might pick another keyword. The point is that with a little research, it is possible to find safe new keywords; it isn't necessary to resort to punctuation marks to add a backwards-compatible foreach capability to Java.

Generics

The major new feature in Java 1.5 will be generics, which will expand the range of static type checking greatly and take out a lot of the drudgery of working with collections. Some argue that in adding generics, Java should follow the syntax established by C++ and the Standard Template Library, which would mean replacing this:

static void expurgate(Collection c) {
    for (Iterator i = c.iterator(); i.hasNext(); ) {
        String s = (String) i.next();
        if(s.length() == 4)
            i.remove();
    }
}

with this:

static void expurgate(Collection<String> c) {
    for (Iterator<String> i = c.iterator(); i.hasNext(); )
        if (i.next().length() == 4)
            i.remove();
}

However, part of the reason Java has succeeded as well as it has is that James Gosling learned from the mistakes of the C++ project -- when he omitted multiple inheritance of implementation, for example, or when he made all reference types heap-based.

Java should continue this practice by adding the generic types popularized by C++ with cleaner and simplified syntax and semantics.

For example, many programmers seem to prefer a generics syntax with curvy brackets. The Generic Java proposal argues that that isn't possible, because it would lead to confusion between type parameters and value parameters. With a little thought, we can find another solution: put the type parameter in parentheses before the parameterized element, resulting in code like this:

static void expurgate((String) Collection c) {
    for ((String) Iterator i = c.iterator(); i.hasNext(); )
        if (i.next().length() == 4)
            i.remove();
}

It might be said that the parenthesized expressions look too much like casts, but they appear in a different context than casts. Also, given the way Java Generics work (type erasure), it's not misleading for the construct to look like a cast. It can be thought of as a "declaration cast" that specifies in one place what cast the compiler should silently insert whenever extracting an element from the collection.

According to this proposal, the cast operator ((TYPE)) is generalized into a type expression. In assignment context it performs a cast. In declaration context it specifies a generic type parameter. The two usages do not overlap, so the kind of operation being performed is always clear.

It is interesting to note that this syntax closely resembles array declaration. In the following code fragment, for example,

String   []   strings1 = new String [10];
(String) List strings2 = new (String) List;

the List declaration and the array declaration are alike except for the absence of parentheses around the array type declaration. If Java 1.5 adds statically safe arrays, they can be distinguished from standard arrays by adding parentheses, thus unifying array declaration syntax with collection syntax.

To sum up, an inverted generics syntax has several advantages:

The following example combines inverted generics syntax and the eachof operator. Where Sun Java 1.5 would say,

void cancelAll(Collection<TimerTask> c) {
    for (TimerTask task : c)
        task.cancel();
}

the proposed syntax would be:

void cancelAll((Timertask) Collection c) {
    for (TimerTask task = eachof c)
        task.cancel();
}

Variance

The latest proposed feature for Java 1.5 is variance, which fixes some flaws in Java's type system and increases the range of static type checking. An introduction to variance is beyond the scope of this article, so I can only refer the reader to the variance white paper and the variance tutorial included with 1.5 Early Access, and move on to the syntax.

The proposed syntax for variance denotes:

The syntax for statically safe arrays is similar.

I submit that this syntax is confusing, especially for the programmer still trying to grasp the meaning of variance.

Instead of trying to map the types of variance to arithmetic symbols, let's refer back to a basic feature of inheritance: a superclass precedes its subclasses, chronologically. Given that fact, we should look for a way to represent chronology typographically. The idea of "reading order" jumps out right away. In English, at least, we start reading at the top left, and continue down and to the right. A suitable syntax that agrees with these observations is to represent a Number and its superclass ancestors, for example, as "..Number", and to represent a Number and all potential subclasses as "Number..". For clarity, these typeranges can have explicitly closed versions: "Object..Number" and "Number..final". In either case, the relative position of "Number" and the two-dot ellipsis determines the direction in the class hierarchy signified.

Having introduced this construct, it is interesting to look back and notice that it isn't unprecedented. Java and other C-like languages already use this kind of positional notation when distinguishing between preincrement (++i), and postincrement (i++), for example.

Here is an extended code sample from the variance tutorial, translated into my proposed syntax:

public abstract class Shape {
    public abstract void drawOn (Canvas c)
}

public class Line extends Shape {
    public int x, y, x2, y2
    public void drawOn (Canvas c) { ... }
}

public class Polygon extends Shape {
    private (Line) List lines;
    public void drawOn (Canvas c) { ... }
}

public class Canvas {
    public void draw (Shape s) {
        s.drawOn (this)
    }

    // Covariant (read-only) list of shapes
    public void drawAll ((Shape..) List shapes) {
        for (Shape s = eachof shapes)
            s.drawOn (this)
    }
}

...

// copy from covariant list (read-only) to contravariant list (write-only)
public void copy ((Shape..) List from, (..Shape) List to) {
    for (Shape s = eachof from)
        to.add (s);
}

// copy from covariant array (read-only) to contravariant array (write-only)
public void copy ((Shape..)[] from, (..Shape)[] to) {
    int i = 0;
    for (Shape s = eachof from)
        to[i++] = s;
}

This syntax has several advantages.

Conclusion

As I hope this article has shown, there are readable alternatives to the syntax developed by Sun for the new features proposed in Java 1.5 — alternatives that can maintain backwards-compatibility with standard Java.

What do you think? As Java programmers, do we need to compromise readability in order to get desirable new features? Are there realistic alternatives to Sun's proposal? Is the author's proposal readable or awkward? Does it improve on Sun's proposed syntax?

Extended Examples

For comparison purposes, here is an extended example in Sun's proposed 1.5 syntax and the author's variation on that.

Java early access 1.5:

import java.util.LinkedList;
import java.util.Collections;
import static java.lang.Math.*; // import static

class Test {

    // enum
    enum Color { red, green, blue };

    // varargs
    public static void printf(String fmt, Object[] args...) {
        int i = 0;
        // foreach on primitive array
        for (char c : fmt.toCharArray()) {
            if (c == '%')
                System.out.print(args[i++]);
            else
                System.out.print(c);
        }
    }

    public static void main(String[] args) {

        // Integer list
        LinkedList<Integer> xs = new LinkedList<Integer>();
        xs.add(new Integer(0)); xs.add(new Integer(1));
        Integer x = xs.iterator().next();
        Integer mb = Collections.max(xs);

        // string list
        LinkedList<String> ys = new LinkedList<String>();
        ys.add("zero"); ys.add("one");
        String y = ys.iterator().next();

        // string list list
        LinkedList<LinkedList<String>> zss = new LinkedList<LinkedList<String>>();
        zss.add(ys); 
        String z = zss.iterator().next().iterator().next();

        // foreach on a collection
        for (String s : ys)
            System.out.println(s);

        // varargs and boxing
        printf("Addition: % plus % equals %\n", 1, 1, 2);

        // use static import
        printf("sin(PI/12) = %\n", sin(PI/12));

        // use enums
        printf("Colors are %\n", Color.VALUES);
        for ( Color c : Color.VALUES ) {
            // switch on enum
            switch(c) {
                case Color.red:
                    System.out.println("found red.");
                    break;
                case Color.green:
                    System.out.println("found green.");
                     break;
                case Color.blue:
                    System.out.println("found blue.");
                    break;
            }
        }
    }
}

Here it is again with the author's proposed syntax:

import java.util.LinkedList;
import java.util.Collections;
import static java.lang.Math.*; // import static

class Test {

    // enum
    enum Color { red, green, blue };

    // varargs
    public static void printf(String fmt, Object[] args...) {
        int i = 0;
        // foreach on primitive array
        for (char c = eachof fmt.toCharArray()) {
            if (c == '%')
                System.out.print(args[i++]);
            else
                System.out.print(c);
        }
    }

    public static void main(String[] args) {

        // Integer list
        (Integer) LinkedList xs = new LinkedList;
        xs.add(new Integer(0)); xs.add(new Integer(1));
        Integer x = xs.iterator().next();
        Integer mb = Collections.max(xs);

        // string list
        (String) LinkedList ys = new LinkedList;
        ys.add("zero"); ys.add("one");
        String y = ys.iterator().next();

        // string list list
        ((String) LinkedList) LinkedList zss = new LinkedList;
        zss.add(ys); 
        String z = zss.iterator().next().iterator().next();

        // foreach on a collection
        for (String s = eachof ys)
            System.out.println(s);

        // varargs and boxing
        printf("Addition: % plus % equals %\n", 1, 1, 2);

        // use static import
        printf("sin(PI/12) = %\n", sin(PI/12));

        // use enums
        printf("Colors are %\n", Color.VALUES);
        for ( Color c = eachof Color.VALUES ) {
            // switch on enum
            switch(c) {
                case Color.red:
                    System.out.println("found red.");
                    break;
                case Color.green:
                    System.out.println("found green.");
                     break;
                case Color.blue:
                    System.out.println("found blue.");
                    break;
            }
        }
    }
}

References

Stephen Jungels is a computer consultant with over five years of experience, currently specializing in Web applications development.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.