Wrappers feature proposal
Dimitris Paltatzidis
dcrystalmails at gmail.com
Wed Jan 13 13:22:08 UTC 2021
We use wrappers to override 1 or more instance methods of an instance.
*The wrappers that you and I have in our mind might not be the same. Bear
with me, for the concept.
Suppose we have an instance of class A and we want to override 1 of its
methods.
Currently, to achieve that we have to write:
class Wrapper extends A {//here A could be an interface (implements), but
we'll touch that later
private final A a;
Wrapper(A a) {
//We hope that A has a no args constructor
if (a == null) throw new NullPointerException();
this.a = a;
}
@Override public void method1() {this.a.method1();}
@Override public int method2() {return this.a.method2();}
.
.
.
@Override public void methodN() {this.a.methodN();}
@Override public void methodToOverride() {/*We override this method*/}
}
A few problems with the above implementation are:
1. Most of the time we want to override 1 or a small portion of all the
methods of A,
but we have to override all of them (all they way up the inheritance
hierarchy),
delegating to the originals of our A instance, just to reach the few we
actually
need to override. That is, we have to write a lot of boilerplate.
2. The class Wrapper is a child of A, so it possibly has instance fields
(hidden in A
and above in the inheritance hierarchy) that adds up to the memory
footprint of Wrapper,
even though there is no need for a Wrapper instance to have its own
state. Its state
is the state of its underlying A instance. It's only purpose is to
override or delegate.
3. There is no guarantee that there is an appropriate constructor in A to
call with super()
in the Wrapper constructor. So either you add one in A (if you have
access to it) that
initializes it in a "default for wrappers" state or you call one with
garbage values,
to hopefully pass any guards there might be. Either case, you end up
with a state that
could be illegal (or meaningless), just to pass through the Wrapper
constructor. All
that with additional work, and again to create a state that shouldn't be
there as it
eats memory and could be illegal and prone to bugs. Yes, interfaces
won't have the
constructor and state problems.
Ok, so how do these wrappers look like then?
wrapper Wrapper extends A {
@Override public void methodToOverride() {/*We override this method*/}
}
That's it. It is equivalent to the above verbose one.
Every single method that is not overridden will be delegated.
And to create it:
A a = new A(..);
Wrapper w = new Wrapper(a);
We can also have anonymous wrappers:
public static A unmodifiable(A a) {
return new wrapper A(a) {/*Override only the necessary methods*/};
}
But why? How can we benefit from wrappers?
- Unmodifiable collections is a good candidate in the JDK. They are the
so-called lightweight
wrappers that throw UnsupportedOperationException in any attempt to
change the underlying
collection through them.
- Spy wrappers. Suppose you have a window, and you want to get notified
every time its size
changes. You can just wrap it and override its getWidth() and getHeight()
methods, giving
the observer pattern another look.
- Possibly more..
What about interfaces?
- Sure, wrappers can work with them too, no matter how many you implement.
All non overridden
methods will be delegated to the underlying instance. Of course you must
supply the wrapper
construction with an instance that implements all of them.
What about anonymous wrappers and multiple interfaces?
- Well, it's a No here, to stay consistent with anonymous classes.
Should you be able to access the wrapped instance from a wrapper? And if
so, how?
- Yes. I'm not sure if the keyword is "super" or something else here
though..
Are wrappers just like classes?
- I like to think of wrappers as enhanced object references. They really do
reference you
to their underlying wrapped instance and sometimes you take a side trip
when methods are
overridden.
Can I add instance fields in wrappers?
- That defeats one of their purposes, No. Wrappers should be treated as if
they are their
underlying instance, not on their own. I'm not sure about static finals
though.
Can I add methods in wrappers?
- Just like the previous answer, wrappers shouldn't be viewed as
independent entities, so
it probably doesn't make sense to add methods. I'm not sure about statics
though.
Can I extend wrappers?
- No. They are implicitly final.
Are wrappers just a child class of the class they extend?
- So, I want the answer to be mostly No, because the semantics of wrappers
is to actually
"mutate" methods of a given instance of the class they extend (yes, that
is a subset
attribute of child classes too). They actually Are the class they extend
and not a child.
But, without breaking major rules here and to honor a lot of the Java
terms, they
technically are a child class.
Can I wrap a wrapper with another wrapper?
- No. One layer is sufficient. That is tricky to implement though, without
letting technical
dept into the game.
Can wrappers wrap a class that may have final methods?
- Sure, you just won't be able to override those final methods. Now, if the
class is not
final, but for some reason all of its methods are, then you would end up
with a wrapper
that basically just delegates. There is nothing illegal here, just
pointless.
Can wrappers wrap final classes?
- No.
Should wrappers be referenced by their type or by the type they wrap?
- I'm not sure if wrappers should be used as public API. I think they can
thrive as hidden
implementations making the API flatter, e.g.
Collections.unmodifiableCollection() returns
a Collection, even though the actual implementation is
UnmodifiableCollection, which is
a private static nested class. But, no one expects UnmodifiableCollection
as the returned
type. The same goes for wrappers too. Wrappers will mostly be returned by
methods as the
type they wrap, and if used in statements, it's better to have A w = new
Wrapper(a); as
opposed to Wrapper w = new Wrapper(a); If wrappers can't have methods of
their own, what's
the point of the latter statement? Also, the latter hides the actual type
of a (yes, good
naming conventions could help).
Can sealed classes and interfaces permit wrappers?
- Sure. Anonymous wrappers are the exception here just like anonymous
classes.
Can wrappers wrap sealed classes that don't permit them?
- No.
What about Serialization?
- Well, wrappers don't have a state of their own, so that's a good thing.
But, I can't tell
if it'll be a nightmare to get them to play together with it or not.
Ok, so .equals() is delegated, but what about == ?
- Normally you would expect wrappers to be instances of their own, having
an identity, but
I'm not sure what that identity is (or maybe they don't need identity
*caught* future
inline from project Valhalla). Are 2 wrappers that wrap the same instance
and come from
the same wrapper "class" different as per == or equal?
So, wrappers are just syntactic sugar.?
- No, but at their most degraded version yes. They can be so much more than
that, and here's how:
1. You can remove their state completely, making them truly lightweight.
Their state is the
state of their underlying instance. The class they wrap doesn't need
to have an appropriate
constructor, as they don't have a state of their own to initialize.
2. Now, to push things a little bit more, wrappers could wrap classes
that have package-private
abstract methods (outside the package). You would be able to override
only the protected/public
methods. Wrappers do not provide implementations, they just override
the already implemented
methods of their underlying instance, so there is a guarantee that the
wrapped instance will
have that package-private method overridden.
The meaning of wrappers is that: Any method that needs to be overridden
(and can be), should be
without any hassle and per instance. e.g. I have this instance and I want
to override 2 of its
methods and only for that instance.
Wrappers should be as close to their underlying wrapped instance as
possible. It's like having a
reference to that instance that magically has that method you want to
override, overridden.
Wrappers and their underlying instances shouldn't be visualized as
different objects.
Who can benefit from wrappers?
- Application authors: Yes, especially using them as spy wrappers, for
events.
- Library authors: Yes. Immutability will be easier to achieve and more
accessible.
- Compiler authors: Maybe?
What about other languages, do they have wrappers?
- Yes, Kotlin has them, under the term Delegation. Although, they are not
quite the same as here,
as they can have a state of their own.
More information about the amber-spec-comments
mailing list