Search code examples
javacoding-styleimmutability

Why a modification of the created object is bad in Java?


We all know that objects in Java can be modified.

I want to present three options when working with an object and indicate their advantages and disadvantages.

public class SetterApplication {
    public static void main(String[] args) {
        User john = new User("John", 22);
        increaseAge(john, 2);
        System.out.println(john);       // User{name='John', age=24}

        User johnUpper = upperName(john);
        System.out.println(johnUpper);  // User{name='JOHN', age=24}
        System.out.println(john);       // User{name='JOHN', age=24}

        User johnLower = lowerName(john);
        System.out.println(johnLower);   // User{name='john', age=24}
        System.out.println(johnUpper);   // User{name='JOHN', age=24}
    }

    /**
     * Case #1
     * change the object passed in and return nothing
     * @param user user
     * @param inc inc
     */
    public static void increaseAge(User user, Integer inc) {
        user.setAge(user.getAge() + inc);
    }

    /**
     * Case #2
     * change the passed object and return the changed object
     * @param user user
     * @return User
     */
    public static User upperName(User user) {
        user.setName(user.getName().toUpperCase());
        return user;
    }

    /**
     * Case #3
     * do not change the passed object and return a completely new object
     * @param user user
     * @return User
     */
    public static User lowerName(final User user) {
        return new User(user.getName().toLowerCase(), user.getAge());
    }
}

class User {
    private String name;
    private Integer age;

    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("User{");
        sb.append("name='").append(name).append('\'');
        sb.append(", age=").append(age);
        sb.append('}');
        return sb.toString();
    }
}

"Case #1" - the most common way, but to contain the corresponding disadvantage, is that the existing object is modified. But this behavior is predictable, since we do not return anything.

"Case #2" - also often found in code. But this case is misleading - because it returns a modified transferred object, which can be misleading that we are returning a new object, but this is not true.

"Case #3" - this method is the most fashionable way. Where we do not modify the transferred object and return a new object. But in this method there is a huge disadvantage that in Java it is not easy to implement (deep) cloning and the second is that it can be very resource-intensive, the object can be very large.

My question is which of the three options is preferable? What do you prefer to use? And which of the options do you consider the worst?


Solution

  • This is a broad topic. Anyway immutability is always to prefer.
    As coding immutable classes is usually tedious (you may want to provide a copy constructor, or a copy method. And what if I want to copy only a certain subset of its properties?) I use the Immutables library.

    Just don't make your class implements Cloneable because you want to use the clone method.
    Avoid that one. You'd also need an explicit cast.