user2363399
user2363399

Reputation: 187

Allowing final fields to take default values

I am writing a parser for a binary file format. I have created classes to represent the deserialized structures I am reading, in which I would like to use final variables to hold the extracted data.

class MyObject {
    final int my_x;
    final int my_y;
    final int my_z;
}

The bump I am running into is that the presence of certain fields depends on certain flags being set. For example:

MyObject(InputStream is) {
    my_x = in.read();
    if (my_x == 1) {
        my_y = in.read();
        if (my_y == 1) {
            my_z = in.read();
        }
    }
}

However, this gives me an error because my_y and my_z may not be initialized. These conditionals can be 5-6 levels deep, and I don't want to track which fields may not be read at each level of the branch tree. Another complication is that, based on certain flags, there may be subobjects that I would like to handle with the same pattern as the top-level structures.

class MyObject {
    final int my_x;
    final SubObject my_subobject;

    MyObject(InputStream is) {
        my_x = is.read();
        if (my_x == 1)
            my_subobject = new SubObject(is);
    }

    class SubObject {
        final int sub_x;
        final int sub_y;

        SubObject(InputStream is) {
            sub_x = is.read();
            if (sub_x == 1)
                sub_y = is.read();
        }
    }
}

Is there any way to make my fields final without twisting the code to handle each possible combination of flags?

Upvotes: 2

Views: 98

Answers (1)

Ted Hopp
Ted Hopp

Reputation: 234795

Use local variables and assign to the final fields at the end of the constructor.

public MyObject(InputStream is) {
    int x = default_x_value;
    int y = default_y_value;
    int z = default_z_value;
    x = in.read();
    if (x == 1) {
        y = in.read();
        if (y == 1) {
            z = in.read();
        }
    }
    my_x = x;
    my_y = y;
    my_z = z;
}

Alternatively (as Jon Skeet suggests in his comment), use a static factory method that computes the appropriate values for a no-default constructor:

public static MyObject makeMyObject(InputStream is) {
    int x = default_x_value;
    int y = default_y_value;
    int z = default_z_value;
    x = in.read();
    if (x == 1) {
        y = in.read();
        if (y == 1) {
            z = in.read();
        }
    }
    return new MyObject(x, y, z);
}

A third approach would be to define an initializer object class:

public class MyObject {
    private static class MyObjectInitializer {
        int x = default_x_value;
        int y = default_y_value;
        int z = default_z_value;
        MyObjectInitializer(InputStream is) {
            x = in.read();
            if (x == 1) {
                y = in.read();
                if (y == 1) {
                    z = in.read();
                }
            }
        }
    }
    public MyObject(InputStream is) {
        this(new MyObjectInitializer(is));
    }
    private MyObject(MyObjectInitializer init) {
        my_x = init.x;
        my_y = init.y;
        my_z = init.z;
    }
}

The initializer class may have utility on its own, in which case you could make it (and the corresponding MyObject constructor) public.

Upvotes: 4

Related Questions