Lección 02


La programación orientada a objetos.


La programación es un actividad humana que se ha desarrollado casi enteramente durante la segunda mitad del siglo XX.

Por tanto podemos suponer que aún está en sus orígenes y que el futuro traerá todavía grandes adelantos técnicos y teóricos que mejorarán sus resultados.

En su corta historia, la programación ha sufrido ya dos pequeñas revoluciones. La primera, protagonizada principalmente por Wirth, Dijstra y de forma menos teórica pero quizás con más impacto por Kernighan y Ritchie. Es lo que se denomina la programación estructurada.

Los primeros lenguajes de programación eran simplemente intrucciones que se le podían dar a un autómata como una computadora, para que realizara ciertas operaciones. Así un programa no era sino una lista de instrucciones encaminadas a realizar algún cálculo.

A medida que las computadoras fueron haciéndose más sofisticadas, sus lenguajes propios o lenguajes de máquina iban cambiando y surgió la necesidad de crear unos lenguajes intermedios que cualquier usuario pudiera aprender y que no dependieran de la máquina concreta en la que se iban a ejecutar los programas. Así surgieron varios lenguajes que se hicieron famosos y también surgieron los primeros compiladores. Un compilador es un programa que traduce las instrucciones de un lenguaje más o menos humano, a las de una máquina.

La aparición de estos lenguajes intermedios y sus compiladores marca el comienzo de la programación como una nueva ciencia.

El tremendo éxito que las computadoras fueron teniendo a lo largo de los años 60 fue llevando a la creación de programas cada vez más complicados que llegaban a tener cientos de miles de líneas de código.

Hacer correcciones a este tipo de programas y agregarles mejoras se fue convirtiendo en una labor imposible. Ante este problema que amenazaba con convertir las computadoras en máquinas estériles, surgió un grupo de científicos de la computación, de los cuales Dijstra y Wirth son dos de los más destacados, que fueron proponiendo una serie de ideas que llevaron a la creación de un nuevo concepto: la programación estructurada.

En pocas palabras, la programación estructurada propone dos ideas básicas: no repetir código y proteger las variables que una parte del programa usa de que sean modificadas accidentalmente por otras partes del programa.

Una sola repetición de código es fuente probable de errores y dificulta el mantenimiento de un programa. Para no repetir código hay que escribir funciones o procedimientos que se encarguen de realizar siempre lo que las diferentes repeticiones realizarían.

Para proteger las variables hay que desarrollar lenguajes que permitan definir variables locales, es decir, que sólo pueden ser utilizadas dentro de una función o procedimiento. De esta manera ninguna otra función del programa puede cambiarla.

Como consecuencia de estas ideas disminuye considerablemente el tamaño de los programas y éstos se hacen más confiables y más fáciles de corregir o mejorar.

Para facilitar la programación estructurada, aparecen nuevos lenguajes, notoriamente el Pascal y el C, que son los dos lenguajes de programación estructurada más conocidos.

Wirth, creador del Pascal, nunca quedó satisfecho con este lenguaje. En su constante análisis de los hábitos de programación encontraba que todavía había muchas cosas que mejorar. Una década después del Pascal publicó otro lenguaje llamado Modula2 en el que presentaban algunas de sus ideas más recientes. Los módulos del Modula2 pretendían representar objetos o clases. Dentro de un módulo se definían variables que representaban el estado del objeto y se definían procedimientos que describían su comportamiento. Las variables y los procedimientos de un módulo podían ser de uso público o privado, exclusivo de la clase, según lo decidiera el creador del módulo. Con todo esto se mejoraba notoriamente sobre el Pascal donde el concepto de procedimientos o funciones privadas no existía y se daba un paso importante hacia la programación de clases o programación orientada a objetos.

Otros científicos experimentaron ideas similares creando diversos lenguajes de programación orientada a objetos como Smalltalk. En ellos se experimentó con otras ideas útiles como la definición de subclases que heredan las propiedades de su superclase o sea de la clase de la que se derivan, pero agregando variables y funciones nuevas. También surgió una idea que ayudaría a evitar los difíciles problemas que surgían en el manejo de la memoria dinámica: los constructores y destructores de objetos.

A principios de los 90 se popularizó un nuevo lenguaje orientado a objetos que se parecía mucho a C. Se trata del C++ creado por Stroustroup. La idea de Stroustrup fue crear un lenguaje orientado a objetos que heredara prácticamente toda la sintaxis y posibilidades del lenguaje que en ese momento era más popular entre los programadores, el C. Este truco ayudó a popularizar la programación orientada a objetos y preparó el camino para la aparición del Java.

Los creadores de Java aprendieron bien la lección de Stroustroup. Un nuevo lenguaje para tener éxito debía ser muy parecido al que en el momento de su lanzamiento fuese el más popular. Así, Java se creó con un gran parecido al C++. Pero Java es un lenguaje más puro de programación orientada a objetos, conserva un poco del C original, pero se parece más al C++. Puede verse al C++ como un paso intermedio en la transición de la programación estructurada del C a la programación orientada a objetos más pura del Java. Actualmente (agosto de 2001) está apareciendo un nuevo lenguaje de Microsoft llamado C# (C sharp) que promete ser aún más puro como lenguaje orientado a objetos, adopta prácticamente todas las mejoras de Java y agrega algunas nuevas. Habrá que estar pendientes de su publicación y de sus posibles ventajas y probables desventajas (en computación nunca se gana algo sin perder otra cosa, lo importante es que en promedio las cosas vayan mejorando).

Java tiene otras virtudes aparte de ser un excelente lenguaje de programación orientada a objetos, virtudes que lo hacen el lenguaje de programación preferido para internet. Pero también tiene la peculiaridad de ser el lenguaje orientado a objetos más popular del momento (2001, antes del lanzamiento de C#).

Las siguientes secciones de esta lección explican el conceptos de clase, cómo se representa este concepto en Java y las demás características que hacen de Java un lenguaje orientado a objetos.


Clases y objetos.


El concepto de clase es el más importante del lenguaje Java y el que lo convierte en un lenguaje de programación orientada a objetos.

Todo lo que se programa en Java debe estar dentro de una clase. Un programa es una clase. Por ejemplo, el sencillo programa HelloWorld que vimos en la lección 01 tiene este código java:

Si su navegador reconociese la etiqueta de applet, aquí vería un applet

en el que puede verse que todo el programa está dentro de una clase llamada "HelloWorld". Dentro de la clase hay un único método llamado "main" que es el que se ejecuta cuando el programa arranca. Esta forma de programar con clases que contienen métodos y variables (HelloWorld no usa variables, pero podría usarlas) es a lo que se ha llegado después de varios años de experiencia en la programación orientada a objetos.

La palabra clase viene del concepto "clase de objetos". Una clase consta de (a) un conjunto de variables que representa el estado de un objeto y (b) un conjunto de métodos que definen el funcionamiento y las operaciones que pueden realizarse sobre los objetos de esa clase.

Como ejemplo consideremos el caso de un punto en el plano cartesiano que puede desplazarse a la izquierda, a la derecha, hacia arriba y hacia abajo.

El estado de este punto queda determinado por dos variables:

X, la abscisa al origen
Y, la ordenada al origen

Además de las variables X e Y hay que incluir los métodos que definen el "funcionamiento" de la clase y además conviene agregar otros métodos auxiliares.

He aquí una definición de la clase punto.

Si su navegador reconociese la etiqueta de applet, aquí vería un applet

A continuación explicamos detalladamente el significado de cada una de las variables y los métodos de esta clase.

X e Y aparecen definidas como variables. En la lección 03 se estudian todos los tipos de variables del lenguaje Java. Los métodos setX y setY sirven para fijar los valores de X e Y, mientras que getX y getY sirven para obtener los valores de estas variables. Los métodos moveLeft, moveRight, moveUp y moveDown definen el "funcionamiento" de la clase punto, es decir, las operaciones que se pueden realizar sobre un punto.

Los métodos setX y setY admiten un párametro cada uno. El valor de ese parámetro se utiliza para fijar el valor de la abscisa del punto y el valor de la ordenada, respectivamente. Los métodos getX y getY no aceptan parámetros. Los métodos moveLeft, moveRight, moveUp y moveDown aceptan un solo parámetro que se interpreta como la magnitud del desplazamiento que se realiza.

Todos los métodos, excepto getX y getY, tienen valor de devolución "void" (vacío), lo que quiere decir que no devuelven valor alguno. Los métodos getX y getY devuelven un valor de tipo "int", es decir, un número entero.

El último "método" que tiene el mismo nombre que la clase y no tiene valor de devolución, ni siquiera "void", es un constructor. Los constuctores se estudian en la siguiente sección.


Creación de objetos.


Para crear un objeto nuevo en Java se antepone la palabra new a un constructor de la clase del objeto que se desea crear.

Por ejemplo, para crear un punto con coordenadas (2,3) basta escribirlas dos líneas:

punto p;
p=new punto(2,3);

La primera línea simplemente declara que p es una variable de tipo punto. La segunda línea es la que en realidad "crea" el nuevo objeto p. Los objetos de una clase se llaman también instancias de la clase.

Toda clase tiene al menos un constuctor. Los constructores pueden tener uno o varios parámetros, y pueden no tener parámetros. Si en una clase no se define explícitamente un constructor, entonces tiene sólo un constructor sin parámetros.

Cuando se crea un objeto en realidad se aparta espacio de memoria para contener las variables del objeto. Un constructor suele además inicializar los valores de las variables del objeto y al hacerlo puede crear otros objetos. Cuando un objeto deja de usarse en un programa porque ya no se hace referencia a él, entonces el espacio de memoria apartado para sus variables queda disponible para que el controlador de memoria RAM de Java lo utilice nuevamente. En Java el programador no tiene que preocuparse de destruir los objetos para liberar memoria (aunque si lo desea puede intervenir acelerando el proceso de recuperación de la memoria liberada usando System.gc() que ejecuta el recolector de basura).

Para hacer referencia a las variables de un objeto, se escribe el nombre del objeto, un punto y el nombre de la variable correspondiente. Por ejemplo:

p.X=7;

asigna el valor 7 a la variable X del objeto p.


Métodos.


Los métodos de un objeto son funciones que pueden operar sobre las variables del objeto.

Para hacer referencia a un método de un objeto, se escribe el nombre del objeto, un punto y el nombre del método agregando entre paréntesis los parámetros del método o bien unos paréntesis sin nada dentro si el método no acepta parámetros.

Por ejemplo, si p es un punto, entonces para llamar al método que lo mueve 3 unidades a la derecha, basta escribir:

p.moveRight(3);

Éste es el código de un pequeño programa que utiliza la clase punto. El programa crea un punto llamado p con coordenadas (10,10). Luego escribe el valor de la abscisa, llama al método moveLeft de p con parámetro 5, con lo cual desplaza al punto cinco unidades a la izquierda, y escribe el nuevo valor de la abscisa de p.

Si su navegador reconociese la etiqueta de applet, aquí vería un applet

Como ejercicio el alumno debe ir al DOS, compilar punto01.java y ejecutarlo. Podrá comprobar por ejemplo que si antes de la llamada a p.moveLeft(5) la abscisa vale 8, después vale 3.

Observe cómo se usa el constructor para crear un objeto p, es decir una instancia de la clase, que es lo que utiliza en el programa. Observe que el programa no manipula directamente las variables de p sino que lo hace a través del método moveLeft.

Una observación que no tiene que ver con el contenido de esta lección pero que viene al caso por el uso que se ha hecho en este ejemplo es la siguiente: la función System.out.println permite que se le pase un parámetro "mixto" de una cadena (String) y un número unidos por un + que actúa concatenando la primera cadena con la que resultaría de convertir el número a una cadena. Este funcionamiento "inteligente" de println es muy útil pero es atípico de Java. El alumno no debe esperar que otros métodos funcionen así. En general estas conversiones deberá hacerlas el programador explícitamente.


Herencia y extensión de clases (subclases).


Además de contener las variables y los métodos, las clases tienen otra función muy importante en la programación orientada a objetos: la herencia.

Una clase puede ser subclase o extensión de otra. En tal caso se dice que hereda las variables y los métodos de la otra.

Para aclarar este concepto crearemos una subclase de punto llamada circulo.

Si su navegador reconociese la etiqueta de applet, aquí vería un applet

La nueva clase circulo tiene una nueva variable R que interpretaremos como el radio. Pero hereda las variables X e Y de la clase punto. Interpretaremos las variables heredadas como las coordenadas del centro del círculo. La clase circulo también hereda los métodos de punto con los cuales se determina el "funcionamiento" del centro, pero tiene unos métodos nuevos para fijar el valor del radio, para informar dicho valor y para aumentarlo y disminuirlo.

El hecho de que circulo sea una subclase de punto se determina en la línea que define la nueva clase indicando que "extiende" a punto:

public class circulo extends punto

Cuando una clase B es extensión de otra clase A, se dice que B es subclase de A y que A es la superclase de B.

Como se verá en el ejemplo de la siguiente sección, al utilizar la clase circulo no se hace ninguna distinción entre las variables y métodos propios y los heredados de la superclase punto.


Análisis de un applet.


Ahora presentamos un ejemplo de un applet en el que se utiliza la clase circulo.

Este applet utiliza la clase circulo y permite al usuario controlar un objeto de esa clase:
Si su navegador reconociese la etiqueta de applet, aquí vería un applet

Estudie el código de circApplet. Encuentre las líneas donde se crea el objeto c y donde se hacen llamadas a los métodos del objeto.

No se preocupe si no entiende algunos detalles del ejemplo. Es natural pues aún no se ha explicado todo lo que se utiliza en él. Ahora explicaremos todas las partes del applet. En los próximos capítulos se estudiará detalladamente cada uno de los conceptos que aquí sólo se mencionan de manera introductoria.

Es importante que el alumno preste atención a esta explicación para adquirir una visión global de lo que estudiará en las siguientes lecciones. A lo largo de la explicación se introducen por primera vez los conceptos más importantes de la programación en Java y en particular los de la programación de un applet.

La clase circApplet es un Applet, es decir, es una subclase de la clase Applet. Esto se especifica en la línea donde se define circApplet que dice:

public class circApplet extends Applet implements ActionListener

En esta línea se está diciendo (además de otras cosas que se explican más adelante) que la clase que estamos construyendo o definiendo es una extensión de la clase Applet. Esto quiere decir que circApplet posee todas las variables y todos los métodos de la clase Applet, además de los que a continuación se definen explícitamente. Pero que contenga las variables y métodos de la clase Applet significa mucho más de lo que podría parecer en un principio. La clase Applet por definición es una extensión de la clase Panel, que es a su vez extensión de Container, que es extensión de Component que es extensión de Object. Object es la clase básica de la que todas las demás son extensión. Cuando se define una clase y no se dice que es extensión de alguna otra, como se hizo en el caso de circulo, de todas formas es extensión de Object. Por tal motivo, un applet tiene todos los métodos y variables de todas las clases de las que se deriva directa o indirectamente.

A continuación de la declaración de que circApplet es una extensión (o subclase) de Applet, se definen dos variables, una de tipo circulo que se llama c, y otra que es una matriz de 6 botones y se llama b.

Luego aparecen seis métodos cuya función es ofrecer al usuario las operaciones concretas de mover el círculo 10 unidades en cada dirección y aumentar o disminuir el radio en 5 unidades. Hay otro método llamado redraw que se encarga de ofrecer al usuario la opción de dibujar el círculo después de cada movimiento (para entender cómo funciona redraw debe leer la última parte de esta lección donde se explica el método paint). Estos siete métodos se declaran como públicos para que se puedan usar desde otro programa. En particular se usarán desde la página donde se insertará el applet y se usarán a través de JavaScript. En los ejercicios se aprovecha el hecho de que las funciones son públicas. Dentro del applet se usan también las funciones pero para ello no haría falta que fuesen públicas.

A continuación aparece el método

public void init()

Éste es uno de los métodos de la clase Applet. Cuando un navegador abre un applet lo primero que hace es ejecutar el método init() y por tanto es allí donde se debe realizar la construcción del applet. El hecho de definir otra vez este método significa que deseamos "sobreescribirlo", es decir, deseamos modificar su contenido. El contenido original de init() es vacío, por lo tanto si no lo sobreescribimos, no hace nada.

En nuestra implementación de init() creamos los seis botones que servirán para que el usuario pueda mover el círculo. Para crear los botones usamos el constructor de la clase Button y aprovechamos a darle un título a cada botón que nos indique lo que ocurrirá al pulsarlo.

Después aparece el método

public void start()

Éste es otro de los métodos den la clase Applet y el navegador lo ejecuta automáticamente cada vez que se carga el applet y cuando ya está definido su tamaño. Este hecho es muy importante porque en el método init no pueden saberse todavía el ancho y alto del applet. En el ejemplo sobreescribimos el método start para crear en él el objeto circulo que va a utilizarse en el programa.   Al crear el círculo c usamos el constructor de la clase y allí decidimos que su centro tenga inicialmente las coordenadas del centro del panel en donde se dibuja y que su radio sea 40.

La llamada a un constructor siempre se hace poniendo el nombre de una variable de la clase que se va a crear, un signo = y la palabra "new" precediendo al constructor con sus parámetros. La palabra "new" nos indica que se creará un objeto. Esta creación va acompañada de un reclamo de memoria del sistema para acomodar las variables del objeto. Sin embargo el programador de Java no tiene que preocuparse de pedir al sistema esa cantidad de memoria ni de liberarla cuando ya no la utilice. Ambas operaciones las realiza el intérprete de Java automáticamente.

Las últimas seis líneas del método init() realizan las siguientes operaciones:
- Se escoge que el color del fondo del applet sea negro;
- Se escoge que la manera de acomodar los componentes en el applet será con el despliegue "BorderLayout" (ver lección 4);
- Luego se crea un Panel auxiliar que se coloca al "Sur" del applet aprovechando el "BorderLayout"
- En el panel auxiliar se colocan los seis botones tras asignarle al Panel un modelo para acomodar las componentes del tipo "GridLayout" con una fila y seis columnas y se les agrega a los botones el applet como "escucha".

(El tema de los despliegues o "Layouts" se explica detalladamente en la lección 4 y el tema de los escuchas o "Listeners" en la lección 5).

Como hemos dicho anteriormente, los métodos init() y start() son dos de los métodos predefinidos de la clase Applet y nosotros lo hemos sobreescrito haciendo que se encargue de crear el círculo y los botones para moverlo y de acomodar esos botones de alguna manera. Como ya dijimos, init() es el primer método que se llama cuando el navegador crea un applet.  init() sólo se llama una vez en la vida del applet y por ello conviene utilizarlo para construir el applet. En la lección 6 estudiaremos todos los métodos de Applet.

En nuestro circApplet hay dos métodos más que explicar: actionPerformed y paint.

public boolean actionPerformed(ActionEvent ev)

es un método de la interfaz ActionListener que nuestro Applet se ha comprometido a implementar en la última parte de la línea de definición de la clase:

public class circApplet extends Applet implements ActionListener

La interfaz ActionListener consta de un solo método:

public void actionActionListener (ActionEvent ev)

Este método se ejecuta cada vez que se produce un evento del tipo ActionEvent, en particular, cada vez que se pulsa un botón, siempre y cuando el botón haya sido agregado a la lista de "escuchas" de ActionEvent del applet, lo cual se realiza en la línea del método init() que dice:

b[i].addActionListener(this);

El control de eventos es un tema muy importante que se estudia detalladamente en la lección 5. Por ahora será suficiente explicar que cuando se pulsa un botón se llama al método actionPerformed pasándole en el parámetros ev información necesaria para interpretar el evento y actuar. El parámetro ActionEvent ev lleva la información de a cual componente (en este caso un botón) se le hizo un click y dentro del método actionPerformed se determina la acción que se va a realizar según el botón pulsado. Para ello se hace un recorrido por todos los botones usando un "for" (esta instrucción y otras que se usan para crear ciclos en la programación, se estudian en la lección 3). Para separar los seis posibles casos se utiliza un "switch" que es la manera que en Java se hace una disyunción condicional de opción múltiple. Los "switches" y las condicionales binarias, que se hacen con "if" y "else", se estudian también en la lección 3.

Finalmente explicaremos el método

public void paint(Graphics g)

que es un método heredado de la clase Component. El método paint por defecto es vacío. Hay tres métodos de Component que se encargan de "pintar" en varios pasos las componentes y en particular los applets. Estos son repaint(), update(Graphics g) y paint(Graphics g). El primero es el que se llama cada vez que el sistema decide que se tiene que pintar la componente o parte de ella. repaint() obtiene el ambiente gráfico g de la componente, que es un objeto de la clase Graphics (que se estudiará detalladamente en la lección 4) y llama a update(g). update(g) pinta la componente con el color de fondo y llama a paint(g) que por defecto es vacío. Pero el nuevo método paint sí hace algo pues lo hemos sobreescrito, de hecho dibuja el círculo utilizando la función adecuada de la clase Graphics que se llama fillOval y sirve para dibujar círculos y elipses rellenas. A fillOval se le deben pasar como parámetros las coordenadas del punto superior izquierdo del mínimo rectángulo que contiene al "óvalo" y el ancho y alto de dicho rectángulo. Tal vez el alumno piense que sería más cómodo pasarle las coordenadas del centro y los semiejes de la elipse, pero por razones de eficiencia ésta es la función que nos ofrece la clase Graphics.


Paquetes, modificadores, subclases, clases abstractas e interfaces.


Cuanto más grandes son sus programas mayor necesidad tiene el programador de organizar su trabajo de manera cómoda y segura. Java ofrece varias herramientas de organización y protección del código que son el objetivo de la última parte de esta  lección.

Las herramientas de protección y organización forman un rico entramado de relaciones entre las clases, sus variables y sus métodos. Una clase puede extender a otra, puede implementar una interfaz, puede ser abstracta, puede ser o no ser pública y puede pertenecer a un paquete. Las variables y los métodos pueden ser públicos, privadosprotegidos o del paquete. Al final de la lección el alumno deberá comprender todos estos términos.

Provisionalmente se presentan algunas definiciones que servirán para dar una primera idea de estos conceptos.

Los paquetes son conjuntos organizados de clases que pueden compartir algunas variables y métodos sin compartirlos con las clases de otros paquetes. Ejemplos: java.lang.String y java.lang.System son dos clases del paquete java.lang.

Se llaman modificadores a los términos que especifican algunas características o propiedades de una clase, variable, método o interfaz. Ejemplos: public, static, abstract.

Una clase A que extiende a otra clase B se dice que es una subclase de B y también se dice que B es la superclase de A. Una subclase hereda todas las variables y métodos de su superclase y puede tener otros más.

Los métodos abstractos son encabezados de métodos sin implementación. Una clase que contiene métodos abstractos debe declararse como abstract y se dice que es una clase abstracta. Una clase que extiende a una clase abstracta debe implementar los métodos abstractos de su superclase o declararse a su vez abstracta. Una clase totalmente abstracta y pública es lo que se llama una interfaz.

Las interfaces funcionan como contratos que una clase se compromete a cumplir cuando de ella se dice que implemente una interfaz. Las interfaces contienen únicamente los encabezados de los métodos que una clase se compromete a implementar si en su definición se dice que implementa dicha interfaz. Ejemplo: public class ejemplo implements Runnable obliga a la clase ejemplo a implementar el método public void run() porque la interfaz Runnable consta precisamente de tal encabezado.


Paquetes.


Los paquetes son conjuntos de clases. Para declarar que una clase pertenece al paquete ejem02.paquete1 es necesario que la primera línea del archivo .java de esa clase sea:

package ejem02.paquete1;

También es necesario que el archivo .class se coloque dentro del subdirectorio ./ejem02/paquete1. El nombre de un paquete puede tener puntos, y entonces eso indica que cada palabra separada por los puntos corresponde a un subdirectorio en la organización final de las clases.

El que dos paquetes compartan parte de su nombre, como sucede por ejemplo con los paquetes  java.awt y java.awt.Image, no tiene ninguna implicación respecto a los paquetes, excepto la relativa a la localización de las clases. El uso de estos nombres parcialmente compartidos es sólo una forma de organización mnemotécnica de los contenidos.

Como veremos en la siguiente sección sobre modificadores, las clases de un mismo paquete comparten cierta información que no comparten con las de otros paquetes. Esta es la principal aplicación de los paquetes.

Todas las clases pertenecen a algún paquete aunque en sus archivos .java no se escriba explícitamente. Cuando en un mismo directorio se desarrollan varias clases a las que no se les pone una declaración de pertenencia a algún paquete, el compilador de Java las considera a todas como miembros del paquete sin nombre de las clases que ocupan ese directorio. Dicho paquete se llama el paquete por defecto.

Una clase puede usar libremente las clases de su mismo paquete sin necesidad de importar nada. Sin embargo, si una clase usa clases de otro paquete, es necesario importar la clase o el paquete que la contiene. La única clase que nunca hay que importar es java.lang  pues se importa implícitamente de manera automática en todas las clases. Para importar una clase se debe escribir, antes de la definición de la clase que se está desarrollando, una línea con la palabra import seguida del nombre de la clase que se desea importar y de un punto y coma. Por ejemplo si deseamos importar la clase java.awt.Graphics debemos escribir:

import java.awt.Graphics;

Si uno desea utilizar varias clases de un paquete, lo más cómodo es importar todo el paquete y esto se hace escribiendo import seguido del nombre del paquete y luego ".*;" .  Por ejemplo, para importar todas las clases del paquete java.awt, hay que escribir:

import java.awt.*;

En ambos casos, para referirse a la clase Graphics, una vez hecha la importación, basta usar su nombre prescindiendo del nombre de la clase. Por ejemplo para declarar una variable de la clase Graphics basta escribir:

Graphics g;

Es posible utilizar la clase Graphics si haber hecho la importación al principio del archivo .java. Para ello hay que usar el nombre completo java.awt.Graphics. Por ejemplo, si se desea declarar una variable de este tipo siempre se puede hacer escribiendo:

java.awt.Graphics g;

aunque no se haya importado la clase o el paquete al principio del archivo .java.

Es frecuente que los programadores principiantes tengan dificultades con los nombres de las clases y los paquetes en Java porque el sistema empleado tiene algunas sutilezas. Pare evitar problemas, es importante saber que cuando una clase pertenece a un paquete, el nombre de la clase debe ir precedido por el del paquete de manera que, por ejemplo,  el nombre completo de Graphics es java.awt.Graphics. No siempre es necesario emplear el nombe completo de una clase, muchas veces, para hablar de ella se omite el prefijo del nombre del paquete y también se puede omitir al referise a ella cuando se ha importado previamente el paquete. Pero los nombres completos de las clases de Java tienen mucha importancia en la organización de las clases para la ejecución. Es común que los programadores encuentren dificultades para lograr ejecutar sus applets o sus aplicaciones porque el intérprete no encuentra la clase que ha de ejecutar. La mayor parte de las veces esto ocurre porque el programador olvida que el nombre completo de una clase incluye el nombre del paquete al que pertenece, o bien porque olvida localizar la clase dentro del directorio adecuado..

Para concretar esta explicación consideremos este ejemplo:

  Aquí vería un applet si su navegador reconociese la etiqueta de APPLET.

Se trata de una clase Java ejecutable (es decir que tiene un método public static void main(String[] args), escrita en un archivo llamado Aplicacion1.java y que pertenece al paquete ejem02.paquete1. El archivo Aplicacion1.java se encuentra en el subdirectorio ejem02 y para compilarlo podemos hacer la llamada:

javac ejem02\Aplicacion1.java

y esto produce un archivo Aplicacion1.class que se encuentra en el directorio ejem02. Pues aunque parezca increibe, este archivo nunca se poodrá ejecutar si no se pone ne otro sitio. Si Ud. intenta ejecutarlo llamando a

java ejem02\Aplicacion1

obtendrá un error. Si intenta esto otro:

java ejem02.Aplicacion1

también obtendrá un error. Si intenta

java ejem02.paquete1.Aplicacion1

Y si se cambia al directorio ejem02 y hace intentos similares también obtendrá error. La única manera de ejecutar Aplicacion1 es crear un subdirectorio de ejem02 llamado paquete1, colocar allí Aplicacion1.class y desde el directorio principal hacer la llamada:

java ejem02.paquete1.Aplicacion1

El alumno debe hacer esta prueba para comprobar y comprender lo que se ha explicado.  

Las clases de un paquete tienen acceso a todas las clases del mismo paquete, sean públicas o no. Sólo las clases públicas de un paquete pueden ser utilizadas por clases de otros paquetes.Por tanto si una clase no se declara pública, queda restringido su acceso a las clases del mismo paquete. Las variables y métodos de una clase pueden tener diferentes niveles de accesibilidad dentro y fuera del paquete de la clase que los contiene. La siguiente sección se ocupa de los modificadores que determinan estos niveles de acceso.


Modificadores.


Las palabras public y static que el alumno habrá visto en las declaraciones de algunas clases, variables y métodos, se llaman modificadores. Su papel es especificar algunas propiedades para dichas clases, variables o métodos.

La siguiente tabla presenta todos los modificadores y una descripción de su función en el lenguaje cuando se aplica a clases, variables, métodos o interfaces. El significado y la importancia de los modificadores se irán aclarando a lo largo de esta lección y más adelante con las aplicaciones más avanzadas. Esta tabla debe servir como referencia.

modificador clases variables métodos interfaces
public visible en todas partes visible donde su clase lo sea visible donde su clase lo sea visible en todas partes
protected no se aplica a clases visible en el mismo paquete y en todas las subclases visible en el mismo paquete y en todas las subclases no se aplica a interfaces
ninguno (default) visible sólo en su paquete visible en el paquete de su clase visible en el paquete de su clase visible sólo en su paquete
private no se aplica visible sólo en su clase visible sólo en su clase no se aplica
abstract la clase tiene métodos no implementados no se aplica no hay implementación, sólo se da el encabezado y se termina con ; todas las interfaces son abstractas (aunque no se declaren así)
final no pueden crearse subclases su valor no se puede cambiar (es constante y el compilador puede pre-calcular el valor) no se puede sobreescribir no se aplica
native no se aplica no se aplica. está implementado en un lenguaje dependiente de plataforma, sólo se presenta el encabezado y se termina con ; no se aplica
static no se aplica es una variable de la clase, sólo hay una para todos los objetos de la clase y por tanto en todos los objetos tiene el mismo valor. Se puede invocar con el nombre de la clase. es un método de la clase, sólo puede usar las variables de clase. Se puede invocar con el nombre de la clase. no se aplica
synchronized no se aplica no se aplica. si es estático se bloquea al ejecutarse, y si no lo es, se bloquea su objeto. El bloqueo impide que se ejecuten otros hilos durante la ejecución del método. Este modificador se requiere cuando el método hace cambios no atómicos a la clase o al objeto no se aplica
transient no se aplica no es parte del estado del objeto (actualmente no se usa) no se aplica. no se aplica
volatile no se aplica se modifica asincrónicamente, puede ser un registro de hardware, siempre que se usa se verifica su valor por si ha cambiado. no se aplica no se aplica

El resto de esta sección la dedicaremos a un análisis del efecto de los modificadores en las variables de una clase sobre la accesibilidad de las variables desde las clases derivadas y desde otras clases.

Para fijar ideas consideremos los siguientes ejemplos. Se trata de cuatro clases llamadas claseA, claseB, claseC y claseD. Las dos primeras están en el paquete  ejem02.paquete1 y las otras en  ejem02.paquete2. claseB y claseC son extensiones de claseA. claseD declara y utiliza un objeto de claseA. La claseA tiene cuatro variables a,b,c y d con diferentes modificadores. La accesibilidad de estas variables desde las otras clases es el tema que vamos a explicar.

Aquí vería un applet si su navegador reconociese la etiqueta de APPLET. Aquí vería un applet si su navegador reconociese la etiqueta de APPLET. Aquí vería un applet si su navegador reconociese la etiqueta de APPLET. Aquí vería un applet si su navegador reconociese la etiqueta de APPLET.

El alumno puede ver que la variable pública d es accesible desde todas las clases. Las variables sin modificador y la protegida son accesibles desde claseB por ser ésta una subclase de claseA dentro del mismo paquete, sin embargo la variable sin modificador a no es accesible desde claseC a pesar de ser extensión de claseA y esto se debe a que está en otro paquete.   La variable privada c no es accesible más que desde la propia claseA donde fue definida.

Las clases claseA y claseB se definieron como públicas pues de otra manera no hubieran podido utilizarse desde el paquete ejem02.paquete2. En resumen, si se quiere que una variable o método sea accesible para otros paquetes, debe declararse como público y estar en una clase pública. El extremo opuesto a público es privado, que es accesible sólo desde la propia clase. Las variables y métodos declarados sin modificador de acceso, son accesibles para todas las clases del mismo paquete. Finalmente, las variables declaradas protegidas son accesibles para las clases en el mismo paquete y para todas las subclases estén o no en el mismo paquete.

Algunas variables y métodos juegan un papel importante en el funcionamiento interno de una clase, pero no deben ser modificados por los usuarios de la clase, que son los programadores que la utilizan. Tales variables deben declararse private. Algunas variables y métodos son importantes para varias clases de un paquete, pero no deben ser modifcados por los usuarios del paquete. Éstos deben declararse sin modificador de acceso. Finalmente, hay variables a las que debe poderse acceder desde cualquier subclase aunque no esté en el mismo paquete. Éstas deben declararse protected. Algo parecido ocurre con las propias clases. Hay clases que juegan un papel importante dentro de un paquete pero que no deben usarse fuera del paquete. Éstas deben declararse sin modificador de acceso. Las clases que están diseñadas para ser usadas fuera del paquete deben declarase public. Si se quiere que una clase no tenga subclases hay que declararla final.

¿Por qué hay tantas opciones de accesibilidad? ¿No sería más fácil que todo fuese público? Limitar el acceso a ciertas variables y métodos es importante para proteger el funcionamiento de los objetos de una clase. ¿De quién hay que protegerlo? De los programadores, de nosotros mismos. Todo programador sabe que a medida que un programa se complica es cada vez más difícil llevar control mental del papel que juega cada variable, cada método y cada clase y es muy fácil malinterpretar algo y usarlo erróneamente. Las partes afinadas de un pograma son muy valiosas y hay que protegerlas incluso del propio creador del programa y con mayor razón de un posible usuario del código que puede aprovechar el funcionamiento de una clase pero, por su propio bien, no debe modificar los detalles delicados.

Si el alumno no tiene mucha experiencia en otros lenguajes de programación es posible que tarde en reconocer la importancia de estas sutilezas. A la larga las agradecerá. De entrada lo mejor es no usar modificadores, con lo cual se adopta un punto intermedio que es lo más recomendable. Cuando se encuentre con que no tiene acceso a algo será porque está en otro paquete y el hecho de estar necesitando aquello es una señal de que debe declararse public. Y así, poco a poco, aprenderá a usar todos los modificadores correctamente.


Subclases.


La programación orientada a objetos y enparticular la programación en Java hace uso frecuente de la creación de subclases. En esta lección se explican algunos detalles de la relación que existe entre una subclase y la clase de la que es extensión, su superclase.

Una subclase es una clase que extiende a otra clase. La sintaxis para crear una subclase es esta:

class B extends A

En este caso se dice que B es una subclase de A o que B es una clase derivada de A y que A es la superclase de B. También se dice que A es una clase antecesora de B y se emplean todo tipo de términos que relacionan a B con la idea de hijo y a A con el de padre o madre.

Si B es una clase derivada de A entonces B tiene todas las variables y los métodos de A y puede tener además otras variables y otros métodos. Se utiliza la analogía genética del hijo heredando todos los rasgos genéticos del padre o la madre, pero teniendo algunas características propias. El símil puede ayudar a la comprensión del concepto, pero no conviene tomarlo muy en serio.

Una clase A puede tener muchas extensiones o clases derivadas (muchos hijos). Por otro lado, todas las clases, excepto java.lang.Object, son extensión de otra clase (todas descienden de otra clase). Si en la definición de una clase no se escribe explícitamente extends y el nombre de la clase que extiende, entonces extiende automáticamente a la clase Object. Una clase hereda las variables y métodos de su superclase, algunos de las y los cuales pueden ser heredados de otra clase. En este sentido una clase es extensión de una cadena de clases cada una de las cuales es extensión de otra, hasta llegar a Object que es el ancestro común de todas las clases. Pero a diferencia de los mamíferos que tienen dos ancestros directos (el padre y la madre), las clases de Java solo descienden de un solo ancestro inmediato, su superclase. Esto se pone de manifiesto en el hecho de que el compilador dará un error si al definir una clase se trata de decirle que extiende a más de una. En otras palabras, a la palabra extends en la definición de una clase sólo puede seguirle el nombre de una clase. Esto es diferente de lo que sucede con la palabra implements que se usa en relación con las interfaces, las cuales se estudian en la siguiente lección. Una clase puede implementar tantas interfaces como se desee.

Si la clase A tiene un solo constructor A() entonces la creación de un objeto de la subclase B llama automáticamente al constructor de A y el programador no tiene que ocuparse de ello. Sin embargo cuando una clase tiene varios constructores el programador puede decidir cual de ellos llamar al construir un objeto de la clase derivada B. Para ello puede utilizar la palabra super como si fuese el constructor de A y entre paréntesis pasarle los parámetros que le corresponden. Pero esto sólo puede hacerse en los constructores de B y sólo como primera instrucción dentro del constructor. Esto se debe a que cualquier cosa que se desee hacer con las variables de la clase derivada B, debe hacerse después de construida la parte correspondiente a la superclase A.


Sobrecarga y sobreescritura de métodos.


En Java varios métodos de una misma clase pueden tener el mismo nombre. Por ejemplo:

public static String valueOf(int i)

y

public static String valueOf(double d)

son dos métodos estáticos de la clase String que tienen el mismo nombre (e incluso los mismos modificadores: public y static; los modificadores se estudian en la lección 3), pero en cambio tienen diferentes parámetros. La posiblidad de definir varios métodos con el mismo nombre que sólo se distinguen por recibir diferentes parámetos, se llama sobrecarga. Se trata de algo muy sencillo, en realidad lo que ocurre es que el compilador de Java utiliza todo el encabezado como nombre de un método, así es que si dos métodos tienen diferente número de parámetros o alguno de ellos es de diferente tipo, eso basta para que sean diferentes para el compilador. Sin embargo al compilador no se le puede engañar tratando de definir dos métodos que sólo difieren en el nombre de sus parámetros...

La sobrecarga es muy útil en casos donde se necesitan varios métodos para realizar alguna función sobre objetos de diferente tipo como en el ejemplo indicado en donde se trata de una función que devuelve la cadena de dígitos (y punto decimal) que expresa la representación decimal de un número. Pero en un caso el número es de tipo int y en el otro de tipo double. Las implementaciones de ambos métodos son diferentes, el segundo debe hacer más trabajo pues debe distinguir dónde poner el punto decimal.

La sobrecarga se utiliza mucho y es muy cómoda para el programador pues le permite usar la misma nomenclatura para métodos análogos, con lo cual se ahorra tener que recordar nombres diferentes.

Ahora estudiaremos otra posibilidad que ofrece Java relacionada con los métodos que se llama la sobreescritura y que no debemos confundir con la sobrecarga.

Los métodos de una clase pueden sobreescribirse en sus clases derivadas. Consideremos los ejemplos siguientes:

Aquí vería un applet si su navegador reconociese la etiqueta de APPLET.
Aquí vería un applet si su navegador reconociese la etiqueta de APPLET.

holaApplet es una extensión de helloApplet, y lo único que hace es sobreescribir el método public void start() de su superclase helloApplet. La sobreescritura consiste en crear un método con el mismo nombre de alguno de la superclase. En la ejecución de la subclase se ejecuta el método sobreescrito y no el de la superclase.

Si una clase B es extensión de otra clase A y sobreescribe un método de A, entonces tiene la posibilidad de utilizar ambos métodos. El suyo propio lo utiliza llamándolo por su nombre, el de la superclase lo utiliza anteponiendo super. al nombre del método. Por ejemplo, si en holaApplet se quisiera utilizar el método public void start() de helloApplet en lugar del suyo propio, tendría que llamarlo así:

super.start();

La sobreescritura es muy útil pues permite aprovechar todo el código que nos interese de una clase que ya existe y solamente sobreescribir las partes que nos interesa modificar. Esto se hace extendiendo la clase original para conservarla intacta y realizando sólo el trabajo necesario en la subclase, como se ilustra en el sencillo ejemplo anterior donde se aprovecha el trabajo hecho en los métodos public void init() y public void paint(Graphics g) de helloApplet en los que se definen los colores, la fuente y se centra el mensaje, y solamente se sobreescribe public void start() substituyendo el mensaje.


Clases abstractas.


Se dice que una clase es abstracta si tiene métodos no implementados. Los métodos no implementados constan sólo de un encabezado con los modificadores, el nombre y los parámetros y en lugar de la implementación entre llaves, se pone punto y coma. Por ejemplo

abstract public void miMétodo(int x,String s);

podría ser la declaración de un método no implementado de una clase abstracta. El modificador abstract es necesario, sin él el compilador marcaría un error al encontrar  ;  en lugar de la llave { .

Una clase que tiene métodos no implementados, debe declararse abstract. Por ejemplo:

Aquí vería un applet si su navegador reconociese la etiqueta de APPLET.

es una clase abstracta. A partir de esta clase hemos creado tres subclases llamadas triangulo, cuadrado y circunferencia, que implementan el método void dibujar(Graphics g) como corresponde al nombre de cada una. El applet que se muestra a continuación utiliza un objeto cd de la clase abstracta cosaDibujable para presentar la figuras. Al pulsarse uno de los botones se crea un objeto de la subclase correspondiente, se asigna a la variable cd y se dibuja. El método public void paint(Graphics g) simplemente llama al método void dibujar(Graphics g) de cd sin hacer distinción de la realización concreta de cd en ese momento. Esto es posible porque las clases triangulo, cuadrado y circunferencia son extensiones de la clase abstracta cosaDibujable   y esto garantiza que cada una implementa su propio método void dibujar(graphics g).

Este es el código del applet:

Aquí vería un applet si su navegador reconociese la etiqueta de APPLET.

y éstos los códigos de las tres clases derivadas :

Aquí vería un applet si su navegador reconociese la etiqueta de APPLET.

Aquí vería un applet si su navegador reconociese la etiqueta de APPLET.

Aquí vería un applet si su navegador reconociese la etiqueta de APPLET.

Podrían crearse muchas otras extensiones de cosaDibujable y el mismo applet dibujador podría encargarse de dibujarlas.

Las clases abstractas son muy útiles para crear familias de clases que comparten cierta funcionalidad pero la implementación concreta de algunos de sus métodos pueden ser diferentes. El siguiente ejemplo ilustra esta aplicación de las clases abstractas. Primero se define la clase abstract public insercionBinaria, que contiene la implementación del método de ordenación por inserción binaria, uno de los más rápidos y útiles. Se define como una clase abstracta porque todo depende del método:

abstract public boolean menor_o_igual(Object o1,Object o2) ;

que compara dos objetos arbitrarios. Si hubiésemos implementado este método habríamos limitando la aplicación de la clase a un solo método de comparación entre objetos de algún tipo. Dejando este método como abstracto abrimos la posibilidad de extender la clase a otras que ordenen objetos concretos. Este es el código de la clase abstacta insercionBinaria:

Aquí vería un applet si su navegador reconociese la etiqueta de APPLET.

Como ejemplos de extensiones concretas de insercionBinaria   presentamos las clases ejecutables:

public class ordenaCadenas y public class ordenaNumeros.

La primera obtiene de los parámetros de ejecución una matriz de cadenas, la ordena y escribe el resultado. Aquí está su código:

Aquí vería un applet si su navegador reconociese la etiqueta de APPLET.

La segunda genera una matriz de veinte números enteros entre 0 y 1000, la escribe, la ordena y la vuelve a escribir:

Aquí vería un applet si su navegador reconociese la etiqueta de APPLET.

El alumno debe estudiar cuidadosamente las tres clases anteriores. En los ejercicios se le pedirá un análisis crítico detallado de ellas. En particular debe reflexionar sobre el ahorro de código que se logra con esta organización de los programas de ordenación y sobre la protección que se logra al haber declarado como privados ciertos métodos.. 


Interfaces.


Una interfaz es como una clase abstracta que no puede tener ninguna variable y ningún método implementado. Es decir, las interfaces constan exclusivamente de métodos abstractos y constantes (variables declaradas como static final). Si las interfaces son algo así como clases abstractas con restricciones, ¿para qué sirven?

La respuesta está en el hecho de que mientras una clase sólo puede extender a una clase, en cambio puede implementar tantas interfaces como quiera.

Las interfaces son contratos de implementación a los que una clase se compromete.

Como primer ejemplo de uso de una interfaz presentamos otra manera de organizar la ordenación binaria usando una sencilla interfaz y sin usar clases abstractas. Primero creamos una interfaz llamada ordenable con el siguiente código:

Aquí vería un applet si su navegador reconociese la etiqueta de APPLET.

Luego creamos una clase insercionBinariaBis que realiza la ordenación utilizando un objeto ordenable:

Aquí vería un applet si su navegador reconociese la etiqueta de APPLET.

Finalmente creamos el programa para ordenar cadenas como una clase que implementa la interfaz ordenable y crea una instancia de insercionBinariaBis pasándole como parámetro el objeto ordenable en el que se ha implementado el método menor_o_igual.

Aquí vería un applet si su navegador reconociese la etiqueta de APPLET.

Conviene observar algunos detalles. Por ejemplo, el método menor_o_igual debe aparecer como public   porque todos los métodos de una interfaz son implícitamente abstractos y públicos. En segundo lugar, la clase final ordenaCadenasBis no es extensión de nada, por lo que la posibilidad de convertirla en extensión de cualquier otra clase queda libre gracias a que el ser ordenable se lo hemos dado diciendo que implementa una interfaz y no que extiende una clase abstracta. Por tal motivo, en este caso al menos parece más cómoda la organización de los programas de ordenamiento usando la interfaz ordenable que la clase abstracta de la sección anterior.

En muchos casos el programador puede elegir entre estas dos opciones pues muchas veces una clase abstracta se puede sustituir por una clase concreta que opera sobre un objeto de una clase que implementa una interfaz que contiene todos los métodos abstractos de la clase abstracta, como ha ocurrido en este caso. Queda a criterio del programador cual de las opciones le resulta más cómoda. La ventaja principal de usar interfaz en lugar de clase abstracta suele radicar en la posiblidad de múltiples implementaciones contra la unicidad de la extensión.


Índice

Ejercicios de la Lección 02.


José Luis Abreu y Marta Oliveró