One of the complaints that often comes up in any discussion on Java is getters/setters. If there are c# developers present they sometimes suggest that Java would be better off with c# style properties.
c# properties, replace separate getters and setter methods that often clutter Java code. They allow the getters/setters to be combined together, allow passing of a property reference as a whole.
Here’s the c# example. We have a property Hours that stores its value in seconds.
class TimePeriod { private double seconds; public double Hours { get { return seconds / 3600; } set { seconds = value * 3600; } } } |
and here’s the same thing implemented in Java using Lambdas.
class TimePeriod { private double seconds; public final Property<Double> Hours = get(() -> seconds / 3600) .set(value -> seconds = value * 3600); } |
We can now get and set the Hours property which modifies the seconds field as shown below. At this point you might be wondering how we can modify the value of seconds from inside a lambda, since it is not effectively final. In this case we are really modifying this.seconds and “this” is effectively final.
@Test public void property_with_behaviour() { TimePeriod period = new TimePeriod(); period.Hours.set(2D); assertEquals(7200D, period.seconds, 0); assertEquals(2D, period.Hours.get(), 0); period.Hours.set(3D); assertEquals(10800D, period.seconds, 0); assertEquals(3D, period.Hours.get(), 0); } |
We can also pass around references to either the whole property, or just the getter/setter component.
@Test public void pass_around_references_to_hours() { TimePeriod period = new TimePeriod(); period.Hours.set(2D); takesAProperty(period.Hours, 3D); assertEquals(3D, period.Hours.get(), 0); takesASetter(period.Hours::set, 2D); assertEquals(2D, period.Hours.get(), 0); Double got = takesAGetter(period.Hours::get); assertEquals(2D, got, 0); } private <T> void takesAProperty(Property<T> property, T newValue) { property.set(newValue); } private <T> void takesASetter(Consumer<T> setter, T newValue) { setter.accept(newValue); } private <T> T takesAGetter(Supplier<T> getter) { return getter.get(); } |
Read-only and write-only properties would also work, where it would be a compile failure to call .set(value) or .get() respectively.
public final Readonly<String> ReadOnlyName = get(() -> name).readonly(); public final Writeonly<String> WriteOnlyname = set(value -> name = value); |
The implementation is really quite trivial. We just wrap Supplier and Consumer functions.
Another useful aspect of properties in c# is that we can find out their names for things like building SQL with LINQ.
We could make people using our Properties specify a name like so, for the same reasons.
public static class ExplicitPropertyNames { private String foo = "foo"; public final Named<String> Foo = get(() -> foo).set(value -> foo = value).named("Foo"); public final Named<String> Bar = get(() -> foo).set(value -> foo = value).named("Bar"); } @Test public void explicit_named_property() { ExplicitPropertyNames o = new ExplicitPropertyNames(); assertEquals("Foo", o.Foo.name()); assertEquals("Bar", o.Bar.name()); } |
It is a shame we have to repeat the name like this, if we are willing to take the performance hit we could try and infer the property names like so
public static class GuessablePropertyNames { private String foo = "foo"; public final Named<String> Foo = get(() -> foo).set(value -> foo = value).named(); public final Named<String> Bar = get(() -> foo).set(value -> foo = value).named(); } @Test public void guessable_named_property() { GuessablePropertyNames o = new GuessablePropertyNames(); assertEquals("Foo", o.Foo.name()); assertEquals("Bar", o.Bar.name()); } |
This last example is only possible with reflection. When instantiating the property we create a stack trace and record the position and class that contains the property.
int expectedDepth = 3; StackTraceElement stackTraceElement = new Throwable().fillInStackTrace().getStackTrace()[expectedDepth]; this.declaringClassName = stackTraceElement.getClassName(); this.declaringLineNumber = stackTraceElement.getLineNumber(); |
When we ask for the name later we try and match up the field we are looking at
Class<?> cls = unchecked(() -> Class.forName(declaringClassName)); Object o = createInstanceOfDeclarer(); Optional<Field> field = asList(cls.getDeclaredFields()) .stream() .filter(f -> f.getType().isAssignableFrom(GuessesName.class)) .filter(f -> unchecked(() -> (GuessesName) f.get(o)).declaringLineNumber == this.declaringLineNumber) .findFirst(); return field.get().getName(); |
This is pretty horrific, but is an option if you object to specifying a name. Other options include using cglib to record the property name using a proxy.
More examples here on Github and implementation as well