|
Version 4.10.0 | ||||||||
PREV CLASS NEXT CLASS | FRAMES NO FRAMES | ||||||||
SUMMARY: NESTED | FIELD | CONSTR | METHOD | DETAIL: FIELD | CONSTR | METHOD |
Object hirondelle.web4j.model.ModelUtil
public final class ModelUtil
Collected utilities for overriding Object.toString()
, Object.equals(java.lang.Object)
,
and Object.hashCode()
, and implementing Comparable
.
All Model Objects should override the above Object
methods.
All Model Objects that are being sorted in code should implement Comparable
.
In general, it is easier to use this class with object fields (String, Date, BigDecimal, and so on), instead of primitive fields (int, boolean, and so on).
See below for example implementations of :
toString()
This class is intended for the most common case, where toString is used in
an informal manner (usually for logging and stack traces). That is,
the caller should not rely on the toString() text returned by this class to define program logic.
Typical example :
@Override public String toString() { return ModelUtil.toStringFor(this); }
There is one occasional variation, used only when two model objects reference each other. To avoid a problem with cyclic references and infinite looping, implement as :
@Override public String toString() { return ModelUtil.toStringAvoidCyclicRefs(this, Product.class, "getId"); }Here, the usual behavior is overridden for any method in 'this' object which returns a Product : instead of calling Product.toString(), the return value of Product.getId() is used instead.
hashCode()
Example of the simplest style :
@Override public int hashCode() { return ModelUtil.hashFor(getSignificantFields()); } ... private String fName; private Boolean fIsActive; private Object[] getSignificantFields(){ //any primitive fields can be placed in a wrapper Object return new Object[]{fName, fIsActive}; }
Since the Object.equals(java.lang.Object)
and
Object.hashCode()
methods are so closely related, and should always refer to the same fields,
defining a private method to return the Object[] of significant fields is highly
recommended. Such a method would be called by both equals and hashCode.
If an object is immutable, then the result may be calculated once, and then cached, as a small performance optimization :
@Override public int hashCode() { if ( fHashCode == 0 ) { fHashCode = ModelUtil.hashFor(getSignificantFields()); } return fHashCode; } ... private String fName; private Boolean fIsActive; private int fHashCode; private Object[] getSignificantFields(){ return new Object[]{fName, fIsActive}; }The most verbose style does not require wrapping primitives in an Object array:
@Override public int hashCode(){ int result = ModelUtil.HASH_SEED; //collect the contributions of various fields result = ModelUtil.hash(result, fPrimitive); result = ModelUtil.hash(result, fObject); result = ModelUtil.hash(result, fArray); return result; }
equals()
Simplest example, in a class called Visit (this is the recommended style):
@Override public boolean equals(Object aThat) { Boolean result = ModelUtil.quickEquals(this, aThat); if ( result == null ){ Visit that = (Visit) aThat; result = ModelUtil.equalsFor(this.getSignificantFields(), that.getSignificantFields()); } return result; } ... private final Code fRestaurantCode; private final Date fLunchDate; private final String fMessage; private Object[] getSignificantFields(){ return new Object[] {fRestaurantCode, fLunchDate, fMessage}; }Second example, in a class called Member :
@Override public boolean equals( Object aThat ) { if ( this == aThat ) return true; if ( !(aThat instanceof Member) ) return false; Member that = (Member)aThat; return ModelUtil.equalsFor(this.getSignificantFields(), that.getSignificantFields()); } ... private final String fName; private final Boolean fIsActive; private final Code fDisposition; private Object[] getSignificantFields(){ return new Object[]{fName, fIsActive, fDisposition}; }See note above regarding getSignificantFields().
More verbose example, in a class called Planet :
@Override public boolean equals(Object aThat){ if ( this == aThat ) return true; if ( !(aThat instanceof Planet) ) return false; Planet that = (Planet)aThat; return EqualsUtil.areEqual(this.fPossiblyNullObject, that.fPossiblyNullObject) && EqualsUtil.areEqual(this.fCollection, that.fCollection) && EqualsUtil.areEqual(this.fPrimitive, that.fPrimitive) && Arrays.equals(this.fArray, that.fArray); //arrays are different! }
compareTo()
The Comparable
interface is distinct, since it is not an overridable method of the
Object
class.
Example use case of using comparePossiblyNull, (where EQUAL takes the value 0) :
public int compareTo(Movie aThat) { if ( this == aThat ) return EQUAL; int comparison = ModelUtil.comparePossiblyNull(this.fDateViewed, aThat.fDateViewed, NullsGo.LAST); if ( comparison != EQUAL ) return comparison; //this field is never null comparison = this.fTitle.compareTo(aThat.fTitle); if ( comparison != EQUAL ) return comparison; comparison = ModelUtil.comparePossiblyNull(this.fRating, aThat.fRating, NullsGo.LAST); if ( comparison != EQUAL ) return comparison; comparison = ModelUtil.comparePossiblyNull(this.fComment, aThat.fComment, NullsGo.LAST); if ( comparison != EQUAL ) return comparison; return EQUAL; }
Nested Class Summary | |
---|---|
static class |
ModelUtil.NullsGo
Define hows null items are treated in a comparison. |
Field Summary | |
---|---|
static int |
HASH_SEED
Initial seed value for a hashCode. |
Method Summary | ||
---|---|---|
static boolean |
areEqual(boolean aThis,
boolean aThat)
Equals for boolean fields. |
|
static boolean |
areEqual(char aThis,
char aThat)
Equals for char fields. |
|
static boolean |
areEqual(double aThis,
double aThat)
Equals for double fields. |
|
static boolean |
areEqual(float aThis,
float aThat)
Equals for float fields. |
|
static boolean |
areEqual(long aThis,
long aThat)
Equals for long fields. |
|
static boolean |
areEqual(Object aThis,
Object aThat)
Equals for an Object. |
|
static
|
comparePossiblyNull(T aThis,
T aThat,
ModelUtil.NullsGo aNullsGo)
Utility for implementing Comparable . |
|
static boolean |
equalsFor(Object[] aThisSignificantFields,
Object[] aThatSignificantFields)
Return the result of comparing all significant fields. |
|
static int |
hash(int aSeed,
boolean aBoolean)
Hash code for boolean primitives. |
|
static int |
hash(int aSeed,
char aChar)
Hash code for char primitives. |
|
static int |
hash(int aSeed,
double aDouble)
Hash code for double primitives. |
|
static int |
hash(int aSeed,
float aFloat)
Hash code for float primitives. |
|
static int |
hash(int aSeed,
int aInt)
Hash code for int primitives. |
|
static int |
hash(int aSeed,
long aLong)
Hash code for long primitives. |
|
static int |
hash(int aSeed,
Object aObject)
Hash code for an Object. |
|
static int |
hashCodeFor(Object... aFields)
Return the hash code in a single step, using all significant fields passed in an Object sequence parameter. |
|
static Boolean |
quickEquals(Object aThis,
Object aThat)
Quick checks for possibly determining equality of two objects. |
|
static String |
toStringAvoidCyclicRefs(Object aObject,
Class aSpecialClass,
String aMethodName)
As in toStringFor(java.lang.Object) , but avoid problems with cyclic references. |
|
static String |
toStringFor(Object aObject)
Implements an override of Object.toString() (see class comment). |
Methods inherited from class Object |
---|
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait |
Field Detail |
---|
public static final int HASH_SEED
Method Detail |
---|
public static String toStringFor(Object aObject)
Example output format, for an Rsvp object with 4 fields :
hirondelle.fish.main.rsvp.Rsvp { Response: null MemberId: 4 MemberName: Tom Thumb VisitId: 13 }(There is no indentation since it causes problems when there is nesting.)
The only items which contribute to the result are :
These items are excluded from the result :
Object
Reflection is used to access field values. Items are converted to a String simply by calling their toString method, with the following exceptions :
Util.getArrayAsString(Object)
is used
If the method name follows the pattern 'getXXX', then the word 'get' is removed from the result.
WARNING: If two classes have cyclic references
(that is, each has a reference to the other), then infinite looping will result
if both call this method! To avoid this problem, use toStringFor
for one of the classes, and toStringAvoidCyclicRefs(java.lang.Object, java.lang.Class, java.lang.String)
for the other class.
aObject
- the object for which a toString() result is required.public static String toStringAvoidCyclicRefs(Object aObject, Class aSpecialClass, String aMethodName)
toStringFor(java.lang.Object)
, but avoid problems with cyclic references.
Cyclic references occur when one Model Object references another, and both Model Objects have their toString() methods implemented with this utility class.
Behaves as in toStringFor(java.lang.Object)
, with one exception: for methods of aObject that
return instances of aSpecialClass, then call aMethodName on such instances,
instead of toString().
public static final int hashCodeFor(Object... aFields)
Object
sequence parameter.
(This is the recommended way of implementing hashCode.)
Each element of aFields must be an Object
, or an array containing
possibly-null Objects. These items will each contribute to the
result. (It is not a requirement to use all fields related to an object.)
If the caller is using a primitive field, then it must be converted to a corresponding
wrapper object to be included in aFields. For example, an int field would need
conversion to an Integer
before being passed to this method.
public static int hash(int aSeed, boolean aBoolean)
public static int hash(int aSeed, char aChar)
public static int hash(int aSeed, int aInt)
Note that byte and short are also handled by this method, through implicit conversion.
public static int hash(int aSeed, long aLong)
public static int hash(int aSeed, float aFloat)
public static int hash(int aSeed, double aDouble)
public static int hash(int aSeed, Object aObject)
aObject is a possibly-null object field, and possibly an array.
Arrays can contain possibly-null objects or primitives; no circular references are permitted.
public static Boolean quickEquals(Object aThis, Object aThat)
This method exists to make equals implementations read more legibly, and to avoid multiple return statements.
It cannot be used by itself to fully implement equals.
It uses == and instanceof to determine if equality can be
found cheaply, without the need to examine field values in detail. It is
always paired with some other method
(usually equalsFor(Object[], Object[])
), as in the following example :
public boolean equals(Object aThat){ Boolean result = ModelUtil.quickEquals(this, aThat); if ( result == null ){ //quick checks not sufficient to determine equality, //so a full field-by-field check is needed : This this = (This) aThat; //will not fail result = ModelUtil.equalsFor(this.getSignificantFields(), that.getSignificantFields()); } return result; }
This method is unusual since it returns a Boolean that takes 3 values : true, false, and null. Here, true and false mean that a simple quick check was able to determine equality. The null case means that the quick checks were not able to determine if the objects are equal or not, and that further field-by-field examination is necessary. The caller must always perform a check-for-null on the return value.
public static boolean equalsFor(Object[] aThisSignificantFields, Object[] aThatSignificantFields)
Both Object[] parameters are the same size. Each includes all fields that have been deemed by the caller to contribute to the equals method. None of those fields are array fields. The order is the same in both arrays, in the sense that the Nth item in each array corresponds to the same underlying field. The caller controls the order in which fields are compared simply through the iteration order of these two arguments.
If a primitive field is significant, then it must be converted to a corresponding wrapper Object by the caller.
public static boolean areEqual(boolean aThis, boolean aThat)
public static boolean areEqual(char aThis, char aThat)
public static boolean areEqual(long aThis, long aThat)
Note that byte, short, and int are handled by this method, through implicit conversion.
public static boolean areEqual(float aThis, float aThat)
public static boolean areEqual(double aThis, double aThat)
public static boolean areEqual(Object aThis, Object aThat)
The objects are possibly-null, and possibly an array.
Arrays can contain possibly-null objects or primitives; no circular references are permitted.
public static <T extends Comparable<T>> int comparePossiblyNull(T aThis, T aThat, ModelUtil.NullsGo aNullsGo)
Comparable
. See class example
for illustration.
The Comparable
interface specifies that
blah.compareTo(null)should throw a
NullPointerException
. You should follow that
guideline. Note that this utility method itself
accepts nulls without throwing a NullPointerException
.
In this way, this method can handle nullable fields just like any other field.
There are
special issues
for sorting String
s regarding case, Locale
,
and accented characters.
aThis
- an object that implements Comparable
aThat
- an object of the same type as aThisaNullsGo
- defines if null items should be placed first or last
|
Version 4.10.0 | ||||||||
PREV CLASS NEXT CLASS | FRAMES NO FRAMES | ||||||||
SUMMARY: NESTED | FIELD | CONSTR | METHOD | DETAIL: FIELD | CONSTR | METHOD |