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