El lenguaje Java

Java es un lenguaje orientado a objetos. Esto significa que posee ciertas características que hoy día se consideran estándares en los lenguajes OO:

Estos conceptos no son simples, por lo que explicaremos su significado más adelante en el curso.

Por el momento, comenzaremos por mostrar el lado oscuro de los objetos:

Para programar orientado a objetos es necesario primero diseñar un conjunto de clases. La claridad, eficiencia y mantenibilidad del programa resultante dependerá principalmente de la calidad del diseño de clases. Un buen diseño de clases significará una gran economía en tiempo de desarrollo y mantención.

Lamentablemente se necesita mucha habilidad y experiencia para lograr diseños de clases de calidad. Un mal diseño de clases puede llevar a programas OO de peor calidad y de más alto costo que el programa equivalente no OO.

Es válido entonces preguntarse: ¿Por qué programar en un lenguaje OO, si se requiere una experiencia que probablemente uno nunca tendrá el tiempo de práctica para llegar a dominarla?

La respuesta a esta pregunta es la siguiente: Java es un lenguaje multiparadigma (como muchos otros lenguajes de programación). Uno no necesita hacer un diseño de clases para programar una aplicación de mil líneas.

¿Entonces por qué no usar otro lenguaje más simple como Visual Basic, si no necesito orientación a objetos?

Porque la ventaja potencial más importante de un lenguaje OO está en las bibliotecas de clases que se pueden construir para él. Una biblioteca de clases cumple el mismo objetivo de una biblioteca de procedimientos en una lenguaje como C. Sin embargo:

Una biblioteca de clases es mucho más fácil de usar que una biblioteca de procedimientos, incluso para programadores sin experiencia en orientación a objetos. Esto se debe a que las clases ofrecen mecanismos de abstracción más eficaces que los procedimientos.

Más adelante quedará clara esta afirmación cuando examinemos ejemplos de bibliotecas de clases.

Por lo tanto podemos distinguir entre varios tipos de programadores en Java:

Tanto programadores de clases como clientes de bibliotecas pueden llegar a convertirse en buenos diseñadores de clases en la medida que adquieran experiencia, comparando los diseños de las bibliotecas que utilicen.

Por lo tanto es importante destacar que no se necesita gran experiencia en diseño orientado a objetos para poder aprovechar las ventajas de la orientación a objetos.


La Simplicidad de Java

Java ha sido diseñado de modo de eliminar las complejidades de otros lenguajes como C y C++.

Si bien Java posee una sintaxis similar a C, con el objeto de facilitar la migración de C hacia a Java, Java es semánticamente muy distinto a C:


Java posee bibliotecas de clases estándares

Toda implementación de Java debe tener las siguientes bibliotecas de clases:

La interfaz de programación de estas clases es estándar, es decir en todas ellas las operaciones se invocan con el mismo nombre y los mismos argumentos.


Java es multiplataforma

Los programas en Java pueden ejecutarse en cualquiera de las siguientes plataformas, sin necesidad de hacer cambios:

La compatibilidad es total:


El Look-and-Feel

Lo único que varia de acuerdo a la plataforma es el look-and-feel. Un programa en Windows/95 tendrá el aspecto característico de esta plataforma (en cuanto a la forma de los botones, barras de deslizamiento, menúes, etc.). El mismo programa en Unix tendrá el aspecto característico de Motif. Y en Power/Mac se verá como un programa para Macintosh.

Sin embargo el código que escriben los programadores no tiene que tener presente las características de ninguna de estas plataformas. Es la implementación de la interfaz gráfica estándar de Java la que se encarga de desplegar las ventanas con el look-and-feel de la plataforma local.


C es poco robusto

Se dice que el lenguaje C es un lenguaje poco robusto porque a menudo un error de programación se traduce en un mensaje críptico del estilo segmentation fault. Este tipo de mensajes se origina en 4 errores clásicos:

Todos estos errores conducen a que tarde o temprano se use un puntero que direcciona un área de memoria no asignada por el sistema operativo. Esto es lo que detiene la ejecución con el mensaje segmentation fault.

Lo más desagradable de este tipo de errores es que es muy difícil determinar en qué línea del código está la verdadera fuente del error. Podría ser en cualquier parte del programa. Encontrar la línea puede llevar varios días y hasta semanas, incluso en el caso de programadores expertos.

Java sí es robusto

En Java no se pueden cometer los 4 errores mencionados:

Por lo tanto Java no es un lenguaje para hacer sistemas operativos o administradores de memoria, pero sí es un excelente lenguaje para programar aplicaciones.


Java es flexible

Pascal también es un lenguaje robusto, pero logra su robustez prohibiendo tener punteros a objetos de tipo desconocido. Lamentablemente esta prohibición es demasiado rígida. Aunque son pocos los casos en que se necesita tener punteros a objetos de tipo desconocido, las contorsiones que están obligados a realizar los programadores cuando necesitan estos punteros dan origen a programas ilegibles.

Lisp por su parte es un lenguaje flexible y robusto. Todas las variables son punteros a objetos de cualquier tipo (un arreglo, un elemento de lista, etc.). El tipo del objeto se encuentra almacenado en el mismo objeto. Durante la ejecución, en cada operación se chequea que el tipo del objeto manipulado sea del tipo apropiado. Esto da flexibilidad a los programadores sin sacrificar la robustez. Lamentablemente, esto hace que los programas en Lisp sean poco legibles debido a que al estudiar su código es difícil determinar cuál es el tipo del objeto que referencia una variable.

Java combina flexibilidad, robustez y legibilidad gracias a una mezcla de chequeo de tipos durante la compilación y durante la ejecución. En Java se pueden tener punteros a objetos de un tipo específico y también se pueden tener punteros a objetos de cualquier tipo. Estos punteros se pueden convertir a punteros de un tipo específico aplicando un cast, en cuyo caso se chequea en tiempo de ejecución de que el objeto sea de un tipo compatible.

El programador usa entonces punteros de tipo específico en la mayoría de los casos con el fin de ganar legibilidad y en unos pocos casos usa punteros a tipos desconocidos cuando necesita tener flexibilidad. Por lo tanto Java combina la robustez de Pascal con la flexibilidad de Lisp, sin que lo programas pierdan legibilidad en ningún caso.


Java administra automáticamente la memoria

En Java los programadores no necesitan preocuparse de liberar un trozo de memoria cuando ya no lo necesitan. Es el recolector de basuras el que determina cuando se puede liberar la memoria ocupada por un objeto.

Un recolector de basuras es un gran aporte a la productividad. Se ha estudiado en casos concretos que los programadores han dedicado un 40% del tiempo de desarrollo a determinar en qué momento se puede liberar un trozo de memoria.

Además este porcentaje de tiempo aumenta a medida que aumenta la complejidad del software en desarrollo. Es relativamente sencillo liberar correctamente la memoria en un programa de 1000 líneas. Sin embargo, es difícil hacerlo en un programa de 10000 líneas. Y se puede postular que es imposible liberar correctamente la memoria en un programa de 100000 líneas.

Para entender mejor esta afirmación, supongamos que hicimos un programa de 1000 líneas hace un par de meses y ahora necesitamos hacer algunas modificaciones. Ahora hemos olvidado gran parte de los detalles de la lógica de este programa y ya no es sencillo determinar si un puntero referencia un objeto que todavía existe, o si ya fue liberado. Peor aún, suponga que el programa fue hecho por otra persona y evalúe cuan probable es cometer errores de memoria al tratar de modificar ese programa.

Ahora volvamos al caso de un programa de 100000 líneas. Este tipo de programas los desarrolla un grupo de programadores que pueden tomar años en terminarlo. Cada programador desarrolla un módulo que eventualmente utiliza objetos de otros módulos desarrollados por otros programadores. ¿Quién libera la memoria de estos objetos? ¿Cómo se ponen de acuerdo los programadores sobre cuándo y quién libera un objeto compartido? ¿Como probar el programa completo ante las infinitas condiciones de borde que pueden existir en un programa de 100000 líneas?

Es inevitable que la fase de prueba dejará pasar errores en el manejo de memoria que sólo serán detectados más tarde por el usuario final. Probablemente se incorporán otros errores en la fase de mantención.

Se puede concluir:

La pregunta es: ¿Cuál es el impacto de un recolector de basura en el desempeño de un programa?

El sobrecosto de la recolección de basuras no es superior al 100%. Es decir si se tiene un programa que libera explícitamente la memoria y que toma tiempo X, el mismo programa modificado de modo que utilice un recolector de basuras para liberar la memoria tomará un tiempo no superior a 2X.

Este sobrecosto no es importante si se considera el periódico incremento en la velocidad de los procesadores. El impacto que un recolector de basura en el tiempo de desarrollo y en la confiabilidad del software resultante es muchos más importante que la pérdida en eficiencia.


Resumen

Java es un lenguaje que ha sido diseñado para producir software: