Composition problem with Java Record canonical constructor

104 views Asked by At

I want to use composition pattern and create a record from another record type in Java but I have some problems with the usage, maybe I am misusing the record in Java, could you please guide me, how you would handle this situation?

Imagine we have the following record

public record Request(String id, String name, String from) {

}

And now I want to warp it in another record which enriches some field of this record with useful data:

public record RequestWrapper(Request originalRequest, Name name, String from) {

    public RequestWrapper(Request originalRequest) {
        this.originalRequest = originalRequest;
        this.name = new Name(originalRequest.name());
        this.from = originalRequest.from();
    }

    public record Name(String name) {
        public Name(String name) {
            this.name = convert(name);
        }
    }

    static String convert(String str) {
        return str + " converted";
    }
}

but compiler complain about Non-canonical constructor must delegate to another constructor, in other words record should always have canonical constructor. this encourage me to add other fields to the constructor as well, as bellow:

public record RequestWrapper(Request originalRequest, Name name, String from) {

    public RequestWrapper(Request originalRequest, Name name, String from) {
        this.originalRequest = originalRequest;
        this.name = new Name(originalRequest.name());
        this.from = originalRequest.from();
    }

    public record Name(String name) {
        public Name(String name) {
            this.name = convert(name);
        }
    }

    static String convert(String str) {
        return str + " converted";
    }
}

But this is not convincing for me as I have to pass some data that are not being used in the constructor, and I want the record to be created from the Original request and not other passed arguments, how would you handle this situation, am I misusing records in this situation?

2

There are 2 answers

2
knittl On

I see two options: static factory method or delegating constructor.

Static factory method

public record RequestWrapper(Request originalRequest, Name name, String from) {

    public static RequestWrapper fromRequest(Request originalRequest) {
        return new RequestWrapper(
                originalRequest,
                new Name(originalRequest.name()),
                originalRequest.from());
    }
}

Delegating constructor

public record RequestWrapper(Request originalRequest, Name name, String from) {

    public RequestWrapper(Request originalRequest) {
        this(
                originalRequest,
                new Name(originalRequest.name()),
                originalRequest.from());
    }
}
0
Hulk On

As suggested by a comment by Holger to an answer of this related question, you could use a sealed interface like this:

public record Request(String id, String name, String from) {}

public sealed interface RequestWrapper permits RequestWrapperImpl {
    
    static RequestWrapper fromRequest(Request originalRequest) {
        return new RequestWrapperImpl(originalRequest, 
            new Name(originalRequest.name()), originalRequest.from);
    }
    
    Name name();
    
    String from();
}

public record Name(String name) {
    public Name(String name) {
        this.name = convert(name);
    }
    
    private static String convert(String str) {
        return str + " converted";
    }
}

private record RequestWrapperImpl(Request originalRequest, Name name, 
    String from) 
    implements RequestWrapper{}

public static void main(String[] args) {
    Request orig = new Request("someId", "aName", "fromHere");      
    RequestWrapper wrapper = RequestWrapper.fromRequest(orig);      
    System.out.println(wrapper.name().name() + " - " + wrapper.from()); 
    // prints "aName converted - fromHere"
}

This has the public interface you want, and it returns a record. It is not very idiomatic, however, and it may have downsides with regard to future features for records.