JAVA - Implementing a serialisable and mutable Singleton

58 Views Asked by At

I am currently developing an Object-Oriented system in java. I am required to assign a unique integer identifier for every object within the application, and to be able to serialise and deserialise the entire contents of the application. I was pointed to the Singleton design pattern as a means of storing the java equivalent of "global variables", but the common implementations in java using a Class or Enum are unserialisable or immutable respectively. How would I go about making a singleton that is both mutable and serialisable?

Below is my current java code for a Singleton class I have written which should be non-serialisable since idCountSingleton is static:

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

2

There are 2 best solutions below

0
tgdavies On BEST ANSWER

The tricky part is to get INSTANCE assigned correctly on deserialisation.

If you deserialise and then simply call getInstance you'll get a fresh instance of your singleton and lose the value of id.

You can set INSTANCE by adding a side-effect to readObject which sets the static field:


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

    }
}

I have changed your class to increment the id when you get an id from an instance, instead of when getInstance is called, so that you can't accidentally get the same id twice.

0
EarthTurtle On

A singleton does not have to be immutable: all that the singleton pattern asserts is that there only exists one of this object type within the whole application. Other objects that want to access an idCountSingleton can only retrieve it using the getInstance() method, and cannot create it themselves.

To finish making the class singleton:

  1. Ensure the getInstance() method is also static, so that other classes can access it by the class name (like idCountSingleton.getInstance())
  2. I'd highly recommend following this pattern within the getInstance() method:
public static synchronized idCountSingleton getInstance() {
    if(INSTANCE == null) {
        INSTANCE = new ClassSingleton();
    }
    return INSTANCE;
}

The if-null check lazily constructs the object, waiting for getInstance() to be called before creating the singleton object. The synchronized keyword in the method signature ensures that only one thread at a time can enter the method to additionally handle race conditions (e.g., what happens if two threads make it past the if-null check before either creates the object?)

Serializing the object (presumably, on shutdown/autosave) can then be handled by accessing the object with getInstance() from whatever class saves the application state, and deserialization can happen within the private constructor (assuming that this is a singleton object, you can save its data to a fixed location)

To note: the only thing you need to serialize is the count field. static fields are functionally transient.