Summary
Many people think that inner classes in Java could be better; this Blog presents a new syntax that emphasizes Clarity, Consistency, and Conciseness (in that order!). An example usage is: withLock lock, method { out.println "Hello" };
Advertisement
Clear, Consistent, and Concise Syntax (C3S) for Java
Many people think that inner classes in Java could be better and many proposals
have been put forward. However none of these proposals, including this author's,
have really gained broad support. This Blog presents an alternative, building
on and hopefully unifying past proposals, that emphasizes Clarity, Consistency,
and Conciseness (in that order!). An example usage is: withLock lock, method{out.println "Hello"};
Like normal writing a program code should express intent clearly; you should
not be left wondering what a line does and the compiler should not have to
make assumptions that may or may not agree with the programmers assumptions.
The language should be consistent; it should not have many similar concepts
or syntaxes with usage overlap (a few general features are better than many
specific features even if for some use cases the specific features are better
than the general features). The syntax should be concise, since verbosity can
obscure intent (not to save keystrokes). In the ideal world you would meet
all three Cs (Clear, Consistent, and Concise), however the Cs are at times
contradictory and a compromise has to be made. It is therefore important to
prioritise the Cs and for a language like Java that aspires to ubiquity the
priority order should be: Clear, Consistent, and Concise. Much of the Consistent
constraint will be consistency with existing Java features and the existing
Java syntax style, e.g. non-abbreviated keywords rather than symbols. The consistency
constraint also precludes lifting syntax directly from other languages unless
that syntax is similar in style to existing Java syntax. From the priority
given it is obvious that I am not equating minimum typing with simplicity,
however I am advocating conciseness because I think that in places the current
verbosity obscures intent.
Previous proposals are summarized in a previous blog;
telling comment on that blog about these proposals include: “... all
of the syntax you've shown me is more painful than just doing without” (Todd
Blanchard), “The
heavy reliance on the correct use use of differently shaped brackets, colon
and semi-colons is steadily reducing Java's scope from being a language for
use by general programmers to a notation decipherable only by geeks” (Vincent
O'Sullivan), and “All of the proposals are a bit painful syntactically” (Martin
Odersky). These comments strike at the crux of the problem, the proposals are
not Clear or Consistent. Also Neal Gafter pointed out in an email that there
are more use cases than iterating over collections and therefore the examples
given in the previous blog were not the complete story. The new proposal is
in the Appendix 1, along with justifications, and three examples are given
below: time, withLock, and each. It is hoped that this proposal unifies other
proposals by adopting features from other proposals and by suggesting a staged
introduction of features (see Appendix) and that the use cases are considered
realistic.
Examples
Java time
Method time times a series of methods and reports their timings, and as is
the case for all the examples I will present the current Java code first to
provide a basis of comparison. Formatting of the example is close to what I
use in practice; this choice of formatting although realistic is less concise
than often shown in blogs and other example forums where an unrealistically
short format is used to save space and also confer (sometimes inadvertently)
simplicity. In my preferred style I declare variables final (except for ones
that do change!); which is common (or mandatory) in functional languages, but
unusual in Java. As an aside – take a look at your own code, I bet nearly
all variables should be final so that it is clear to humans and machines that
the variable is a pure declaration. This use of final in the example below
is not because variables are passed to inner classes; though in some
of the other examples it is necessary to use final.
Use:
final Method0< R, RuntimeException > method1 = new Method0< R, RuntimeException >() {
public void call() { ... }
public String toString() { return "Method1"; }
} );
final Method0< R, RuntimeException > method2 = new Method0< R, RuntimeException >() {
public void call() { ... }
public String toString() { return "Method2"; }
} );
time( method1, method2 );
Given:
public interface Method0< R, Ts extends Throwable > { R call() throws Ts; }
...
public static < R, Ts extends Throwable > void time( final Method0< R, Ts >... methods ) throws Ts {
for ( final Method0< R, Ts > m : methods ) {
final long start = currentTimeMillis();
final R result = m.call();
final long end = currentTimeMillis();
out.println( m + " returned " + result + " and took " + (end - start) + " ms." );
}
}
C3S time
The same example above in the new syntax is given below, the most striking
features are the use of type inference and the new method declaration using
the keyword method. The method declaration using a keyword is similar syntax
to other languages, e.g. Javascript, however method is chosen instead of function
because it emphasizes dynamic dispatch instead of a static binding.
Use:
final method1 = Method0.new {
method call { ... }
method toString { "Method1" }
};
final method2 = Method0.new {
method call { ... }
method toString { "Method2" }
};
time method1, method2;
Or shorter:
timenew {
method call { ... }
method toString { "Method1" }
}, new {
method call { ... }
method toString { "Method2" }
};
Or shorter still:
timeenum {
Method1 { method { ... } },
Method2 { method { ... } }
}.values;
Given:
public interface< R, Throwable... Ts > Method0 { method R call throws Ts }
...
public static method< R, Throwable... Ts > void time( final Method0< R, Ts >... methods ) throws Ts {
for ( final m : methods ) {
final start = currentTimeMillis;
final result = m.call;
final end = currentTimeMillis;
out.println m + " returned " + result + " and took " + (end - start) + " ms."
}
}
The new syntax is explained fully in Appendix 1. The most interesting aspects,
not all of which are demonstrated above, are:
Keyword final infers a variables type (there is an equivalent keyword,
declare, for non-final variables).
Keyword return as the last statement of a method may be omitted, even
if it returns a value. If the method is of type Void (object not primitive)
then the method always returns null, even if an explicit return or return
value aren't given.
Keyword new follows the type and it uses a dot to closely associate it
with the type in a manor similar to a static call. Keyword new can also
be used for constructor declaration within a class and for creating instances
of classes for method arguments. When used for constructor declarations
constructor name, modifiers, argument types, and simple argument copy to
fields are inferred by new. When used to create an instance for a class
the type of the class to be created is inferred.
Keyword method identifies the block as a method block with the given arguments
and body and also indicates object creation. The syntax mimics the established
syntax for other blocks, e.g. if as in keyword brackets braces,
and it uses a dot to closely associate it with the object type in a manor
similar to a static call. Keyword method can also be used for method declaration
within a class and for creating instances of classes for method arguments.
When used within a class method name, method modifiers, and argument types
are inferred by method. When used to create an instance for a method argument,
method also infers the type of the class to be created.
Keywords enum, interface, and class can be used to declare anonymous types,
as opposed to anonymous instances of anonymous types that new and method
create. Static methods can be called on these anonymous types, as they
can on anything of type Class. The notation Base.class
{ ... } declares an anonymous class type that extends Base, similarly enum
and interface. If anonymous types are used in a method call the type to
extend is inferred, like new and method.
Generic type declarations are unified to appear in the same position in
a declaration, generalized to any declaration including variables, their
syntax for common cases changed to shorter variable like declarations,
and varargs for Throwables and derivatives are allowed.
The number of brackets and semicolons are reduced.
Java withLock
Method withLock allows the use of the new concurrent locks conveniently. Medium
formatting is adopted in this example.
Method each steps through each item in a collection. The example assumes that
collections are given new members functions (i.e. each) and given vararg constructors.
(However, modifying the collections library is really a separate issue to C3S.)
The example uses short names and is shown with minimum white space and it inlines
definitions. I don't consider this extra short formatting realistic; but it
is used in other blogs, some people prefer it, and a proposal must look OK
in many different formatting styles.
Use:
final ArrayList list=new ArrayList(1,2,3);
final Tuple1 sum=new Tuple1(0);
list.each(new Block1(){
public void call(x){sum.e1+=x;}
});
Given:
public class Tuple1{
public E1 e1;
Tuple1(E1 e1){this.e1=e1;}
}
...
public interface Block1extends Throwable>extends Method1{}
...
public extends Throwable> void each(Block1 b)throws Ts{
for(E e:es){b.call(e)}
}
C3S each
Use:
final list=ArrayList.new(1,2,3);
declare sum=0;
list.each method(x){sum+=x};
Given:
...
public method void each(Method1.interface{} b)throws Ts{
for(final e:es){b.call(e)}
}
Summary
Whilst the syntax proposed is not the shortest possible; I think it is the
best proposal to date because it balances: Clarity, Consistency, and Conciseness
and because it unifies previous proposals. The syntax synergy of the proposal
is demonstrated in the table below (syntax in table slightly simplified to
keep table short).
Anonymous Instance as Method Arguments or Variable Assignment
What do other people think – is this syntax an improvement on the other
proposals? Does this proposal unify the other proposals?
Appendix 1 – C3S Rules
Stage 1
The concept is to implement stage 1 rules and then see if stage 2 suggestions
are still considered necessary.
Rule 0: For a method call allow brackets, (), to be omitted
if there are no arguments and for the last call in a chain of Top Level calls
(method, constructor, or modified new, see Rules 2 and 3 below); provided that
the call isn't ambiguous with respect to a field. Top Level means
the call isn't an argument to another call. Note braces start a new statement
with a new top level. The last call means that for the top level calls only:
it is either the only call (and that call isn't qualified) or it is the call
following the last dot. The last call rule means it must follow the last dot,
even if that dot is a qualifier dot rather than a call dot; so that an integer
argument is distinct from a real argument (see last example Rule 3). This rule
is more concise. E.g.:
Rule 1: Allow final and a new keyword, declare, to infer
the type of a declaration based on the initial value. If no initial value is
given or the value is given as null then the type must be specified as normal
(also the declare keyword may be used like final is currently used). Function
arguments are as is, except that declare may prefix the declaration to emphasize
that the parameter isn't final. To specify primitives the type must explicitly
be given, see first example below. Generic arguments follow the keyword and
are useful for recursive or repeated types. This rule improves clarity by using
a keyword, consistency by always using
a keyword and allowing generic parameters, and conciseness of by allowing type
inference. E.g. (also see rules 2 and 3 for new new notation):
declare sum = 0; // Integer, for int use: int sum = 0 or declare int sum = 0
final< X > Tuple2< X, X > duplicate; // Not the same as Tuple< ?, ? >!
declare strings = ArrayList< String >.new.add "A";
final strings = ArrayList< String >.new;
Rule 2: Allow new to be used as a qualifier with a . (dot)
like a static method call (but generic arguments retain their normal position
after the type) instead of as a prefix operator. This rule improves readability
and hence clarity since it tightly associated with the type, is more consistent
with the rest of Java, and (importantly) enables Rule 3. E.g.:
declare strings = ArrayList< String >.new( "a", "b", "c" ); // Assuming ArrayList had a vararg constructor!
declare numbers = (T[])Number.new[ 1 ]; // where T extends Number
Rule 3: Allow new to infer generic type from constructor
argument and use <> to mean a raw type. This rule considerably shortens
generic declarations and also instances of anonymous inner classes. E.g.:
declare strings = ArrayList<>.new; // Explicit raw type
final strings = ArrayList< String >.new; // Explicit generic type
final strings = ArrayList.new( "a", "b", "c" ); // Inferred generic type, assuming vararg constructor
Rule 4: Allow new to declare constructors in classes and
to infer argument types from fields of the same name if no argument types are
given (like final and declare do for variables – see Rule 1). If argument
names are given without qualification and body is given as default then
infer a simple Assignment of arguments to fields. This inferring
of Assignment is similar default functionality to ML, Scala, and Fortress,
but is more flexible since multiple constructors with different access and
annotation modifiers are possible. Inside an instance method the notation this.new(
... )opt may be used. This use of new improves clarity since new
is a keyword, improves consistency since it emphasizes the similarity between
constructors and static methods and is consistent with the new use of .new
after a type name (Rule 2), and is more concise. E.g. (also see Rule 5 for
omitting trailing ; and Rule 15 for position of generic arguments):
class< E1 > T1 {
declare E1 i;
new( final E1 i ) { this.i = i }
}
class< E1 > T1 {
declare E1 i;
new( final i ) { this.i = i } // Infer argument type from field
}
class< E1 > T1 {
declare E1 i;
new( i ) { default } // Infer argument type from field and infer copy of argument into field
}
Rule 5: If an expression ends in } then there is no need
for ; before the brace (like , in array initializers and also note , at end
of argument lists is an error). IE treat ; as a statement separator rather
than a statement terminator. This rule helps the readability of instances of
anonymous classes and encourages braces to always be used even if a single
statement is possible. E.g.:
if ( end ) { break }
Rule 6: Introduce a new keyword, method, for declaring methods
which is syntactically placed just before any generic parameters (i.e. Immediately
after annotations and modifiers). If the method overrides a method then optionally
infer @Override,
other modifiers, return type, method argument types, and throws clause. If
there is only one method to override then the method name can be inferred.
If the method has no arguments the brackets may be omitted. This new keyword
is clearer because it is a keyword, is more consistent because other declarations
have keywords, and is more concise because it allows inference. E.g. (also
see Rule 9 for new generic type syntax and Rule 12 for omitting keyword return):
public static method< K, V > Map< K, V > unmodifiableMap( Map< K ?, V ? > map ) ...
method toString { "Hello" }
Rule 7: Keyword, method, can also be used with a
type followed by a dot notation to be a shorthand for creating an anonymous
instance of an anonymous inner class. This new syntax is clearer because its
intent is not obscured by verbosity, is more consistent because its syntax
is like other blocks (e.g. if), and is more concise because of inference. E.g.:
final action = AbstractAction.method ( final notUsed ) {
controller.updateAllViews updateModel
};
Rule 8: Keyword, method, can also be used to create anonymous
instances of anonymous inner classes without a type and a dot notation
if the type can be inferred from a methods arguments. This new syntax is clearer
because its intent is not obscured by verbosity, is more consistent because
its syntax is like other blocks (e.g. if), and is more concise because of inference.
E.g.:
final button = JButton.new method ( final notUsed ) {
controller.updateAllViews updateModel
};
Rule 9: Allow extends type generic arguments to be shortened
to < Type name > (i.e. like normal variable declarations), e.g.:
public static method< K, V > Map< K, V > unmodifiableMap( Map< K ?, V ? > map ) ...
public class Enum< Enum< E > E > ...
Which are equivalent to
public static < K, V > Map< K, V > unmodifiableMap( Map< ? extends K, ? extends K > map ) ...
public class Enum< E extends Enum< E > > ...
Rule 10: A throws clause can be empty, which means the method
doesn't throw anything (equivalent to an absent throws clause). This is useful
in conjunction with generics, see Rule 11 below. E.g.:
method Void call() throws;
Rule 11: Generics are extended to allow varargs (only for
use with Throwables and derivatives). An empty generics varargs list is allowed
and it is equivalent to an absent throws clause (note Rule 10 above). E.g.:
method< R, Throwable... Ts > R call() throws Ts;
Rule 12: Keyword return may be omitted for last line of
a method and the returned value is the value of the last expression, e.g.:
method toString { "Hello" };
Rule 13: If a method returns a Void, then make the end of
a method without a return and a return without an argument synonymous with
return null. E.g. the following are identical.
Given:
interface< Throwable... Ts > Block0 extends Method0< Void, Throwable... Ts > {}
Then:
final block = Block0.method {};
final block = Block0.method { return };
final block = Block0.method { return null };
final block = Block0.new {
method { return null }
};
final block = Block0.new {
@Override method call { return null }
};
final block = Block0.new {
method call { return null }
};
final Block0< RuntimeException > block = new Block0< RuntimeException >() {
Void call() { return null; }
};
Are all the same.
Rule 14: Non-final locals that are referenced by an inner
class are automatically wrapped in a final-tuple instance, like C# 3.0 does.
Note: special treatment, name mangled temporary required, of non-final arguments
is needed (not shown in example below). E.g.:
Given:
public class< E1 > Tuple1 {
public E1 e1;
public new( e1 ) { default }
}
public class< E1, E2 > Tuple2 extends Tuple1< E1 > ...
public interface< R, A1, Throwable... Ts > Method1 { R call( A1 a1 ) throws Ts }
public interface< A1, Throwable... Ts > Predicate1 extends Method1< Boolean, A1, Ts > {}
...
public static method< T, Throwable... Ts > List< T > select( final Iterable< T > c, final Predicate1< T ?, Ts > f ) throws Ts ...
Then:
final beginning = "Fred";
final names = ArrayList.new"Frederick";
final filtered = names.select method ( name ) { name.startsWith beginning };
Which is equivalent to the following verbose version:
final Tuple1< String > beginning = new Tuple1< String >( "Fred" );
ArrayList< String > names = (new ArrayList< String >( "Frederick" ));
ArrayList< String > filtered = names.select( new Predicate1< String >() {
public Boolean call( final String name ) {
return name.startsWith( beginning.e1 );
}
} );
Rule 15: Allow generic parameters to follow keyword for
class and
interface,
i.e. like method and like
Type[] for arrays. This improves consistency since in some circumstances it
cannot follow the name, e.g. method declarations. E.g.:
public class< E1 > Tuple1 {
public E1 e1;
public new( e1 ) { default }
}
Rule 16: Allow anonymous type declarations, TypeToExtend.optclass
{ ... }, for
class, interface, and enum. This is intentionally similar syntax to anonymous
instances of anonymous classes, i.e.: similar syntax to Type.optnew
( ... )opt { ...
} and Type.optmethod ( ... )opt { ... }. If used as
an initializer for a variable the type is inferred. as Class< $1 >. If used
as a method argument the type to extend may be inferred. This is a useful generalization
of the notion of creating anonymous instances of anonymous classes to the creation
of anonymous types. E.g. (also see Rule 17):
public method< Throwable... Ts > void each( final Method1< Void, E, Ts >.interface{} b )throws Ts {
for ( final e : es ) { b.call( e ) }
}
final singleton = Method0.enum { // Singleton is of type Class< $1 > where $1 implements Method0 and is an Enum
Method1 { method { ... } },
Method2 { method { ... } }
};
time singleton.values;
timeenum { // Infer Method0
Method1 { method { ... } },
Method2 { method { ... } }
}.values;
Rule 17: Allow static methods to be called on variables
of type Class< T > using the normal dot notation. This is consistent with the
concept that static members are what other languages call class members, e.g.
smalltalk. E.g. (also see Rule 16):
final singleton = Method0.enum { // Singleton is of type Class< $1 > where $1 implements Method0 and is an Enum
Method1 { method { ... } },
Method2 { method { ... } }
};
time singleton.values;
Rule 18: Allow variable declarations . E.g.:
final singleton = Method0.enum { // Singleton is of type Class< $1 > where $1 implements Method0 and is an Enum
Method1 { method { ... } },
Method2 { method { ... } }
};
time singleton.values;
Stage 2
Having implemented stage 1 and gained some experience a second stage could
be added with the following suggestions (these are very much suggestions and
this section should be treated as a list of open issues). I have stuck with
syntax related suggestions that are essentially sugar and no new semantics
or modifications to JVM.
Suggestion A: Add support for statements break, continue, and exit
multiple enclosing blocks using break, continue, or return. The syntax for
break and continue for an enclosing loop would be unchanged. For exiting an
enclosing method the syntax could be MethodName.return
value;. This could be implemented using pre-made checked exceptions, thus allowing
compile time checking of multiple block exits. Syntax could be unified by allowing
Name.break and Name.continue and named blocks, e.g. if Name (
... ) { ... }.
Suggestion B: Add support for long strings with embedded variables using
a new keyword, string, e.g.:
string { // Trailing white space and single new line on { line not part of string
Embed variables including optional formatting, e.g.: %2.1f{ real }
} // Leading white space and single new line on } line not part of the string
Suggestion C: Add support for union of types using & like generics
(particularly for catch blocks).
Suggestion D: Allow final and declare to be used for the variable declaration
in a catch block and allow type inference as the
union of all declared or inferred (checked or not) exceptions.
Suggestion E: Add support for properties by inferring bodies
and declarations for: method getName { default }and method setName { default
}; i.e. like new { default }.
Suggestion F: Universally allow () in C3S
to be omitted, e.g. if condition { ... ; ... } and method Type name ...,
... { ... ; ... }.
Suggestion G: Unify variable (field, local, and
argument) declarations around the standard syntax, e.g. final Type { name1;
name2 }.
Suggestion H: Make new line act as a semicolon
and allow ,, +, -, =, etc. as last non-white space or non-comment on a line
to mean continued on next line.
This looks interesting. It wouldn't be Java, but it might be something better that would run in the JVM/rt.jar environment and interact vith java libraries. Maybe you should write at C3S-to-java compiler. Or, better, develop the syntax with IntelliJ MetaProgramming System and give us all an interactive editor/compiler.
I would still consider it Java, because it is just syntax sugar and because current Java is a subset
> but it might > be something better that would run in the JVM/rt.jar > environment and interact vith java libraries.
Scala does this, but its syntax isn't upwardly compatible with Java and it has some very un-Java like features (not that these features are a bad thing - Scala is a well designed language)
> Maybe you > should write at C3S-to-java compiler. Or, better, develop > the syntax with IntelliJ MetaProgramming System and give > us all an interactive editor/compiler.
I am not familiar with IntelliJ, but hear good things about it. I use Netbeans and unfortunately its transformation engine for refactoring, Jackpot, requires its input and its output to be current Java. Therefore you can't extend the syntax of Java in Netbeans :(
Ehmm...what happened to the simple object-oriented language for the masses called Java? more bloat, more rules, more exceptions is not good for the language, is it?
I don't really see anything here that is valuable to me. There are some typing time savers, but IDEs like NB and others can trivially solve this problem for you. I have abreviations in the editor for th e most common action listener and other anonymous classes and methods that I use.
The added rules and syntaxes will just make more things that the developer who is coding, or reading has to remember the symantics for.
I haven't been watching this debate, so I'm tempted to just ask a simple question. What problem needs to be solved by all of this? Is it just that a few people don't feel that the current annonymous class implementation is "pretty" enough?
I guess my view is that the current implementation is very simple, completely readable, and I really had no problem jumping in and using it immediately.
What barriers are there really, to using anonymous classes?
It seems like a lot of the discussion presented here is about less text. That can be good for typing speed improvement, but it can lead to ambiguity and lack of information for those reading.
I didn't grasp how I would implement a MouseAdapter that overroad mousePressed() and mouseReleased(), and checked for isPopupTrigger() to display a menu.
I'm also not sure how I would declare multiple methods that each throughs 2 or more exceptions each that are unrelated. It looked to me like the trailing Throwable... e would only handle a single method.
> I don't really see anything here that is valuable to me. > There are some typing time savers, but IDEs like NB and > d others can trivially solve this problem for you. I have > abreviations in the editor for th e most common action > listener and other anonymous classes and methods that I > use.
I agree with you that a good IDE goes a long way to solve the problem and I also agree that we don't really need any new semantics. IE I think what ever is done should have a direct mechanical translation into current Java.
> The added rules and syntaxes will just make more things > that the developer who is coding, or reading has to > remember the symantics for.
Assuming that the new syntax becomes the norm then it will reduce the load on the developer because the syntax is more consistent. I can't see a way forward without introducing new syntax, so the proposal is to keep the syntax as much like Java currently is as possible whilst at the same time cleaning up the rough edges on the current syntax. Particularly round generics where the declarations get a uniform position in the statement and most declarations are considerably shorter.
> I haven't been watching this debate, so I'm tempted to > just ask a simple question. What problem needs to be > solved by all of this? Is it just that a few people don't > feel that the current annonymous class implementation is > "pretty" enough? > > I guess my view is that the current implementation is very > simple, completely readable, and I really had no problem > jumping in and using it immediately. > > What barriers are there really, to using anonymous > classes?
Some people find the code hard to read because the verbosity hides the intent. It is also difficult to teach, for the same reason. There is also competition, when inner classes were introduced they were a step up from mainstream languages. But now that Ruby has become popular they are not quite so leading edge. The verbosity definitely discourages people from using them. Generics have also increased the verbosity somewhat as well. Although I totally agree with you that inner classes are quite serviceable.
> It seems like a lot of the discussion presented here is > about less text. That can be good for typing speed > improvement, but it can lead to ambiguity and lack of > information for those reading.
Hopefully the proposal isn't ambiguous, I will try some examples on you below and see what you think. I think they present well and are more readable than current Java because intent isn't obscured by verbosity and because the new keyword method helps clarify that you are declaring a method not a field or local.
> I didn't grasp how I would implement a MouseAdapter that > overroad mousePressed() and mouseReleased(), and checked > for isPopupTrigger() to display a menu.
The example doesn't use generics which have increased the verbosity somewhat, but never the less it is still pleasingly shorter than current Java and I would say clearer because of the extra keyword, method.
final mouse = MouseAdapter.new {
method mousePressed( event ) {
if ( !event.isPopupTrigger ) { return }
...
}
method mouseReleased( event ) {
if ( !event.isPopupTrigger ) { return }
...
}
};
panel.addMouseListener mouse;
or
panel.addMouseListener new {
method mousePressed( event ) {
if ( !event.isPopupTrigger ) { return }
...
}
method mouseReleased( event ) {
if ( !event.isPopupTrigger ) { return }
...
}
};
> I'm also not sure how I would declare multiple methods > that each throughs 2 or more exceptions each that are > unrelated. It looked to me like the trailing > Throwable... e would only handle a single method.
Is this the sort of example you had in mind. Note the nearest you can do at present is to throw a single Throwable or use a fixed sized set of exceptions, each with its own generic parameter. This is a good example of how the proposal simplifies things.
interface TwoMethodsTwoExceptions {
method< Throwable... T1s > void method1( boolean which ) throws T1s;
method< Throwable... T2s > void method2( boolean which ) throws T2s;
}
which might be used like:
final example = TwoMethodsTwoExceptions.new {
method method1( which ) {
throw which ? Exception1A.new : Exception1B.new;
}
method method2( which ) {
throw which ? Exception2A.new : Exception2B.new;
}
}
In current Java the above is roughly (only two exceptions):
interface TwoMethodsTwoExceptions {
< T1A extends Throwable, T1B extends Throwable > void method1( boolean which ) throws T1A, T1B;
< T2A extends Throwable, T2B extends Throwable > void method2( boolean which ) throws T2A, T2B;
}
which might be used like:
final TwoMethodsTwoExceptions example = new TwoMethodsTwoExceptions() {
publicvoid method1( boolean which ) throws Exception1A, Exception1B {
throw which ? (new Exception1A()) : (new Exception1B());
}
publicvoid method2( which ) throws Exception2A, Exception2B {
throw which ? (new Exception2A()) : (new Exception2());
}
}
Personally I have no interest in using such syntax beautifications, and neither will my software team. Our aim is to have good and consistent design...these low level details are irrelevant to good code.
Furthermore, I find syntactic exceptions dangerous: they make the code easier to write, but harder to read, especially if you go back a few months and want to change it or incorporate a new member in the development team.
I prefer simplicity, even if it takes a few more keystrokes.
Greg, while I agree on reading being more important than writing, there are SERIOUS reading problems in Java that this syntax partially fix.
The primary problem with Java is that there is extreme amounts of redundancy - "boilerplate code" - that you still need to process on reading. This is noise that is in the way of seeing real differences.
The proposal remove a lot of that noise. This is important. Whether you can use an editor template to save typing is unimportant - removing the boilerplate you inserted from my reading is critical.
This seems to be impossible to archieve if Java is taken as the base.
Lots of rules with subtle constraints on when they can and cannot be used are ugly, and vanilla Java is verbose. The only way out of this mess is to start from scratch. It can be done (I'm quite happy with the syntax of my language).
The proposal doesn't actually remove anything. Instead, it introduces alternative ways of doing things that can already be done, more syntax variations and therefore more barriers to learning the language.
The benefit of removing some boilerplate code is lost by the need for everyone to now know what is being implicitly coded that which was explicitly coded before. In any case, since the 'old' forms are still valid, programmers still need to be able to deal with them.
I really can't imagine a world in which Java makes a set of syntax changes that are that radical. IMHO, I think you're better off giving it a new name, compiling it to the JVM and announcing it as a new language.
> I really can't imagine a world in which Java makes a set > of syntax changes that are that radical. IMHO, I think > you're better off giving it a new name, compiling it to > the JVM and announcing it as a new language.
I agree with that. If its going to be radically different, then there is no point in mixing the two...a different language that targets the same VM is preferrable than throwing everything in the same language.
If you are wanting a new language for the JVM then perhaps you should take a look at Scala, which has very concise syntax that at least to my eyes is clear. However it has many ways of doing the same thing, essentially long hand and short hand. Particularly with respect of inner classes.
If no changes to Java are made then I think it will eventually stagnate, this is what has happened to other languages in the past. I think it is valuable to update the language and keep it fresh and it has happened in the past. For example inner classes and generics. In both cases they offered nothing that wasn't already possible in Java but provided some syntax sugar to make the programmers intent clearer. I would like to continue along that path and to also deprecate the old syntax and when a syntax is replaced have the -Xlint option to the compiler issue a warning that the old syntax is deprecated. Java already has examples of this, e.g. Type[] name and Type name[]. I feel the trick is to be respectful to the original design of the language but to not treat it as ever finished but always work in progress.
(The above statement re. inner classes isn't quite true; they assign to the pointer to the enclosing class before calling super which isn't possible in standard Java.)
> If no changes to Java are made then I think it will > eventually stagnate, this is what has happened to other > languages in the past. I think it is valuable to update > the language and keep it fresh and it has happened in the > past. For example inner classes and generics.
Inner classes and generics are not examples of changes introduced to the language to "keep it fresh". They were introduced to extend the capabilities of the language in line with current thinking on software development practise.
Change for the sake of change (to "keep it fresh") is sterile and will fool no-one into thinking a language is still growing.
> In both cases they offered nothing that wasn't already possible in > Java but provided some syntax sugar to make the > programmers intent clearer. I would like to continue along > that path and to also deprecate the old syntax and when a > syntax is replaced have the -Xlint option to the compiler > issue a warning that the old syntax is deprecated.
Deprecating language features without ever deleting them has proven to be a poor long term strategy by Sun. It's unwanted baggage that is never discarded. Even if developers themselves never use those features they need to be familiar with them so that they can, as professionals, deal with version 1.0 code that might be recompiled under version 1.6, etc. (does that ever really happen?) or code developed by others who have ignored depecation warnings to implement something that works in a way they are familiar with.
So, should new features be introduced to Java? Yes, if they extend the language in a useful manner, and yes if they make a simplification that outweighs the complification of introducing that simplification (sic.). No, if their purpose is to keep the language "fresh" or if they are just trivial syntactic tweaks that do no more than save typing.
Hang on a minute, inner classes and generics implemented nothing that could be done before their introduction. I can hand code an inner class (well almost as I previously noted) and I can use casts instead of generics (generics do increase compile time safety). In both cases they primarily increased clarity and to a secondary extent reduced typing. This is what these proposals aim to do: increase clarity, increase consistency, and reduce verbosity.
Just like generics and inner classes the pre-their-introduction Java syntax remains and the programmer needs to have at least a passing familiarity with the old way. E.G. the example from the Javadoc for java.util.concurrent.Future<V>:
In C3S:
interface ArchiveSearcher { method String search(String target) }
class App {
declare executor = ...
declare searcher = ...
method void showSearch(final String target) throws InterruptedException {
declare future = executor.submit method { searcher.search target };
displayOtherThings; // do other things while searching
try {
displayText future.get // use future
} catch (ExecutionException ex) { cleanup; return }
}
}
In Current Java (from Javadoc):
interface ArchiveSearcher { String search(String target); }
class App {
ExecutorService executor = ...
ArchiveSearcher searcher = ...
void showSearch(final String target) throws InterruptedException {
Future<String> future = executor.submit(new Callable<String>() {
public String call() { return searcher.search(target); }
});
displayOtherThings(); // do other things while searching
try {
displayText(future.get()); // use future
} catch (ExecutionException ex) { cleanup(); return; }
}
}
In Java pre inner classes and generics:
interface ArchiveSearcher { String search(String target); }
class Inner implements Callable {
final App outer;
final String target
Inner(final App outer, final String target) {
this.outer = outer;
this.target = target;
}
public Object call() { return outer.searcher.search(target); }
}
class App {
ExecutorService executor = ...
ArchiveSearcher searcher = ...
void showSearch(final String target) throws InterruptedException {
Future future = executor.submit(new Inner(this, target));
displayOtherThings(); // do other things while searching
try {
displayText(((String)future.get())); // use future
} catch (ExecutionException ex) { cleanup(); return; }
}
}
The progression of these examples, to my eyes, starting at the top, is decreasing in clarity and increasing in verbosity. So I think shorter syntax has been and should continue to be added to Java; provided that it is consistent with previous syntax and provided that it improves clarity.
Flat View: This topic has 23 replies
on 2 pages
[
12
|
»
]