@Documented @Retention(value=RUNTIME) @Target(value=TYPE) public @interface ValueObject
Boolean, Integer, ArrayList, and
HashSet.
This annotation is currently verified, but not defined, by
restricting which Annotation.equals(Object) and Annotation.hashCode() methods
instances invoke at runtime. All value objects must override these
methods on Object. The override of the
Annotation.equals(Object) and Annotation.hashCode() methods is allowed to be done
within a parent type (for a good example of this approach, see the
implementation of the AbstractList class—and note
that this class is declared abstract).
Equality comparisons on value objects are checked in code outside of its
defining type hierarchy: no client code may perform a comparison of a value
object with another object using ==, the Annotation.equals(Object)
method must be used for all comparisons.
A type may not be annotated with both @ValueObject and
@ReferenceObject.
Subtypes of a type annotated with @ValueObject must also be
explicitly annotated @ValueObject. It is a modeling error
if they are not. In addition, only the leaf nodes of a hierarchy of classes
annotated with @ValueObject are allowed to be
instantiable—all parent classes (except for Object)
must be declared abstract. This is because there is no way to
extend an instantiable class and add a value component while preserving the
Annotation.equals(Object) contract in the Java programming language. Please
refer to Item 8: Obey the general contract when overriding equals in
Effective Java (Second Edition) by Joshua Bloch (Addison-Wesley 2008).
for further information about this problem.
An interface may be annotated with @ValueObject. In this
case all subtypes must also be explicitly annotated with
@ValueObject. It is a modeling error if they are not. In
some cases this may be useful to get ensure that no comparisons with the
interface type are using ==.
An annotation type declaration may not be annotated with
@ValueObject.
All enum type declarations are reference objects simply due the
restrictions imposed by the Java programming language. They are treated as if
they were annotated with @ReferenceObject—but no
user-supplied annotation is needed, it is implied. It is a modeling problem
for any enum type to be annotated as either an
@ReferenceObject or a @ValueObject.
@ValueObject is determined by its attributes. Two distinct
value objects in the heap may be equal.
Annotation.equals(Object) and Annotation.hashCode()
methods.
@Mutable
@ValueObject
public final class Point {
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public Point(Point p) {
x = p.x;
y = p.y;
}
private int x, y;
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + x;
result = prime * result + y;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Point other = (Point) obj;
if (x != other.x)
return false;
if (y != other.y)
return false;
return true;
}
}
The following client code would be identified as problematic.
public class Base {
Point center = new Point(1, 1);
public void setCenter(Point p) {
if (p != null && p != center) {
center = p;
}
}
public boolean isCenter(Point p) {
return p != null && p == center;
}
...
}
The comparison p != center in the setCenter method uses
reference equality rather than value equality—it should be replaced
with either !p.equals(center) or !center.equals(p). The
comparison p == center in the isCenter method also uses
reference equality rather than value equality—it should be replaced
with either p.equals(center) or center.equals(p).
It is also possible to have immutable value objects, we could change
Point implementation to be immutable as shown in the example code
below.
@Immutable
@ValueObject
public final class Point {
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public Point(Point p) {
x = p.x;
y = p.y;
}
private final int x, y;
public int getX() {
return x;
}
public int getY() {
return y;
}
@Override
public int hashCode() {
// SAME
}
@Override
public boolean equals(Object obj) {
// SAME
}
}
Note that the client code shown in the Base class above would
still be problematic even if the immutable implementation of
Point is used. To change the Point class into a reference
object a Java implementation of the Flyweight pattern must be
implemented. This design pattern is described by Gamma, Helm, Johnson, and
Vlissides in Design Patterns: Elements of Reusable Object-Oriented
Software (Addison-Wesley 1995). One such approach is shown in the code
below. Note that the code below is likely to be much slower than
implementing Point as we did above.
@Immutable
@ReferenceObject
public final class Point {
private static final List<Point> INSTANCES = new ArrayList<Point>();
public static Point getOnlyInstance(int x, int y) {
for (Point p : INSTANCES) {
if (p.x == x && p.y == y)
return p;
}
Point result = new Point(x, y);
INSTANCES.add(result);
return result;
}
private Point(int x, int y) {
this.x = x;
this.y = y;
}
public Point(Point p) {
x = p.x;
y = p.y;
}
private final int x, y;
public int getX() {
return x;
}
public int getY() {
return y;
}
}
@annotate tag.
/**
* @annotate ValueObject
*/
public final class Point {
...
}
Copyright © 2012 Surelogic, Inc.. All Rights Reserved.