Hay libros buenos y hay libros excelentes. “Effective Java” es uno de esos pocos libros que simplemente uno hubiera querido leer al principio de la carrera, cuando estaba empezando a programar. El libro es conciso, con ejemplos prácticos y sobre todo con explicación de técnicas avanzadas de programación que se espera un desarrollador profesional tenga a la mano.
Por ejemplo, yo nunca había tenido la necesidad de “opacar” los métodos “equals” o “hashCode” para hacer comparaciones profundas, pero el autor lo explica muy bien (por ejemplo, no habia visto a nadie en ningún libro proponer un método simple para implementar hashCode). La explicación de la interfaz “comparable” es muy clara y útil también. El siguiente ejemplo (un poco largo, lo sé) muestra como hacer todo lo anterior en una clase de Java trivial:
1:import java.util.HashMap;
2:
3:/**
4: * This is a very basic class, just to test the proper methods equal and hasCode method overriding.
5: * This code is distributed under the GNU GPL Licence.
6: * @author José Vicente Núñez Zuleta (josevnz@users.sourceforge.net)
7: * @version 0.1 - 11/04/2004
8: * @since 0.1
9: */
10:public class DataContainer implements Comparable {
11:
12: private float salary;
13: private double savings;
14: private int age;
15: private String fullname;
16: private boolean married;
17: private String [] brothers;
18: private boolean isValid;
19:
20: /**
21: * Parametric constructor. It only provides basic validations, nothing fancy here
22: * @param salary
23: * @param savings
24: * @param age
25: * @param fullname
26: * @param married
27: * @param brothers
28: * @throws IllegalArgumentException
29: * @throws NullPointerException
30: */
31: public DataContainer(float salary, double savings, int age, String fullname, boolean married, String [] brothers) throws IllegalArgumentException, NullPointerException {
32:
33: if (salary < 0) {
34: throw new IllegalArgumentException("The salary cannot be negative");
35: }
36: if (savings < 0) {
37: throw new IllegalArgumentException("The savings cannot be negative");
38: }
39: if (age < 1 && age > 100) {
40: throw new IllegalArgumentException("Age must be between [1-100]");
41: }
42: if (fullname == null) {
43: throw new NullPointerException("Please provide the full name");
44: }
45: if (brothers == null) {
46: throw new NullPointerException("Please provide information about your brothers name (if any)");
47: }
48: this.salary = salary;
49: this.savings = savings;
50: this.age = age;
51: this.fullname = fullname;
52: this.married = married;
53: this.brothers = brothers;
54: this.isValid = true;
55: }
56:
57: /**
58: * Calling this method ensures that some memory is release. Any other method call after this one will result in an error
59: */
60: public void destroy() {
61: if (isValid) {
62: fullname = null;
63: if (brothers != null) {
64: for (int idx = 0; idx < brothers.length; idx++) {
65: brothers[idx] = null;
66: }
67: }
68: isValid = false;
69: }
70: }
71:
72: /**
73: * Last chance to release resources
74: */
75: protected void finalize() throws Throwable {
76: destroy();
77: super.finalize();
78: }
79:
80: /**
81: * Get the salary
82: * @return float
83: */
84: public float getSalary() {
85: if (! isValid) {
86: throw new IllegalStateException();
87: }
88: return salary
89: ;}
90:
91: /**
92: * get the savings
93: * @return double
94: */
95: public double getSavings() {
96: if (! isValid) {
97: throw new IllegalStateException();
98: }
99: return savings;
100: }
101:
102: /**
103: * Get the age
104: * @return int
105: */
106: public int getAge() {
107: if (! isValid) {
108: throw new IllegalStateException();
109: }
110: return age;
111: }
112:
113: /**
114: * Get the full name
115: * @return String
116: */
117: public String getFullname() {
118: if (! isValid) {
119: throw new IllegalStateException();
120: }
121: return fullname;
122: }
123:
124: /**
125: * It is married
126: * @return boolean
127: */
128: public boolean getMarried() {
129: if (! isValid) {
130: throw new IllegalStateException();
131: }
132: return married;
133: }
134:
135: /**
136: * Get the brother names (if any)
137: * @return String []
138: */
139: public String [] getBrothers() {
140: if (! isValid) {
141: throw new IllegalStateException();
142: }
143: String [] brotherCopy = new String[brothers.length];
144: System.arraycopy(brothers, 0, brotherCopy, 0, brotherCopy.length);
145: return brotherCopy;
146: }
147:
148: /**
149: * Check if two objects are equal
150: * @param obj The object to compare
151: * @return boolean
152: * @since 0.1
153: */
154: public boolean equals(Object obj) {
155: if (! isValid) {
156: throw new IllegalStateException();
157: }
158: boolean status = false;
159: if (obj instanceof DataContainer) {
160: DataContainer cont = (DataContainer) obj;
161: if (
162: salary == cont.getSalary() &&
163: savings == cont.getSavings() &&
164: age == cont.getAge() &&
165: married == cont.getMarried() &&
166: (fullname == null? cont.getFullname() == null: fullname.equals(cont.getFullname())) &&
167: (brothers == null? cont.getBrothers() == null: brothers.length == cont.getBrothers().length)
168: ) {
169: String [] brothers = cont.getBrothers();
170: int idx = 0;
171: for (idx = 0; idx < brothers.length; idx++) {
172: if (! this.brothers[idx].equals(brothers[idx])) {
173: break;
174: }
175: }
176: if (idx == brothers.length) {
177: status = true;
178: }
179: }
180: }
181: return status;
182: }
183:
184: /**
185: * Generate the hashCode for the object
186: * @return int
187: * @since 0.1
188: */
189: public int hashCode() {
190: if (! isValid) {
191: throw new IllegalStateException();
192: }
193: int result = 17;
194: result = result * 37 + ((int)(Double.doubleToLongBits(savings)^(Double.doubleToLongBits(savings) >> 32)));
195: result = result * 37 + Float.floatToIntBits(salary);
196: result = result * 37 + age;
197: result = result * 37 + fullname == null? 0: fullname.hashCode();
198: result = result * 37 + (married? 0: 1);
199: result = result * 37 + (isValid? 0: 1);
200: if (brothers != null) {
201: for (int idx = 0; idx < brothers.length; idx++) {
202: if (brothers[idx] == null) {
203: result = result * 37 + 0;
204: } else {
205: result = result * 37 + brothers[idx].hashCode();
206: }
207: }
208: } else {
209: result = result * 37 + 0;
210: }
211: return result;
212: }
213:
214: /**
215: * This is the method called for command line execution
216: * @param args
217: * @throws Exception
218: * @since 0.1
219: */
220: public static void main (String [] args) throws Exception {
221: String [] bro = {"Curly", "Larry", "Moe"};
222: DataContainer cont1 = new DataContainer(100.0f, 1000.56, 50, "Pedro", true, bro);
223: HashMap map = new HashMap();
224: map.put(cont1, "Pedro");
225: System.out.println(cont1 + ", hash code: " + cont1.hashCode());
226: DataContainer cont2 = new DataContainer(100.0f, 1000.56, 50, "Pedro", true, bro);
227: System.out.println(cont2 + ", hash code: " + cont1.hashCode());
228: DataContainer cont3 = new DataContainer(400.0f, 1500.56, 28, "Jose", false, bro);
229: System.out.println(cont3 + ", hash code: " + cont1.hashCode());
230: if (cont1.equals(cont2) && cont2.equals(cont1)) {
231: System.out.println(cont1 + " == " + cont2);
232: }
233: if (! cont3.equals(cont2) && ! cont2.equals(cont3)) {
234: System.out.println(cont3 + " != " + cont2);
235: }
236: if (map.get(new DataContainer(100.0f, 1000.56, 50, "Pedro", true, bro)) != null) {
237: System.out.println("Name is: " + map.get(new DataContainer(100.0f, 1000.56, 50, "Pedro", true, bro)));
238: } else {
239: System.out.println("Name not found, error in hashCode?");
240: }
241: }
242:
243: /**
244: * Clone operator is not supported. Throw an exception always.
245: * @return Object
246: * @throws CloneNotSupportedException
247: */
248: public Object clone() throws CloneNotSupportedException {
249: throw new CloneNotSupportedException();
250: }
251:
252: /**
253: * String representation of the object
254: * @return String
255: */
256: public String toString() {
257: if (! isValid) {
258: throw new IllegalStateException();
259: }
260: StringBuffer repr = new StringBuffer("{");
261: repr.append(salary + ", ");
262: repr.append(savings + ", ");
263: repr.append(age + ", ");
264: repr.append(fullname + ", ");
265: repr.append(married + ", [");
266: for (int idx = 0; idx < brothers.length; idx++) {
267: repr.append(brothers[idx]);
268: if (idx < brothers.length -1) {
269: repr.append(", ");
270: }
271: }
272: repr.append("], " + isValid + "}");
273: return repr.toString();
274: }
275:
276: /**
277: *
278: *
279: *
280: */
281: public int compareTo(Object obj) {
282: int status = 0;
283:
284: return status;
285: }
286:
287:}
La salida de este programa sería algo como esto:
[josevnz@god tmp]$ java DataContainer
{100.0, 1000.56, 50, Pedro, true, [Curly, Larry, Moe], true}, hash code: -837126408
{100.0, 1000.56, 50, Pedro, true, [Curly, Larry, Moe], true}, hash code: -837126408
{400.0, 1500.56, 28, Jose, false, [Curly, Larry, Moe], true}, hash code: -837126408
{100.0, 1000.56, 50, Pedro, true, [Curly, Larry, Moe], true} == {100.0, 1000.56, 50, Pedro, true, [Curly, Larry, Moe], true}
{400.0, 1500.56, 28, Jose, false, [Curly, Larry, Moe], true} != {100.0, 1000.56, 50, Pedro, true, [Curly, Larry, Moe], true}
Name is: Pedro
[{400.0, 1500.56, 23, Andres, true, [Curly, Larry, Moe], true}, {400.0, 1500.56, 28, Jose, false, [Curly, Larry, Moe], true}, {100.0, 1000.56, 50, Pedro, true, [Curly, Larry, Moe], true}]
[josevnz@god tmp]$
Aqui hay un enlace a una entrevista con el autor, muy ilustrativa, la cual es casí un resumén de los planteamientos hechos en el libro. Si bien hay algunos tópicos en los que es un poco conservador (pudiera ser más pragmático) es definitivamente una muestra de como el buen diseño puede ser incorporado a los programas sin sacrificar claridad.
Si tiene el dinero, entonces le recomiendo que se compré este libro y que lo tenga como una referencia para programar mejor en Java. No se va a decepcionar.
Para comparar si dos objetos son exacatmente iguales en vez de hacer esa extensa implementacion del metodo equals podias haber simplemente usado una comparacion sobre el metodo hashCode de ambos objetos 😉