Method Chaining (enhancing Java syntax)

Red IO redio.development at gmail.com
Thu Jun 8 19:36:51 UTC 2023


That doesn't really solve the problem and requires an unchecked cast to T
which isn't guaranteed to be correct.
This "solution" while not working correctly at all also causes problems
with reflection code and causes problems and confusion where it's used. The
biggest roadblock for generics in enums is that enum is defined that way
while not doing what it was intended to do.
The following code compiles without a problem:

class A <T extends A<?>> {}

class B extends A<B> {}

class C extends A<B> {}

Here casting this to T in class C is a class cast exception.
The recursive generic bound is not creating a strong enough bound to use
this pattern.

A "This" type referring to the inheriting type is a good solution to that
problem and was discussed and requested multiple times. The reason why it's
not implemented is unknown to me.

One unsolved problem is how inherited methods returning This should behave
when the method is returning an instance of the parent class rather than
"this" .
1 solution might be to automatically make it required to override methods
that return This in all subclasses.

Great regards
RedIODev


On Thu, Jun 8, 2023, 15:35 Mark Vieira <portugee at gmail.com> wrote:

> You can solve this problem by generifying the Shape class.
>
> public class Shape<T extends Shape<?>> {
>     public T scale(double ratio) {
>         return (T) this;
>     }
> }
>
> public class Rectangle extends Shape<Rectangle> {
>     public Rectangle roundCorners(double pixels) {
>         return this;
>     }
> }
>
> On Thu, Jun 8, 2023 at 6:12 AM <blesa at anneca.cz> wrote:
>
>> Hi all,
>>
>> this is a request for feedback on a topic that has been on my mind for a
>> few weeks. I have written a short document in JEP format and would like to
>> ask you to comment if you find the described proposal useful.
>>
>> Thanks,
>> Tomas Blesa
>>
>> ===========================
>>
>> Summary
>> -------
>> Enhance the Java language syntax to better support the method chaining
>> (named parameter idiom) programming pattern.
>>
>> Goals
>> -----
>> The primary goal is to remove unnecessary boilerplate code in class
>> methods designed for type-safe chained calls, especially when combined with
>> inheritance.
>>
>> Motivation
>> ----------
>> [Method chaining](https://en.wikipedia.org/wiki/Method_chaining) is a
>> widely used and popular programming pattern, particularly in creating
>> libraries (APIs) or configuration objects. Programmers can easily create a
>> method that returns `this` with a method signature that specifies the
>> returning type of the containing class.
>>
>> ```java
>> class Shape {
>>     public Shape scale(double ratio) {
>>         // recalculate all points
>>         return this;
>>     }
>> }
>> ```
>>
>> The problem arises when we combine this pattern with inheritance. We can
>> lose type information when calling the method on a subclass. For example,
>> let's create two subclasses of the `Shape` superclass:
>>
>> ```java
>> class Rectangle extends Shape {
>>     public Rectangle roundCorners(double pixels) {
>>         // ...
>>         return this;
>>     }
>> }
>>
>> class Circle extends Shape {
>> }
>> ```
>>
>> Now, imagine the following piece of code using the mini-library above:
>>
>> ```java
>> var myRect = new Rectangle().scale(1.2).roundCorners(10);
>> ```
>>
>> The code won't compile because `scale()` returns the type `Shape`, which
>> doesn't have the `roundCorners` method. There is also a problem even
>> without the final `roundCorners()` call:
>>
>> ```java
>> var myRect = new Rectangle().scale(1.2);
>> ```
>>
>> The inferred type of `myRect` is `Shape` and not `Rectangle`, so the
>> following line will also be invalid:
>>
>> ```java
>> myRect.roundCorners(10);
>> ```
>>
>> Straightforward solutions to the problem could be:
>>
>> 1) Override the `scale()` method in all subclasses and change the return
>> type:
>>
>> ```java
>> class Rectangle extends Shape {
>>     // ...
>>     @Override
>>     public Rectangle scale(double ratio) {
>>         super.scale(ratio);
>>         return this;
>>     }
>> }
>> ```
>>
>> 2) Split object construction and method calls:
>>
>> ```java
>> var myRect = new Rectangle();
>> myRect.scale(1.2);
>> myRect.roundCorners(10);
>> ```
>>
>> 3) Partial solution - reorder chained calls (if possible):
>>
>> ```java
>> var myRect = new Rectangle();
>> myRect.roundCorners(10).scale(1.2); // roundCorners called first
>> ```
>>
>> All of these solutions add unnecessary lines of code, and as the library
>> of shapes grows, keeping the desired return type will introduce more and
>> more boilerplate code.
>>
>> Description
>> -----------
>> The proposed solution to the problem described in the previous section is
>> to extend the Java syntax for the returned type in method signatures:
>>
>> ```java
>> class Shape {
>>     public this scale(double ratio) { // <=== returned this
>>         // recalculate all points
>>         return this;
>>     }
>> }
>> ```
>>
>> Methods declared or defined as returning `this` can only return the
>> instance on which they are called. The following code will be type-safe and
>> perfectly valid:
>>
>> ```java
>> var myRect =                     // inferred Rectangle type
>>     new Rectangle()              // returns Rectangle instance
>>     .scale(1.2)                  // returns Rectangle instance
>>     .roundCorners(10);           // returns Rectangle instance
>> ```
>>
>> The constructed type `Rectangle` is preserved throughout the entire call
>> chain.
>>
>> It will be possible to override methods returning `this`, but the
>> subclass' implementation must also be declared with the `this` keyword
>> instead of a concrete returning type.
>>
>> It is even possible to remove the explicit return statement altogether:
>>
>> ```java
>> class Shape {
>>     public this scale(double ratio) {
>>         // recalculate all points
>>     }
>> }
>> ```
>>
>> Or simply remove the value `this` from the return statement:
>>
>> ```java
>> class Shape {
>>     public this scale(double ratio) {
>>         // recalculate all points
>>         if (condition) return;         // <== automatically returns this
>>         // do something else
>>     }
>> }
>> ```
>>
>> In the Java world, it is common to create getters and setters according
>> to the Java Beans specification in the form of `getProperty`/`setProperty`
>> pairs or `isProperty`/`setProperty`. Setters are defined as returning
>> `void`. These setters can be more useful if defined as returning `this`:
>>
>> ```java
>> class Customer {
>>     public this setFirstname() { ... }
>>     public this setSurname() { ... }
>>     public this setEmail() { ... }
>> }
>> ```
>>
>> This allows for more concise usage when constructing and configuring an
>> instance without adding more code:
>>
>> ```java
>> customers.add(
>>     new Customer()
>>         .setFirstname(resultSet.getString(1))
>>         .setSurname(resultSet.getString(2))
>>         .setEmail(resultSet.getString(3))
>> );
>> ```
>>
>> It is also possible to declare an interface with methods returning `this`:
>>
>> ```java
>> interface Shape {
>>     this scale(double ratio);
>> }
>> ```
>>
>> In this case, all implementing classes must define the method as
>> returning `this`.
>>
>> The proposed syntax is a bit less useful for enums or records, as neither
>> of them allows for inheritance. But enums and records can also implement
>> interfaces and for this reason and for overall consistency, "return this"
>> syntax should be allowed for enums and records.
>>
>> To accommodate the syntax with the Java Reflection API, it will probably
>> be required to create a special final placeholder class `This` (with an
>> uppercase "T"), similar to `java.lang.Void`.
>>
>> Alternatives
>> ------------
>> It is probably possible to help auto-generate overriding methods in
>> subclasses using annotation processing, but this option wasn't fully
>> explored. However, such an approach would add extra unnecessary code to
>> compiled subclasses and go against the primary goal of reducing boilerplate.
>>
>> Risks and Assumptions
>> ---------------------
>> The proposed syntax is likely to break the compatibility of
>> library-dependent code whose author decides to switch to the "return this"
>> syntax between versions.
>>
>> Older code that looks like this:
>>
>> ```java
>> class MyUglyShape extends Shape {
>>     @Override
>>     public MyUglyShape scale(double ratio) {
>>         return this;
>>     }
>> }
>> ```
>>
>> will have to be rewritten as:
>>
>> ```java
>> class MyUglyShape extends Shape {
>>     @Override
>>     public this scale(double ratio) {    // signature change
>>         // optional removal of the return this statement
>>     }
>> }
>> ```
>>
>> or
>>
>> ```java
>> class MyUglyShape extends Shape {
>> // override removed
>> }
>> ```
>>
>> This problem can be mitigated with the help of smart IDEs automatically
>> suggesting such refactoring.
>>
>> Another possible risk is breaking old code that relies on the Reflection
>> API for scanning the returning types of methods.
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/discuss/attachments/20230608/dbdbea30/attachment-0001.htm>


More information about the discuss mailing list