How do I identify immutable objects in Java

后端 未结 15 1247
小蘑菇
小蘑菇 2020-11-30 22:40

In my code, I am creating a collection of objects which will be accessed by various threads in a fashion that is only safe if the objects are immutable. When an attempt is m

相关标签:
15条回答
  • 2020-11-30 22:52

    In my code, I am creating a collection of objects which will be accessed by various threads in a fashion that is only safe if the objects are immutable.

    Not a direct answer to your question, but keep in mind that objects that are immutable are not automatically guaranteed to be thread safe (sadly). Code needs to be side-effect free to be thread safe, and that's quite a bit more difficult.

    Suppose you have this class:

    class Foo {
      final String x;
      final Integer y;
      ...
    
      public bar() {
        Singleton.getInstance().foolAround();
      }
    }
    

    Then the foolAround() method might include some non-thread safe operations, which will blow up your app. And it's not possible to test for this using reflection, as the actual reference can only be found in the method body, not in the fields or exposed interface.

    Other than that, the others are correct: you can scan for all declared fields of the class, check if every one of them is final and also an immutable class, and you're done. I don't think methods being final is a requirement.

    Also, be careful about recursively checking dependent fields for immutability, you might end up with circles:

    class A {
      final B b; // might be immutable...
    }
    
    class B {
      final A a; // same so here.
    }
    

    Classes A and B are perfectly immutable (and possibly even usable through some reflection hacks), but naive recursive code will go into an endless loop checking A, then B, then A again, onwards to B, ...

    You can fix that with a 'seen' map that disallows cycles, or with some really clever code that decides classes are immutable if all their dependees are immutable only depending on themselves, but that's going to be really complicated...

    0 讨论(0)
  • I appreciate and admire the amount of work Grundlefleck has put into his mutability detector, but I think it is a bit of an overkill. You can write a simple but practically very adequate (that is, pragmatic) detector as follows:

    (note: this is a copy of my comment here: https://stackoverflow.com/a/28111150/773113)

    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.

    0 讨论(0)
  • 2020-11-30 22:54

    why do all the recommendations require the class to be final? if you are using reflection to check the class of each object, and you can determine programmatically that that class is immutable (immutable, final fields), then you don't need to require that the class itself is final.

    0 讨论(0)
  • 2020-11-30 22:55

    This could be another hint:

    If the class has no setters then it cannot be mutated, granted the parameters it was created with are either "primitive" types or not mutable themselves.

    Also no methods could be overriden, all fields are final and private,

    I'll try to code something tomorrow for you, but Simon's code using reflection looks pretty good.

    In the mean time try to grab a copy of the "Effective Java" book by Josh Block , it has an Item related to this topic. While is does not for sure say how to detect an inmmutable class, it shows how to create a good one.

    The item is called: "Favor immutability"

    link: http://java.sun.com/docs/books/effective/

    0 讨论(0)
  • 2020-11-30 22:56

    Basically no.

    You could build a giant white-list of accepted classes but I think the less crazy way would be to just write in the documentation for the collection that everything that goes is this collection must be immutable.

    Edit: Other people have suggested having an immutable annotation. This is fine, but you need the documentation as well. Otherwise people will just think "if I put this annotation on my class I can store it in the collection" and will just chuck it on anything, immutable and mutable classes alike. In fact, I would be wary of having an immutable annotation just in case people think that annotation makes their class immutable.

    0 讨论(0)
  • 2020-11-30 22:56

    Try this:

    public static boolean isImmutable(Object object){
        if (object instanceof Number) { // Numbers are immutable
            if (object instanceof AtomicInteger) {
                // AtomicIntegers are mutable
            } else if (object instanceof AtomicLong) {
                // AtomLongs are mutable
            } else {
                return true;
            }
        } else if (object instanceof String) {  // Strings are immutable
            return true;
        } else if (object instanceof Character) {   // Characters are immutable
            return true;
        } else if (object instanceof Class) { // Classes are immutable
            return true;
        }
    
        Class<?> objClass = object.getClass();
    
        // Class must be final
        if (!Modifier.isFinal(objClass.getModifiers())) {
                return false;
        }
    
        // Check all fields defined in the class for type and if they are final
        Field[] objFields = objClass.getDeclaredFields();
        for (int i = 0; i < objFields.length; i++) {
                if (!Modifier.isFinal(objFields[i].getModifiers())
                                || !isImmutable(objFields[i].getType())) {
                        return false;
                }
        }
    
        // Lets hope we didn't forget something
        return true;
    }
    
    0 讨论(0)
提交回复
热议问题