1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
|
All fields in your enum class should be final and either be primitive or refer
to deeply immutable objects.
Static state is dangerous to begin with, but much worse for enums. We all think
of enum values as constants -- and even refer to them as "enum constants" -- and
would be very surprised if any of their state ever changed, or was not
thread-safe.
To make enums immutable, ensure:
* All fields are final.
* The types of all fields of the enum are deeply immutable. For example, use
`ImmutableList` and `ImmutableSet` instead of `List` and `Set`.
* Types are considered immutable if they are primitives, in a set of types
that are built in to Error Prone (e.g. `java.lang.String`,
`java.util.UUID`), or are annotated with
`com.google.errorprone.annotations.Immutable`.
Other versions of the annotation, such as
`javax.annotation.concurrent.Immutable`, are currently *not* recognized.
See https://errorprone.info/bugpattern/Immutable for additional discussion.
* If the type you're using inside the enum can be annotated with
`@Immutable`, you should do that:
```java
// WARNING: E is not immutable, since MyValueObject isn't Immutable
enum E {
private final MyValueObject mvo = new MyValueObject();
}
// Add @Immutable here
final class MyValueObject {}
```
Note that MyValueObject must actually be immutable. The
[Immutable](Immutable.md) checker will raise a compile-time error if the
`MyValueObject` class isn't actually immutable.
* If the type you're using inside the enum is not considered immutable,
and you can't annotate the type because it's outside the project,
consider using an immutable replacement of the type, or
[suppress this check on the enum](#suppression) with a comment about why
the fields in question are immutable.
TIP: annotating the declaration of the enum class with `@Immutable` is
unnecessary -- Error Prone assumes enums are immutable by default.
Example:
```java
import com.google.errorprone.annotations.Immutable;
@Immutable
class Foo {
final int id;
public Foo(int id) {
this.id = id;
}
}
```
```java
// The declaration doesn't need to be annotated with @Immutable.
enum E {
A("A", ImmutableList.of(new Foo(1), new Foo(2))),
B("B", ImmutableList.of(new Foo(3)));
// All fields are final, and deeply immutable:
// Error Prone knows String is immutable.
private final String name;
// Error Prone knows ImmutableList<T> is an immutable collection of some
// objects, and it recognizes the @Immutable annotation on the declaration of
// Foo, so it can safely determine that this ImmutableList is deeply
// immutable.
private final ImmutableList<Foo> foos;
private E(String name, ImmutableList<Foo> foos) {
this.name = name;
this.foos = foos;
}
public ImmutableList<Foo> foos() {
return foos;
}
public String name() {
return foos;
}
}
```
TIP: Instead of creating an enum with functional interface fields (`Predicate`,
`Function`, etc.), declare abstract methods that are overridden by each
constant. For example, do this:
```java
enum Types {
STRING {
@Override public boolean hasCompatibleType(Object o) {
return o instanceof String;
}
},
NUMBER {
@Override public boolean hasCompatibleType(Object o) {
return o instanceof Number;
}
},
// ...
public abstract boolean hasCompatibleType(Object o);
}
```
... not this:
```java
enum Types {
STRING(o -> o instanceof String),
NUMBER(o -> o instanceof Number),
// ...
final Predicate<Object> hasCompatibleType;
Types(Predicate<Object> hasCompatibleType) {
this.hasCompatibleType = hasCompatibleType;
}
}
```
This has several advantages on top of sidestepping this checker, e.g. not tying
you to a particular functional interface type -- your callers should e.g. use
`STRING::hasCompatibleType` instead of `STRING.hasCompatibleType` which only
works for one interface type.
|