Página principal
Artículos y trucos
Catálogo de productos
Ejemplos y descargas
Mis libros
Cursos de formación
Investigación y desarrollo
Libros recomendados
Mis páginas favoritas
Acerca del autor
 
En colaboración con Amazon
 
Intuitive Sight

C# versus Java: miscelánea

En dos entregas anteriores de esta serie de artículos sobre Java y C#, comparamos el soporte de eventos y de tipos genéricos. Pero, aparte de las divergencias en los grandes temas, hay también muchas ventajas a favor de C# en los pequeños detalles de cada lenguaje. Aquí, sin agotar el tema, veremos algunas de ellas.

PROPIEDADES

Como ya hemos visto, los eventos son sólo uno de los muchos recursos disponibles en otros lenguajes y que están conspicuamente ausentes en Java. Otra característica estrechamente relacionada son las propiedades. En C#, usted modifica el texto de un botón mediante esta instrucción:

button1.Text += “!”;

En Java, necesita hacer todo esto:

button1.setText(button1.getText() + “!”);

Esta vez no se trata de un problema de eficiencia, porque ambas instrucciones se implementan de la misma manera, sino de la claridad del código y, por consiguiente, de su mantenibilidad.

No obstante, la omisión de propiedades en Java sería una mera anécdota... ¡si no fuese porque la mayoría de los entornos visuales de desarrollo para Java las utilizan profusamente! Para “implementarlas”, Java debe recurrir a trucos y especificaciones que palien las carencias del lenguaje. En realidad, Java no es un lenguaje orientado a componentes, sino que técnicamente hablando, su diseño es anterior, si no en el tiempo al menos en la intención, a la revolución de la programación "visual".

TIPOS ENUMERATIVOS

El minimalismo de Java afecta provoca también la ausencia de tipos enumerativos. En C# podemos definir un tipo para representar colores básicos:

enum Colores { Negro, Rojo, Verde, Azul, Blanco }

Esto sería demasiado para el alma minimalista que anida en el corazón de todo aficionado a Java. En este lenguaje, los colores se representarían mediante constantes enteras:

const int Negro = 0;
const int Rojo = 1;
// etcétera

Claro, el compilador de Java no pondrá objeciones si luego intentamos asignar un “lunes” en una variable que debe contener “colores”. El trabajo que se evita el compilador, termina convirtiéndose en horas extras para el sufrido programador.

Otra ventaja de los enumerativos tiene que ver con la reflexión. Si usted imprime una valor enumerativo en la consola, o utiliza el método ToString para transformarlo en una cadena, obtendrá el nombre simbólico asociado al valor. Con una constante entera, siempre obtendrá el valor numérico, sin más.

ESTRUCTURAS

Imagine ahora que debe escribir una aplicación que hará uso constante de puntos, o vectores, o números complejos. Si la escribe en Java, estos tipos se implementarán con la única opción a mano: una clase. Las clases son tipos con semántica de asignación por referencia. Cada vez que necesite un punto, tendrá que usar el operador new y pedir memoria para una instancia. No importa que, por la propia naturaleza de sus algoritmos, el punto sea descartado casi al instante y que vaya a engrosar las filas de instancias fallecidas en combate. Más trabajo para el colector de basura...

En .NET, puntos, vectores y complejos se implementarían como estructuras: tipos similares a las clases, pero con semántica de asignación por valor. Si declara una variable local de estructura, la memoria para la misma se reserva automáticamente dentro del marco de la pila local al método. No sólo se evita la asignación explícita de memoria y su posterior liberación, sino que se evita un nivel de indirección al acceder a los miembros de la estructura.

El resultado: un ahorro más que notable en el manejo de memoria, más rapidez en el acceso a miembros. En otras palabras, aplicaciones más eficientes, con un código más expresivo y más fáciles de mantener.

TRASPASO DE PARÁMETROS

Lo mismo pasa con las técnicas de traspaso de parámetros. Para Java, sólo existe el traspaso por valor. Cualquier otra forma de traspaso mancharía con punteros ocultos la inmaculada pureza de Java y su máquina virtual minimalista. Por si el programador no queda convencido, se le inculca la falsa sospecha de la no verificabilidad del traspaso de parámetros por referencia.

Nada más falso, por supuesto. En .NET se permiten parámetros por referencia, marcados con el modificador ref, y parámetros de salida, que se declaran con el modificador out. Naturalmente, la presencia de estos parámetros no afecta negativamente al algoritmo de verificación del código, y sí aumenta la potencia disponible para beneficio del programador.

DELEGADOS Y CONCURRENCIA

Aunque ya hemos hablado de los tipos delegados al tratar sobre los eventos, hay un detalle importante que se me quedó en el tintero: el soporte para ejecución asíncrona que los tipos delegados ofrecen automáticamente en .NET Framework. Imagine que tiene un método como el siguiente, que quiere ejecutar de forma asíncrona:

public int Algo(string parametro1, double parametro2)
{
   // ...
}

La forma más sencilla y segura de ejecutar esa llamada en .NET pasa por definir un tipo delegado. Aquí voy a irme por las ramas, definiendo un tipo de delegado genérico:

public delegate R Metodo<T1, T2, R>(T1 parametro1, T2 parametro2);

Es decir, en vez de ir al grano y definir un puntero a un método que reciba una cadena y un real, y devuelva un entero, ¿por qué no generalizar un poco, y dejar que esos tipos concretos se conviertan en parámetros de tipos? En todo caso, la declaración de tipo que acabamos de ver viene con un premio. Automáticamente, el compilador declara estos métodos dentro de la clase Metodo:

public IAsyncResult BeginInvoke(T1 p1, T2 p2, AsynCallback c, object data);
public R EndInvoke(IAsynResult result);

No quiero entretenerme demasiado explicando los parámetros de estos dos métodos. Sólo me interesa, de momento, que reconozca la presencia de los parámetros de entrada en el método BeginInvoke, y de los parámetros de salida (ninguno, en este caso) y del valor de retorno en EndInvoke. Gracias a estos dos métodos, por ejemplo, podemos escribir código como el siguiente:

// Obtenemos un puntero al método Algo:
Metodo<string, double, int> d = Algo;
// Ejecutamos Algo de manera asíncrona:
IAsyncResult async = d.BeginInvoke("Cadena", Math.PI, null, null);
// Aquí hacemos algo, mientras se ejecuta Algo:
// ...
// Esperamos, si Algo no hubiese terminado, y recogemos el resultado:
int resultado = d.EndInvoke(async);

Como puede ver, es un mecanismo sencillo, relativamente transparente, que requiere muy poco conocimiento de la biblioteca de clases... y que, sobre todo, es seguro, desde el punto de vista de los tipos de datos, sin perder flexibilidad.

IMPLEMENTACION DE INTERFACES

Ha sido para mí una sorpresa comprobar que Java no tiene mecanismos de solución de conflictos en la implementación de interfaces. El Delphi clásico tenía una cláusula que permitía que un método con el prototipo adecuado implementase determinado método de una interfaz aunque no tuviese el mismo nombre. C# resuelve este mismo problema con la implementación explícita de métodos, propiedades y eventos.

Se puede discutir la frecuencia o improbabilidad de que se presenten conflictos de nombres en sistemas bien diseñados. Pero el caso es que la la utilidad de la implementación explícita va más allá de la mera resolución de estos conflictos. Podemos usar implementación explícita para obligar a usar determinados métodos o propiedades a través del tipo de interfaz, eliminando así la dependencia entre el cliente y la clase que provee dicha funcionalidad.