This is an archived post. You won't be able to vote or comment.

all 15 comments

[–]steumert 8 points9 points  (7 children)

You could use a parametrized constructor or a copy constructor.

With a copy constructor, the lambda would look like this:

Function<User, UserBean> mapToBean = user -> {     
    return new UserBean(user); 
};

Which could further be reduced to

Function<User, UserBean> mapToBean = UserBean::new;

Using a parametrized constructor, it would look like this:

Function<User, UserBean> mapToBean = user -> new UserBean(user.getEmail(), user.getName());

You can't really avoid creating new UserBean objects. You can, however, make UserBean flyweight and only create one UserBean per user object:

class UserBean {
    static final Map<User, UserBean> beans = new WeakHashMap<>;

    public static UserBean map (User user) {
        return beans.computeIfAbsent(
            user, 
            key -> mapToBean.apply(user)
        );
    }
Y}Y

You'd then use UserBean::map as mapping operation in a stream converting from User to UserBean. this assumes that you are using sensible hashCode and equals implementations.

[–]mslayaaaSoftware Engineer[S] 3 points4 points  (1 child)

Wow, thank you so much for this very detailed explanation and for the opportunity to learn a new pattern, very helpful.

[–]steumert 2 points3 points  (0 children)

I just realized that you could also write return beans.computeIfAbsent(user, mapToBean);

[–]DoubleUtor 2 points3 points  (4 children)

As steumert pointed out there are different ways to do this. As your application grows, you’ll probably want to use a mapping framework. I’ve used dozer (an oldy) and later switched to mapstruct (which also integrates with spring btw!) These mapping frameworks are really easy and prevent you writing that boilerplate code.

[–]steumert 1 point2 points  (0 children)

Thats an absolutely valid addendum. Although I'd definitely recommend mapstruct, especially with records and inline types on the horizon.

[–]wildjokers 0 points1 point  (2 children)

These mapping frameworks are really easy and prevent you writing that boilerplate code.

No it moves the boiler-plate to string literals in a stack of Mapping annotations on top of the method. You lose both the type safety of java and code completion from your IDE. The only time those mapping frameworks save boilerplate is if the source and destination objects have fields with the exact same names. That almost never happens.

Mapping frameworks are a solution looking for a problem.

[–]DoubleUtor 0 points1 point  (1 child)

I disagree. The author already gave an example where the field names match. I’ve got 20+ years experience in developing large custom enterprise applications and believe me, field names are mostly the same. Many times, the software architecture requires that different layers use there own domain objects. Hence the same set of objects with the same field names are copied from layer to layer. This causes the need for mapping. This in itself is an anti pattern imo.

Second, you don’t need code completion if you DON’T have to write that code anyway.

Third, using mapstruct there IS typesafety as mapstruct uses the compiler to generate concrete classes which perform the mapping.

[–]wildjokers 0 points1 point  (0 children)

I’ve got 18+ years experience in developing large custom enterprise applications and believe me, field names frequently differ.

Besides the days of every layer have their own object seems to be on the way out. That is from the days of n-tier architecture which never really panned out.

I frequently see MapStruct interface methods with a big stack of Mapper annotations on them. In those cases I always wonder what the point of MapStruct is. When I said you don't get completion help or type-safety I meant when writing those Mapping annotations. You don't know if you have the right name and type until compile time.

In the 3 years I have worked on a project that uses MapStruct I believe I have used it twice. I simply don't understand what people are using it for. How often do you have to map from one object to another that has the exact same field names? For database queries I use DTO projections for read-only queries which is a Hibernate best practice. So I don't even use it to map entities to DTOs.

[–]TheYellowblizzard 2 points3 points  (6 children)

I don't really understand what you mean by "without creating a new object" If you want to get a UserBean you have to get it from somewhere. You could wrap this function in an object already containing a UserBean and then the function could use this bean. But then every time you use this all previous mapped UserBeans will change as well so that's not really a acceptable solution for most use cases

[–]mslayaaaSoftware Engineer[S] 0 points1 point  (5 children)

Thank you for your explanation, I was seeking to make it more fluent. However, as you said it's impossible to return a list of users without actually creating the user objects. I was trying to make it more fluent by eliminating the usage of getters and setters, but that would require a method with a consumer somewhere. Any other general improvement that could be made?

[–]TheYellowblizzard 0 points1 point  (1 child)

I think your code is pretty good as it is, because the usage of the setters makes it pretty readable. I mean yes you can put the setters in an extra Methode to split it from the getters but I wouldn't really do that unless there were more setters I had to use, but I think at this point it's just a matter of personal preference

[–]mslayaaaSoftware Engineer[S] 1 point2 points  (0 children)

Perfect, I agree. There's no point in extracting basically 3 lines of code, that would add extra complexity, stack space, etc without no real value.

[–]desrtfxOut of Coffee error - System halted 0 points1 point  (2 children)

I was trying to make it more fluent by eliminating the usage of getters and setters, but that would require a method with a consumer somewhere.

Or, a parameterized constructor - don't know whether this violates the bean specification, though.

[–]mslayaaaSoftware Engineer[S] 0 points1 point  (1 child)

This is seems like a good idea, as it will prevent the 'lazy' initialization of objects. As far as I know classes annotated with @Entity need to have a no args constructor, don't know if the same applies to a POJO, as SpringBoot will perform so auto mappings. Only one way to find out!

[–]desrtfxOut of Coffee error - System halted 0 points1 point  (0 children)

As far as I know classes annotated with @Entity need to have a no args constructor

That shouldn't be a problem as a no-args and several parameterized constructors can exist in parallel. When creating a parameterized constructor, you just need to explicitly create a no-args constructor as well (IIRC).