Подтвердить что ты не робот

Как создать переменную, которая может быть установлена ​​только один раз, но не является окончательной в Java

Мне нужен класс, в котором я могу создавать экземпляры с одной переменной unset (id), а затем инициализировать эту переменную позже и иметь неизменяемый после инициализации. Фактически, мне нужна переменная final, которую я могу инициализировать вне конструктора.

В настоящее время я импровизирую это с установщиком, который выбрасывает Exception следующим образом:

public class Example {

    private long id = 0;

    // Constructors and other variables and methods deleted for clarity

    public long getId() {
        return id;
    }

    public void setId(long id) throws Exception {
        if ( this.id == 0 ) {
            this.id = id;
        } else {
            throw new Exception("Can't change id once set");
        }
    }
}

Является ли это хорошим способом заниматься тем, что я пытаюсь сделать? Я чувствую, что должен быть способен установить что-то неизменное после его инициализации или что есть образец, который я могу использовать, чтобы сделать его более элегантным.

4b9b3361

Ответ 1

Позвольте мне предложить вам немного более элегантное решение. Первый вариант (без исключения):

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;
    }

}

Второй вариант (с выбросом исключения):

     public void setId(long id)  {
         this.id = this.id == null ? id : throw_();
     }

     public int throw_() {
         throw new RuntimeException("id is already set");
     }

Ответ 2

Требование "установить только один раз" кажется немного произвольным. Я достаточно уверен, что вы ищете класс, который постоянно переходит из неинициализированного в начальное состояние. В конце концов, может быть удобно установить идентификатор объекта более одного раза (через повторное использование кода или что-то еще), если идентификатор не может быть изменен после того, как объект "построен".

Один довольно разумный шаблон - отслеживать это "построенное" состояние в отдельном поле:

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;
    }
}

Использование:

Example e = new Example();

// do lots of stuff

e.setId(12345L);
e.build();

// at this point, e is immutable

С помощью этого шаблона вы создаете объект, устанавливаете его значения (столько раз, сколько удобно), а затем вызываете build() для его "переустановки".

Для вашего шаблона есть несколько преимуществ по сравнению с вашим первоначальным подходом:

  • Нет никаких магических значений, используемых для представления неинициализированных полей. Например, 0 является столь же достоверным, как и любое другое значение long.
  • У сеттеров последовательное поведение. Прежде чем build() вызывается, они работают. После вызова build() они бросают, независимо от того, какие значения вы передадите. (Обратите внимание на использование исключенных исключений для удобства).
  • Класс помечен final, иначе разработчик может расширить ваш класс и переопределить сеттеры.

Но этот подход имеет довольно большой недостаток: разработчики, использующие этот класс, не могут знать во время компиляции, если определенный объект был инициализирован или нет. Конечно, вы можете добавить метод isBuilt(), чтобы разработчики могли проверить во время выполнения, если объект инициализирован, но было бы гораздо удобнее знать эту информацию во время компиляции. Для этого вы можете использовать шаблон построителя:

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);
        }
    }
}

Использование:

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

Это намного лучше по нескольким причинам:

  • Мы используем поля final, поэтому как компилятор, так и разработчики знают, что эти значения не могут быть изменены.
  • Различие между инициализированными и неинициализированными формами объекта описывается через систему типов Java. Нет простого устройства для вызова объекта после его создания.
  • Экземпляры встроенного класса гарантируют безопасность потока.

Да, это немного сложнее в обслуживании, но IMHO преимущества перевешивают стоимость.

Ответ 3

Вы можете просто добавить булевский флаг, а в вашем setId() установить/проверить логическое значение. Если бы я правильно понял вопрос, нам здесь не нужна сложная структура/шаблон. Как насчет этого:

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");
    }
}

}

таким образом, если вы setId(0l); думают, что идентификатор тоже установлен. Вы можете изменить, если это не соответствует требованиям вашей бизнес-логики.

не отредактировал его в среде IDE, извините за проблему с опечаткой/форматом, если был...

Ответ 4

Здесь решение, которое я придумал, основано на смешении некоторых ответов и комментариев выше, в частности, от @KatjaChristiansen по использованию 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;
    }

}

В конце дня я подозреваю, что моя потребность в этом является признаком плохих дизайнерских решений в другом месте, и я должен скорее найти способ создания объекта только тогда, когда я знаю Идентификатор, и установив id в окончательный, Таким образом, во время компиляции можно обнаружить больше ошибок.

Ответ 5

Вот два способа; первая в основном такая же, как некоторые другие, упомянутые в других ответах, но она здесь, чтобы покорить секунды. Итак, первый способ: один раз иметь значение, которое можно установить только один раз, принудительно применяя это в установщике. Моя реализация требует непустых значений, но если вы хотите иметь возможность установить значение null, вам нужно будет реализовать логический флаг isSet, как это предлагается в других ответах.

Второй способ - Lazy - предоставить функцию, которая лениво передает значение при первом вызове getter.

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 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;
    }
}

Итак, вы можете использовать их следующим образом:

//
// 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

Ответ 6

Google Библиотека Guava (которую я очень рекомендую) поставляется с классом, который очень хорошо решает эту проблему: SettableFuture. Это дает семантику set-once, о которой вы спрашиваете, но также и многое другое:

  • Возможность вместо этого связывать исключение (метод setException);
  • Возможность немедленного отмены события;
  • Возможность регистрировать слушателей, которые будут уведомлены при установке значения, исключение уведомляется или будущее отменяется (ListenableFuture interface).
  • Семейство типов Future в общем случае используется для синхронизации потоков в многопоточных программах, поэтому SettableFuture играет с ними очень хорошо.

Java 8 также имеет свою собственную версию: CompletableFuture.

Ответ 7

У меня есть этот класс, похожий на JDK AtomicReference, и я использую его в основном для устаревшего кода:

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;
    }
}

У меня single responsibilty и проверьте как вызовы get, так и set, поэтому он не срабатывает раньше, когда клиентский код неправильно использует его.

Ответ 8

попробуйте выполнить проверку int, например

private long id = 0;
static int checker = 0;

public void methodThatWillSetValueOfId(stuff){
    checker = checker + 1

    if (checker==1){
        id = 123456;
    } 
}

Ответ 9

//u может попробовать следующее:

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();
    }
}

Ответ 10

Маркировка поля частного и не подверженного воздействию setter должно быть достаточным:

public class Example{ 

private long id=0;  

   public Example(long id)  
   {  
       this.id=id;
   }    

public long getId()  
{  
     return this.id;
}  

если этого недостаточно, и вы хотите, чтобы кто-то мог его изменить X раз, вы можете это сделать:

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");   
        }  
    }  
}  

Этот подход похож на дизайн по контракту, в котором вы проверяете состояние объекта после того, как мутатор (то, что изменяет состояние объекта) вызывается.

Ответ 11

Я думаю, что одноэлементный шаблон может быть чем-то, что вы должны изучить. Google немного, чтобы проверить, соответствует ли этот шаблон вашим целям дизайна.

Ниже приведен некоторый код sudo о том, как сделать singleton в Java, используя enum. Я думаю, что это основано на дизайне Джошуа Блоха, очерченном в "Эффективной Java", и в любом случае это книга, которую стоит собрать, если у вас ее еще нет.

public enum JavaObject {
    INSTANCE;

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

Использование:

JavaObject.INSTANCE.doSomething();