|
||||||||||
PREV CLASS NEXT CLASS | FRAMES NO FRAMES | |||||||||
SUMMARY: NESTED | FIELD | CONSTR | METHOD | DETAIL: FIELD | CONSTR | METHOD |
java.lang.Object org.mutabilitydetector.unittesting.matchers.reasons.FieldAssumptions
public final class FieldAssumptions
Allowed reasons for mutability warnings related to fields.
It is expected that this class will not be used directly. Instead, use the
factory methods provided by AllowedReason
for more fluent unit tests.
AllowedReason.assumingFields(Iterable)
,
AllowedReason.assumingFields(String, String...)
Method Summary | |
---|---|
org.hamcrest.Matcher<MutableReasonDetail> |
areModifiedAsPartOfAnUnobservableCachingStrategy()
Insists that while a field may have been mutated, changes will not be observable. |
org.hamcrest.Matcher<MutableReasonDetail> |
areNotModifiedAndDoNotEscape()
Insists that a mutable field is used safely. |
org.hamcrest.Matcher<MutableReasonDetail> |
areSafelyCopiedUnmodifiableCollectionsWithImmutableElements()
Insists fields of collection types are copied and wrapped safely. |
static FieldAssumptions |
named(Iterable<String> fieldNames)
Advice: use the factory method AllowedReason.assumingFields(String, String...) for greater
readability. |
static FieldAssumptions |
named(String firstFieldName,
String... otherFieldNames)
Advice: use the factory method AllowedReason.assumingFields(String, String...) for greater
readability. |
Methods inherited from class java.lang.Object |
---|
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait |
Method Detail |
---|
public static FieldAssumptions named(String firstFieldName, String... otherFieldNames)
AllowedReason.assumingFields(String, String...)
for greater
readability.
public static FieldAssumptions named(Iterable<String> fieldNames)
AllowedReason.assumingFields(String, String...)
for greater
readability.
public org.hamcrest.Matcher<MutableReasonDetail> areSafelyCopiedUnmodifiableCollectionsWithImmutableElements()
One way to use mutable collection types in an immutable class is to copy
the contents and wrap in an unmodifiable collection. Mutability Detector
has limited support for recognising this pattern, e.g.:
Collections.unmodifiableList(new ArrayList(original));
.
However, the methods used for copying and wrapping must be those
available in the JDK. If you are using your own, or third-party
collection types, Mutability Detector will raise a warning. This allowed
reason will permit those warnings.
Example usage:
import com.google.common.collect.Lists;
@Immutable
public final class SafelyCopiesAndWraps {
private final List<String> unmodifiableCopy;
public SafelyCopiesAndWraps(List original) {
// use Guava method to copy
this.unmodifiableCopy = Collections.unmodifiableList(Lists.newArrayList(original));
}
// ... other methods
}
// a warning will be raised because copy method, Guava's Lists.newArrayList(), is unrecognised
assertInstancesOf(SafelyCopiesAndWraps.class, areImmutable());
// use FieldAssumptions to insist the usage is safe
assertInstancesOf(SafelyCopiesAndWraps.class,
areImmutable(),
assumingFields("unmodifiableCopy").areSafelyCopiedUnmodifiableCollectionsWithImmutableElements());
This case will also work when the collection is declared (with generics) to contain a mutable type.
MutabilityReason.ABSTRACT_COLLECTION_TYPE_TO_FIELD
,
MutabilityReason.ABSTRACT_TYPE_TO_FIELD
,
MutabilityReason.COLLECTION_FIELD_WITH_MUTABLE_ELEMENT_TYPE
public org.hamcrest.Matcher<MutableReasonDetail> areNotModifiedAndDoNotEscape()
A requirement for immutability is "If the instance fields include references to mutable objects, don't allow those objects to be changed" [0]. This necessitates that any mutable fields are not modified (e.g. by calling a method which mutates it) and their reference is not published (where client code could invoke a mutating method). While greater care is needed, it is possible to create immutable objects composed of mutable fields.
Example usage:
import java.util.Date;
@Immutable
public final class UsesMutableField {
private final Date myDate;
public UsesMutableField(Date original) {
this.myDate = new Date(original.getTime());
}
public Date getDate() {
// if we used 'return myDate;' we would be publishing reference
return new Date(myDate.getTime());
}
// ... other methods, which never call myDate.setTime()
// if we called, e.g. setTime() we would be mutating the field
}
// a warning will be raised because myDate is of a mutable type, java.util.Date
assertInstancesOf(UsesMutableField.class, areImmutable());
// use FieldAssumptions to insist the usage is safe
assertInstancesOf(UsesMutableField.class,
areImmutable(),
assumingFields("myDate").areNotModifiedAndDoNotEscape());
Note: this allowed reason also assumes the defensive copy of
original
into myDate
, although there is
currently no support for automatically detecting this.
[0] A Strategy for Defining Immutable Objects
MutabilityReason.MUTABLE_TYPE_TO_FIELD
,
MutabilityReason.COLLECTION_FIELD_WITH_MUTABLE_ELEMENT_TYPE
,
MutabilityReason.ARRAY_TYPE_INHERENTLY_MUTABLE
public org.hamcrest.Matcher<MutableReasonDetail> areModifiedAsPartOfAnUnobservableCachingStrategy()
As described in the documentation for Java Concurrency In Practice's
@Immutable annotation, objects may maintain mutable state, as long
as the mutation is internal, and cannot be observed by callers. This can
be useful for providing caching within the object instance. A classic
example of this is the Open JDK's implementation of
String
. Each instance uses the hash
field
to cache the result of Object.hashCode()
. The hash field is computed
lazily, and the field is reassigned (a mutation), however clients of
String
can not observe the mutation as it is internal.
While this technique is tricky, it can be very useful for performance reasons. Unfortunately, Mutability Detector cannot tell the difference between: lazily storing the result of a computation for future lookup; and a setter method.
This allowed reason will permit mutable fields, and also reassigning field references.
Example usage:
import java.util.Date;
@Immutable
public static final class ReassignsHashCode {
private final String name;
private final Integer age;
private int hash;
public ReassignsHashCode(String name, Integer age) {
this.name = name;
this.age = age;
}
@Override
public int hashCode() {
if (hash == 0) {
hash = name.hashCode() + age.hashCode();
}
return hash;
}
}
// a warning will be raised because the hash field is reassigned, as with a setter method
assertInstancesOf(ReassignsHashCode.class, areImmutable());
// use FieldAssumptions to insist the usage is safe
assertInstancesOf(ReassignsHashCode.class,
areImmutable(),
assumingFields("hash").areModifiedAsPartOfAnUnobservableCachingStrategy());
MutabilityReason.MUTABLE_TYPE_TO_FIELD
,
MutabilityReason.COLLECTION_FIELD_WITH_MUTABLE_ELEMENT_TYPE
,
MutabilityReason.ARRAY_TYPE_INHERENTLY_MUTABLE
,
MutabilityReason.FIELD_CAN_BE_REASSIGNED
,
MutabilityReason.NON_FINAL_FIELD
|
||||||||||
PREV CLASS NEXT CLASS | FRAMES NO FRAMES | |||||||||
SUMMARY: NESTED | FIELD | CONSTR | METHOD | DETAIL: FIELD | CONSTR | METHOD |