Top 5 errores que comenten los programadores Java
Este artículo describe lo que el autor considera que son los cinco errores más comunes que los programadores Java cometen. El primero consiste en creer que estamos sobreescribiendo un método cuando realmente no lo estamos haciendo como por ejemplo:
public class MyWindowListener extends WindowAdapter { public void WindowDeactivate(WindowEvent e) { System.exit(0); } });
Un problema que se soluciona fácilmente en la actualidad empleando @Override. El siguiente es confundir paso por valor y paso por referencia, algo muy común en los programadores novatos que se creen que, por ejemplo, al pasar un objeto a un método y modificarlo dentro, pensando que el objeto que habían pasado inicialmente "no se va a modificar porque el método trabaja con una copia del original".
El tercero error de la lista es dejar vacíos los bloques catch. El siguiente error es el típico de confundir creer que "==" sirve para comparar por igualdad (equals ()), y no por identidad. Aunque en esencia lo que dice el artículo original es cierto, su ejemplo no lo es:
String string1 == “string1”; String string2 == “string1”; boolean iguales = string1 ==string2;
Después de ejecutar este código, al menos en cualquier máquina virtual moderna que yo conozco, iguales vale "true", aunque "no por el motivo adecuado" ;). El último error son lo que él llama errores de capitalización, usar mayúsculas cuando no se deben usar o viceversa. Este, francamente, no lo entiendo. El compilador suele avisar de un error de esta naturaleza, a no ser que estemos hablando de temas como creer que estamos sobreescribiendo un método cuando no lo estamos haciendo.
Yo a esta lista añadiría el creer que los objetos "File" son ficheros que están en el disco duro, y no meramente rutas en un sistema de ficheros, y escribir código del tipo:
File file = new File("path aqui"); // Si el fichero no existe if(file==null) { ... }
O el invocar métodos que devuelven algo sobre objetos inmutables, pensando que el objeto inmutable va a cambiar:
String text0 = "HOLA"; texto.toLowerCase();
¿Cuales son otros errores comunes de programación en Java según vuestra experiencia?
Reader Comments (23)
Uno que veo seguido: por algún motivo es necesario recorrer todos los elementos de un Map, y el programador usa
for(String key: map.keySet()){
System.out.println("key:" + key + " " + map.get(key));
}
en vez de
for(Map.Entry<String, String> entry: map.entrySet()){
System.out.println("key:" + entry.getKey() + " " + entry.getValue());
}
Ja ja ja. Se puede hacer una lista tremenda.
En estos que pongo, algunos son "comunes" y otros no tanto pero las consecuencias son bastantes costosas de arreglar o fastidian bastante.
1. No es un error pero si una mejora.
poner variable.equals("valor") en vez de "valor".equals(variable) y que te llegue que variable vale null.
2. Convertir un for de ascendente a descendente y olvidarte cambiar el ++;
for (int n=0;n<CONS;n++) --> for (int n=CONS-1;n>=0;n++) . ¡Como fastidia cuando te das cuenta tarde!
3. Importar una clase con Ctrl+May+O (Eclipse) y que aparezcan dos iguales (en diferentes paquetes) que pueden ser candidatas y tienen ambas buena pinta. Momentazo "me la juego al...".
4. Escribir un delete from y olvidar poner el where. //Facepalm 2 segundos más tarde.
5. Querer comentar un amplio código (muchas líneas e incluso una clase) con /* */ y justo por el medio había otro /* */ en medio.
6. while(true) insertaEnLog(); //Donde pone true, poner cualquier condición que no acabe nunca. A partir de aquí, si no te dás cuenta, dejas el ordenador sin HD y según como esté la instalación y que sistema operativo tiene puede incluso no poder rearrancar.
......
Uno que no es un error ni tampoco es especifico de java, pero lo veo muy habitualmente y me pone de los nervios :P:
if (condicion) {
return true;
} else {
return false;
}
en lugar de poner simplemente: return condicion
Otro muy tipico es el abuso de "static" cuando no se tiene nada claro como va esto de la OO.
Otro que me hizo gracia porque no lo había oído nunca así, el código "baklava" (postre arabe compuesto de muchas capas finas), es el tipico código lleno de capas innecesarias que sólo se pasan mensajes de una a la otra sin contener ningún tipo de logica.
hola, Abroham no soy programador java, ni tampoco experto y mi comentario es para notificarte de un error de escritura en el titulo de tu post, deberia ser "errores" en lugar de "erores" como lo escribiste.
un saludo y que tengas buen dia
mmm, creo que ya somos dos los de los dedos chuecos...:D escribi mal tu nombre :D
corregido, basilio
No entiendo porque al hacer boolean iguales = string1 ==string2; iguales va a ser siempre true. ¿No se debería hacer con el .equals?
Sí, Albert. Lo correcto es usar el equals. Pero el ejemplo que se ha puesto como error está mal, porque si lo ejecutas, dependiendo de la implementación de la JVM, te dará true.
¿Cómo? Pues porque una de las optimizaciones que hace el compilador es que, aprovechando que los objetos String son inmutables, si se asigna un mismo literal a variables diferentes, el compilador, internamente, hará que apunten a la misma posición de memoria, donde está almacenada la cadena.
Así que si hacemos
String string1 = "string1";
String string2 = "string1";
en realidad, ambas instancias apuntan a lo mismo, y un
string1==string2
devolverátrue
.Sin embargo, si tienes, por ejemplo:
String string1 = "string1";
String string2 = new StringBuilder("string").append("1");
la comparación
string1==string2
devolveráfalse
.La respuesta de adeteran es perfecta; a esto se le conoce como internalización, y se aplica a todos los tipos primitivos inmutables (Float, Doble, Integer...) y a Strings. Y este es, por cierto, un motivo por el cual suele ser una mala idea tomar un lock sobre cualquiera de los tipos de datos mencionados en la frase anterior; podrías estar guardando más cosas de las que crees.
Este artículo puede arrojar toda la luz necesaria sobre la cuestión de la igualdad.
http://www.xyzws.com/Javafaq/what-is-string-literal-pool/3
A destacar este párrafo:
"String allocation, like all object allocation, proves costly in both time and memory. The JVM performs some trickery while instantiating string literals to increase performance and decrease memory overhead. To cut down the number of String objects created in the JVM, the String class keeps a pool of strings. Each time your code create a string literal, the JVM checks the string literal pool first. If the string already exists in the pool, a reference to the pooled instance returns. If the string does not exist in the pool, a new String object instantiates, then is placed in the pool. Java can make this optimization since strings are immutable and can be shared without fear of data corruption."
Es un proceso de la VM, no del compilador.
Este creo que es mas bien de principiantes. Cascar un método de líneas (o más) y llenarlo de return,
@choces, el compilador también entra; la máquina virtual sólo hace lo que el compilador le dice, pero el compilador es quien selecciona los Strings que, en base al contenido de nuestro código fuente, van a ir al pool. En el caso de otras clases wraper (Float, Doble, Integer...) si que probablemente sea sólo la máquina virtual ya que hay un conjunto predefinido de ellas que van a su respectivo pool.
@Abraham, el compilador javac no crea los objetos ni asigna las referencias.
En las especificaciones de la VM
http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.4
se puede ver que el tratamiento de los Strings es idéntico al de otras clases por el estilo: todas usan el Constant_Pool, y es la VM quien decide qué va al pool y cuándo.
El compilador javac no sabe si el String declarado, u otra clase susceptible de usar el pool, ya existe en el pool, ni si se debe crear o no un objeto nuevo. Se puede desensamblar ese código fuente con el javap, y ver el tipo de bytecode tan extremadamente simple que se genera.
Desde hace bastante tiempo, la VM no es un "simple intérprete" del bytecode. Como ejemplo reciente, los nuevos lambdas del JavaSE 1.8 van a usar el invokedynamic, por lo que el javac no va a "generar" el "código ejecutable" de los lambdas.
Desde luego, tiene mucho más sentido que sea la VM quien haga este tipo de optimizaciones, ya que se tiene más información en tiempo de ejecución que en tiempo de compilación. Pero ya me ha picado la curiosidad de qué criterio emplea exactamente, y cómo decide si reutiliza un elemento del pool.
El siguiente código:
public static void main(String[] args) {
String string1 = "string1";
String string2 = new StringBuilder("string").append("1").toString();
System.out.println(string1 == string2);
System.out.println(string1.equals(string2));
}
me escupe por la consola lo siguiente:
false
true
en una 1.6 y en una 1.5 (ambas bajo Windows XP).
Pero la VM debería saber que el resultado del toString() del StringBuilder es un literal que ya existe en el pool.
El siguiente código, en cambio:
public static void main(String[] args) {
String string1 = "string1";
String string2 = "string" + "1";
System.out.println(string1 == string2);
System.out.println(string1.equals(string2));
}
me escupe por la consola lo siguiente:
true
true
¿Alguien se anima a hacer más experimentos? (yo tengo que seguir currando)
Un error muy común que también encuentro es:
String s = "esto "
+ "es "
+ "un "
+ "string "
+ "ultra "
+ "concatenado "
+ "de "
+ "alguien "
+ "que "
+ "no "
+ "conoce "
+ "la "
+ "clase "
+ "StringBuilder()";
O incluso este:
StringBuilder b = new StringBuilder();
b.append("este ");
b.append("es ");
b.append("otro " + "error comun");
Netbeans por lo menos tiene una opción que te muestra estos y otros errores comunes.
En una VM 1.7 el resultado también es el mismo. Si fuese diferente...
Esta es la sección relevante del bytecode para el primer caso:
0: ldc #2 // String string1
2: astore_1
3: new #3 // class java/lang/StringBuilder
6: dup
7: ldc #4 // String string
9: invokespecial #5 // Method java/lang/StringBuilder."<
init>":(Ljava/lang/String;)V
12: ldc #6 // String 1
14: invokevirtual #7 // Method java/lang/StringBuilder.ap
pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;
17: invokevirtual #8 // Method java/lang/StringBuilder.to
String:()Ljava/lang/String;
20: astore_2
y ésta para el segundo caso:
Code:
0: ldc #2 // String string1
2: astore_1
3: ldc #2 // String string1
5: astore_2
He omitido las demás líneas porque solo hacen referencia a la clase y a la impresión por consola.
La diferencia entra ambas, es que en el primer caso se necesita crear un nuevo objeto y
la VM crea una nueva entrada en el Pool, al margen de que ya exista un objeto con el mismo contenido, pero que tendrá una referencia diferente.
El bytecode del segundo caso no puede ser más simple: se limita a ejecutar una instrucción astore con el valor de cada variable. Aquí es donde la VM debe comprobar si ya existe en el Pool, y proceder según el resultado.
@choces Parece que tienes razón. Tengo un recuerdo de que hace tiempo, leyendo cosas sobre internalización de String (hace muchos años, la primera vez que oí hablar del concepto) lo que había leído es que el compilador jugaba un papel en determinar los Strings que iban al pool a la hora de generar el bytecode. No sé si recuerdo mal, o es que han cambiado las cosas.
@Leopard
En ambos ejemplos que has puesto, el compilador realiza directamente la cocatenación, por lo que usar StringBuilder es más lento ;)
Cuando hay que concatenar cadenas explícitas con variables, es cuando StringBuilder es útil si la cocatenación se realiza dentro de un bucle. Aún en este caso, el uso del método concat de la clase String, puede ser más eficiente en algunos casos.
String variable; // se inicializa en algún momento
El compilador convierte esta asignación a StringBuilder en el bytecode:
String cadena = "cadena" + variable;
Por esa razón, a menos que la concatenación se realice en un bucle, tanto da.
El problema con los bucles es que se crea una nueva instancia de StringBuilder en cada iteración, si se usa la concatenación con +
@Abraham,
No sé si te refieres a algo como ésto:
public class internExample{
public static void main(String[] args){
String str1 = "Hello Java";
String str2 = new StringBuffer("Hello").append(" Java").toString();
String str3 = str2.intern();
System.out.println("str1 == str2 " + (str1 == str2)); //false
System.out.println("str1 == str3 " + (str1 == str3)); //true
}
}
No me refiero algo como eso. Pero esto sí que podría ser resultado del compilador:
String str1 = "Hello Java";
String str2 = "Hello " + "Java";
System.out.println("str1 == str2 " + (str1 == str2)); //true
A lo mejor es la máquina virtual; pero se me antoja más eficiente que sea el compilador.
El compilador javac no "conoce" necesariamente todos los String que se declaran y asignan. Imagina que puede haber cadenas que se declaran en librerías ya compiladas, algo muy frecuente en desarrollos modulares, de las que la compilación actual no sabe nada.
Esa es la motivación principal para el uso del Constant_Pool de la VM. Gracias a ese pool, el compilador no necesita saber si una cadena existe o no: la pasa a la VM, y que ella verifique lo necesario.
Muy buen tema mi error mas comun es invocar una lista sin valores
@choces
No sabia que el compilador cambiaba el codigo:
String str = "cadena" + varTipoStr
a algo del tipo
String str = new StringBuilder("cadena").append(varTipoStr).toString()
Es interesante saberlo, pues estaba usando StringBuilder por eficiencia pero parece bastante mas legible usar String