Archivo para Mayo 2009
Sobreescribir métodos estáticos en Java
Menudo título más puñetero para este post… y es que los métodos estáticos no se pueden sobreescribir en Java, en realidad se ocultan. Aunque esta cuestión es algo compleja.
Empecemos con los ejemplos:
class A {
static void method1() {
System.out.println("method1 from A");
}
}
class B extends A {
static void method1() {
System.out.println("method1 from B");
}
}
Si ahora creamos instancias de cada una de estas clases, vemos como parece que el método de B sobreescribe a A:
A a = new A(); a.method1(); // imprime "method1 from A" B b = new B(); b.method1(); // imprime "method1 from B"
Pero la gran y enorme diferencia entre sobreescribir y ocultar sale a la luz cuando queremos hacer uso del polimorfismo:
void someMethod(A a) {
a.method1();
}
// ...
B b = new B();
someMethod(b); // imprime "method1 from A"
Si el método estuviera realmente redefinido por B, entonces el código anterior debería imprimir "method1 from B". Pero como vemos esto no ocurre…
Lo que realmente está pasando guarda relación con el “fallo” de diseño de Java en el acceso a los miembros estáticos. La llamada a method1 se hace sobre el tipo sin importar si la variable tiene una referencia a una instancia o no ni de qué tipo sea ésta. Y someMethod sólo sabe que la variable es de tipo A, por lo que se ejecuta el método estático de esta clase.
Este tipo de llamadas deberían hacerse siempre sobre la clase y no sobre una instancia:
void someMethod(A a) {
// a.method1(); <- NO
A.method1();
}
Por otra parte, si queremos acceder al method1 de A desde B tenemos dos opciones, la primera la acabamos de ver y la segunda es utilizando super (desde un método no estático, claro):
class B extends A {
static void method1() {
A.method1(); // imprime "method1 from A"
super.method1(); // error! no se puede acceder desde un método estático
method1(); // ups, bucle infinito!
}
void method2() {
A.method1(); // imprime "method1 from A"
super.method1(); // imprime "method1 from A"
method1(); // imprime "method1 from B"
}
}
Ya por último un par de notas. Un método de instancia no puede sobreescribir ni ocultar a otro estático, y viceversa un método de clase no puede sobreescribir ni ocultar a otro de instancia. Cualquiera de estas situaciones se traduce en un error de compilación.
¿Enrevesado? Pues acabamos de empezar… welcome to Java world!
Log con plantillas de código en Eclipse
Lo más fácil para usar un logger en nuestras clases en Eclipse es crear una plantilla de código que inserte automáticamente el código y las importaciones necesarias.
Para crear una plantilla o “template” tenemos que entrar en Preferencias -> Java -> Editor – > Templates:

Creamos entonces una nueva plantilla para declarar e instanciar nuestro logger:
${imp:import(org.slf4j.Logger,org.slf4j.LoggerFactory)}
private static final Logger log = LoggerFactory.getLogger(${enclosing_type}.class);
Ojo: los imports automáticos están disponibles a partir de Eclipse Ganymede (3.4).
Y ya que estamos, creamos otra plantilla con una función de guarda para depuración:
if (log.isDebugEnabled())
log.debug("${cursor}");
Programar mediante autocompletado, qué bonito es.
Accediendo a miembros estáticos de una clase desde una instancia
En Java es posible acceder a miembros estáticos (ya sean métodos o propiedades) de una clase desde una instancia de la misma (al contrario de lo que ocurre en C# o Ruby, por ejemplo). Nada mejor que mostrarlo en código para verlo:
String cadena = "cadena";
// el método valueOf es un método estático de la clase String
cadena.valueOf("otra cadena");
Esto está permitido, aunque el compilador lanza un warning para advertir de la situación; es algo que debería evitarse y que muchos piensan es un error de diseño de Java.
Lo curioso y algo que puede llevar a malentendidos es que no hace falta que la variable tenga una referencia a una instancia, ya que el acceso al método estático se hace a través del tipo de la variable.
public class Test {
static String cadena;
public static void main(String[] args) {
System.out.println(cadena.valueOf("cadena")); // imprime "cadena"
}
}
O más chocante (aunque viene a ser lo mismo de arriba):
String cadena = null;
cadena.valueOf("cadena"); // funciona!
Un ejemplo con enumerados (los valores de un enumerado son instancias estáticas del tipo del enumerado):
enum Animals { MONKEY, DONKEY }
Animals a;
void doSomething() {
System.out.println(a.MONKEY.DONKEY.MONKEY); // y así podríamos seguir...
}
Redefiniendo toString() con ToStringBuilder
El método toString es más que útil al imprimir logs y/o depurar una aplicación; sin embargo el redefinirlo en cada uno de nuestros objetos puede ser un verdadero coñazo.
Una forma fácil de hacerlo es utilizar la clase ToStringBuilder, de los commons de Apache.
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
Mediante reflexión este método accede a todas las propiedades del objeto y las imprime en un formato que podemos elegir. Si sólo queremos imprimir unos atributos determinados, podemos indicarlo del siguiente modo:
@Override
public String toString() {
return ToStringBuilder(this).append("name", name).append("age", age).toString();
}
Cuidado al sobreescribir el método hashCode()
Modificar los métodos equals y hashCode de nuestros objetos no es algo trivial, y puede traer efectos secundarios no muy buenos para nuestra aplicación.
Para qué sirve el código hash
Por defecto, hashCode devuelve un entero diferente para cada objeto basándose en la dirección del mismo (aunque esto depende de la implementación). Su uso principal es la optimización de las colecciones basadas en hashtables, como pueden ser HashMap o HashSet.
De modo gráfico, imaginemos una colección como una serie de cajas. En la caja 1 guardaremos los objetos que tengan hashcode 1, en la caja 2 los que tengan un hashcode 2… y así respectivamente. Cuando busquemos un objeto, sólo tenemos que mirar en la caja donde debería estar (mediante su hashcode).
En esta caja puede haber más objetos; por ejemplo, si sobreescribimos el método hashCode para que devuelva la longitud de un string:
private class CustomObject {
private String attribute;
public void setAttribute(String attribute) {
this.attribute = attribute;
}
@Override
public int hashCode() {
return this.attribute.length();
}
}
y tenemos dos objetos, uno con attribute="a" y otro con attribute="b", ambos irán a parar a la caja 1. Por tanto una vez dentro de la caja correspondiente debemos buscar el objeto mediante el método equals.
Esto es más eficiente que recorrer todos los elementos de la colección (siguiendo con el ejemplo anterior, sólo buscaremos en los strings de longitud 1, descartando el resto de elementos nada más empezar) como pasaría en un ArrayList, por ejemplo.
Peligro, ¡cuidado al sobreescribir!
En condiciones normales no deberíamos (ni necesitaremos, seguramente) sobreescribir el método hashCode. El problema viene cuando redefinimos equals (por ejemplo, para ejecutar tests), lo cual implica que también debemos hacer lo mismo con hashCode para mantener la coherencia (dos objetos iguales según equals deben tener el mismo código hash, sino puede ocurrir que por ejemplo tengamos dos instancias idénticas en un conjunto, cuando esto no puede -o no debería- ocurrir).
Dos objetos distintos según equals pueden producir el mismo código hash; por ejemplo:
@Override
public int hashCode() {
return 2;
}
Este código sería válido pero ineficiente, todos los objetos irían a parar a la misma caja, por lo que las búsquedas posteriores serían secuenciales perdiendo la principal ventaja de usar hashtables.
Otro gran peligro que existe al sobreescribir el método hashCode es el siguiente:
// creamos una colección de tipo HashSet
Set<CustomObject> testSet = new HashSet<CustomObject>();
// creamos un objeto de tipo CustomObject
// y lo añadimos a la colección
CustomObject customObject = new CustomObject();
customObject.setAttribute("attribute");
testSet.add(customObject);
// comprobamos que esté en el conjunto
System.out.println(testSet.contains(customObject)); // true
// modificamos su atributo y comprobamos
// de nuevo que esté en la colección
customObject.setAttribute("another attribute");
System.out.println(testSet.contains(customObject)); // false
Como vemos, al modificar la variable privada de customObject el código hash del objeto cambia (se utiliza en el cómputo), y cuando queremos comprobar si el objeto pertenece a la colección el método contains busca en la caja que no es. El objeto ya no está en la colección.
En definitiva, no tocar
Esto se puede complicar todavía mucho más si ponemos sobre la mesa objetos serializables y variables transient (no debemos utilizar variables de este tipo en el método hashCode, ya que al recuperar el objeto serializado tendrán el valor por defecto).
Este tema es importante y está presente en muchas situaciones, por ejemplo en las entidades de Hibernate (que puede completar los objetos de una colección con valores autogenerados cambiando el hashcode de los mismos y provocando el lío que vimos anteriormente) y los tests.
Lo mejor es, mientras no sea estrictamente necesario, no tocar.
Wicket en Google App Engine
A pesar de las limitaciones de la “nube” de Google, parece que en general los frameworks web van funcionando… Después de ver cómo hacer funcionar Struts en AppEngine, ahora le toca el turno a Wicket.
Lo primero es activar el soporte de sesiones, algo esencial en este framework con estado. Para ello debemos escribir la siguiente línea en el archivo appengine-web.xml:
<sessions-enabled>true</sessions-enabled>
Siguiendo con las sesiones, también debemos decirle a Wicket que las almacene en memoria. Sobreescribimos pues el método newSessionStore en el objeto de configuración de la aplicación:
public class ExampleApp extends WebApplication {
public ExampleApp() { }
@Override
protected ISessionStore newSessionStore() {
return new HttpSessionStore(this);
}
}
Por último, ya sólo nos queda inhabilitar el thread que recarga los recursos (html y demás), ya que como sabemos GAE no permite que las aplicaciones creen nuevos hilos.
public class ExampleApp extends WebApplication {
public ExampleApp() { }
@Override
protected ISessionStore newSessionStore() {
return new HttpSessionStore(this);
}
@Override
protected void init() {
super.init();
this.getResourceSettings().setResourcePollFrequency(null);
}
}
Y con esto, todo listo. No debería haber mayores problemas, aunque parece que con Ajax no todo funciona como debería (más info).
Caché en las “wrapper classes” de Java
Una wrapper class (o clase envoltorio) es una clase que contiene a un tipo primitivo. Cada tipo tiene su clase correspondiente: Byte (byte), Short (short), Integer (int), Long (long), Float (float), Double (double), Boolean (boolean) y Character (char). Utilizando estas clases (y sobre todo gracias al autoboxing) podemos trabajar con tipos primitivos como si de objetos se trataran, además por supuesto de añadir métodos de conversión y similares.
Estas clases incorporan un mecanismo de caché y tienen ya precargados algunos objetos en memoria. Por ejemplo, al arrancar la máquina virtual se crean 256 objetos de tipo Integer (que representan los valores entre -127 y 128) o 2 de tipo Boolean (true y false). Como muestra el código fuente de IntegerCache (en la clase Integer):
private static class IntegerCache {
private IntegerCache() { }
static final Integer cache[] = new Integer[-(-128) + 127 + 1];
static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Integer(i - 128);
}
}
Los objetos se crean en el inicializador estático y se usan en el método valueOf:
public static Integer valueOf(int i) {
final int offset = 128;
if (i >= -128 && i <= 127) { // must cache
return IntegerCache.cache[i + offset];
}
return new Integer(i);
}
Conocer esta característica puede ser más que interesante pues evitaremos el crear objetos innecesariamente. Valga el siguiente código para ver cómo podemos sacarle partido:
Integer i1 = new Integer(1); Integer i2 = 1; Integer i3 = Integer.valueOf(1);
Estas 3 formas de crear un objeto Integer que contiene al entero primitivo 1 son equivalentes. O mejor dicho, casi equivalentes, y es que la primera creará un objeto nuevo cada vez que la ejecutemos, mientras las otras dos harán uso de la caché como hemos visto anteriormente.
System.out.println( i1==i2 ); // false System.out.println( i1==i3 ); // false System.out.println( i2==i3 ); // true
Como vemos el operador == nos confirma que i2 e i3 son el mismo objeto. Sin embargo:
Integer i1 = 200; Integer i2 = 200; System.out.println( i1==i2 ); // false Integer i3 = Integer.valueOf(200); Integer i4 = Integer.valueOf(200); System.out.println( i3==i4 ); // false
el valor 200 no está cacheado, por lo que se crean nuevas instancias de Integer.
Una buena práctica, por lo tanto, es crear estas clases envoltorio utilizando el método valueOf o asignando el literal para así evitar la creación de objetos extra.
Trabajando con String en Java (II): creando objetos String
Cuando manejamos strings es bueno conocer cómo funcionan para saber qué consecuencias tiene una forma de programar u otra. Por ejemplo, estas dos sentencias en principio parecen equivalentes:
String s1 = "a string";
String s2 = new String("a string");
pero en realidad la segunda es más ineficiente, ya que crea 2 objetos por 1 de la primera sentencia. Probablemente nuestra aplicación no sufra penalización alguna por crear algún que otro objeto extra, pero al menos debemos ser conscientes de ello. Así, cuando concatenamos dos strings:
String s1 = "string 1"; String s2 = "string 2"; String s3 = s1 + s2;
estamos creando un objeto nuevo (los strings son inmutables y no se pueden modificar). Una clase que nos puede ayudar bastante a la hora de mejorar el rendimiento en este tipo de situaciones es StringBuilder.
String[] arrayOfStrings = new String[] { "1", "2", "3", "4", "5" };
// using "+" operator
String finalString = "";
for (String s : arrayOfStrings) {
finalString += s;
}
// using StringBuilder
StringBuilder sb = new StringBuilder("");
for (String s : arrayOfStrings) {
sb.append(s);
}
String finalString2 = sb.toString();
El primer bucle crea un objeto por cada concatenación que realiza, mientras que utilizando StringBuilder el único objeto de tipo String que se crea es cuando se llama a toString(). Por lo tanto parece sensato usar esta clase cuando se concatenan strings.
Por fortuna cuando las concatenaciones son “obvias”, por ejemplo:
String s = "1" + "2" + "3" + "4" + "5";
el compilador crea directamente un string con valor 12345. En casos más complicados:
String[] aS = new String[] { "1", "2", "3", "4", "5" };
String s = aS[0] + aS[1] + aS[2] + aS[3] + aS[4];
el compilador es lo suficientemente listo como para optimizar el código y crear un bytecode utilizando StringBuilder, por lo que la sentencia anterior sería equivalente a la siguiente:
String[] aS = new String[] { "1", "2", "3", "4", "5" };
String s = new StringBuilder(aS[0]).append(aS[1]).append(aS[2]).append(aS[3]).append(aS[4]).toString();
Más sobre los String
Trabajando con String en Java (I): inmutabilidad
La inmutabilidad de los strings es un concepto muy importante en Java. Al contrario de lo que sucede con la mayoría de objetos, un string no puede ser modificado después de ser creado.
String s = "string"; s = s + " modified"; System.out.println(s);
Este código nos escupe lo siguiente: string modified. Y sin embargo acabamos de decir que los strings son inmutables… ¿se pueden modificar o no entonces?
No. En realidad lo que está pasando es que en la segunda línea se crea un nuevo objeto, un nuevo string fruto de la concatenación de otros dos. s apunta ahora a un objeto de tipo string cuyo valor es string modified, pero en algún lado de la memoria de nuestra máquina siguen vivas las otras dos cadenas presentes en el código: string y modified (en dónde están exactamente hablaremos en otro post).
Por esta razón la sentencia:
String s = "string";
s.concat(" modified");
no modifica el string original y s sigue conteniendo string a secas. Para modificar s debemos realizar una asignación nueva:
s = s.concat(" modified");
Más sobre los String
VNC en el iPhone
Desde que le hice el jailbreak al iPhone, el pobre se las ha tenido que ver con un montón de nuevas aplicaciones y herramientas, en su gran mayoría basura.
En esta categoría no entra Veency, un servidor de VNC con el que nos podremos conectar a nuestro teléfono y manejarlo desde el ordenador. Como cliente podemos usar Chicken of the VNC en Mac, y TightVNC en Windows. En Ubuntu la única solución que he encontrado es la de usar el propio TightVNC sobre Wine (aunque creo que también existe versión para Linux).
¿Y por qué querría nadie manejar el teléfono desde el ordenador? Por ejemplo, si no tienes conexión y quieres mandar un correo largo, siempre es más cómodo escribirlo en un teclado. O para hacer alguna demostración o screencast.
Ahora me queda por comprobar hasta qué punto se resiente la batería y el rendimiento del cacharro. Espero que no mucho.



