![]() |
|
![]() Alumno que no supera a su profesor, ..mal alumno.!! ..y el profesor, peor...!!! Wilucha |
![]() |
Una clase consiste en:
algunas_palabras class nombre_de_la_clase [algo_más] { [lista_de_atributos] [lista_de_métodos] }
Lo que está entre [ y ] es opcional…
Ya veremos qué poner en "algunas_palabras" y "algo_más", por ahora sigamos un poco más.
La lista de atributos (nuestras viejas variables locales) sigue el mismo formato de C: se define primero el tipo y luego el nombre del atributo, y
finalmente el ";".
public final static int MAX_VALUE;
También tenemos "algunas_palabras" adelante, pero en seguida las analizaremos.
En cuanto a los métodos, también siguen la sintaxis del C; un ejemplo:
public int incContador() { // declaración y apertura de {
cnt++; // instrucciones, separadas por ";"
return(cnt);
} // cierre de };
Finalmente, se aceptan comentarios entre /* y */, como en C, o bien usando // al principio del comentario (el comentario termina al final de la línea).
Veamos un ejemplo:
// Implementación de un contador sencillo
// GRABAR EN UN ARCHIVO "Contador.java" (OJO CON LAS MAYUSCULAS!)
// COMPILAR CON: "javac Contador.java" (NO OLVIDAR EL .java!)
// ESTA CLASE NO ES UNA APLICACION, pero nos va a servir enseguida
public class Contador { // Se define la clase Contador
// Atributos
int cnt; // Un entero para guardar el valor actual
// Constructor // Un método constructor…
public Contador() { // …lleva el mismo nombre que la clase
cnt = 0; // Simplemente, inicializa (1)
}
// Métodos
public int incCuenta() { // Un método para incrementar el contador
cnt++; // incrementa cnt
return cnt; // y de paso devuelve el nuevo valor
}
public int getCuenta() { // Este sólo devuelve el valor actual
return cnt; // del contador
}
}
Cuando, desde una aplicación u otro objeto, se crea una instancia de la clase Contador, mediante la instrucción:
// Usemos nuestro contador en una mini-aplicación
new Contador() el compilador busca un método con el mismo nombre de la clase y que se corresponda con la llamada en cuanto al tipo y número de parámetros. Dicho método se llama Constructor, y una clase puede tener más de un constructor (no así un objeto o instancia, ya que una vez que fue creado no puede recrearse sobre sí mismo).
En tiempo de ejecución, al encontrar dicha instrucción, el intérprete reserva espacio para el objeto/instancia, crea su estructura y llama al constructor.
O sea que el efecto de new Contador() es, precisamente, reservar espacio para el contador e inicializarlo en cero.
En cuanto a los otros métodos, se pueden llamar desde otros objetos (lo que incluye a las aplicaciones) del mismo modo que se llama una función desde C.
Por ejemplo, usemos nuestro contador en un programa bien sencillo que nos muestre cómo evoluciona:
// GRABAR EN UN ARCHIVO "Ejemplo1.java" (OJO CON LAS MAYUSCULAS!)
// COMPILAR CON: "javac Ejemplo.java" (NO OLVIDAR EL . java!)
// EJECUTAR CON: "java Ejemplo1" (SIN el .java)
import java.io.*; // Uso la biblioteca de entradas/salidas
public class Ejemplo1 { // IMPORTANTE: Nombre de la clase
// igual al nombre del archivo!
// entero para asignarle el valor del contador e imprimirlo
// aunque en realidad no me hace falta.
static int n;
// y una variable tipo Contador para instanciar el objeto…
static Contador laCuenta;
// ESTE METODO, MAIN, ES EL QUE HACE QUE ESTO SE COMPORTE
// COMO APLICACION. Es donde arranca el programa cuando ejecuto "java
Ejemplo1"
// NOTA: main debe ser public & static.
public static void main ( String args[] ) {
System.out.println ("Cuenta… "); // Imprimo el título
laCuenta = new Contador(); // Creo una instancia del
Contador
System.out.println (laCuenta.getCuenta()); // 0 - Imprimo el valor actual (cero!)
n = laCuenta.incCuenta(); // 1 - Asignación e incremento
System.out.println (n); // Ahora imprimo n
laCuenta.incCuenta(); // 2 - Lo incremento (no uso el valor…
System.out.println (laCuenta.getCuenta()); // …de retorno) y lo imprimo
System.out.println (laCuenta.incCuenta()); // 3 - Ahora todo en un paso!
}
}
En el capítulo III vamos a analizar este programa en detalle. Por ahora veamos la diferencia con un applet que haga lo mismo:
// Applet de acción similar a la aplicación Ventana
// GRABAR EN ARCHIVO: "Ventana.java"
// COMPILAR CON: "javac Ventana.java"
// PARA EJECUTAR: Crear una página HTML como se indica luego
//INICIO
// PROGRAMA: Ventana.java
//HACEMOS REFERENCIA A LA BIBLIOTECA QUE CONTIENE LAS CLASES GRÁFICAS
import java.awt.*;
//CREAMOS NUESTRA CLASE "Ventana" Y LA HACEMOS QUE HEREDE LOS ATRIBUTOS
//Y METODOS DE LA CLASE "Frame"
public class Ventana extends Frame{
//CREAMOS EL CONSTRUCTOR DE NUESTRA CLASE
public Ventana(){
super("Ventana en JAVA"); //LE DAMOS UN NOMBRE A NUESTRA VENTANA
this.setSize(500, 500); //ESTABLECEMOS EL TAMAÑO DE LA VENTANA
}//FIN DEL CONSTRUCTOR DE LA CLASE Ventana
//PROCEDIMIENTO PRINCIPAL DEL PROGRAMA
public static void main(String g[]){
Ventana prog = new Ventana();//Instanciamos la clase que creamos
prog.show();//Mostramos esa ventana
}//FIN DEL PROCEDIMIENTO PRINCIPAL
//PARA PODER CERRAR LA VENTANA
public boolean handleEvent(Event evt){
if (evt.id == Event.WINDOW_DESTROY)
System.exit(0);
return super.handleEvent(evt);
}
}//FIN DE LA CLASE Ventana
//FIN
Ahora es necesario crear una página HTML para poder visualizarlo. Para esto, crear y luego cargar el archivo ejemplo2.htm con un browser que soporte Java (o bien ejecutar en la ventana DOS: "appletviewer ejemplo2.htm"):
Para terminar este capítulo, observemos las diferencias entre la aplicación standalone y el applet:
Además:
Con poco trabajo se pueden combinar ambos casos en un solo objeto, de modo que la misma clase sirva para utilizarla de las dos maneras:
// Archivo: Botones.java
// Compilar con: javac Botones.java
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
public class Botones extends JFrame{
//CREAMOS LOS BOTONES
JButton aceptar = new JButton("Aceptar");
JButton cancelar = new JButton("Cancelar");
//CREAMOS UN PANEL PARA COLOCAR LOS BOTONES
JPanel principal = new JPanel(new BorderLayout());
public Botones(){
super("Botones");
principal.add("North", aceptar);
principal.add("South", cancelar);
getContentPane().add(principal);
//AGREGAMOS TOOL TIPS A LOS BOTONES
aceptar.setToolTipText("Botón ACEPTAR...");
cancelar.setToolTipText("Botón CANCELAR...");
pack();
setResizable(false);
Dimension pantalla, cuadro;
pantalla = Toolkit.getDefaultToolkit().getScreenSize();
cuadro = this.getSize();
this.setLocation(((pantalla.width - cuadro.width)/2), (pantalla.height - cuadro.height)/2);
}//FIN DEL CONSTRUCTOR DE LA CLASE
public static void main(String g[]){
Botones p = new Botones();
p.show();
p.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent evt){
System.exit(0);
}
});
}//FIN DEL MAIN
}//FIN DE LA CLASE
Esta clase puede ejecutarse tanto con "java Botones" en una ventana DOS, como cargarse desde una página HTML con:
Notar que conviene probar el applet con el appletviewer ("appletviewer ejemplo3.htm"), ya que éste indica en la ventana DOS si hay algún error durante la ejecución. Los browsers dejan pasar muchos errores, simplemente suprimiendo la salida a pantalla del código erróneo.
Notar que en todo este desarrollo de las clases Ejemplo1, Ejemplo2 y Ejemplo3, en ningún momento volvimos a tocar la clase Contador!
En el próximo capítulo analizaremos el código línea por línea para ir comprendiendo los distintos elementos que allí aparecen.
Estructura de clases
Vamos a comenzar analizando la clase Contador, para ir viendo las partes que forman una clase una por una y en detalle. Este capítulo va a ser un poco aburrido por lo exhaustivo (aunque algunos puntos más complicados como las excepciones y los threads los dejaremos para después), pero me parece bueno tener un resumen completo de la sintaxis desde ahora.
Luego iremos armando pequeñas aplicaciones para probar cada cosa.
Recordemos la definición de la clase Contador:
// Implementación de un contador sencillo
public class Contador {
// Atributos
int cnt;
// Constructor
public Contador() {
cnt = 0;
}
// Métodos
public int incCuenta() {
cnt++;
return cnt;
}
public int getCuenta() {
return cnt;
}
}
La clase se declara mediante la línea public class Contador. En el caso más general, la declaración de una clase puede contener los siguientes elementos:
[public] [final | abstract] class Clase [extends ClaseMadre] [implements Interfase1 [, Interfase2 ]…] o bien, para interfaces:
[public] interface Interfase [extends InterfaseMadre1 [, InterfaseMadre2 ]…]
Como se ve, lo único obligatorio es class y el nombre de la clase. Las interfases son un caso de clase particular que veremos más adelante.
Definir una clase como pública (public) significa que puede ser usada por cualquier clase en cualquier paquete. Si no lo es, solamente puede ser utilizada por clases del mismo paquete (más sobre paquetes luego; básicamente, se trata de un grupo de clases e interfaces relacionadas, como los paquetes de biblioteca incluídos con Java).
Una clase final (final) es aquella que no puede tener clases que la hereden. Esto se utiliza básicamente por razones de seguridad (para que una clase no pueda ser reemplazada por otra que la herede), o por diseño de la aplicación.
Una clase abstracta (abstract) es una clase que puede tener herederas, pero no puede ser instanciada. Es, literalmente, abstracta (como la clase Number definida en java.lang). ¿Para qué sirve? Para modelar conceptos. Por ejemplo, la clase Number es una clase abstracta que representa cualquier tipo de números (y sus métodos no están implementados: son abstractos); las clases descendientes de ésta, como Integer
o Float, sí implementan los métodos de la madre Number, y se pueden instanciar.
Por lo dicho, una clase no puede ser final y abstract a la vez (ya que la clase abstract requiere descendientes…)
¿Un poco complejo? Se va a entender mejor cuando veamos casos particulares, como las interfases (que por definición son abstractas ya que no implementan sus métodos).
Extends
La instrucción extends indica de qué clase desciende la nuestra. Si se omite, Java asume que desciende de la superclase Object. Cuando una clase desciende de otra, esto significa que hereda sus atributos y sus métodos (es decir que, a menos que los redefinamos, sus métodos son los mismos que los de la clase madre y pueden utilizarse en forma transparente, a menos que sean privados en la clase madre o, para subclases de otros paquetes, protegidos o propios del paquete). Veremos la calificación de métodos muy pronto, a no desesperar!
Implements
Una interfase (interface) es una clase que declara sus métodos pero no los implementa; cuando una clase implementa (implements) una o más interfases, debe contener la implementación de todos los métodos (con las mismas listas de parámetros) de dichas interfases.
Esto sirve para dar un ascendiente común a varias clases, obligándolas a implementar los mismos métodos y, por lo tanto, a comportarse de forma similar en cuanto a su interfase con otras clases y subclases.
Interface
Una interfase (interface), como se dijo, es una clase que no implementa sus métodos sino que deja a cargo la implementación a otras clases. Las interfases pueden, asimismo, descender de otras interfases pero no de otras clases.
Todos sus métodos son por definición abstractos y sus atributos son finales (aunque esto no se indica en el cuerpo de la interfase).
Son útiles para generar relaciones entre clases que de otro modo no están relacionadas (haciendo que implementen los mismos métodos), o para distribuir paquetes de clases indicando la estructura de la interfase pero no las clases individuales (objetos anónimos).
Si bien diferentes clases pueden implementar las mismas interfases, y a la vez descender de otras clases, esto no es en realidad herencia múltiple ya que una clase no puede heredar atributos ni métodos de una interface; y las clases que implementan una interfase pueden no estar ni siquiera relacionadas entre sí.
El cuerpo de la clase, encerrado entre { y }, es la lista de atributos (variables) y métodos (funciones) que constituyen la clase. No es obligatorio, pero en general se listan primero los atributos y luego los métodos.
Declaración de atributos
En Java no hay variables globales; todas las variables se declaran dentro del cuerpo de la clase o dentro de un método. Las variables declaradas dentro de un método son locales al método; las variables declaradas en el cuerpo de la clase se dice que son miembros de la clase y son accesibles por todos los métodos de la clase.
Por otra parte, además de los atributos de la propia clase se puede acceder a todos los atributos de la clase de la que desciende; por ejemplo, cualquier clase que descienda de la clase Polygon hereda los atributos npoints, xpoints e ypoints.
Finalmente, los atributos miembros de la clase pueden ser atributos de clase o atributos de instancia; se dice que son atributos de clase si se usa la palabra clave static: en ese caso la variable es única para todas las instancias (objetos) de la clase (ocupa un único lugar en memoria). Si no se usa static, el sistema crea un lugar nuevo para esa variable con cada instancia (o sea que es independiente para cada objeto).
La declaración sigue siempre el mismo esquema:
[private|protected|public] [static] [final] [transient] [volatile] Tipo NombreVariable [= Valor];
Private, protected o public
Java tiene 4 tipos de acceso diferente a las variables o métodos de una clase: privado, protegido, público o por paquete (si no se especifica nada).
De acuerdo a la forma en que se especifica un atributo, objetos de otras clases tienen distintas posibilidades de accederlos:
Acceso desde: private protected public (package) la propia clase S S S S subclase en el mismo paquete N S S S otras clases en el mismo paquete N S S
S subclases en otros paquetes N X S N otras clases en otros paquetes N N S N
S: puede acceder
N: no puede acceder
X: puede acceder al atributo en objetos que pertenezcan a la subclase, pero no en los que pertenecen a la clase madre. Es un caso especial ; más adelante veremos ejemplos de todo esto.
Static y final
Como ya se vio, static sirve para definir un atributo como de clase, o sea único para todos los objetos de la clase.
En cuanto a final, como en las clases, determina que un atributo no pueda ser sobreescrito o redefinido.
O sea: no se trata de una variable, sino de una constante.
Transient y volatile
Son casos bastante particulares y que no habían sido implementados en Java 1.0.
Transient denomina atributos que no se graban cuando se archiva un objeto, o sea que no forman parte del estado permanente del mismo.
Volatile se utiliza con variables modificadas asincrónicamente por objetos en diferentes threads (literalmente "hilos", tareas que se ejecutan en paralelo); básicamente esto implica que distintas tareas pueden intentar modificar la variable simultáneamente, y volatile asegura que se vuelva a leer la variable (por si fue modificada) cada vez que se la va a usar (esto es, en lugar de usar registros de almacenamiento como buffer).