C++ vs. Java: Mutability

« previous entry | next entry »
Feb. 23rd, 2004 | 04:41 am

This is inspired by a little private discussion we're having on Tahir's blog about his article on why he prefers C++ over Java: “Why I Program in C++”.

Java is missing a lot of C++'s features, and the converse is true as well. But one of C++'s features that I haven't yet learnt to live without in Java is the ability to have const references to objects, rendering the objects immutable through those references. In Java, either all instances of a class are immutable (e.g. java.lang.String), or none of them are immutable. Mutability of an object is determined by the design of its class and is in no way influenced by the way it is being referenced in the system. So all String objects in Java are immutable, whereas all ArrayLists are mutable.

To illustrate, here's a contact class in C++:

class Contact
{
private:
    string name;
public:
    void setName(const string& name)
    {
        this->name = name;
    }
    const string& getName() const
    {
        return name;
    }
};

Some sample C++ code where an object of Contact is created and referenced:

void print(const Contact&);

Contact myContact;
myContact.setName("Manish");
print(myContact);

void print(const Contact& c)
{
    cout << c.getName() << endl;
}

When the above code calls void print(const Contact&), the caller is guaranteed that the Contact object will remain unmodified, since it is illegal to call a non-const function like void Contact::setName(const string&) on a const reference. That way, the programmer can be sure that the void print(const Contact&) function does not mess with the data of the Contact object. This is very important; whether and in what way a function modifies an object passed to it should be part of the contract between the caller and the called.

Now, it is impossible to write an equivalent of the above code in Java:

class Contact
{
    private String name;
    public void setName(String name)
    {
        this.name = name;
    }
    public String getName()
    {
        return name;
    }
}

...

Contact myContact = new Contact();
myContact.setName("Manish");
print(myContact);

void print(Contact c)
{
    System.out.println(c.getName());
    /* XXX: It is possible to call c.setName(String) here */
}

When a reference to the Contact object is passed around the program, there's no way to ensure that the object is not mutated. In C++, passing const references does the job. It's possible in C++ to separate the mutator methods from the non-mutator methods; in Java, the entire class needs to be made immutable—java.lang.String being the best example of this—which adds significant overhead, amongst other things, because everytime an entirely immutable object needs to be modified, a new copy of it needs to be created with the modified contents. Besides this, in Java, there's no way to tell a mutable class from an immutable class without looking at its documentation. Mutability (immutability) is not a language feature in Java, in much the same way as vtables (that enable polymorphic behaviour in C++) are not a language feature in C—hackable but ugly.

Java newbies (and C++ newbies alike) tend to compare Java's final keyword with C++'s const keyword. While final does serve one of const's purposes of having constant values for variables (primitives and references), it does not help in marking objects as “not modifiable”.

Consider this Java code, where the Contact class is being used:

class AddressBook
{
    /** A list of {@link Contact} objects. */
    private List contacts;

    public void addContact(Contact c)
    {
        contacts.add(c);
    }

    public Contact getContactByName(String name)
    {
        Contact c = null;
        /* find contact by name */
        return c;
    }
}

Now, any reference to an AddressBook, final or otherwise:

  • Can be used to modify the state of the object by adding more Contacts.
  • Can be used to modify all contained objects, by calling getContactByName and then calling setName on the obtained Contact.

Poorly-designed classes like these make the public-private scheme in Java seem superficial. (It isn't of much help to have Contact.name and AddressBook.contacts private, now, is it? Yet I've had a hard time explaining this to Java programmers.)

With C++ it is even possible to have two versions of the getContactByName method, one called through non-const references and the other called through const references:

Contact& getContactByName(const string& name);
const Contact& getContactByName(const string& name) const;
/* Note the difference in return types (const-ness) */

In Java, the desired effect of making AddressBook objects appear as mutable to certain parts of the system and as immutable to other parts can be achieved by using access specifiers (private, public, protected, and package-level). The addContact method can be made package-level, and those parts of the code that must add contacts to an AddressBook can become part of the same package. These are the “trusted” parts of the system. The getContactByName method can return a copy of the Contact object. (Hint: Override clone.)

I believe this very basic feature of C++ may have been left out of Java to avoid making the language too complex (easing its early and widespread adoption). It's difficult to live without it though.

Language does influence the way we think, and as much as purists like to deny it, a programming language does significantly influence program design decisions.

Link