JLS tweaks

Archie Cobbs archie at dellroad.org
Sun Mar 2 09:28:13 PST 2014


Hi folks,

I'm new to this list so here's a quick introduction... I have worked on
various open source projects for a long time, have used Java extensively in
my day job since the late 90's, and have contributed to a few JVM
implementations over the years (kaffe, harmony, Classpath libs), and even
wrote my own experimental JVM at one point (jcvm).

Over the years I've accumulated a list of unnecessary restrictions in the
JLS that crop up over and over again to annoy me. I'd like to propose JLS
changes that address these restrictions.

This mailing list seems the right place to get feedback and any
recommendations on how to proceed with JEPs if the ideas are deemed
generally useful.

I won't attempt to give a lot of motivation for these examples other than
to say that in my "day job" the goal is clear and maintainable software,
and these problems crop up repeatedly to make that job harder.

More generally, I hope you agree that whether or not a particular issue
affects someone, when you can remove that restriction and it doesn't cost
you anything in terms of semantic clarity or backward compatibility, then
it seems like it should be a worthwhile thing to do. In such cases, the
only factor in the "cost" equation is how hard it would be to implement
these changes in the compiler, and I'd like your input on that. All of
these changes are fully backward-compatible with existing source semantics.

*Idea #1: Allow final index variable in Iterable loops*

This change would allow the index variable of an Iterable loop to be
decared final. The compiler would disallow assignments to the variable
within the loop in that case.

for (final Person p : this.getPeople()) {   // compiler allows this
    p.setUpdated(true);
    p = p.getParent();                      // compiler error
}

*Idea #2: Allow non-this code prior to super()/this() call in constructors*

The JLS requires that constructors invoke super()/this() as the first
*statement*, whereas the JVM only requires constructors invoke
super()/this() prior to any operations on this other than assigning
instance fields.

So there is a "gap" between what the JVM allows and what the JLS allows,
with the JLS being more restrictive than necessary.

For example, one would like to be able to do this:

import java.util.*;
public class IntersectionSet<E> extends HashSet<E> {

    private final boolean wasEmpty;

    public IntersectionSet(Set<? extends E> set1, Set<? extends E> set2) {
        final HashSet<E> temp = new HashSet<E>();
        for (E e : set1) {
            if (set2.contains(e))
                temp.add(e);
        }
        this.wasEmpty = temp.isEmpty();
        super(temp);
    }
}

but instead you have to do this:

import java.util.*;
public class IntersectionSet<E> extends HashSet<E> {

    private final boolean wasEmpty;

    public IntersectionSet(Set<? extends E> set1, Set<? extends E> set2) {
        super(IntersectionSet.intersect(set1, set2));
        this.wasEmpty = IntersectionSet.intersect(set1, set2).isEmpty();
// ugh, redundant calculation!
    }

    // This method only exists because of JLS restriction that "super()"
must be first statement
    private static <E> Set<E> intersect(Set<? extends E> set1, Set<?
extends E> set2) {
        final HashSet<E> temp = new HashSet<E>();
        for (E e : set1) {
            if (set2.contains(e))
                temp.add(e);
        }
        return temp;
    }
}

*Idea #3: Allow generic type declarations wherever normal type declarations
are allowed*

Generic type variable declarations are currently only allowed at the class
level and the method level. This proposal would allow them to be declared
everywhere a normal type can be declared.

For example, imagine we have this type:

import java.util.*;
public abstract class StringEncoder<T> {

    private final Class<T> type;

    protected StringEncoder(Class<T> type) {
        this.type = type;
    }

    public Class<T> getType() {
        return this.type;
    }

    public abstract String toString(T obj);
}

The following code doesn't work due to the unbound generic type:

public static void showEncodings(Collection<? extends StringEncoder<?>>
encoders, Object obj) {
    for (StringEncoder<?> encoder : encoders)
        System.out.println(encoder.toString(encoder.getType().cast(obj)));
           // error: no suitable method found for toString(Object)
}

So instead you have to do this:

public static void showEncodings(Collection<? extends StringEncoder<?>>
encoders, Object obj) {
    for (StringEncoder<?> encoder : encoders)
        show(encoder, obj);
}

// This method only exists so we can bind generic type <X>
private static <X> void show(StringEncoder<X> encoder, Object obj) {
    System.out.println(encoder.toString(encoder.getType().cast(obj)));
}

This proposal would allow you do this:

public static void showEncodings(Collection<? extends StringEncoder<?>>
encoders, Object obj) {
    for (<X> StringEncoder<X> encoder : encoders)
        System.out.println(encoder.toString(encoder.getType().cast(obj)));
}

More generally, any variable declaration in a method could also declare a
generic type variable.

*Idea #4: Teach compiler that an exception means the last assignment
statement was not executed*

The JLS/compiler seems to think that exceptions can occur after the last
statement of a try {} block has executed. For example:

import java.util.Date;
public class Foo {

    public Date example1(boolean setTimestamp) {
        final Date var;
        if (setTimestamp)
            var = new Date();
        else
            var = null;                 // no error here
        return var;
    }

    public Date example2() {
        final Date var;
        try {
            var = new Date();
        } catch (NullPointerException e) {
            var = null;                  // error: variable var might
already have been assigned
        }
        return var;
    }
}

Of course the JVM may choose to randomly throw an exception at any time,
but it seems the compiler can safely avoid giving an error when the
conflicting assignment is the last statement of the try {} block; after
all, the JVM doesn't really care in this case.

*Idea #5: Allow enum types to have generic type parameters*

An example of doing this:

public enum Primitive<T> implements Comparator<T> {
    BOOLEAN<Boolean> {

        @Override
        public Boolean getDefaultValue() {
            return false;
        }

        @Override
        public int compare(Boolean value1, Boolean value2) {
            return Boolean.compare(value1, value2);
        }
    },

    BYTE<Byte> {

        @Override
        public Byte getDefaultValue() {
            return (byte)0;
        }

        @Override
        public int compare(Byte value1, Byte value2) {
            return Byte.compare(value1, value2);
        }
    },

    ... etc ...;

    public abstract T getDefaultValue();
}

Some of these obviously would take more compiler work than others. However,
I want to make sure to keep separate feedback based on practical concerns
(e.g., "too hard to implement") vs. more fundamental language issues that I
may be missing.

Thanks for your time & feedback.

-Archie

-- 
Archie L. Cobbs
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://mail.openjdk.java.net/pipermail/compiler-dev/attachments/20140302/07c461f0/attachment-0001.html 


More information about the compiler-dev mailing list