How to create a variable that can be set only once but isn't final in Java

后端 未结 12 1548
感情败类
感情败类 2020-12-14 05:43

I want a class that I can create instances of with one variable unset (the id), then initialise this variable later, and have it immutable after initial

相关标签:
12条回答
  • 2020-12-14 06:30

    Marking a field private and not exposing a setter should be sufficient:

    public class Example{ 
    
    private long id=0;  
    
       public Example(long id)  
       {  
           this.id=id;
       }    
    
    public long getId()  
    {  
         return this.id;
    }  
    

    if this is insufficient and you want someone to be able to modify it X times you can do this:

    public class Example  
    {  
        ...  
        private final int MAX_CHANGES = 1;  
        private int changes = 0;    
    
         public void setId(long id) throws Exception {
            validateExample(); 
            changes++; 
            if ( this.id == 0 ) {
                this.id = id;
            } else {
                throw new Exception("Can't change id once set");
            }
        }
    
        private validateExample  
        {  
            if(MAX_CHANGES==change)  
            {  
                 throw new IllegalStateException("Can no longer update this id");   
            }  
        }  
    }  
    

    This approach is akin to design by contract, wherein you validate the state of the object after a mutator (something that changes the state of the object) is invoked.

    0 讨论(0)
  • 2020-12-14 06:31

    Here's the solution I came up with based on mixing some of the answers and comments above, particularly one from @KatjaChristiansen on using assert.

    public class Example {
    
        private long id = 0L;
        private boolean idSet = false;
    
        public long getId() {
            return id;
        }
    
        public void setId(long id) {
            // setId should not be changed after being set for the first time.
            assert ( !idSet ) : "Can't change id from " + this.id + " to " + id;
            this.id = id;
            idSet = true;
        }
    
        public boolean isIdSet() {
            return idSet;
        }
    
    }
    

    At the end of the day, I suspect that my need for this is an indication of poor design decisions elsewhere, and I should rather find a way of creating the object only when I know the Id, and setting the id to final. This way, more errors can be detected at compile time.

    0 讨论(0)
  • 2020-12-14 06:33

    You can simply add a boolean flag, and in your setId(), set/check the boolean. If I understood the question right, we don't need any complex structure/pattern here. How about this:

    public class Example {
    
    private long id = 0;
    private boolean touched = false;
    
    // Constructors and other variables and methods deleted for clarity
    
    public long getId() {
        return id;
    }
    
    public void setId(long id) throws Exception {
        if ( !touchted ) {
            this.id = id;
             touched = true;
        } else {
            throw new Exception("Can't change id once set");
        }
    }
    
    }
    

    in this way, if you setId(0l); it thinks that the ID is set too. You can change if it is not right for your business logic requirement.

    not edited it in an IDE, sorry for the typo/format problem, if there was...

    0 讨论(0)
  • 2020-12-14 06:34

    Google's Guava library (which I recommend very highly) comes with a class that solves this problem very well: SettableFuture. This provides the set-once semantics that you ask about, but also a lot more:

    1. The ability to communicate an exception instead (the setException method);
    2. The ability to cancel the event explicitly;
    3. The ability to register listeners that will be notified when the value is set, an exception is notified or the future is canceled (the ListenableFuture interface).
    4. The Future family of types in general used for synchronization between threads in multithreaded programs, so SettableFuture plays very nicely with these.

    Java 8 also has its own version of this: CompletableFuture.

    0 讨论(0)
  • 2020-12-14 06:39

    I have this class, similar to JDK's AtomicReference, and I use it mostly for legacy code:

    import static com.google.common.base.Preconditions.checkNotNull;
    import static com.google.common.base.Preconditions.checkState;
    
    import javax.annotation.Nonnull;
    import javax.annotation.concurrent.NotThreadSafe;
    
    @NotThreadSafe
    public class PermanentReference<T> {
    
        private T reference;
    
        public PermanentReference() {
        }
    
        public void set(final @Nonnull T reference) {
            checkState(this.reference == null, 
                "reference cannot be set more than once");
            this.reference = checkNotNull(reference);
        }
    
        public @Nonnull T get() {
            checkState(reference != null, "reference must be set before get");
            return reference;
        }
    }
    

    I has single responsibilty and check both get and set calls, so it fails early when client code misuse it.

    0 讨论(0)
  • 2020-12-14 06:39

    Here are two ways; the first is basically the same as some others mentioned in other answers, but it is here to constrast with the seconds. So the first way, Once is to have a value that can be set only once by enforcing that in the setter. My implementation requires non-null values, but if you want to be able to set to null, then you would need to implement an 'isSet' boolean flag as suggested in other answers.

    The second way, Lazy, is to provide a function that lazily supplies the value once the first time the getter is called.

    import javax.annotation.Nonnull;
    
    public final class Once<T> 
    {
        private T value;
    
        public set(final @Nonnull T value)
        {
            if(null != this.value) throw new IllegalStateException("Illegal attempt to set a Once value after it's value has already been set.");
            if(null == value) throw new IllegalArgumentException("Illegal attempt to pass null value to Once setter.");
            this.value = value;
        }
    
        public @Nonnull T get()
        {
            if(null == this.value) throw new IllegalStateException("Illegal attempt to access unitialized Once value.");
            return this.value;
        }
    }
    
    public final class Lazy<T>
    {
        private Supplier<T> supplier;
        private T value;
    
        /**
         * Construct a value that will be lazily intialized the
         * first time the getter is called.
         *
         * @param the function that supplies the value or null if the value
         *        will always be null.  If it is not null, it will be called
         *        at most one time.  
         */
        public Lazy(final Supplier<T> supplier)
        {
            this.supplier = supplier;
        }
    
        /**
         * Get the value.  The first time this is called, if the 
         * supplier is not null, it will be called to supply the
         * value.  
         *
         * @returns the value (which may be null)
         */
        public T get()
        {
            if(null != this.supplier) 
            {
                this.value = this.supplier.get();
                this.supplier = null;   // clear the supplier so it is not called again
                                        // and can be garbage collected.
            }
            return this.value;
        }
    }
    

    So you might use these as follows;

    //
    // using Java 8 syntax, but this is not a hard requirement
    //
    final Once<Integer> i = Once<>();
    i.set(100);
    i.get();    // returns 100
    // i.set(200) would throw an IllegalStateException
    
    final Lazy<Integer> j = Lazy<>(() -> i);
    j.get();    // returns 100
    
    0 讨论(0)
提交回复
热议问题