Skip to main content

Default values + Generics = Better together

9 replies [Last post]
regexguy
Offline
Joined: 2003-06-20
Points: 0

Default values would be nice... but I think it would be cool if we could have something nicer than what C++ has. Here's a snippet of how I imagine it....

public class Foo {
public Foo(int x=1,int y=2,int z=4,Class c=T.class) {
..
}
public static void main(String[] args) {
// this constructor...
Foo f1 =
new Foo(z=>2,y=>3);
// does the same as this
Foo f2 =
new Foo(1,3,2,4,String.class);
}
}

The idea is that the X => Y construct identifies which
argument you want to set. So if you use it, you don't have to specify the arguments in order.

It would be optional, of course, but once the construct => was used in an argument list, all remaining arguments would have to use it.

This would be really simplify writing constructors for complex objects with lots of default parameters.

Also, having the compiler fill in the class argument would get around some of the problems people have with generics. We could get the type information into the class (if it wants it). If, for some reason, the compiler could not figure out the type to put in an expression it would put in a null.

The great thing about this mechanism is that you would not need to use it in the constructor. Sure you could use it there so that you could remember your type info, but you could also use it as follows...

import java.lang.reflect.Array;
public class MyList {
T[] data;
public MyList(T[] data) {
this.data = data;
}
// wish I could say here instead of ...
X[] toArray(Class c=X.class) {
X[] xa = (X[])Array.newInstance(c,data.length);
for(int i=0;i m = new MyList(da);
// the compiler fills in the Number.class default arg
Number[] na = m.toArray();
}
}

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
mthornton
Offline
Joined: 2003-06-10
Points: 0

I think it would be preferable to resolve the default values at run time (when the classes are loaded) and not at compile time. Otherwise it is potentially very confusing if you have different classes compiled against different versions of the class declaring default versions.

The existing inlining of constant strings and primitives declared in other classes is already annoying. It was an optimisation which no longer seems worthwhile.

brucechapman
Offline
Joined: 2004-03-18
Points: 0

The major use case for this is constructors.

Here is a current technology solution that takes a bit of coding in the classdeclaration, but provides a pretty nice mechanism where you call the constructor.

The idea extends from the StringBuffer idiom of methods return "this" so you can chain the method calls together. To make this work in a Constructor (& with final fields), you define a separate static inner class to contain the configuration, and pass one of these in to the constructor.

You need a static method on the outer class to return a default configuration object.

here is an example with 4 final fields.
[code]
public class SparseObj {

final private int height, width;
final private String name;
final private Alignment alignment;

private enum Alignment { left,right,centre }

public SparseObj(Config c) {
this.height = c.height;
this.width = c.width;
this.name = c.name;
this.alignment = c.alignment;
}

public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("SparseObj: size=").append(width).append(',').append(height);
sb.append(", name='").append(name).append("', alignment=").append(alignment);
return sb.toString();
}

public static Config defaults() {
return new Config();
}

public static class Config {
private int height=100, width=100;
private String name="";
private Alignment alignment=Alignment.centre;

private Config() {}

public Config height(int h) {
height = h;
return this;
}

public Config width(int w) {
this.width = w;
return this;
}

public Config name(String n) {
name = n;
return this;
}

public Config left() {
alignment = Alignment.left;
return this;
}
public Config centre() {
alignment = Alignment.centre;
return this;
}

public Config right() {
alignment = Alignment.right;
return this;
}
}
}

class UseCase {
public static void main(String[] args) {
System.out.println(
new SparseObj(
SparseObj.defaults().height(200).left()
)
);
System.out.println(new SparseObj(SparseObj.defaults()));
System.out.println(new SparseObj(
SparseObj.defaults().right().name("Bruce")));
System.out.println(
new SparseObj(
SparseObj.defaults().centre().name("Bruce").height(10).width(20)
)
);
}
}
[/code]running this gives[code]
U:\java>java -cp . UseCase
SparseObj: size=100,200, name='', alignment=left
SparseObj: size=100,100, name='', alignment=centre
SparseObj: size=100,100, name='Bruce', alignment=right
SparseObj: size=20,10, name='Bruce', alignment=centre
[/code]Its quite a lot of work, but potentially worthwhile when there are lots of final fields that all need to be set, but many have commonly acceptable default values, and where the overhead of writing the class is justified because there are many points in the code where the constructor is called.

If you want to write even more code in the declaration, and simplify the constructor calls even further, you add a static method on the outer for each final field, and return a Config, like this one (all the others follow the same pattern).[code]public Config width(int w) {
return new Config().width(w);
}[/code] then the invocation can be written thus [code]
new SparseObj(SparseObj.width(200).left());[/code]This gets pretty close to being as elegant as the call with the proposed new syntax.[code]new SparseObj(width:200,alignment:SparseObj.Alignment.left)[/code] and allows you to do things like hide the Alignment enum with individual methods as I have done (but didn't need to).

mgrev
Offline
Joined: 2003-08-12
Points: 0

That is a smart solution indeed. Though a bit too complicated for day-to-day use for smallish immutable objects (IMO).
Default values, resolvable at compile time, would be a good simple solution, again IMO.

Cheers,
Mikael

brucechapman
Offline
Joined: 2004-03-18
Points: 0

Certainly the benefits seems to go up exponentially with the number of fields, but also the benefits come from being invoked from many different places.

I am still processing this myself, I had roughed out the solution in the past, but never got around to implementing it. That code I posted above was the first time I had used it in anger (ahh - confession time).

Thinking about this a little further, I am pretty sure I could generate the Config class with "apt", not as an inner class, but as one in the same package. All you would need to code would be[code]
public class SparseObj {
final private int height, width;
final private String name;
final private Alignment alignment;
private enum Alignment { left,right,centre }
public SparseObj(@GeneratedConfigurator SparseObjConfig c) {
height = c.getHeight();
width = c.getWidth();
name = c.getName();
alignment = Alignment.valueOf(c.getAlignment());
}

public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("SparseObj: size=").append(width).append(',').append(height);
sb.append(", name='").append(name).append("', alignment=").append(alignment);
return sb.toString();
}

public static SparseObjConfig defaults() {
return new SparseObjConfig();
}
}
[/code]
and make sure you didn't declare the same Class name (in this case SparseObjConfig) as an argument with the annotation more than once per package.

Now that's rockin' ( am I allowed to say that myself, or should I implement it first then let someone else say it :) )

pparikh
Offline
Joined: 2005-06-13
Points: 0

I am interested to find out if anything ever came out of this. This seems to be good idea to me.

patrikbeno
Offline
Joined: 2004-10-11
Points: 0

This may not be as easy as it seems.

For this feature to work, names of the parameters would need to be present in the bytecode otherwise compiler has nothing to validate your call against. (Imagine you have only compiled JAR).

Furthermore, you have to specify how conflicts would be resolved. Consider this:
[code]
String name = "John";
String surname = "Doe";
call(name:name, surname:surname);
[/code]

This can be unambiguous because part before colon refers to parameter name and part after colon refers to local variable. But it seems confusing

on the other hand, this can make code even more verbose, so let's be careful. It definitely increases clarity and understandability of the code, especially when constructing large immutable objects with huge amount of constructor arguments.

As for syntax, I would also prefer ':' notation ('=' cannot be used due to conflict with assignment)

call(name:"John", surname:"Doe");

Anyway, I would welcome this feature as an option, not requirement, but it is not on my hotlist :-)

vhi
Offline
Joined: 2004-10-11
Points: 0

Basically, I think what you want is a support for named parameter similar to that available in Python (it also support default values). There are other languages (Smalltalk, Objective C) that support them, but I am not sure if they also support default values. Maybe the Java development team can look into these languages for inspiration.

I also would like to see this in Java. It makes the code much more readable.

map.put(key:"item", value:"radio"); //Personally, I prefer 'z:2' to 'z=>2'.

This is much more understandable than the current Java equivalent.

mgrev
Offline
Joined: 2003-08-12
Points: 0

+100 (All of my family and friends concurr) ;)

I must say this is the most creative and new(?) suggestion I've read for a very long time regarding language changes. I'm usually not that keen on language changes and I've always found default values in C++ to be a bit half baked.

I hate chained constructors, yet they are necessary epsecially for immutable objects (which I like) since they tend to have quite a few arguments.

I'd [b]really[/b] like Sun to take this up for further discussion.

Cheers,
Mikael Grev

regexguy
Offline
Joined: 2003-06-20
Points: 0

What can we do to make that more likely to happen?