H
Harold Yarmouth
There's probably plenty of these floating around already, but here's
bits of mine:
* Sort-of structural typing. Any object can be used where an interface
type is expected if it has the right set of method signatures, whether
or not it "implements" the interface using that keyword. (It still must
to inherit constants defined in the interface, and methodless interfaces
would not get this behavior else everything becomes, among other things,
serializable!)
* Allow extending final classes, subject to the constraint that new
instance fields cannot be defined and none of the final class's methods
can be overriden regardless of whether the method is explicitly "final".
Essentially allows wrapping final classes.
* Add an unsigned byte type.
* Add some way to specify that something should never be null. Perhaps a
! or a * before or after the variable name or type name.
* Allow omitting the names of unused parameters. Common cases would include:
public void ActionPerformed (ActionEvent) {
foo.setVisible(false);
bar.purge();
}
catch (IOException) {
return false;
}
* Constructor type inference. Also allow an alternate constructor
syntax that doesn't repeat the class name, say using * in its place.
So we can avoid some cases of repeating type names, assign
Map<K, V> myMap = new HashMap(), and perhaps also do e.g.
ConcreteType x = new(argument) where ConcreteType has a suitable
public one-argument constructor.
* Disallow public constructors on abstract classes. They are not
callable by any more code than a protected constructor anyway.
* When a type has a one-argument plus() method, + can be used when the
left hand operand is of that type and the right hand operand is of an
argument type for which the former has a one-argument plus() method.
This + binds with the usual precedence for binary + and x + y is
simply an alternate way of saying x.plus(y). Similarly .times and
some others. <= and >= and < and > use compareTo. === and !== get
added and use .equals, and bind with the same precedence as == and !=.
x + y * z is converted by the compiler to the same bytecodes as
x.plus(y.times(z)) and x * y + z to x.times(y).plus(z). += and the
like combine assignment with addition. ++ does .plus(1) and --
.plus(-1). Unary - does .negated() and x - y is x.plus(y.negated()).
For consistency's sake a .plus method is added to String that returns
a concatenation. None of the +=, *= etc. change objects, only
reassigning references; String s; ... s += t is legal and reassigns
the reference s to a new string object. Compiler remains free to
optimize string accumulations to use StringBuilder under the hood,
and programmer remains encouraged to use it explicitly at least in
loops. StringBuilder also gets a .plus method. Both of these take
Object and use its toString if necessary.
In short, a hygienic form of operator overloading.
* Clean up ImageIO and JAI. A lot of the code in these throws various
RuntimeExceptions in response to seeing something it doesn't like in
file or network data. Really, only checked exceptions (and mainly
IOException) should be thrown in response to seeing something you
don't like in externally-obtained data.
* Inverse to the above, get rid of some awkward checked exceptions or
make them RuntimeExceptions. Culprits include NoSuchAlgorithmException
(usually, the algorithm name is hard-coded and so this exception
signals a bug close to where it is thrown, rather than an erroneous
or unexpected condition the program is expected to cope with in
production) and most others where the usual handling by the coder is
to let the exception kill the thread, catch it and throw new
RuntimeException() or Error(), or catch it and do nothing.
* Dead code in a method should be a warning, not an error.
* Hiding a local variable with a nested-scope local variable should be a
warning, not an error.
* Provide a better way to deal with OutOfMemoryError or low memory in
general, as well as a JVM mode in which a program can use as much
memory as it needs, but the VM will also not greedily take everything
the OS will give it. In this, it may maintain a "load factor" similar
to a HashMap for the entire heap, trying to keep it near 75% full, and
a JVM option to tweak this may exist. Higher gives poorer GC
performance but leaves more RAM for the OS; lower gives better GC
performance but uses more memory; no value throws OOME unless the OS
won't give the Java process memory it needs.
Add System.addLowMemoryListener() or similarly. Listeners are notified
if free memory dips below some threshold, running on a temporary
worker thread and perhaps doing things like flush caches.
* Provide a nicer way to deal with resource release. Possibly have a
Releasable interface added, which specifies a dispose() method and
which would be applied to many objects like streams and JFrames
that already have such a method or a similar method. Methods that
get a non-Swing-container Releasable and don't either return it or
use try...finally to dispose it generate a warning. A do() method
in the same interface takes a Runnable and calls the runnable
then disposes the releasable.
* Especially given the above: a shortcut for new Runnable() { public
void run () { ... }}. Let a brace block appearing where an expression
is expected be equivalent to the above, with the contents of the
brace block in place of the ellipsis (...). Then you can do e.g.
SwingUtilities.invokeLater({foo.setVisible(true);});
Also allow this short form to throw checked exceptions, which it
will be assumed the calling code might throw. (So
SwingUtilities.invokeLater({myOutputStream.write("foo");});
would be treated by the compiler as able to throw IOException,
though in fact it would arise in another thread entirely; on
the other hand, using Runnables as custom control blocks like
the Releasable.do() suggested above would result in the
exception being thrown out of do() and into the code that
physically contains the block. Most invocations of runnables
into separate threads are not with ones that throw unhandled checked
exceptions anyway.
* Allow array literals in expressions other than assignment RHS.
Why the devil is foo(1, "string literal", 3) allowed but
foo(1, {array, literal}, 3) is not, even when foo's second
parameter type is Object?
(The above two can be disambiguated: array literals and statement
blocks are syntactically distinguishable, and array literals will
generally be used where the parameter type is an array type or
Object and runnable literals where the parameter type is Runnable.)
* Have array constructors added to the collection classes:
(with the above as well)
new HashSet({2, 3, 5, 7, 11, 13});
new ArrayList({"foo", "bar", "baz", "quux"});
new HashMap({{"key1", "value1"}, {"key2", "value2"}});
* Add java.lang.Pair<K, V>. Make some existing classes Pair
implementations, e.g. Dimension extends Pair<Integer, Integer>,
possibly also Rectangle extends Pair<Dimension, Dimension>,
Map.Entry<K,V> extends Pair<K,V>, etc.
Two-element array literals allowed where Pair is expected and
produce Pair literals.
* [int x]; defines a final box containing an int, an instance of a
Box<T> with T == Integer, which has methods T get() and void set(T).
One can then do e.g.
[JFrame foo = new JFrame()];
...
SwingUtilities.invokeLater({foo.get().setVisible(true);});
given the above runnable-literal suggestion.
Add a : operator that expands to ".get()." and we have:
SwingUtilities.invokeLater({foo:setVisible(true);});
Almost a closure, there!
The : operator also lets us use WeakReference and the like
more nicely, if we add another touch to handle the case of null.
ref:doSomething()?System.out.println("something was null!");, say.
* In many more cases it might be useful to have a shortcut to
if (x == null) value = this else value = that
so how about more generally allowing x.foo?ifNull -- the ? clause
is executed whenever x is null and the method call when it's not.
Or just allow x?expression1:expression2 for non-Booleans testing
for null.
* Another source of boilerplate code is:
Type t;
try {
t = someExpressionThatMayThrow();
} catch (FooException e) {
...
return/continue/break/throw foo/whatever
}
t.doSomething();
where you don't want to doSomething() if the exception got raised,
doSomething() itself may raise the same exception but it shouldn't be
caught in the same catch, or similarly.
Let an alternate form be:
try {
Type t = someExpressionThatMayThrow();
} continue {
t.doSomething();
} catch (FooException e) {
...
}
which is not shorter, but doesn't break up t's declaration and
assignment. This, among other things, lets t be final, and also
lets the catch fall through without "t may not be assigned!". In
some cases it may save lots of boilerplate code:
Type t = null;
try {
t = someExpressionThatMayThrow();
} catch (FooException e) {
fooNotifier.logError(e.getMessage());
failed = true;
}
if (t != null) {
t.doSomething();
}
Yuck! This occurs a lot in I/O code though, usually with Type
actually InputStream or one of its close relatives. Often we
want to handle failure to open the stream specially versus
a failure once we already have the stream open, though both
throw IOException. Sometimes, for instance, we want to flag
an error in some way if the open fails, but let the IOException
bubble up to the caller if the subsequent I/O fails, as the
latter is "more exceptional" than the former.
The key to the above is to have t remain in scope in the
"continue" block.
(Other ugliness this might relieve includes some cases of nested try
blocks.)
* Full closures. Let anonymous inner classes access nonfinal local
variables of the enclosing scope, by having the compiler put all
such variables used by such inner classes transparently into an
Object[] array rather than directly on the stack, or something of
the sort. (The syntax of using them wouldn't change; only how they
got stored, so they had mutable references on the heap, and thus also
the bytecode generated where they were accessed. The compiler would
also not need to generate type checks when dereferencing this array,
as there'd be no way of getting at it directly in user code.)
Furthermore, let {code} used as an expression rather than a statement
stand for new Object() { public Type1 run(Type2 arg1, Type3 arg2...)
{ code }}. Add "it implements the interface if it is method-compatible
with it" from high up on this list and this gets you Runnable literals
in the void return, no arguments case. Types inferred by what the
expression's type needs to be to fit -- there would need to be a new
type in the system for type-tuples, which would also be useful for
reifying generics and where the <void> tuple represents Runnable,
<void, int> an interface specifying void run (int x), and so on.
Lastly, closures could use "return return x" to throw a
ClosureNonLocalReturnException wrapping an x, and passing a closure
to a method would implicitly wrap the method call in try { ... }
catch (ClosureNonLocalReturnException e) { return e.getWrapped(); }
-- implementing non-local returns. (If the closure "escaped" its
context and then non-local returned, it would manifest in a
RuntimeException: the uncaught ClosureNonLocalReturnException.
The JIT compiler could optimize the generation of nonlocal returns
that proved to be always implicitly caught by eliding generation of
their stack traces. (In practise, each nonlocal return would have
to throw a new anonymous subclass of that exception -- each time
a method was invoked that contained such closures it would cause
the generation of a new anonymous subclass and that class would
be caught by the implicit catch blocks in that invocation of that
method. This would be the conceptual behavior, anyway. In actuality
the generated bytecodes would probably look like those from an
explicit throw of an exception that takes a wrapped value and
an id integer, plus those from an explicit catch clause resembling
if (e.getID() != id) throw e; return e.getWrapped();
The method bytecode would also need to generate this id, and a new
one each call; System.nanoTime() might work on present processors.
An efficient atomic integer incrementor might be needed ultimately,
or an efficient implementation of ThreadLocal, with a bipartite ID
of the thread's identityHashCode and a thread-local incrementing ID.
* Object.identityHashCode().
* Equalizer to go with Comparator, provides equals and hashCode by
proxy, can be used with HashSet and HashMap to provide substitute
key equality and hash functions the way Comparator can provide a
substitute compareTo for TreeSet and TreeMap.
* WeakValueHashMap, LinkedWeakHashMap, and LinkedWeakValueHashMap.
These would all be useful implementing caches where you want the
mapping to go away if the value is no longer in use, but calls
for the value to return the pre-existing object if one existed.
WeakValueHashMap already sort-of exists in one under-the-hood bit
of non-user-reusable Java: the implementation of string interning,
starting whenever they finally got it to GC unused interned strings.
Trying to implement one yourself is a bear, because there's no way
to automatically do something when a reference dies.
* ReferenceQueue.addListener(Reference r) to let you automatically do
something when a reference is enqueued. Or provide a protected method
in Reference that is called when the reference is cleared or is
about to be cleared, so you can e.g. subclass WeakReference, override
this method, and do something specific (such as remove a mapping
from a map).
Closest you can currently get is to use a WeakHashMap of the
same object to
new Object() { protected void finalize() { do.whatever(); }}
and pray.
* Allow subclassing an enum with the subclasses able to REMOVE (not
add!) values. An enum that can be any of X, Y, or Z is a subtype,
rather than a supertype, of one that can be any of X, Y, Z, A, or B
because it is substitutable for the latter but not vice-versa; the
actual created classes would be Super with subclasses Sub, Super.A,
Super.B, Super.X, Super.Y, and Super.Z and subclasses of Sub Sub.X,
Sub.Y, and Sub.Z, with some change to the underlying code to not treat
Sub itself as an enum value for Super and to treat Sub.X, Sub.Y, and
Sub.Z as convertible to their Super counterparts. The former change is
easy: ignore non-leaf subclasses. The latter is also easy, so long as
enum subclasses can't add methods, fields, or similarly.
Might as well also allow creating supertypes, an enum with additional
values that is assignable FROM the original enum but not TO it.
These might be best implemented not as true under-the-hood subclasses
at all, but as parallel types with implicit conversions among them.
bits of mine:
* Sort-of structural typing. Any object can be used where an interface
type is expected if it has the right set of method signatures, whether
or not it "implements" the interface using that keyword. (It still must
to inherit constants defined in the interface, and methodless interfaces
would not get this behavior else everything becomes, among other things,
serializable!)
* Allow extending final classes, subject to the constraint that new
instance fields cannot be defined and none of the final class's methods
can be overriden regardless of whether the method is explicitly "final".
Essentially allows wrapping final classes.
* Add an unsigned byte type.
* Add some way to specify that something should never be null. Perhaps a
! or a * before or after the variable name or type name.
* Allow omitting the names of unused parameters. Common cases would include:
public void ActionPerformed (ActionEvent) {
foo.setVisible(false);
bar.purge();
}
catch (IOException) {
return false;
}
* Constructor type inference. Also allow an alternate constructor
syntax that doesn't repeat the class name, say using * in its place.
So we can avoid some cases of repeating type names, assign
Map<K, V> myMap = new HashMap(), and perhaps also do e.g.
ConcreteType x = new(argument) where ConcreteType has a suitable
public one-argument constructor.
* Disallow public constructors on abstract classes. They are not
callable by any more code than a protected constructor anyway.
* When a type has a one-argument plus() method, + can be used when the
left hand operand is of that type and the right hand operand is of an
argument type for which the former has a one-argument plus() method.
This + binds with the usual precedence for binary + and x + y is
simply an alternate way of saying x.plus(y). Similarly .times and
some others. <= and >= and < and > use compareTo. === and !== get
added and use .equals, and bind with the same precedence as == and !=.
x + y * z is converted by the compiler to the same bytecodes as
x.plus(y.times(z)) and x * y + z to x.times(y).plus(z). += and the
like combine assignment with addition. ++ does .plus(1) and --
.plus(-1). Unary - does .negated() and x - y is x.plus(y.negated()).
For consistency's sake a .plus method is added to String that returns
a concatenation. None of the +=, *= etc. change objects, only
reassigning references; String s; ... s += t is legal and reassigns
the reference s to a new string object. Compiler remains free to
optimize string accumulations to use StringBuilder under the hood,
and programmer remains encouraged to use it explicitly at least in
loops. StringBuilder also gets a .plus method. Both of these take
Object and use its toString if necessary.
In short, a hygienic form of operator overloading.
* Clean up ImageIO and JAI. A lot of the code in these throws various
RuntimeExceptions in response to seeing something it doesn't like in
file or network data. Really, only checked exceptions (and mainly
IOException) should be thrown in response to seeing something you
don't like in externally-obtained data.
* Inverse to the above, get rid of some awkward checked exceptions or
make them RuntimeExceptions. Culprits include NoSuchAlgorithmException
(usually, the algorithm name is hard-coded and so this exception
signals a bug close to where it is thrown, rather than an erroneous
or unexpected condition the program is expected to cope with in
production) and most others where the usual handling by the coder is
to let the exception kill the thread, catch it and throw new
RuntimeException() or Error(), or catch it and do nothing.
* Dead code in a method should be a warning, not an error.
* Hiding a local variable with a nested-scope local variable should be a
warning, not an error.
* Provide a better way to deal with OutOfMemoryError or low memory in
general, as well as a JVM mode in which a program can use as much
memory as it needs, but the VM will also not greedily take everything
the OS will give it. In this, it may maintain a "load factor" similar
to a HashMap for the entire heap, trying to keep it near 75% full, and
a JVM option to tweak this may exist. Higher gives poorer GC
performance but leaves more RAM for the OS; lower gives better GC
performance but uses more memory; no value throws OOME unless the OS
won't give the Java process memory it needs.
Add System.addLowMemoryListener() or similarly. Listeners are notified
if free memory dips below some threshold, running on a temporary
worker thread and perhaps doing things like flush caches.
* Provide a nicer way to deal with resource release. Possibly have a
Releasable interface added, which specifies a dispose() method and
which would be applied to many objects like streams and JFrames
that already have such a method or a similar method. Methods that
get a non-Swing-container Releasable and don't either return it or
use try...finally to dispose it generate a warning. A do() method
in the same interface takes a Runnable and calls the runnable
then disposes the releasable.
* Especially given the above: a shortcut for new Runnable() { public
void run () { ... }}. Let a brace block appearing where an expression
is expected be equivalent to the above, with the contents of the
brace block in place of the ellipsis (...). Then you can do e.g.
SwingUtilities.invokeLater({foo.setVisible(true);});
Also allow this short form to throw checked exceptions, which it
will be assumed the calling code might throw. (So
SwingUtilities.invokeLater({myOutputStream.write("foo");});
would be treated by the compiler as able to throw IOException,
though in fact it would arise in another thread entirely; on
the other hand, using Runnables as custom control blocks like
the Releasable.do() suggested above would result in the
exception being thrown out of do() and into the code that
physically contains the block. Most invocations of runnables
into separate threads are not with ones that throw unhandled checked
exceptions anyway.
* Allow array literals in expressions other than assignment RHS.
Why the devil is foo(1, "string literal", 3) allowed but
foo(1, {array, literal}, 3) is not, even when foo's second
parameter type is Object?
(The above two can be disambiguated: array literals and statement
blocks are syntactically distinguishable, and array literals will
generally be used where the parameter type is an array type or
Object and runnable literals where the parameter type is Runnable.)
* Have array constructors added to the collection classes:
(with the above as well)
new HashSet({2, 3, 5, 7, 11, 13});
new ArrayList({"foo", "bar", "baz", "quux"});
new HashMap({{"key1", "value1"}, {"key2", "value2"}});
* Add java.lang.Pair<K, V>. Make some existing classes Pair
implementations, e.g. Dimension extends Pair<Integer, Integer>,
possibly also Rectangle extends Pair<Dimension, Dimension>,
Map.Entry<K,V> extends Pair<K,V>, etc.
Two-element array literals allowed where Pair is expected and
produce Pair literals.
* [int x]; defines a final box containing an int, an instance of a
Box<T> with T == Integer, which has methods T get() and void set(T).
One can then do e.g.
[JFrame foo = new JFrame()];
...
SwingUtilities.invokeLater({foo.get().setVisible(true);});
given the above runnable-literal suggestion.
Add a : operator that expands to ".get()." and we have:
SwingUtilities.invokeLater({foo:setVisible(true);});
Almost a closure, there!
The : operator also lets us use WeakReference and the like
more nicely, if we add another touch to handle the case of null.
ref:doSomething()?System.out.println("something was null!");, say.
* In many more cases it might be useful to have a shortcut to
if (x == null) value = this else value = that
so how about more generally allowing x.foo?ifNull -- the ? clause
is executed whenever x is null and the method call when it's not.
Or just allow x?expression1:expression2 for non-Booleans testing
for null.
* Another source of boilerplate code is:
Type t;
try {
t = someExpressionThatMayThrow();
} catch (FooException e) {
...
return/continue/break/throw foo/whatever
}
t.doSomething();
where you don't want to doSomething() if the exception got raised,
doSomething() itself may raise the same exception but it shouldn't be
caught in the same catch, or similarly.
Let an alternate form be:
try {
Type t = someExpressionThatMayThrow();
} continue {
t.doSomething();
} catch (FooException e) {
...
}
which is not shorter, but doesn't break up t's declaration and
assignment. This, among other things, lets t be final, and also
lets the catch fall through without "t may not be assigned!". In
some cases it may save lots of boilerplate code:
Type t = null;
try {
t = someExpressionThatMayThrow();
} catch (FooException e) {
fooNotifier.logError(e.getMessage());
failed = true;
}
if (t != null) {
t.doSomething();
}
Yuck! This occurs a lot in I/O code though, usually with Type
actually InputStream or one of its close relatives. Often we
want to handle failure to open the stream specially versus
a failure once we already have the stream open, though both
throw IOException. Sometimes, for instance, we want to flag
an error in some way if the open fails, but let the IOException
bubble up to the caller if the subsequent I/O fails, as the
latter is "more exceptional" than the former.
The key to the above is to have t remain in scope in the
"continue" block.
(Other ugliness this might relieve includes some cases of nested try
blocks.)
* Full closures. Let anonymous inner classes access nonfinal local
variables of the enclosing scope, by having the compiler put all
such variables used by such inner classes transparently into an
Object[] array rather than directly on the stack, or something of
the sort. (The syntax of using them wouldn't change; only how they
got stored, so they had mutable references on the heap, and thus also
the bytecode generated where they were accessed. The compiler would
also not need to generate type checks when dereferencing this array,
as there'd be no way of getting at it directly in user code.)
Furthermore, let {code} used as an expression rather than a statement
stand for new Object() { public Type1 run(Type2 arg1, Type3 arg2...)
{ code }}. Add "it implements the interface if it is method-compatible
with it" from high up on this list and this gets you Runnable literals
in the void return, no arguments case. Types inferred by what the
expression's type needs to be to fit -- there would need to be a new
type in the system for type-tuples, which would also be useful for
reifying generics and where the <void> tuple represents Runnable,
<void, int> an interface specifying void run (int x), and so on.
Lastly, closures could use "return return x" to throw a
ClosureNonLocalReturnException wrapping an x, and passing a closure
to a method would implicitly wrap the method call in try { ... }
catch (ClosureNonLocalReturnException e) { return e.getWrapped(); }
-- implementing non-local returns. (If the closure "escaped" its
context and then non-local returned, it would manifest in a
RuntimeException: the uncaught ClosureNonLocalReturnException.
The JIT compiler could optimize the generation of nonlocal returns
that proved to be always implicitly caught by eliding generation of
their stack traces. (In practise, each nonlocal return would have
to throw a new anonymous subclass of that exception -- each time
a method was invoked that contained such closures it would cause
the generation of a new anonymous subclass and that class would
be caught by the implicit catch blocks in that invocation of that
method. This would be the conceptual behavior, anyway. In actuality
the generated bytecodes would probably look like those from an
explicit throw of an exception that takes a wrapped value and
an id integer, plus those from an explicit catch clause resembling
if (e.getID() != id) throw e; return e.getWrapped();
The method bytecode would also need to generate this id, and a new
one each call; System.nanoTime() might work on present processors.
An efficient atomic integer incrementor might be needed ultimately,
or an efficient implementation of ThreadLocal, with a bipartite ID
of the thread's identityHashCode and a thread-local incrementing ID.
* Object.identityHashCode().
* Equalizer to go with Comparator, provides equals and hashCode by
proxy, can be used with HashSet and HashMap to provide substitute
key equality and hash functions the way Comparator can provide a
substitute compareTo for TreeSet and TreeMap.
* WeakValueHashMap, LinkedWeakHashMap, and LinkedWeakValueHashMap.
These would all be useful implementing caches where you want the
mapping to go away if the value is no longer in use, but calls
for the value to return the pre-existing object if one existed.
WeakValueHashMap already sort-of exists in one under-the-hood bit
of non-user-reusable Java: the implementation of string interning,
starting whenever they finally got it to GC unused interned strings.
Trying to implement one yourself is a bear, because there's no way
to automatically do something when a reference dies.
* ReferenceQueue.addListener(Reference r) to let you automatically do
something when a reference is enqueued. Or provide a protected method
in Reference that is called when the reference is cleared or is
about to be cleared, so you can e.g. subclass WeakReference, override
this method, and do something specific (such as remove a mapping
from a map).
Closest you can currently get is to use a WeakHashMap of the
same object to
new Object() { protected void finalize() { do.whatever(); }}
and pray.
* Allow subclassing an enum with the subclasses able to REMOVE (not
add!) values. An enum that can be any of X, Y, or Z is a subtype,
rather than a supertype, of one that can be any of X, Y, Z, A, or B
because it is substitutable for the latter but not vice-versa; the
actual created classes would be Super with subclasses Sub, Super.A,
Super.B, Super.X, Super.Y, and Super.Z and subclasses of Sub Sub.X,
Sub.Y, and Sub.Z, with some change to the underlying code to not treat
Sub itself as an enum value for Super and to treat Sub.X, Sub.Y, and
Sub.Z as convertible to their Super counterparts. The former change is
easy: ignore non-leaf subclasses. The latter is also easy, so long as
enum subclasses can't add methods, fields, or similarly.
Might as well also allow creating supertypes, an enum with additional
values that is assignable FROM the original enum but not TO it.
These might be best implemented not as true under-the-hood subclasses
at all, but as parallel types with implicit conversions among them.