Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Introducing Serializable to Existing Code

Tags:

java

I believe this should be a really common case, yet I can't find any best practices. Assume I have the following class:

public class Equation {

    private Operator operator;
    private Object leftValue;
    private Object rightValue;

    // getters and setters
}

public enum Operator  {...}

This class has been with us for some years already and is well used. Now I need to make it serializable. How do I do that?

Just add implements Serializable

In that case, the Equation class only works as long as the values are Serializable. Since equations only really work on numbers (maybe dates and strings?) that might work. But the values could be any kind of Object, so there has to be a better way.

Make values Serializable

public class Equation implements Serializable{

    private Operator operator;
    private Serializable leftValue;
    private Serializable rightValue;

    // getters and setters 
}

This works in any case, but these changes are an API break. And no matter what I do I need to change all the code using the class, which leads to potentially even more API breaks. For a big software system that might take ages.

Make values Serializable, leave getters and setters as is

public void setLeftValue(Object leftValue) {
    if (!(leftValue instanceof Serializable)) 
        throw new IllegalArgumentException("Value must be Serializable!");
    this.leftValue = leftValue;
}

This code breaks no existing API, but changes how the code behaves. Yet if I assume that all the values are Serializable anyways, I feel like this might be the way to go. I can even put the new setters next to the old ones and deprecated them to make it obvious to future developers what objects to use.

Make values transient:

At least that's what Sonar suggests. Yet it leads to an unusable class, at least in all the cases where we actually need Equation to be Serializable.

Create implementation that is Serializable:

public class SerializableEquation extends Equation implements Serializable{

    private Serializable leftValue;
    private Serializable rightValue;

    // override getters and setters
}

That way we would have to use an entire different class for serialization purposes, which seems kind of ugly, doesn't it?

Question:

What is a good way to handle this use case? I ideally don't want to break the API. And seeing as Java has yet to break the API there must be a way to handle cases like this.

like image 820
Steffi S. Avatar asked Nov 21 '16 12:11

Steffi S.


People also ask

What happens if we implement Serializable?

If a super class implements Serializable, then its sub classes do automatically. When an instance of a serializable class is deserialized, the constructor doesn't run. If a super class doesn't implement Serializable, then when a subclass object is deserialized, the super class constructor will run.

What does it mean to serialize code?

Serialization is the process of converting an object into a stream of bytes to store the object or transmit it to memory, a database, or a file. Its main purpose is to save the state of an object in order to be able to recreate it when needed. The reverse process is called deserialization.

How do you implement a Serializable class?

If you want a class object to be serializable, all you need to do it implement the java. io. Serializable interface. Serializable in java is a marker interface and has no fields or methods to implement.

Do I need to implement Serializable with Jackson?

Jackson actually doesn't need classes to implement Serializable but I'm going to add it anyway. Writing about a serialization framework and not implementing Serializable might seem strange.


1 Answers

In this kind of problem, a correct implementation should have used interfaces to limit the field of action.

What is the point the add an UrlConnection to a String ? Well, this should have been something like this :

public class Equation {
    Operator operator;
    Operand leftOp, rightOp;

    ...
}

interface Operand {

    ...
}

And then, for specific type of data, you would have implements specific classs

public IntegerOperand implements Operand {

    public Integer value;
    ...
}

From this, you only need to add the Serialiszable to Operator and Operand. This will be a contract that the dev need to follow, so every implementation need to be serializable (fully serializable since the interface asked it), this will be easy to test with JUnit.

But

In you case, you can't update the code because this would break the compatibility. So I would put the serialisation to the test, meaning that I woulld check if both Object instance ARE serializable, if not, then you do what you want with it.

You, either, check that with a method when you want (before serialisation) to prevent that this action can't be done with this data or with the setter of both value to restraint the possibilities.

public boolean isSerialisable(){ 
    return Serializable.class.isAssignable(leftValue.class)
            && Serializable.class.isAssignable(rightValue.class);
}

This will be call before you need to serialize the instance, as a warning or error. (or directly in the setters if you want to break everything ;) )

Last resort, you serialize yourself the data, you can use some library to generate different type of structure, XML, JSON, .. or probably in bytes directly (no example in mind)

EDIT:

Here is a quick (and ugly example) of serialisation with Object

public class Main implements Serializable{

    public Object value;

    public Main(Object o){ this.value = o; }

    public static void main(String[] args){
        Main m = new Main(new A());

        try {
             FileOutputStream fileOut = new FileOutputStream("test.ser");
             ObjectOutputStream out = new ObjectOutputStream(fileOut);
             out.writeObject(m);
             out.close();
             fileOut.close();
        }catch(IOException i) {
            i.printStackTrace();
        }

        m = null;
        try {
            FileInputStream fileIn = new FileInputStream("test.ser");
            ObjectInputStream in = new ObjectInputStream(fileIn);
            m = (Main) in.readObject();
            in.close();
            fileIn.close();
        }catch(Exception i) {
            i.printStackTrace();
            return;
        }
        System.out.println(" M : " + m);
    }

    @Override
    public String toString() {
        return value == null ? "null" : value.toString();
    }

    static class A implements Serializable{
        String s = "foo";

        @Override
        public String toString() {
            return s;
        }
    }
}

By removing the implementation if serializable from A, this will failed. But like this, we see that this is working like expected.

like image 163
AxelH Avatar answered Oct 18 '22 12:10

AxelH