JAVA — реализация сериализуемого и изменяемого синглтона

В настоящее время я разрабатываю объектно-ориентированную систему в Java. Мне необходимо назначить уникальный целочисленный идентификатор каждому объекту в приложении, а также иметь возможность сериализовать и десериализовать все содержимое приложения. Мне указали на шаблон проектирования Singleton как на средство хранения Java-эквивалента «глобальных переменных», но общие реализации в Java с использованием Class или Enum являются несериализуемыми или неизменяемыми соответственно. Как мне сделать синглтон, который был бы одновременно изменяемым и сериализуемым?

Ниже приведен мой текущий Java-код для написанного мною класса Singleton, который не должен быть сериализуемым, поскольку idCountSingleton является статическим:

import java.io.Serializable;


public class idCountSingleton implements Serializable{

    private static idCountSingleton INSTANCE;
    private int count;

    private idCountSingleton(int id){

        idCountSingleton INSTANCE = new idCountSingleton(id);
        count = id + 1;

    }

    public int getID(){
        return this.count;
    }

    public idCountSingleton getInstance(){
        return this.INSTANCE;
    }
}

Этот код не компилируется. Пожалуйста, опубликуйте свой фактический код.

tgdavies 13.03.2024 22:07

Я не думаю, что ваш код делает то, что вы хотите. Вы объявляете локальную переменную с именем INSTANCE и присваиваете ей значение, поэтому ваше статическое поле INSTANCE всегда имеет значение null. Поле count никогда не меняется. Это то что ты хочешь?

tgdavies 14.03.2024 00:38

@tgdavies Цель состоит в том, чтобы счетчик увеличивался при каждом вызове getInstance(). Очевидно, что я не смог это реализовать.

megidderp 14.03.2024 02:17

1. Это не синглтон. Это не что-то конкретное, просто кусок неработающего кода. 2. Никто не может вызвать getInstance(), так как для его вызова нужен экземпляр, а получить его можно только из getInstance(), который, следовательно, должен быть static, и в этом случае он не скомпилируется из-за this, который также необходимо изменить. 3. Это сериализуемо. 4. Переменная count всегда должна быть равна 1 для реального синглтона, поэтому она бессмысленна. Непонятно, о чем вы спрашиваете.

user207421 14.03.2024 06:58
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
4
79
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

Чтобы завершить создание синглтона класса:

  1. Убедитесь, что метод getInstance() также статичен, чтобы другие классы могли получить к нему доступ по имени класса (например, idCountSingleton.getInstance()).
  2. Я настоятельно рекомендую следовать этому шаблону в методе getInstance():
public static synchronized idCountSingleton getInstance() {
    if (INSTANCE == null) {
        INSTANCE = new ClassSingleton();
    }
    return INSTANCE;
}

Проверка if-null лениво конструирует объект, ожидая вызова getInstance() перед созданием одноэлементного объекта. Ключевое слово synchronized в сигнатуре метода гарантирует, что только один поток одновременно может войти в метод для дополнительной обработки условий гонки (например, что произойдет, если два потока пройдут проверку if-null до того, как один из них создаст объект?)

Сериализация объекта (предположительно, при завершении работы/автосохранении) может быть обработана путем доступа к объекту с помощью getInstance() из любого класса, сохраняющего состояние приложения, а десериализация может произойти внутри частного конструктора (при условии, что это одноэлементный объект, вы можете сохранить его данные в фиксированное место)

Обратите внимание: единственное, что вам нужно сериализовать, — это поле count. static поля функционально преходящи.

Ответ принят как подходящий

Сложная часть — правильно назначить INSTANCE при десериализации.

Если вы десериализуете, а затем просто вызываете getInstance, вы получите новый экземпляр вашего синглтона и потеряете значение id.

Вы можете установить INSTANCE, добавив побочный эффект к readObject, который устанавливает статическое поле:


package com.example.so;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serial;
import java.io.Serializable;

public class SerializableSingleton implements Serializable {
    private static SerializableSingleton INSTANCE;

    private long id;

    public static synchronized SerializableSingleton getInstance() {
        if (INSTANCE == null) {
            System.out.println("Creating new SerializableSingleton");
            INSTANCE = new SerializableSingleton();
        }
        return INSTANCE;
    }

    long getAndIncrementId() {
        return id++;
    }

    @Serial
    private synchronized void readObject(java.io.ObjectInputStream s)
            throws IOException, ClassNotFoundException {
        s.defaultReadObject();
        INSTANCE = this;
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        if (new File("test").exists()) {
            try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test"))) {
                // read and discard the object
                ois.readObject();
                System.out.println("Read singleton, next id:" + SerializableSingleton.getInstance().getAndIncrementId());
            }
        }
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test"))) {
            oos.writeObject(SerializableSingleton.getInstance());
        }

    }
}

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

Другие вопросы по теме