12

I want to make sure that a given group of objects is immutable.

I was thinking about something along the lines of:

  1. check if every field is private final
  2. check if class is final
  3. check for mutable members

So I guess my question is: is 3. possible ?

I can check recursively whether every member of a class has its fields private final, but this is not enough since a class can have e method named getHaha(param) which adds the given param to an array for instance.

So is there a good way to check if an object is immutable or is it even possible ?

Thanks,

Simeon
  • 7,582
  • 15
  • 64
  • 101
  • 1
    possible duplicate of [How do I identify immutable objects in Java](http://stackoverflow.com/questions/203475/how-do-i-identify-immutable-objects-in-java) – dogbane Jul 19 '11 at 07:58
  • There are ways to reliably detect immutability in Java. It's a huge subject, though, on how to go about figuring that out. I have provided an Answer to another very similar question addressing how you might go about thoroughly approaching this problem space. stackoverflow.com/a/75043881/501113 – chaotic3quilibrium Jan 07 '23 at 21:48

6 Answers6

5

You may want to check out this project:

Mutability Detector

This library attempts to analyse the bytecode of a particular class, to discover if it is immutable or not. It allows testing for this condition in a unit test, as demonstrated in a video available here. It is certainly not perfect (a String field will be considered mutable, and your array example is not handled well) but it's more sophisticated than what FindBugs offers (i.e. only checking that every field is final).

Disclaimer: I wrote it ;-)

Grundlefleck
  • 124,925
  • 25
  • 94
  • 111
3

Yes, you can write an immutability detector.

First of all, you are not going to be just writing a method which determines whether a class is immutable; instead, you will need to write an immutability detector class, because it is going to have to maintain some state. The state of the detector will be the detected immutability of all classes which it has examined so far. This is not only useful for performance, but it is actually necessary because a class may contain a circular reference, which would cause a simplistic immutability detector to fall into infinite recursion.

The immutability of a class has four possible values: Unknown, Mutable, Immutable, and Calculating. You will probably want to have a map which associates each class that you have encountered so far to an immutability value. Of course, Unknown does not actually need to be implemented, since it will be the implied state of any class which is not yet in the map.

So, when you begin examining a class, you associate it with a Calculating value in the map, and when you are done, you replace Calculating with either Immutable or Mutable.

For each class, you only need to check the field members, not the code. The idea of checking bytecode is rather misguided.

First of all, you should not check whether a class is final; The finality of a class does not affect its immutability. Instead, a method which expects an immutable parameter should first of all invoke the immutability detector to assert the immutability of the class of the actual object that was passed. This test can be omitted if the type of the parameter is a final class, so finality is good for performance, but strictly speaking not necessary. Also, as you will see further down, a field whose type is of a non-final class will cause the declaring class to be considered as mutable, but still, that's a problem of the declaring class, not the problem of the non-final immutable member class. It is perfectly fine to have a tall hierarchy of immutable classes, in which all the non-leaf nodes must of course be non-final.

You should not check whether a field is private; it is perfectly fine for a class to have a public field, and the visibility of the field does not affect the immutability of the declaring class in any way, shape, or form. You only need to check whether the field is final and its type is immutable.

When examining a class, what you want to do first of all is to recurse to determine the immutability of its super class. If the super is mutable, then the descendant is by definition mutable too.

Then, you only need to check the declared fields of the class, not all fields.

If a field is non-final, then your class is mutable.

If a field is final, but the type of the field is mutable, then your class is mutable. (Arrays are by definition mutable.)

If a field is final, and the type of the field is Calculating, then ignore it and proceed to the next field. If all fields are either immutable or Calculating, then your class is immutable.

If the type of the field is an interface, or an abstract class, or a non-final class, then it is to be considered as mutable, since you have absolutely no control over what the actual implementation may do. This might seem like an insurmountable problem, because it means that wrapping a modifiable collection inside an UnmodifiableCollection will still fail the immutability test, but it is actually fine, and it can be handled with the following workaround.

Some classes may contain non-final fields and still be effectively immutable. An example of this is the String class. Other classes which fall into this category are classes which contain non-final members purely for performance monitoring purposes (invocation counters, etc.), classes which implement popsicle immutability (look it up), and classes which contain members that are interfaces which are known to not cause any side effects. Also, if a class contains bona fide mutable fields but promises not to take them into account when computing hashCode() and equals(), then the class is of course unsafe when it comes to multi-threading, but it can still be considered as immutable for the purpose of using it as a key in a map. So, all these cases can be handled in one of two ways:

  1. Manually adding classes (and interfaces) to your immutability detector. If you know that a certain class is effectively immutable despite the fact that the immutability test for it fails, you can manually add an entry to your detector which associates it with Immutable. This way, the detector will never attempt to check whether it is immutable, it will always just say 'yes, it is.'

  2. Introducing an @ImmutabilityOverride annotation. Your immutability detector can check for the presence of this annotation on a field, and if present, it may treat the field as immutable despite the fact that the field may be non-final or its type may be mutable. The detector may also check for the presence of this annotation on the class, thus treating the class as immutable without even bothering to check its fields.

I hope this helps future generations.


EDIT in 2023

I ended up writing a library that implements all of the above.

You can find it here: https://github.com/mikenakis/Bathyscaphe

In my opinion, static analysis tools such as MutabilityDetector (see Grundlefleck's answer) can be useful at certain times and under certain scenarios, but they do not (and cannot) provide a complete solution to the problem at all times. A complete solution requires runtime checking as I explain on my blog here: https://blog.michael.gr/2022/05/bathyscaphe.html, and that's why I have written Bathyscaphe.

Mike Nakis
  • 56,297
  • 11
  • 110
  • 142
  • 1
    Pretty much all of what you said is spot on, and is complexity that is arrived at fairly quickly. I hope future generations will not implement this themselves, but will instead use and potentially contribute to https://github.com/MutabilityDetector/MutabilityDetector which has already encountered many and more of the problems you described :) – Grundlefleck Apr 07 '17 at 15:00
  • There are ways to reliably detect immutability in Java. It's a huge subject, though, on how to go about figuring that out. I have provided an Answer to another very similar question addressing how you might go about thoroughly approaching this problem space. stackoverflow.com/a/75043881/501113 – chaotic3quilibrium Jan 07 '23 at 21:49
3

If you generate your data model and all its code, you can ensure the possible Data Value objects you create will be immutable to meet your needs.

The problem you have is that there is different forms of immutability. Even String would fail your test Are String, Date, Method immutable? You can prove that a class is strictly immutable this way, but you are likely to be better off generating your data model.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • 1
    To generate a data model, I start with a class with just the fields I want. After compiling this, I generate code which fills in the methods, constructors and builders. (In the template class the fields are non-final, in the generated class they are final) – Peter Lawrey Jul 19 '11 at 08:06
1

I doubt you can do this with unit tests. The best way would be to be careful during writing the class or looking into the code. Precisely because of the problem that methods on the object can mutate its state which you might not see from the outside. Just because it's discouraged doesn't mean it doesn't happen :-)

Joey
  • 344,408
  • 85
  • 689
  • 683
1

Pretty sure it is impossible. Consider this function:

public void doSomething() {
    if (System.currentTimeMillis() % 100000 == 0) {
        this.innerMember.changeState();
    }
}

First, you won't be able to detect it by running every class function, as this function changes the state of object precisely only once in 100 seconds.

Second, you won't be able to detect it by parsing code, as you do not know if changeState() function changes the state of innerMember or not.

bezmax
  • 25,562
  • 10
  • 53
  • 84
  • You can look at the byte code for this class and the classes it uses. However this is a lot of effort for not much gain IMHO. – Peter Lawrey Jul 19 '11 at 07:59
  • 1
    @Peter Lawrey: Not really, what if `changeState()` uses reflection to change it's state? Didn't include this case to not overcomplicate things, but in theory it is possible, and your bytecode scan method won't find it. – bezmax Jul 19 '11 at 08:01
  • 2
    You would reject any class which uses reflections as unsafe. There is no reason a Data Value object should be using reflection. – Peter Lawrey Jul 19 '11 at 08:05
1

This thread can help How do I identify immutable objects in Java. Take a look at the second popular answer, it might be possible to check for any immutability problems with FindBugs. If you run it on every commit then you can call it a unit test :)

EDIT

It seems that FindBugs only check for final, that's not much. You could implement your own rule according to you patterns and classes which you use in the code.

Community
  • 1
  • 1
denis.solonenko
  • 11,645
  • 2
  • 28
  • 23