41
public class Test {
    private final String url;
    public Test(String url) {
        this.url = url;
    }
    public String getUrl() {
        return url;
    }
}

The Test class has:

  1. Only one instance variable which is private and final.
  2. No setters.
  3. The only way to initialize the instance variable is through the constructor.
  4. And once the URL is set, it can't be modified even in getUrl even if that method is overridden by any subclass of Test.

But a book that I am reading says the above Test class is mutable because:

  • Neither class is final so that it can be extended, and a subclass can override instance methods. But the Test class does not really have any instance methods other than the constructor.

  • Nor is the constructor private.

Can you please help me in understanding why the Test class is mutable?

cs95
  • 379,657
  • 97
  • 704
  • 746
Ram
  • 1,153
  • 4
  • 16
  • 34

2 Answers2

58

An arbitrary instance of Test isn't guaranteed to be immutable, although direct instances of Test are. But consider this subclass:

public class MutableTest extends Test {
        private int mutable;
        public MutableTest(String url) {
                super(url);
        }

        @Override
        public String getUrl() {
                return super.getUrl() + mutable++;
        }
}

Then you can write something like this:

Test instance = new MutableTest("http://example.com/");
String firstGet = instance.getUrl();
String secondGet = instance.getUrl();
assertEquals(firstGet, secondGet); // Boom!
gustafc
  • 28,465
  • 7
  • 73
  • 99
  • 1
    Agree, but still, the state of the object (the value of url in test object) is not modified. Its just that the caller is now getting different/wrong values. – Ram Jan 14 '19 at 08:04
  • 8
    @Ram The field `mutable` is added as a part of the state. So, the state is changed each time you call the getter. – bvdb Jan 14 '19 at 08:10
  • I guess from the user/caller's point of view, its not getting the expected correct url returned from Test instance, which breaks the contract and hence its not immutable. But as such the data is not modifiable though. Thanks. – Ram Jan 14 '19 at 08:14
  • 6
    "But as such the data is not modifiable though" - that depends on whose point of view you take. For the client, the field is `private`, so the only "data" that this class has is what you can get through `getUrl`. And that is modifiable, in fact even by simply calling the function. Imagine even that `getUrl` returns the same string, but still increases the counter... no consumer of `Test` can be sure (without ugly tricks, like inspecting the runtime type) that calling `getUrl` won't generate an overflow exception sometimes, but not others, depending on where your instance has been. – CompuChip Jan 14 '19 at 13:21
  • 3
    @CompuChip catches the essence. I can sort-of agree that this is splitting hairs, but nevertheless, an arbitrary `Test` instance cannot be guaranteed to be immutable (as it could just as well be a mutable subclass), although all fields in `Test` are final and immutable. And the _interface_ that `Test` presents to clients is definitely not immutable. – gustafc Jan 14 '19 at 15:16
  • 2
    Well yes but immutable does not mean it returns same value for each instance method call. The value it provide can change, it does not mean its state has changed. You can have a `RandomNumberGenerator` that is immutable (where you can not modify the seed for example) but returns you a new random number each time you ask it for one. – Koray Tugay Jan 14 '19 at 15:56
  • @KorayTugay I disagree. That would be a mutable class where the initial seed cannot be modified. When we use the term "immutable" we generally mean that if you pass around the instance on multiple threads there isn't any possibly race condition, it does not matter which methods are called in which order and how many times by the concurrent threads without synchronization. Your example would show race conditions without synchronizing method accesses so it's not an example of "immutable". – Bakuriu Jan 14 '19 at 18:52
  • @Bakuriu I think that would highly depend on the implementation of our imaginary RNG generator. The worst thing that can happen I can think of is returning the same random number from the sequence to 2 different threads (which may or may not be acceptable), which can be avoided as well and would not require synchronization in the caller. Otherwise an immutable class would just mean some raw data and nothing else. – Koray Tugay Jan 14 '19 at 19:10
  • @KorayTugay Yes, immutable classes _are_ just data, possibly with methods providing (immutable) views on that data. An RNG that returns a new value every time you call `rnd.next()` is as mutable as they get - the progression of the RNG is part of the instance's state (just have a look at `java.util.Random`!). An immutable RNG returns two things when you call `next`; the generated value and a new immutable RNG instance that has progressed one step, which you then can use to get the next random number and the next RNG instance, and so on. – gustafc Jan 15 '19 at 08:22
  • @gustafc I see, lets agree we do not agree then. (I never referred to java.util.Random btw so I will not respond to that part at all, no hard feelings please.) – Koray Tugay Jan 15 '19 at 14:00
  • 1
    @KorayTugay this is not a matter of agreeing on disagreeing. Immutability is a very well defined concept and a RandomNumberGenerator is by definition mutable. – Zo72 Jan 15 '19 at 17:20
  • 1
    @KorayTugay I don't see what there is to argue here. How does `next` return a different value every time? Because it changes some state. That's obvious. Hence it's mutable. The only "change of state" that an immutable object can do is *caching* a result or optimizing their internal structure to improve retrieval times (or changing random memory that is not used meaningfully in any way) – Bakuriu Jan 15 '19 at 17:48
0

An object that receives an object which is known to be of type Test would know the object to be immutable. An object receiving a non-null reference of type Test, however, would have no language-defined way of ensuring that the object identified thereby isn't mutable. Some people seem to regard this as a major worry. In general, however, I don't think it should be.

If a class is usefully inheritable, it will generally be easy to contrive a derived type that would be totally unsuitable for any non-contrived purpose. The only ways by which a language could even try to prevent that would be by greatly limiting the range of useful things that derived types can do. Outside of a few specialized kinds of classes (typically those related to security), however, it's generally better to ensure that derived classes can do useful things than worry about the possibility of them doing contrived and useless things.

supercat
  • 77,689
  • 9
  • 166
  • 211