3

I have following code:

package me.immutable;

import java.util.*;

public class Immutable {
    public static void main(String[] args) {
        //this is immutable using builder
        SimpleClass wb = new SimpleClass.Builder()
                .addString("a")
                .addString("b")
                .addString("c")
                .withWillNotChange("notchange")
                .build();

        //this is new immutable with changes only needed properties
        //wb object remains unchanged
        SimpleClass newWb = wb.withDifferentStrings(Arrays.asList("d", "e", "f"));
    }
}

class SimpleClass {
    private final List<String> strings;
    private final SimpleObservable simpleObservable;
    private final String willNotChange;

    private SimpleClass(Builder builder){
        this.strings = builder.strings;
        this.willNotChange = builder.willNotChange;
        this.simpleObservable =
                builder.simpleObservable == null ?
                        new SimpleObservable(strings) :
                        builder.simpleObservable;
    }

    SimpleClass withDifferentStrings(List<String> strings){
        return builder(this)    //create new immutable object using builder
                .withStrings(strings)   //modify only what changes
                .withSimpleObservable(simpleObservable.changeCanChange(strings))
                .build();
    }

    Builder builder(SimpleClass sc){
        return new Builder(sc);
    }

    static class Builder{
        private List<String> strings = new ArrayList<>();
        private SimpleObservable simpleObservable;
        private String willNotChange;

        Builder(){}

        Builder(SimpleClass sc){
            this.strings = sc.strings;
            this.willNotChange = sc.willNotChange;
            this.simpleObservable = sc.simpleObservable;
        }

        Builder addString(String s){
            strings.add(s);
            return this;
        }

        Builder withWillNotChange(String s){
            willNotChange = s;
            return this;
        }

        Builder withStrings(List<String> strings){
            this.strings = strings;
            return this;
        }

        private Builder withSimpleObservable(SimpleObservable simpleObservable){
            this.simpleObservable = simpleObservable;
            return this;
        }

        SimpleClass build(){
            return new SimpleClass(this);
        }
    }

}

class SimpleObservable{
    private SimpleObserver observer;
    private List<String> canChange = new ArrayList<>();

    public SimpleObservable(List<String> canChangeSet){
        canChange.addAll(canChangeSet);
        observer = new SimpleObserver(canChange);
    }

    private SimpleObservable(SimpleObservable so){
        canChange = so.canChange;
        observer = so.observer;
    }

    public SimpleObservable changeCanChange(List<String> canChangeSet){
        //No builder so we use copy constructor to build new immutable object and not change this one
        SimpleObservable o = new SimpleObservable(this);
        o.canChange = canChangeSet; //update only what changes
        o.observer = observer.notifyMe(o.canChange);
        return o;
    }
}

class SimpleObserver{
    private String name;

    public SimpleObserver(List<String> canChange){
        this.name = canChange.get(0);
    }

    private SimpleObserver(SimpleObserver so){
        name = so.name;
    }

    public SimpleObserver notifyMe(List<String> changed){
        SimpleObserver o = new SimpleObserver(this);
        o.name = changed.get(0);
        return o;
    }
}

Here, I'm having 3 classes: SimpleClass, SimpleObservable and SimpleObserver.

SimpleClass is using builder inside and I can create immutable copy of it with changing only few fields using following code:

return builder(this)    //create new immutable object using builder
                .withStrings(strings)   //modify only what changes
                .withSimpleObservable(simpleObservable.changeCanChange(strings))
                .build();

How could I do something similar in other two classes with using final fields? Currently I do not use final's, and copy-constructor works.

SimpleObserver o = new SimpleObserver(this);
        o.name = changed.get(0); //if name is final, I could not do this
        return o;

Is there some option to have final fields, and keep ability to create new immutable classes with changing few properties?

Bojan Vukasovic
  • 2,054
  • 22
  • 43

1 Answers1

0

I guess there is no easier way than something like this (lombok), but this is not plane java so...

@Builder(toBuilder = true)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
final class SimpleObservable{
    private final SimpleObserver observer;
    private final List<String> canChange;

    public SimpleObservable(List<String> canChangeSet){
        canChange = new ArrayList<>(canChangeSet);
        observer = SimpleObserver.builder()
                .name(canChangeSet.get(0))
                .build();
    }

    public SimpleObservable changeCanChange(List<String> canChangeSet){
        SimpleObservableBuilder sob = this.toBuilder();
        sob.canChange(new ArrayList<>(canChangeSet));
        sob.observer(observer.notifyMe(sob.canChange));
        return sob.build();
    }
}

@Builder(toBuilder = true)
final class SimpleObserver{
    private final String name;

    public SimpleObserver notifyMe(List<String> changed){
        SimpleObserverBuilder sob = this.toBuilder();
        sob.name(changed.get(0));
        return sob.build();
    }
}
Bojan Vukasovic
  • 2,054
  • 22
  • 43
  • Have you tried Lombok's [`@With`](https://projectlombok.org/features/With) annotation? – jaco0646 Nov 28 '20 at 01:24
  • Yeah, sure - I guess that one is nice to use if you have single property to change. In case you have to change multiple - better to use builder in order not to create multiple objects that will be GC-ed. – Bojan Vukasovic Nov 28 '20 at 10:47
  • It's a choice between readability and micro-optimization. [Premature optimization](https://stackoverflow.com/questions/385506/when-is-optimisation-premature) is the root of all evil. – jaco0646 Nov 28 '20 at 16:04