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

后端 未结 12 1547
感情败类
感情败类 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:18

    try have an int checker like

    private long id = 0;
    static int checker = 0;
    
    public void methodThatWillSetValueOfId(stuff){
        checker = checker + 1
    
        if (checker==1){
            id = 123456;
        } 
    }
    
    0 讨论(0)
  • 2020-12-14 06:23

    The "set only once" requirement feels a bit arbitrary. I'm fairly certain what you're looking for is a class that transitions permanently from uninitialized to initialized state. After all, it may be convenient to set an object's id more than once (via code reuse or whatever), as long as the id is not allowed to change after the object is "built".

    One fairly reasonable pattern is to keep track of this "built" state in a separate field:

    public final class Example {
    
        private long id;
        private boolean isBuilt;
    
        public long getId() {
            return id;
        }
    
        public void setId(long id) {
            if (isBuilt) throw new IllegalArgumentException("already built");
            this.id = id;
        }
    
        public void build() {
            isBuilt = true;
        }
    }
    

    Usage:

    Example e = new Example();
    
    // do lots of stuff
    
    e.setId(12345L);
    e.build();
    
    // at this point, e is immutable
    

    With this pattern, you construct the object, set its values (as many times as is convenient), and then call build() to "immutify" it.

    There are several advantages to this pattern over your initial approach:

    1. There are no magic values used to represent uninitialized fields. For example, 0 is just as valid an id as any other long value.
    2. Setters have a consistent behavior. Before build() is called, they work. After build() is called, they throw, regardless of what values you pass. (Note the use of unchecked exceptions for convenience).
    3. The class is marked final, otherwise a developer could extend your class and override the setters.

    But this approach has a fairly big drawback: developers using this class can't know, at compile time, if a particular object has been initialized or not. Sure, you could add an isBuilt() method so developers can check, at runtime, if the object is initialized, but it would be so much more convenient to know this information at compile time. For that, you could use the builder pattern:

    public final class Example {
    
        private final long id;
    
        public Example(long id) {
            this.id = id;
        }
    
        public long getId() {
            return id;
        }
    
        public static class Builder {
    
            private long id;
    
            public long getId() {
                return id;
            }
    
            public void setId(long id) {
                this.id = id;
            }
    
            public Example build() {
                return new Example(id);
            }
        }
    }
    

    Usage:

    Example.Builder builder = new Example.Builder();
    builder.setId(12345L);
    Example e = builder.build();
    

    This is much better for several reasons:

    1. We're using final fields, so both the compiler and developers know these values cannot be changed.
    2. The distinction between initialized and uninitialized forms of the object is described via Java's type system. There is simply no setter to call on the object once it has been built.
    3. Instances of the built class are guaranteed thread safe.

    Yes, it's a bit more complicated to maintain, but IMHO the benefits outweigh the cost.

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

    I think the singleton pattern might be something you should look into. Google around a bit to check if this pattern meets your design goals.

    Below is some sudo code on how to make a singleton in Java using enum. I think this is based off Joshua Bloch's design outlined in Effective Java, either way it's a book worth picking up if you don't have it yet.

    public enum JavaObject {
        INSTANCE;
    
        public void doSomething(){
            System.out.println("Hello World!");
        }
    }
    

    Usage:

    JavaObject.INSTANCE.doSomething();
    
    0 讨论(0)
  • 2020-12-14 06:27

    Let me suggest you a little bit more elegant decision. First variant (without throwing an exception):

    public class Example {
    
        private Long id;
    
        // Constructors and other variables and methods deleted for clarity
    
        public long getId() {
            return id;
        }
    
        public void setId(long id) {
            this.id = this.id == null ? id : this.id;
        }
    
    }
    

    Second variant (with throwing an exception):

         public void setId(long id)  {
             this.id = this.id == null ? id : throw_();
         }
    
         public int throw_() {
             throw new RuntimeException("id is already set");
         }
    
    0 讨论(0)
  • 2020-12-14 06:28

    //u can try this:

    class Star
    {
        private int i;
        private int j;
        static  boolean  a=true;
        Star(){i=0;j=0;}
        public void setI(int i,int j) {
            this.i =i;
            this.j =j;
            something();
            a=false;
        }
        public void printVal()
        {
            System.out.println(i+" "+j);
        }
        public static void something(){
             if(!a)throw new ArithmeticException("can't assign value");
        }
    }
    
    public class aClass
    {
        public static void main(String[] args) {
            System.out.println("");
            Star ob = new Star();
            ob.setI(5,6);
            ob.printVal();
            ob.setI(6,7);
            ob.printVal();
        }
    }

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

    I recently had this problem when writing some code to construct an immutable cyclic graph where edges reference their nodes. I also noticed that none of the existing answers to this question are thread-safe (which actually allows the field to be set more than once), so I thought that I would contribute my answer. Basically, I just created a wrapper class called FinalReference which wraps an AtomicReference and leverages AtomicReference's compareAndSet() method. By calling compareAndSet(null, newValue), you can ensure that a new value is set at most once by multiple concurrently modifying threads. The call is atomic and will only succeed if the existing value is null. See the example source below for FinalReference and the Github link for sample test code to demonstrate correctness.

    public final class FinalReference<T> {
      private final AtomicReference<T> reference = new AtomicReference<T>();
    
      public FinalReference() {
      }
    
      public void set(T value) {
        this.reference.compareAndSet(null, value);
      }
    
      public T get() {
        return this.reference.get();
      }
    }
    
    0 讨论(0)
提交回复
热议问题