Java PDF
Java PDF
Java PDF
Programacion Java
Septiembre 2004
2
Indice general
1. Introduccion 7
3
2.10.1. Operadores de Incremento Posfijo . . . . . . . . . . . 37
2.10.2. Operadores Unarios . . . . . . . . . . . . . . . . . . . 38
2.10.3. El operador new . . . . . . . . . . . . . . . . . . . . . 39
2.10.4. El operador cast . . . . . . . . . . . . . . . . . . . . . 39
2.10.5. Operadores Artimeticos . . . . . . . . . . . . . . . . . 40
2.10.6. Operador concatenacion de cadenas de caracteres . . . 41
2.10.7. Operadores de corrimiento . . . . . . . . . . . . . . . . 41
2.10.8. Operadores relacionales . . . . . . . . . . . . . . . . . 42
2.10.9. Operadores Logicos a bit . . . . . . . . . . . . . . . . 42
2.10.10.AND y OR booleanos . . . . . . . . . . . . . . . . . . 43
2.10.11.Operador condicional . . . . . . . . . . . . . . . . . . 43
2.10.12.Operadores de asignacion . . . . . . . . . . . . . . . . 43
2.11. Enunciados . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
2.11.1. El enunciado if . . . . . . . . . . . . . . . . . . . . . 45
2.11.2. El enunciado switch . . . . . . . . . . . . . . . . . . . 46
2.12. Ciclos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
2.12.1. El ciclo while . . . . . . . . . . . . . . . . . . . . . . . 48
2.12.2. El ciclo do . . . . . . . . . . . . . . . . . . . . . . . . . 48
2.12.3. El ciclo for . . . . . . . . . . . . . . . . . . . . . . . . 49
2.13. Control de ejecucion . . . . . . . . . . . . . . . . . . . . . . . 51
2.13.1. break . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
2.13.2. continue . . . . . . . . . . . . . . . . . . . . . . . . . 51
2.13.3. return . . . . . . . . . . . . . . . . . . . . . . . . . . 52
4
3.2.6. Redefinicion de Metodos . . . . . . . . . . . . . . . . . 82
3.2.7. Metodos Finales . . . . . . . . . . . . . . . . . . . . . 85
3.2.8. Expresiones de invocacion a Metodos . . . . . . . . . . 85
3.2.9. Busqueda de nombre modificado para miembros de
una clase . . . . . . . . . . . . . . . . . . . . . . . . . 93
3.2.10. Metodos abstractos . . . . . . . . . . . . . . . . . . . . 95
3.2.11. Metodos heredados de la clase Object . . . . . . . . . 96
3.3. Herencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
3.3.1. Palabras Clave private, protected, y Herencia . . . 103
3.3.2. Constructores y Herencia . . . . . . . . . . . . . . . . 106
5
6
Captulo 1
Introduccion
7
No hay aritmetica de apuntadores: las unicas operaciones permitidas
sobre las referencias a un objeto son asignacion de otra referencia a
objeto y comparacion con otra referencia. Por lo tanto, el programador
tiene menor posibilidad de escribir codigo susceptible a errores que
modifique un apuntador.
3. Core Java.
Gary Cornell and Cay S. Horstsmann.
Prentice-Hall, 1996.
4. Java in a Nutshell.
David Flanagan.
OReilly, 1996.
8
Estas notas dan una introduccion al lenguaje de programacion Java.
Su objetivo es servir al estudiante como una referencia sencilla y rapida al
lenguaje. Su enfoque considera primero presentar elementos de programacion
basica (como son identificadores, tipos, variables arreglos, operaciones, enun-
ciados de control, etc.) que Java tiene en comun con otros lenguajes de
programacion. En seguida, se presenta como Java presenta los conceptos
de programacion Orientada a Objetos (principalmente, clases, metodos y
herencia). Finalmente, se introducen varios topicos especiales exclusivos del
lenguaje Java, que utilizan la programacion basica y orientada a objetos
(temas como paquetes, interfaces, excepciones, hebras de control y ven-
tanas). Sin embargo, estas notas no describen las bibliotecas de clases de
Java. Estas estan extensivamente descritas en la documentacion de JDK que
se encuentra disponible en las paginas de Web anteriormente mencionadas.
9
10
Captulo 2
2.1.2. Comentarios
Los comentarios en Java tienen tres formas:
11
/** Este es un comentario
para documentacion */
@author. Nombre del o los autores del codigo que se comenta. El nom-
bre del autor se escribe simplemente como texto:
12
@deprecated No sera disponible en siguientes versiones
13
@see java.lang.Integer
@see Integer
@see Integer#intValue
@see Integer#getInteger(String)
@see <a href="info.html">Vease aqui para mayor informacion</a>
@since JDK1.0
14
/** Esta es una clase de prueba para mostrar el uso de comentarios para
documentacion.
@author Daniel Lopez
@see java.lang.String
@version 1.0 10.8.1997
*/
System.out.println(s);
}
15
2.2. Identificadores
Un identificador (o simplemente un nombre) en Java es basicamente una se-
cuencia de caracteres alfabeticos o dgitos que comienza con un caracter alfabetico.
Los identificadores pueden incluir tambien el subrayado (underscore: ) y $ (aun
cuando $ no debe ser utilizado en codigo normal).
Ejemplos son:
16
2.3. Valores
Los valores en Java pueden ser de los siguientes tipos:
Un valor puede ser utilizado en cualquier expresion donde su uso sea del tipo
correcto.
Los valores octales de tipo int son precedidos por un cero (0), seguido por
dgitos en el rango de 0 a 7:
Los valores hexadecimales del tipo int se preceden por 0x o 0X. Los dgitos
hexadecimales pueden representarse por cifras del 0 al 9 y de A a F (o a a f):
Los valores de tipo long (64 bits) se denotan por anadirles una L o l a cualquier
representacion de un entero:
17
Un sufijo D o d puede de forma similar implicar tipo double.
Los numeros de punto flotante pueden ser escritos utilizando el formato con
exponente, mediante el uso de e o E, considerando las reglas anteriormente senaladas
al anadir f, F, d y D:
4.54003e+24, 5.3453E-22, 1.2e5f, -1.978748E+33D, 2.5444e+9f
Los valores de punto flotante no pueden ser escritos utilizando notaciones octal
o hexadecimal.
18
2.3.5. Valores de cadenas de caracteres
Los valores de cadenas de caracter se encierran en comillas dobles, y se almace-
nan como objetos String cuando el programa se ejecuta. Pueden incluir caracteres
especiales (comunmente, la lnea aparte):
19
2.4. Tipos
Un tipo es un nombre que representa un conjunto de valores. Java tiene dos
categoras de tipos: tipos primitivos y tipos de referencia. Por cada tipo hay un
conjunto de operadores para los valores de tal tipo (por ejemplo, +, -, *, etc.).
Hay otros tres valores especiales de tipo flotante: infinito positivo (positive
infinity), infinito negativo (negative infinity) y no un numero (not a number,
o NaN). Estos se generan cuando una operacion de punto flotante sobrepasa la
capacidad de la computadora (overflow), u ocurre una division entre cero.
20
Los operadores definidos para cada uno de estos tipos primitivos se describen
mas adelante.
Existe aun un tipo mas que pertenece a los tipos primitivos del lenguaje. Este
es el tipo void (void es una palabra reservada). No existen valores de tipo void,
as que no tiene representacion. El unico uso de void es para la declaracion de
metodos que no retornan ningun resultado (o para ser mas precisos, retornan un
valor de tipo void para el cual no hay representacion, y por lo tanto, no requiere
forma de almacenamiento en memoria).
21
Los siguientes tipos de conversion automatica se soportan:
short a = 12;
long b = a;
Es posible hacer esto aun cuando a primera vista a y b son de tipos dife-
rentes. Cuando este codigo es compilado, el compilador insertara las conversiones
necesarias automaticamente y sin comentarios. La mayor parte del tiempo esto no
causara problemas, pero ocasionalmente puede generar sorpresas, particularmente
en el caso de las conversiones de referencias.
22
2.5. Entornos (Scope)
Un programa puede tener los siguientes entornos:
Entorno Global. Los nombres declarados en este entorno son accesibles para
cualquier enunciado dentro de todo el programa. Solo los nombres de pa-
quetes (packages) son globales.
Entorno de Paquetes. Un paquete (package) define un entorno y una unidad
de encapsulacion. Los nombres declarados dentro de un paquete pueden ser
globales para el paquete mismo, y pueden hacerse publicos (public), de tal
modo que puedan ser accesados por los usuarios del paquete. Los paquetes
no pueden ser anidados, es decir, estar uno dentro de otro, pero un paquete
puede usar cualquier otro.
Entorno de Unidad de Compilacion. Una unidad de compilacion es, en efecto,
un sinonimo de archivo fuente. Los nombres pueden ser globales solo dentro
del archivo. Todos los contenidos de cualquier archivo son siempre parte de
un paquete.
Entorno de Clase. Los nombres dentro de una clase son accesibles a cualquier
parte de la misma clase o de sus subclases si son declarados publicos (public)
o protegidos (protected). Mas aun, si los nombres son declarados como
publicos, pueden ser accedidos por los usuarios de la clase, o si no se les
especifica como publicos, protegidos o privados (private), pueden ser acce-
didos por otras clases dentro del mismo paquete.
Entorno Local. Un enunciado compuesto (aquel enunciado delimitado por {
y }, y que contiene una secuencia de otros enunciados) define un entorno
local. Los cuerpos de metodos y enunciados de control son tambien entornos
locales. Los entornos locales pueden ser anidados a cuantos niveles como sea
necesario (por ejemplo, un entorno local puede ser declarado dentro de otro
entorno local). Los nombres declarados en un entorno local son unicamente
accesibles dentro de tal entorno o en cualquiera de sus entornos anidados. Mas
aun, los nombres son solo utilizables desde el punto textual de declaracion y
hasta el fin del entorno, es decir, no puede usarse un nombre antes de que
haya sido declarado en el texto (notese la diferencia con el entorno de clase).
Los entornos son siempre anidados entre s, siendo el entorno global el de mas
externo nivel. Un nombre declarado en un entorno anidado o contenido es accesible
por el entorno anidado o contenido, a menos que sea oculto por otra declaracion
que use el mismo nombre.
23
2.6. Declaracion de Variables
La sintaxis basica para declarar variables es:
tipo identificador;
o
tipo identificador = expresion;
Es posible declarar dos o mas variables del mismo tipo en la misma declaracion:
Ademas de las dos categorasde variables, varios tipos de variables pueden ser
identificadas basandose en cuando son declaradas:
24
Una variable se declara mediante especificar su tipo y su nombre, donde el nom-
bre es un identificador, siguiendo las reglas de construccion de identificadores. Por
ejemplo, las siguientes variables son declaradas con una inicializacion por defecto:
int j;
long d;
int a, b, c;
25
2.7. Inicializacion de Variables
Los requerimientos de inicializacion de variables varan dependiendo del tipo
de variable.
byte: (byte) 0
short: (short) 0
int: 0
long: 0l
float: 0.0f
double: 0.0d
char: \u0000 (el caracter nulo)
boolean: false
tipos de referencia: null
Si una variable de referencia es inicializada a ser null, mantiene tal valor sin
referenciar ningun objeto, hasta ser inicializada o definida.
int i = 100;
char c = d;
float f = 10.345;
int i;
{i = 100 * 2 + 5}
26
3. Por asignacion en el cuerpo de un constructor.
int a = 10, b, c = 5;
{
int i = 10;
...
}
o por una asignacion hecha de la variable antes de ser usada en cualquier otra
expresion:
{
int i;
... // donde no hay enunciados que utilicen i
i = 10; // asigna un valor a i
... // i puede ser ahora usada
}
27
2.7.5. Variables Finales
Normalmente una instancia final o variable local se inicializan como parte de
una declaracion, como se muestra anteriormente. Sin embargo, una variable final
vaca (blank final) se permite tambien, en la cual la inicializacion es diferida. Antes
de usar una variable final vaca, esta debe ser inicializada por asignacion, tras lo
cual no podra ser modificada.
import java.util.Date;
import java.util.Vector;
System.out.println(j);
28
// Declaracion e inicializacion de una variable de clase
static int m = 10;
29
2.8. Arreglos de Variables
Los arreglos son contenedores de objetos y pueden almacenar un numero fijo
de elementos (o valores) que pueden ser accesados mediante indexado del propio
arreglo. Una variable del arreglo mantiene una referencia al objeto arreglo que es
creado por una expresion de inicializacion del arreglo. Los corchetes se utilizan para
denotar arreglos:
int [] x = ...;
donde se declara una variable x a ser una variable de tipo array de int.
Como en el caso de las variables simples, los arreglos pueden declararse e ini-
cializarse en expresiones de la forma:
tipo [ ] identificador;
o
tipo [ ] identificador = expresion de inicializacion del arreglo;
o
tipo identificador [ ];
o
tipo identificador [ ] = expresion de inicializacion del arreglo;
Pueden declararse arreglos de cualquier tipo (incluso, tipo arreglo), sobre los
cuales el compilador de Java realiza una verificacion de tipos para asegurarse que
los valores del tipo correcto se almacenan en el arreglo:
30
String [] palabras = new String[100];
donde length es una variable de instancia que representa el tamano o longitud del
arreglo.
31
double [][] d = new double [renglones][columnas];
De la misma manera en que las variables de tipos primitivos pueden ser ini-
cializadas directamente, tambien lo son los elementos de un arreglo. El siguiente
ejemplo muestra la sintaxis necesaria:
int [] n = {1,2,3,4,5};
Una lista de inicializacion se delimita por llaves ({ }), con un valor para cada
elemento del arreglo separado por comas. Notese que las llaves aqu no denotan un
enunciado compuesto. El ejemplo anterior crea un arreglo de tamano 5, determina-
do por la longitud de la lista de inicializacion, e inicializa cada elemento siguiendo
el orden descrito por los valores de inicializacion. Cuando se usa una lista de ini-
cializacion o usando new, la creacion del arreglo es implcita, por lo que no requiere
ser hecha por el programador.
Esto crea un arreglo de 4 por 4 con el primer renglon inicializado por los valores
1, 2, 3, y 4, y el segundo renglon por 4, 5, 6 y 7. Si se da un conjunto incompleto
de elementos:
la inicializacion del arreglo sera aun valida, pero dara un arreglo de tamanos dis-
pares, unicamente con las posiciones de los elementos inicializados a ser accesibles.
El intento de accesar cualquier elemento en una posicion no inicializada genera una
excepcion.
32
int [] n; // Inicializacion por defecto
...
n = new int [] {5,6,7}; // Creacion de un arreglo anonimo y
// asignacion como referencia de n
Finalmente, queda aun el tema de indexado en arreglos. Sin embargo, este tema
se difiere para cubrirse en la seccion de operadores.
33
2.9. Expresiones Primarias
Las expresiones primarias son bloques basicos para la construccion de progra-
mas, que permiten la busqueda de valores que seran manipulados por otras expre-
siones. Las siguientes son expresiones primarias:
Las palabras clave this y super (que seran descritas mas adelante), as como
el valor null
Un valor literal
( expresion )
identificador
o
expresion.identificador
o
paquete.identificador
objeto.variable;
34
En este ejemplo, el lado izquierdo es esencialmente una referencia a objeto, y
el caracter . se utiliza para seleccionar una variable con nombre perteneciente
a tal objeto (como se define por su clase).
null evalua la referencia nula y puede ser usado para probar la variable de un
tipo referencia para ver si tiene el valor null:
La primera expresion de indexacion a[1] arroja una expresion del tipo int
[][] para la cual la segunda expresion puede ser evaluada. Esta, a la vez, arroja
35
una expresion de tipo int [], la cual retorna el valor requerido de la variable
cuando la expresion de indexacion del tercer arreglo es evaluado.
Las expresiones de alocacion usan la palabra clave new para crear un objeto o
arreglo. La palabra new es seguida por el tipo (ya sea un tipo referencia o primitivo)
y opcionalmente una lista de parametros en parentesis (que pueden estar vacos),
o la creacion de un arreglo utilizando corchetes.
36
2.10. Operadores
Los operadores con mas alta precedencia dentro de una expresion son siempre
aplicados primero. La siguiente lista muestra la precedencia de todos los operadores,
indexado de arreglos, seleccion de campo (usando .), cast y new. La lista va de
la mas alta a la mas baja precedencia. Operadores en el mismo renglon tienen la
misma precedencia:
[] . () ++ --
++ -- + - ~ !
new cast
* / %
+ -
<< >> >>>
< > >= <= instanceof
== !=
&
^
|
&&
||
?
= += -= *= /= %= >>= <<= >>>= &= ^= |=
byte i = 1;
float j = 10.4;
i++; // incrementa i
j--; // decrementa j
// aqui, i == 2 y j == 11.4
37
int i = 1;
int j = i++; // j se asigna el valor de 1
int k = i; // k se asigna el valor de 2
Esencialmente, estos dos operadores generan efectos colaterales por actualizar
el valor de la variable tras retornar el valor original de la variable.
38
Este operador se utiliza para realizar operaciones de bit (bitwise) sobre numeros
binarios. Es util cuando se requiere operaciones a nivel de bit, como manipulaciones.
Se utiliza como herramienta de programacion de sistemas mas que de programacion
de aplicaciones. Por ejemplo, el siguiente metodo utiliza y el operador de bit &
para retornar un patron de bits con los cuatro bits menos significativos (los cuatro
ultimos) forzados a ser cero:
while(true){
...
if (...) break;
...
}
39
cast posibles a aquellas que preserven la intencion de los valores, previniendo de
aquellas operaciones que modifiquen substancialmente el valor, como sera un cam-
bio de double a boolean.
float f = 2.3;
int n = (int)f;
Adicion (+)
Sustraccion (-)
Multiplicacion (*)
Division (/)
Residuo ( %)
Todas estas son operaciones que requieren dos datos, realizando esencialmente
las operaciones aritmeticas obvias de adicion, sustraccion, multiplicacion y division.
El operador residuo ( %) es menos familiar, pero simplemente retorna el residuo
entero de una operacion de division.
40
Las operaciones de punto flotante (aquellas que involucran tipos float y double)
son algo mas complicadas, ya que pueden representar los valores mediante NaN (No-
un-numero o not-a-number), que se usa cuando no es posible representar un valor
numerico.
int a = 1000;
...
a << 5; // a se recorre 5 bits a la izquierda
41
2.10.8. Operadores relacionales
Los siguientes operadores relacionales se encuentran disponibles:
igual a (==)
no igual a (!=)
Cada una de estas operaciones permiten evaluar las operaciones logicas AND,
OR o XOR considerando los operandos como conjuntos de bits individuales. Por
ejemplo, el siguiente codigo imprime una direccion IP de 32 bits que se contiene en
una variable int llamada ip address, como 4 octetos decimales:
42
2.10.10. AND y OR booleanos
Los operadores booleanos son:
AND condicional booleano (&&)
OR condicional booleano (||)
La diferencia crucial entre los operadores && y ||, y los operadores & y |, recae en
la forma en que evaluan sus operandos. && y || primero evaluan su operador del lado
izquierdo. Si el valor del operando es suficiente para determinar el valor de toda la
expresion, la evaluacion del lado derecho no se realiza. Esto significa que cuando
una operacion && se aplica, el operando del lado derecho se evalua solo cuando el
operando del lado izquierdo tiene el valor de true. Si el operando izquierdo tiene el
valor false, el valor de la expresion entera debe ser false, y no es necesario evaluar
el lado derecho para determinar el resultado. Una situacion similar se aplica para la
operacion ||. Si el operando de la izquierda es true, entonces la expresion completa
debe ser true, y el operando de la derecha no necesita ser evaluado.
Una expresion que usa este operador se evalua como sigue: primero, la expresion
booleana es evaluada. Si tiene el valor true, la expresion inmediatamente despues
de ? se evalua y su resultado se retorna como el resultado del operador. Si por el
contrario, tiene el valor false, la expresion despues de : se evalua y retorna su
resultado como el resultado del operador condicional.
43
Los otros operadores de asignacion son todos binarios, y combinan la asignacion
con varias operaciones aritmeticas o de bit:
+=, -=, *=, /=, %=, |=, &=, ^=, <<=, >>=, >>>=
44
2.11. Enunciados
2.11.1. El enunciado if
if (ExpresionBooleana)enunciado
o
if (ExpresionBooleana)enunciado
else enunciado
class IfTest1 {
public static void main (String [] args) {
int a = 1;
int b = 2;
if (a < b ) {
System.out.println("a < b");
}
else {
System.out.println("a >= b");
}
}
}
class IfTest2 {
public static boolean f (int x, int y) {
return (x < y );
}
public static void main (String [] args) {
int a = 1;
int b = 2;
if (f(a,b)) {
System.out.println("f retorna true");
}
else {
45
System.out.println("f retorna false");
}
}
}
class Switch1 {
public static void main (String[] args) {
KeyboardInput input = new KeyboardInput();
System.out.print("Escriba un numero entre 1 y 7: ");
int day = input.readInteger();
switch (day) {
case 1: System.out.println("Domingo"); break;
case 2: System.out.println("Lunes"); break;
case 3: System.out.println("Martes"); break;
case 4: System.out.println("Miercoles"); break;
case 5: System.out.println("Jueves"); break;
case 6: System.out.println("Viernes"); break;
case 7: System.out.println("Sabado"); break;
default:System.out.println(day + " no es dia"); break;
}
}
}
46
Si un entero entre 1 y 7 se da como entrada, el nombre del correspondiente
da de la semana se escribe en pantalla, utilizando el switch para seleccionarlo.
Si el numero es mayor que 7 o menor que 1, el mensaje por defecto (default) se
despliega. Cualquiera que sea la entrada, solo el mensaje cuya etiqueta corresponda
a la entrada se despliega.
El enunciado switch puede utilizar tipos char, byte, short, e int. El siguiente
ejemplo funciona con char:
class Switch2 {
public static void main(String [] args) {
KeyboardInput input = new KeyboardInput();
System.out.print("Escriba una vocal y presione retorno: ");
char c = input.readCharacter();
switch(c) {
casea : System.out.println("Se escribio a"); break;
casee : System.out.println("Se escribio e"); break;
casei : System.out.println("Se escribio i"); break;
caseo : System.out.println("Se escribio o"); break;
caseu : System.out.println("Se escribio u"); break;
default: System.out.println("No es una vocal"); break;
}
}
}
47
2.12. Ciclos
2.12.1. El ciclo while
while ( expresion booleana)enunciado
2.12.2. El ciclo do
do enunciado while (Expresion booleana) ;
48
do {
System.out.println("Contando hacia abajo " + n);
n--;
} while (n > 0);
}
}
El primer ejemplo es equivalente a los ejemplos para los ciclos while y do:
49
System.out.println(x[i]);
}
}
}
50
2.13. Control de ejecucion
2.13.1. break
break;
o
break etiqueta;
La expresion boolena del ciclo while tiene el valor de true, de tal modo que
tal ciclo potencialmente es infinito. Usando el enunciado break se provee de una
forma conveniente de salir de un ciclo sin introducir una variable booleana.
2.13.2. continue
continue;
o
continue etiqueta;
51
El siguiente ejemplo usa continue para mostrar que parte del cuerpo de un
ciclo puede saltarse facilmente si es necesario.
2.13.3. return
return;
o
return expresion;
52
Captulo 3
Programacion Orientada a
Objetos en Java
3.1. Clases
3.1.1. Declaracion de clases
La declaracion de una clase introduce un nuevo tipo de referencia del mismo
nombre. Una clase describe la estructura y comportamiento de sus instancias u
objetos en terminos de variables de instancia y metodos. La accesibilidad de las
variables y metodos puede ser explcitamente controlada, permitiendo a la clase
actuar como una unidad de encapsulacion.
class identificador{
declaraciones del constructor
declaraciones de metodos
declaracion de metodos estaticos
declaracion de variables de instancia
declaraciones de variables estaticas
}
Una clase se introduce por la palabra clave class, la cual se sigue de un iden-
tificador que da nombre a la clase. Por convencion, el nombre de una clase siempre
comienza con mayuscula. El cuerpo de la clase se delimita entre llaves, y puede
contener una serie de declaraciones de variables, metodos, clases o interfaces. La
palabra clave class puede precederse de los modificadores, como se muestra en las
secciones subsecuentes.
53
el operador new en una expresion de alojamiento en memoria, como por ejemplo:
Stack s = new Stack();
Una clase actua como una unidad de encapsulacion y crea un entorno de clase.
Todas las clases son declaradas dentro de un paquete (package), el cual puede ser el
paquete anonimo por defecto (default anonymous package). Ademas, normalmente
una clase esta contenida en un archivo fuente o unidad de compilacion, que es parte
de un paquete. Ambos, el paquete y el archivo, proveen entornos. En general, a
menos que este anidada, cada clase se declara en un archivo fuente por separado,
pero es posible tambien tener varias declaraciones de clases en el mismo archivo.
El nombre del archivo fuente que contiene la declaracion de una clase debe ser el
mismo que el nombre de la clase que contiene, con la extension .java. Por ejemplo,
la clase Stack debe ser almacenada en un archivo con el nombre Stack.java. Si hay
varias clases dentro del mismo archivo fuente, el archivo debe nombrarse a partir
de la unica clase publica que contiene. Si esto no se hace correctamente, el archivo
fuente no podra ser compilado.
class ClassScope {
// Una variable privada solo accesible dentro del entorno de
// la clase
private int n = 1;
54
public String toString() {
return new Integer(n).toString();
}
}
55
semantica difiere de aquella utilizada en C++. Sin embargo, parece ser que
C++ esta en lo correcto, mientras que Java parece tenerlo errado).
private hace una declaracion accesible solo dentro de la clase en que se
declara.
Si no se provee ninguna de estas tres palabras clave, se dice que la declaracion
tiene accesibilidad por defecto (default accesibility), lo que significa que es accesible
por cualquier otra clase dentro del mismo paquete. Notese, sin embargo, que tal
declaracion no es accesible a cualquier subclase dentro de un paquete diferente, por
lo que se concluye que la accesibilidad por defecto se controla estrictamente por el
entorno de paquete.
Las reglas para la inicializacion requieren que las variables de instancia sean
inicializadas cuando se declaran, o se hace una inicializacion por omision. Tambien,
es necesario que el programador decida si se trata de una variable publica, privada,
protegida o de acceso por defecto.
Una variable de instancia puede ser declarada como final, lo que significa que
es una constante y no puede ser cambiada por asignacion. Esto resulta muy util
si la variable debe ser publica y debe tambien siempre ser usada sin cambio en el
valor inicial de la variable.
56
long size = 12356L;
}
Integer.MAX_VALUE;
import java.util.Vector;
class Static1 {
// Constantes accesibles publicamente
public final static int UP = 1;
public final static int DOWN = 2;
57
// modificada por asignacion
// NO SE HAGA ESTO EN UN PROGRAMA REAL,
// A MENOS QUE *REALMENTE* SEA NECESARIO
public static String s = "default";
Este codigo muestra que las variables estaticas pueden ser directamente acce-
sadas por ambos tipos de metodos, normales y estaticos.
58
abstract. Una clase abstracta no puede tener intancias u objetos, y es
disenada para que se herede de ella.
final. Una clase final no puede ser heredada, es decir, no puede tener sub-
clases
Si una clase de alto nivel no es declarada como publica, solo puede ser accesada
por las otras clases dentro del mismo paquete. De otra manera, las clases de alto
nivel se comportan tal y como se ha descrito.
Las clases anidadas de alto nivel pueden ser decladas como publicas, requiriendo
que su nombre sea cualificado por el nombre de la clase donde estan anidadas, y si
son usadas por otras clases:
Esto puede ser util si el entorno del nombre de la clase anidada necesita ser
restringido. Sin embargo, de manera mas comun, las clases anidadas de alto nivel
son declaradas protegidas o privadas, y actuan como bloques de construccion de las
estructuras de datos internas de la clase donde estan anidadas. Por ejemplo, una
clase de listas ligadas puede tener una estructura interna consistente en una cadena
de objetos nodo. Cada nodo podra ser una instancia de una clase anidada de alto
nivel. La clase se hace privada ya que no hay necesidad de que los clientes de la
lista sepan acerca de ella o intenten usarla; simplemente, funciona como una pieza
de la estructura interna.
Una clase anidada de alto nivel puede accesar variables estaticas declaradas en
su clase residente aun cuando sean declaradas como privadas, pero no tiene acceso
a las variables de instancia. Clases anidadas de alto nivel pueden contener aun mas
clases anidadas de alto nivel.
59
El siguiente ejemplo usa una clase anidada de alto nivel llamada CountedString.
Los objetos de la clase asocian una cuenta con una cadena de caracteres, de tal mo-
do que la clase StringCounter puede usarlas para contar el numero de veces que
una cadena de caracteres se anade usando el metodo addString. Notese que las
variables de instancia de CountedString no son privadas y pueden ser directamente
accesadas por la clase residente. Esta decision se hace debido a que la clase anidada
se usa como bloque de construccion, y no requiere una fuerte encapsulacion.
import java.util.Vector;
class StringCounter {
60
if ((c.item).equals(s)) return c;
}
return null;
}
Clases miembro
Una clase miembro es una variedad de clase anidada o interna que no es declara-
da como estatica. Un objeto de una clase miembro es directamente asociado con el
objeto de la clase residente que lo ha creado, y automaticamente tiene referencia
implcita a el. Como resultado, el objeto de la clase miembro tiene acceso directo a
las variables de instancia del objeto de la clase residente.
Las clases miembro comparten las caractersticas estandar de las clases anidadas,
pero no pueden ser declaradas estaticas, ni pueden declarar variables estaticas,
metodos, o clases anidadas de alto nivel. Una clase miembro no puede tener el mis-
mo nombre de ninguna de las clases residentes o de paquetes (lo cual es diferente
de las reglas acerca de los nombres de variables y metodos).
Las palabras clave this, new, y super tienen una sintaxis extendida para usarse
en clases miembro:
nombreDeLaClase.this
61
referenciaAObjeto.new
referenciaAObjeto.super
Un objeto de una clase miembro (u objeto miembro) tiene una referencia im-
plcita al objeto de la clase residente que lo ha creado, el cual es automaticamente
anadido por el compilador de Java. Esto permite el acceso al estado del objeto de
la clase residente. El acceso en la direccion contraria (de la clase residente a la clase
miembro) respeta la encapsulacion del objeto de la clase miembro, de tal modo que
sus variables y metodos privados no pueden ser accesados directamente.
Los objetos miembro son tpicamente usados para crear estructuras de datos,
cuyos objetos necesitan saber que objeto los contiene, y ayudan a accesar tales
estructuras de datos. El ejemplo principal de esto son los objetos de enumeracion,
los cuales iteran a traves de estructuras de datos privadas.
Para dar soporte a las clases miembro se provee de muchos otros tipos de
expresiones extra. La primera es una extension a las expresiones con this. Una
expresion como:
x = this.yes;
sera solo valida si yes es una variable de instancia declarada por la clase miembro,
y no si yes es declarada por la clase residente. Si yes pertenece a la clase residente,
entonces tiene que ser accesada usando una expresion como:
x = TestClass.this.yes;
class A {
class B {
class C {
...
int c;
void f() {
A.this.a; //Variable declarada en la clase A
B.this.b; //Variable declarada en la clase B
this.c; //Variable declarada en la clase C
}
}
...
int b;
62
}
...
int a;
}
Esto permite que las variables declaradas en cada clase residente puedan ser
accesadas mediante nombrar la clase en una expresion this. Notese que una expre-
sion como:
A.B.C.this.a;
donde el operador new se precede por la referencia al objeto, seguido por el operador
punto. Esto significa crear un nuevo objeto miembro B usando el objeto referenciado
por la variable a (la cual debe ser una instancia de la clase residente correcta). El
nuevo objeto miembro tendra entonces acceso al estado del objeto referenciado por
a.
Esto significa declarar una variable llamada c de tipo C la cual esta anidada
en B, que a la vez esta anidada en A (notese la parte A.B.C). Entonces crea un
nuevo objeto C dada una referencia de un objeto de tipo B. Notese que se usa una
expresion b.new C(), y no algo como b.new A.B.C(). Esto se debe a que la clase
de un objeto a ser creado se da relativo a la clase del objeto residente. La seccion
de ejemplos ilustra mas adelante esta sintaxis.
Una consecuencia del acceso de una clase miembro a su clase residente es que
las reglas de entorno se vuelven mas complicadas, ya que ahora hay dos formas de
63
considerar un nombre: (a) usando la jerarqua de herencia, y (b) usando la jerarqua
de contencion de objetos residentes.
class Class5 {
private class Member {
// Este metodo muestra que las variables de instancia
// privadas del objeto de la clase residente pueden ser
// accesadas
public void test() {
i += 10;
System.out.println(i);
System.out.println(s);
}
}
Clases Locales
Una clase local es aquella declarada dentro del entorno de un enunciado com-
puesto, es decir, es local al cuerpo del metodo o inicializador de instancias en que
se encuentra, como si fuera una variable local. Con ciertas restricciones, una clase
local es tambien una clase miembro, ya que es creada dentro del objeto de una
clase residente. As como cualquier clase miembro tiene acceso a datos miembros
en cualquier clase residente, una clase local tiene acceso a cualquier variable de
parametro o local (sujeto al orden de declaracion) que se declaran en el mismo
entorno, siempre y cuando tengan el modificador final.
64
un metodo), entonces el objeto local permanece en existencia, manteniendo acceso
a los parametros finales o variables locales finales de su entorno de creacion.
El siguiente ejemplo muestra como las clases locales pueden accesar variables
de su entorno residente y de su objeto residente:
class Class8 {
public void f (final String h, String w) {
int j = 20;
final int k = 30;
class Local {
public void test() {
System.out.println(h); // h es local
// System.out.println(w); ERROR!
// No es posible imprimir w, dado que
// w no es final
// System.out.println(j); ERROR!
// Tampoco puede imprimirse j, dado que
// no es final
System.out.println(k); // k es final
// System.out.println(i); ERROR!
// i aun no ha sido declarada
System.out.println(name);
65
// Como una clase miembro, las variables de
// instancia del objeto residente pueden ser
// accesadas. No necesitan ser finales.
}
}
Clases Anonimas
Una clase anonima es una clase local que no tiene nombre. Se declara como
parte de una expresion new, y debe ser o una subclase o implementar una interfaz:
66
Cuando una clase anonima se declara como subclase, la intencion es que los
metodos heredados sean sobreescritos (overriden) y especializados. Hay pocas ra-
zones para anadir otros metodos publicos, dado que no pueden ser llamados. Sin
embargo, se pueden anadir metodos privados.
interface Thing {
void test(final String s);
}
class Subclass {
public void doSomething() {
System.out.println("Do something");
}
}
class Class 10 {
// Retorna un objeto creado usando una clase anonima que
// implementa la interface
public Thing i1() {
Thing t = new Thing() {
public void test (final String s) {
System.out.println(s);
}
}; // Notese el punto y coma aqui
return t;
}
67
public Subclass f1() {
Subclass t = new Subclass() {
public void doSomething() {
something();
}
private void something() {
System.out.println(name);
}
String name = "Anonymous 1";
};
return t;
}
68
tenga sentido. Si hay alguna duda o no se desea tener subclases, la clase debe
ser declarada como final, permitiendo al compilador verificar que la generacion
de subclases no sea hecha. Una consecuencia de declarar una clase final es que el
compilador no puede optimizar su codigo. En particular, no es posible redefinir
metodos de subclases, as que es factible realizar un ligado estatico en lugar de un
ligado dinamico, que es mas costoso.
Una clase declarada como abstract puede incluir cualquier variable estandar y
declaracion de metodos, pero no puede ser usada en una expresion. Puede tambien
incluir declaraciones de metodos abstractos. La clase define un tipo, as que las
variables del tipo pueden ser declaradas y pueden mantener referencias a objetos
de una subclase.
Una subclase de una clase abstracta puede ser tambien abstracta. Sin embargo,
en general es una clase concreta (es decir, disenada para tener instancias u objetos).
69
3.2. Metodos
3.2.1. Declaracion de Metodos
La declaracion basica de un metodo es de la siguiente forma:
La lista de nombres de tipos que sigue a la palabra clave throws es una lista
separada por comas de uno o mas nombres de los tipos de excepciones que pueden
ser arrojadas.
modificadores tipo[]nombre(listadeparametros) {
Secuencia de enunciados
}
Los arreglos con mas de una dimension simplemente requieren conjuntos extra
de corchetes, exactamente como en una declaracion de variables.
70
Los metodos son procedimientos llamados o invocados por un objeto especfico
utilizando una llamada de campo o una expresion de llamada a metodo. Hay dos
tipos de metodos: aquellos que no retornan valor (void) y aquellos que son de
retorno de valor (value-returning).
Los metodos de retorno vaco (o metodos void) se declaran con tipo de retorno
void. El cuerpo de un metodo void simplemente realiza un procesamiento, que
tendra el efecto colateral de cambiar el estado del objeto para el cual fue invocado, y
termina sin explcitamente retornar un valor, aun cuando puede usarse el enunciado
return sin argumento:
void f(){
a = 4; // Supongase que a es una variable de instancia
return;
}
Los metodos con retorno de valor (o metodos no-void) se declaran con un tipo
de valor de retorno diferente a void. El cuerpo del metodo debe contener al menos
un enunciado return, retornando un valor del tipo que coincida con o pueda ser
convertido al tipo especificado de retorno:
int f(){
return a * 10; // Supongase que a es una variable accesible
}
Como un valor debe ser retornado por un metodo con retorno de valor, el com-
pilador verifica que todas las salidas posibles de un metodo resultan en la ejecucion
de un enunciado return. En el siguiente ejemplo este no es el caso, ya que cuando
x tiene un valor mayor que 9 el control pasa al final del cuerpo del metodo sin al-
canzar el enunciado return. Como resultado, el compilador no aceptara el metodo
hasta que otro enunciado return sea anadido:
int f(){
// Cuidado: esto no compilara
if (x < 10) // x es una variable de instancia
return value;
// ERROR: debe haber un enunciado return aqui
}
Los metodos pueden ser sobrecargados (overloaded), lo que significa que dos o
mas metodos en la misma clase pueden tener el mismo nombre, siempre y cuando
tengan una lista de parametros diferente. Por ejemplo:
71
Todos estos metodos se llaman f, pero pueden distinguirse por su diferente lista
de parametros (en una llamada a un metodo, el numero y tipos de los argumentos
son utilizados para determinar cual metodo debe usarse). Los tipos de retorno no
se toman en cuenta, y pueden diferir como se muestra aqu.
class Class1 {
// Asigna todos los elementos de un arreglo con un valor dado
public int [] fill(int [] array, int value){
for (int i = 0; i < array.length; i++) {
array[i] = value;
}
return array;
}
72
El siguiente ejemplo muestra una serie de metodos sobrecargados max, que re-
tornan el maximo de entre dos valores. Cada metodo trabaja con tipos primitivos
diferentes.
class Class2{
public byte max(final byte a, final byte b){
return a > b ? a : b;
}
public short max(final short a, final short b){
return a > b ? a : b;
}
public int max(final int a, final int b){
return a > b ? a : b;
}
public long max(final long a, final long b){
return a > b ? a : b;
}
public float max(final float a, final float b){
return a > b ? a : b;
}
public double max(final double a, final double b){
return a > b ? a : b;
}
public char max(final char a, final char b){
return a > b ? a : b;
}
73
3.2.2. Metodos Estaticos o de clase
Un metodo estatico o de clase pertenece a una clase, y no es parte de la imple-
mentacion de objetos o instancias individuales. Los metodos estaticos pueden ser
llamados directamente en lugar de invocarse mediante un objeto.
Los metodos estaticos pueden arrojar excepciones, y por lo tanto, pueden ser
declarados usando la clausula throws:
La lista de nombres de tipo que sigue a la palabra clave throws es una lista
separada por comas de uno o mas nombres de tipos de excepciones que pueden ser
arrojadas.
Los metodos estaticos estan sujetos por mucho a las mismas reglas que los
metodos estandar, con las siguientes excepciones:
Un metodo estatico pertenece a una clase, y no a sus objetos o instancias.
Un metodo estatico puede ser llamado tanto directamente como por un objeto
de la misma clase.
Un metodo estatico no puede accesar ningun variable o metodo de instancia
(dado que no pertenece a ninguna instancia), pero puede accesar cualquier
variable o metodo estatico.
La palabra clave this no puede ser usada sobre metodos estaticos.
A pesar de estas restricciones, un metodo estatico puede ser declarado y uti-
lizado como sea requerido.
Hay un uso especial de los metodos estaticos en la forma del static main.
Cuando una clase declara un metodo public static main, este provee de un punto
74
de inicio para la ejecucion de un programa utilizando esa clase. Todos los ejemplos
de programas vistos hasta ahora incluyen tal metodo. Este metodo se hace cargo
de establecer el estado inicial del programa, creando el conjunto inicial de objetos,
y haciendo las llamadas a los metodos apropiados para continuar la ejecucion.
java MyClass
donde, en este caso, el primer metodo a ser ejecutado es el main de la clase MyClass.
class Static2 {
public static void g(final int i) {
System.out.println("En static void g(i = " + i + ")");
}
75
Notese que en la lnea s2.f();, un metodo estatico es llamado como un metodo
de instancia. Parece indistinguible de un metodo de instancia, pero sin embargo, es
una llamada a metodo estatico.
3.2.3. Constructores
Un constructor se declara como cualquier metodo, contando con una lista de
parametros. Sin embargo, la declaracion de un constructor no tiene tipo de retorno,
y su nombre debe coincidir con el nombre de la clase dentro de la cual es declarado.
El constructor tiene la siguiente forma:
nombredelconstructor (listadeparametros) {
Secuencia de enunciados
}
La palabra clave new se sigue por el nombre de una clase y opcionalmente por
una lista de parametros actuales. La lista de parametros actuales se hace coincidir
con aquellas provistas por los constructores de la clase, a fin de determinar cual
76
constructor llamar. Una lista vaca de parametros coincide con el constructor por
omision. Siempre y cuando se encuentre una coincidencia, el constructor sera llama-
do. Antes de que el cuerpo del constructor sea ejecutado, el constructor de cualquier
superclase sera ejecutado, seguido de cualquier inicializacion de variables de instan-
cia declaradas en la clase. Esto permite que el cuerpo del constructor sea escrito
con el conocimiento de que toda otra inicializacion sera realizada antes. La buena
practica dicta que el cuerpo del constructor solo realice inicializaciones relevantes
a su clase, y no intente inicializar variables de sus superclases.
Como las variables de instancia y de clase pueden ser inicializadas por ex-
presiones o bloques de inicializacion, puede no haber necesidad de explcitamente
declarar ningun constructor, y la clase puede simplemente depender del construc-
tor por omision generado por el compilador de Java. Sin embargo, los constructores
son la unica forma general y efectiva de parametrizar la creacion de objetos, de
tal modo que la creacion e inicializacion pueda ser ajustada para cada situacion
en particular. Por ejemplo, la clase String del paquete java.lang provee de los
siguientes constructores. Cada uno crea un objeto de tipo String a partir de un
conjunto diferente de argumentos:
String()
String(byte[])
String(byte[],int)
String(byte[],int,int)
String(byte[],int,int)
String(byte[],int,String)
String(byte[],String)
String(char[])
String(char[],int,int)
String(String)
String(StringBuffer)
Los constructores pueden ser tanto protegidos como privados, dando una forma
de limitar el acceso a algunos o todos los constructores, y por lo tanto, controlando
cuales otras partes de un programa pueden crear instancias de una clase. En par-
ticular, si todos los constructores se hacen privados, entonces solo los metodos de
la misma clase pueden crear objetos de la clase. Esto puede en realidad ser muy
util; algunas clases pueden declarar unicamente metodos estaticos, y no necesitar
instancias, o bien puede requerirse que una clase no permita a sus clientes crear
objetos utilizando new.
77
Cuando un objeto deja de referenciarse, es destruido por el recolector de basura
(garbage collector) que es parte del sistema de ejecucion de Java. Un metodo llamado
finalize puede ser definido para ser llamado justo antes de la destruccion.
import java.util.vector;
class Constructor1 {
// Otros constructores
Constructor1 c2 = new Constructor1(10);
Constructor1 c3 = new Constructor1(10,"Hello");
}
78
// Variables de instancia que seran inicializadas por defecto
// a menos que sean asignadas por un constructor
private int initialSize;
private Vector v;
}
class Example {
...
static int i = 10;
static int j;
static { j = i * 10; }
79
class Example {
...
static { j = i * 10; } // Error: referencia ilegal
3.2.5. this
this es una variable final que se declara automaticamente en los constructores,
metodos de instancia e inicializadores de instancias, y se inicializa para mantener
una referencia al objeto creado por el constructor, metodo o inicializador que lo
invoca. this es de tipo referencia al objeto.
this puede ser usado como cualquier otra variable de tipo referencia a clase,
sin olvidar que no se permite su asignacion dado su atributo final. Esto significa
que dentro de un metodo es posible escribir enunciados usando el operador punto
como:
{
...
this.x = 10;
this.f();
...
}
{
...
x = 10;
f();
...
}
Es decir, estos enunciados son en realidad una abreviatura de escribir una ver-
sion mas larga.
80
Algunas veces usar la forma larga puede ser util. Por ejemplo, cuando el nombre
de una variable de instancia queda oculto por el nombre de un parametro dentro
de un entorno anidado:
class Example {
...
void f (int x) { // el parametro x oculta la variable de instancia
this.x = x; // this.x es usado para tener acceso a
// la variable de instancia oculta
}
...
private int x = 10;
}
Otro uso comun de this es pasar la referencia del objeto actual a un metodo
de otra clase:
{
...
A a = new A();
...
a.f(this) ; // Pasa this al metodo f de la clase A
...
}
public Test() {
this(10); // Llama al constructor con int
}
Las llamadas a constructores usando this no pueden ser recursivas. Por ejem-
plo, el siguiente constructor no compilara:
public Test(int x) {
this(10); // Error: Llamada recursiva al mismo constructor
}
81
3.2.6. Redefinicion de Metodos
Una subclase puede redefinir un metodo heredado mediante proveer una nue-
va definicion del metodo que tenga el mismo nombre, el mismo numero y tipo de
parametros, y el mismo tipo de resultado de retorno que el metodo heredado. El
metodo heredado se oculta al entorno de la subclase. Cuando el metodo es invoca-
do por un objeto de la subclase, el metodo redefinido es ejecutado usando ligado
dinamico.
Los metodos privados no pueden ser redefinidos, as que que un metodo coinci-
dente de la subclase se considera completamente separado. Por otro lado, el acceso
de un metodo redefinido publico (public), protegido (protected) o por defecto (si
no hay modificador) debe tener el mismo acceso que el metodo de la superclase, o
hacerse mas accesible (por ejemplo, de protected a public). Un metodo redefinido
no puede hacerse menos accesible.
la version de f que se llama depende de la clase del objeto que se referenca por x
cuando la llamada al metodo es evaluada en tiempo de ejecucion, y no en el tipo
de la variable x. La mayor consecuencia de esto es que una variable de tipo de una
superclase puede asignarse a una referencia a un objeto de la subclase, pero cuando
se llama a los metodos, el ligado dinamico asegura que los metodos redefinidos de
la subclase sean llamados. Por tanto, la misma expresion evaluada a diferentes mo-
mentos durante la ejecucion de un programa puede resultar en llamados a diferentes
metodos. Los metodos estaticos no pueden usar ligado dinamico.
82
El uso del ligado dinamico es parte esencial de la programacion Orientada a Ob-
jetos, pero puede hacer difcil averiguar cual metodo sera llamado al leer el codigo
fuente. Dada una expresion como x.f() o solo f() (que realmente es this.f()), el
programador debe primero determinar el tipo de x o this, y entonces inspeccionar
la clase correspondiente, superclases y subclases, para averiguar que clase de meto-
do es f, y si es redefinido. El programador debe tambien determinar los tipos de
objetos que pueden ser referenciados por x. Esto puede ser tardado y confuso, pero
considerar esto es importante. Es claro que un buen ambiente de desarrollo puede
dar soporte para esta busqueda, pero esto no significa que el comentario no deba
hacerse.
class Superclass {
// Metodo que puede ser redefinido
public void f(int x){
System.out.println("Superclass f: " + x);
// Siempre llama al metodo g declarado en esta clase,
// ya que g es privado
g();
}
83
public static void s() {
System.out.println("Superclass static s");
}
}
class Override {
public static void main (String[] args) {
Superclass superclass = new Superclass();
// Llama a la version de f en Superclass
superclass.f(1);
superclass.h();
superclass.k();
superclass.s();
84
// Llama a la version de k en Superclass, ya que no
// esta redefinida
subclass.k();
Los metodos finales previenen de que un metodo que tiene el mismo nombre y
los mismos tipos de parametros sea declarado en una subclase (el tipo del resultado
es ignorado). Esto toma en cuenta tanto variables estaticas como de instancia. Sin
embargo, el modificador final no previene que los metodos sean sobrecargados en
una subclase.
Al igual que las clases estaticas, los metodos estaticos potencialemente permiten
al compilador hacer optimizaciones en las llamadas a metodos.
85
para determinar exactamente cual metodo es invocado. Un metodo (que puede ser
estatico) se invoca mediante una expresion que consiste en una identificacion de
metodo seguida de una lista de parametros actuales o argumentos:
La lista de argumentos puede estar vaca, pero el parentesis debe estar presente.
expresionprimaria.identificador(lista de argumentos);
donde la expresion primaria antes del punto debe evaluarse como referencia
a objeto que puede entonces ser usada para seleccionar el nombre del metodo
a ser invocado. Tal nombre debe ser accesible, lo cual generalmente significa
que debe ser publico, protegido, o declarado en la clase actual. El metodo
puede ser estatico.
Usando una expresion de campo con el nombre de una clase o interfaz:
nombredeclaseointerfaz.identificador(lista de argumentos);
Esto permite invocar metodos estaticos o de instancia mediante especificar
cual clase o interfaz debe ser usada para hallar el metodo.
A fin de invocar un metodo, se usa un proceso llamado viga de metodo
(method lookup). Este es usado para determinar exactamente cual metodo llamar,
y que la llamada no sea ambigua. En algunos casos, un proceso viga de metodo
completo puede ser producido por el compilador, permitiendo exactamente deter-
minar cual metodo llamar en tiempo de compilacion. Esto es lo que se llama ligado
estatico (static binding): la invocacion del metodo se liga o asocia con un metodo
durante la compilacion.
86
conocido. El ligado dinamico es particularmente asociado con los tipos de referencia
y herencia, ya que una referencia de un tipo superclase puede referirse a un objeto
de cualquiera de un numero de tipos de las subclases.
class Example {
...
public void f(int x) { ...}
public void f(double x) { ... }
...
public void test(){
f(10); // Argumento int
f(1.1); // Argumento double
}
}
class Example {
public void f(int x, double y) { ... }
public void f(double x, int y) { ... }
...
87
public void test() {
f(1.0,1);
f(1,1);
f(1,1.0);
}
}
Los ejemplos anteriores usan los tipos primitivos int y double explotando el
hecho de que un int es compatible por asignacion con un double. Un razonamiento
analogo se aplica para el uso de cualquier tipo y sus compatibles por asignacion.
En particular, no se olvide que al tratar con referencias, un subtipo es compatible
por asignacion con su supertipo.
88
de conversiones que otro, aquel con mayor numero de conversiones es elimi-
nado. Tras la eliminacion, si un metodo permanece como el mas especfico,
la invocacion es valida y el metodo es seleccionado. Si dos o mas metodos
permanecen entonces la invocacion es ambigua, y se reporta un error.
El proceso viga de metodo se modifica para lidiar con estas cuestiones mediante
cambiar la definicion de lo que significa metodo mas especfico al tratar con
metodos heredados o sobrecargados, y mediante depender del ligado dinamico.
class A {
void f(double d){ ... }
}
class B extends A {
void f(int i) { ... }
}
...
B b = new B();
b.f(1); // Que metodo se llama?
89
seleccionado, lo que no es claramente notorio solamente al inspeccionar el codigo. Sin
embargo, si el ejemplo se cambia como sigue, la llamada resultara ahora ambigua:
class A {
void f(int i){ ... } // Notese el cambio de tipo
}
class B extends A {
void f(double d) { ... } // Notese el cambio de tipo
}
...
B b = new B();
b.f(1); // Ambiguo
Aun cuando podra parecer que f(int) debiera coincidir, falla respecto a la
regla 1, mientras que f(double) falla en la regla 2, y ningun metodo se puede
considerar como mas especfico. Resulta todo en un mensaje de error:
class A {
void g(double d, int i) { ... }
}
class B extends A {
void g(int i, double d) { ... }
}
...
B b = new B();
b.g(1,1.0); // OK
b.g(1.0,1); // OK
b.g(1,1); // Ambiguo
90
podran coincidir con ella, pero de acuerdo con la regla, ninguna resulta la mas es-
pecfica. Intercambiar las declaraciones de los dos metodos no hace mayor diferencia
en este caso.
Los mismos principios se aplican cuando los argumentos son de tipos referencia:
class X { ... }
class Y extends X { ... }
class A {
void h(X x) { ... }
}
class B extends A {
void h(Y y) { ... }
}
...
B b = new B();
X x = new X();
Y y = new Y();
b.h(x); // OK
b.h(y); // OK
Aqu ambas llamadas al metodo h son validas. Con la primera, solo hay un
metodo que puede coincidir: h(X) en la clase A. Con el segundo, ambos metodos h
podran coincidir, pero la regla dice que h(Y) en la clase B resulta el mas especfico,
y es el que termina siendo llamado.
class X { ... }
class Y extends X { ... }
class A {
void h(Y y) { ... }
}
class B extends A {
void h(X x) { ... }
}
...
91
B b = new B();
X x = new X();
Y y = new Y();
b.h(x); // OK
b.h(y); // Ambiguo
El ligado dinamico trabaja utilizando codigo para hallar la clase del objeto por
el que un metodo es invocado, verificando que la clase declara un metodo redefinido
con el nombre, tipo de parametros y tipo de retorno correctos, y entonces invocando
al metodo. Aun cuando esto suena lento y costoso para ejecutarse, la informacion
para hacer la coincidencia esta codificada para reducir el tiempo extra al mnimo.
Los archivos .class contienen la informacion, as que la verificacion puede hacerse
una vez que una clase es cargada.
92
5. Determinar si la invocacion al metodo necesita ser dinamica, y si as es,
generar una llamada dinamica a metodo, y en caso contrario, una llamada
estatica.
nombredeclase.this.nombredevariable
nombredeclase.this.nombredemetodo
Como hay dos distintas jerarquas en que buscar por nombres de variables y
metodos, hay un problema potencial si el nombre se encuentra en ambas. La jerar-
qua de herencia se busca primero, seguida de la jerarqua de contencion. Si el mismo
nombre con el mismo tipo se encuentra en ambas jerarquas, ambas declaraciones
son accesibles, y se reporta un error. Estos conflictos pueden resolverse utilizando
la sintaxis extendida de this para excplcitamente nombrar la clase que declara el
nombre a ser usado. Por ejemplo, si ambas jerarquas declaran una variable accesible
x dentro del entorno, entonces una expresion como:
A.this.x
puede ser usada para acceder a x, donde A es el nombre de la clase que declara la
variable que necesita ser accesada.
class A {
public void f() {
System.out.println(name + " f");
93
}
protected static String name = "A";
}
class Superclass {
public void name(){
System.out.println(name);
}
protected static String name = "Superclass";
protected String vname = "Superclass instance variable";
}
System.out.println(Subclass.this.name);
System.out.println(Superclass.this.name);
System.out.println(A.this.name);
System.out.println(Subclass.this.vname);
System.out.println(Superclass.this.vname);
94
// Lo siguiente es ambiguo, dado el conflicto
// entre name por herencia y por contencion
// f(); // ERROR!
A.this.f();
Subclass.this.f();
}
class Lookup1 {
public static void main(String[] args) {
Subclass subclass = new Subclass();
subclass.test();
}
}
95
modificadores abstract tipo nombredelmetodo(lista de parametros);
Una clase que declara uno o mas metodos abstractos debe ser declarada como
clase abstracta. Los metodos privados y estaticos no pueden ser abstractos, ya que
no hay manera de redefinirlos.
Los metodos abstractos permiten que una clase abstracta especifique la interfaz
completa de un metodo publico o protegido, aun cuando no puede proveer un cuerpo
para el metodo. Las reglas del lenguaje aseguran que las subclases concretas deben
proveer de un implementacion completa para cada metodo abstracto heredado. Las
subclases abstractas de una clase abstracta pueden escoger entre implementar un
metodo abstracto heredado o anadir metodos abstractos adicionales.
La clase Object declara los siguientes metodos que pueden ser redefinidos por
las subclases:
De los cinco metodos que pueden ser redefinidos, tres son publicos y pueden
ser invocados por cualquier objeto o instancia de cualquier clase. Los otros dos son
protegidos, y necesitan ser redefinidos como metodos publicos para que puedan ser
invocados en la clase que los declara.
96
boolean equals(Object obj)
El metodo equals es usado para comparar el objeto que lo llama con el objeto
en su argumento. La implementacion por defecto retorna true si los dos objetos son
en realidad el mismo objeto (usando el operador ==). Una subclase debe redefinir
este metodo a fin de comparar los valores de los dos objetos, en general mediante
sistematicamente comparar uno a uno los valores de sus variables de instancia.
97
String toString()
Este metodo es utilizado para obtener una cadena de caracteres representando
el valor del objeto para el cual se invoca. Por defecto, el metodo de la clase Object
retorna una cadena como:
ClassName@1cc7a0
Este es el nombre de la clase del objeto, seguido por una @, que se sigue por
el valor hexadecimal del codigo hash del objeto (vease la siguiente seccion). Para
generar una cadena mas util, una subclase puede redefinir este metodo y retornar
cualquier cadena que sea una representacion razonable del valor del objeto.
int hashCode()
Un codigo hash es un valor entero unico que representa el valor de un objeto,
de tal modo que el valor del objeto completo puede ser relacionado con un entero.
Cada valor distinto que un objeto puede representar debe tener un codigo hash
relativamente unico (ya que el numero de valores es frecuentemente mayor que lo
que se puede representar mediante un int). Los codigos hash se usan como valores
clave en una tabla hash (como la que se implementa en la clase HashTable en
java.util).
En cualquier momento que se invoca el mismo objeto mas de una vez durante
la ejecucion de una aplicacion Java, el metodo hashCode debe consistentemente
retornar el mismo valor int. Este valor int no requiere permanecer consistente de
una ejecucion a otra. Si dos objetos son iguales de acuerdo con el metodo equals,
entonces llamar al metodo hashCode para cada uno de estos objetos debe producir
el mismo resultado int.
En la practica, solo aquellos objetos que pueden ser almacenados en una tabla
hash requieren del metodo hashCode. Los programadores generalmente dependen
de la implementacion por defecto de hashCode, en lugar de implementar una nueva
version que podra significar una dura labor.
98
Object clone()
El metodo clone crea una copia de un objeto. Por defecto, solo el objeto es
copiado, es decir, se copian los valores de sus variables primitivas. Sin embargo,
los valores de tipos referencia contenidos en el objeto no se copian. Esto se conoce
como copia superficial (shallow copy).
La debilidad potencial del metodo clone por defecto es que su uso puede resul-
tar en dos copias de un objeto, ambas referenciando o compartiendo una coleccion
de otros objetos. Si el estado de alguno de los objetos referenciados cambia, en-
tonces efectivamente tambien cambia el estado de todos los objetos que le hacen
referencia. Para sobrellevar esto, clone puede ser redefinido para relizar una copia
profunda (deep copy), en donde tanto el objeto a copiar como los objetos a los que
hace referencia son copiados. La redefinicion de clone tambien necesita ser hecha
publica, si se espera utilizarse en general.
Una copia profunda completa depende de clone o una operacion de copia simi-
lar, que requiere ser implementada correctamente por cada clase de los objetos
a los que se hace referencia. Como es el caso con equals, invocar a clone puede
resultar en una larga secuencia de metodos invocados, los cuales pueden usar mucho
del tiempo de ejecucion de un programa. Como resultado, es importante definir
cuidadosamente que significa copiar un objeto y que realmente debe hacerse.
Un metodo clone debe ser responsable de clonar el estado (es decir, el valor de
las variables) declaradas en su clase, y debe invocar a super.clone() para copiar el
estado de la superclase, a menos que tal clase sea Object, en cuyo caso la invocacion
al metodo clone por defecto puede no ser necesaria.
void finalize()
El metodo finalize es llamado automaticamente por el recolector de basura
cuando un objeto no es referenciado y puede ser desechado. La version por defecto
en la clase Object es un metodo con cuerpo vaco. El recolector de basura puede
ejecutarse en cualquier momento, por lo que no hay una forma confiable de deter-
minar cuando sera invocado finalize para un objeto en particular. Tpicamente,
sin embargo, el recolector de basura es normalmente invocado cuando la memoria
disponible para el programa en ejecucion disminuye.
finalize solo necesita ser redefinido si un objeto tiene un estado que no puede
ser correctamente manipulado simplemente con desecharlo. Por ejemplo, cuando
99
sus datos deban ser escritos a un archivo antes de desecharlo o podran perderse, o
una conexion de red que debe ser propiamente cerrada.
100
3.3. Herencia
Una subclase hereda de una superclase mediante extenderla. La subclase toma
todas las declaraciones de variables y metodos de la superclase (aun cuando no todos
puedan ser accesibles) y puede anadir nuevas variables o metodos, o sobrecargar
(override) los existentes.
La herencia se aplica a las clases estandar de alto nivel, las clases anidadas de
alto nivel, las clases miembro, las clases locales y las clases anonimas.
Una clase puede heredar de cualquier otra clase que no es final. Los objetos de
la subclase contienen todas las variables de instancia declaradas por la superclase.
Tambien, todos los metodos declarados por la superclase pueden ser llamados desde
el objeto de la subclase. El hecho que la subclase tiene copias de las variables de
instancia de la superclase hace que esto ultimo sea razonable y permisible. Notese,
sin embargo, que las reglas de accesibilidad son respetadas, as que las variables y
metodos privados no son accesibles a los metodos e inicializadores de la subclase.
Una clase puede contener partes que algunos de sus propios metodos no pueden
accesar.
Crear subclases puede ser repetido tantas veces, o hasta tantos niveles, como se
desee; una subclase puede tener a la vez otra subclase, y esta otra, etc. Esto es a lo
que se refiere como una cadena de herencia. Sin embargo, es una buena practica
limitar el numero de niveles a menos de cinco.
Una clase puede solo tener una superclase, pero puede tener tantas subclases
como sea necesario. Una coleccion de clases en una relacion de herencia es referida
como una jerarqua de clases (class hierarchy). Debido a la regla de una sola
superclase, esta jerarqua forma un arbol.
Todas las clases heredan directa o indirectamente de la clase Object (es decir,
todas son inmediatamente subclases de Object o se encuentran en un nivel mas
abajo en una cadena jerarquica que comienza con Object). Esto es cierto aun
cuando una clase no haya sido explcitamente declarada como una subclase de
Object usando la palabra clave extends.
La relacion de herencia entre clases tambien se aplica a los tipos que las clases
definen, de tal modo que es posible hablar de supertipos y subtipos. Esto
101
permite compatibilidad para la asignacion o conversion entre tipos de referencia,
as que es posible asignar una referencia a un objeto de un subtipo a una variable
cuyo tipo es el supertipo. Esto tambien funciona para el paso de parametros y el
retorno de resultados de un metodo.
El siguiente ejemplo muestra un uso muy simple de la herencia, e ilustra que meto-
dos de ambas subclase y superclase pueden ser invocados por los objetos de la sub-
clase. Tambien, notese que la referencia de la subclase es asignada a una variable
del tipo de la superclase.
class Superclass {
public void supermethod() {
System.out.println("Superclass");
}
}
102
class Subclass extends Superclass {
public void submethod(){
System.out.println("Subclass");
}
}
class Inherit1 {
public static void main(String[] args) {
// Crea un objeto Superclass y llama al metodo
// supermethod()
La declaracion private ofrece una garanta de que ninguna otra clase, in-
cluyendo las subclases, pueden acceder al metodo o variable. Esto evita
cualquier dependencia directa en los detalles de las declaraciones. Por tanto,
si se realizan cambios a los metodos o variables privados en una clase, entonces
ninguna otra clase necesita ser editada. Sin embargo, esto puede requerir la
adicion de metodos de acceso a la superclase para accesar indirectamente sus
metodos o estado privado.
103
La declaracion protected permite que una subclase (tal vez de un paquete
diferente) accese directa y eficientemente a su superclase cuando pueda ser
util para las variables de instancia de la subclase y para los metodos auxiliares
que no deban ser publicos. Esto permite una forma controlada de compartir.
Sin embargo, esto puede llevar a que en la subclase se utilice erronamente las
caractersticas heredadas y volverse innecesariamente dependiente de ellas.
La declaracion public hace accesible a la subclase y a todo lo demas. Esto es
esencial si la clase tiene instancias que proveen de un servicio util, pero puede
ser peligroso si mucho se hace publico, especialmente en el caso de variables
de instancia.
El acceso por omision es igual al acceso publico si una subclase se encuentra
en el mismo paquete, e igual al acceso privado si no.
Como una regla general, las declaraciones deben ser publicas si clientes no rela-
cionados deben usarlas, o de otra manera deberan hacerse privadas por omision a
menos que sea claro que las subclases necesitan acceso directo, en cuyo caso podran
ser protegidas. Como una superclase puede ser escrita antes que una subclase haya
sido pensada, las declaraciones protegidas requieren una planeacion cuidadosa.
class Superclass {
// Metodo que sera heredado pero no accesible a una subclase
private void f() {
System.out.println("f");
// Puede llamarse desde aqui a g y h
// g();
// h();
}
104
// Metodo compartido que no es sobreescrito en la subclase
public void h() {
System.out.println("Shared");
// Puede llamarse desde aqui a f y g
// f();
// g();
}
public int i = 5;
protected int j = 10;
private int k = 20;
}
class Inherit2{
public static void main(String[] args) {
Superclass superclass = new Superclass();
// superclass.f(); // ERROR. El metodo f es privado
105
3.3.2. Constructores y Herencia
En Java, todas las clases existen como parte de una jerarqua de herencia.
Cuando un objeto es creado, un constructor debe ser invocado por cada superclase
y subclase de la jerarqua. Esto se refuerza por las reglas propias del lenguaje y el
compilador.
La palabra clave super puede ser usada para explcitamente llamar al construc-
tor de una superclase:
super(lista de argumentos);
106
el constructor utilizando la palabra clave super seguido por una lista de argumen-
tos. Si se usa tal enunciado, este debe aparecer como el primer enunciado dentro
del cuerpo del constructor:
public A() {
super();
...
// el resto del cuerpo del constructor
}
107
108
Captulo 4
4.1. Paquetes
Un paquete permite que una coleccion de clases sea agrupada en una sola unidad
y bajo un mismo nombre que tambien actua como un entorno. Hay dos partes de
la sintaxis de paquete: una para declarar los paquetes en s y otra para importar
clases de otros paquetes.
package nombredelpaquete;
import nombredelpaquete.nombredelaclase;
o
import nombredelpaquete.*;
La primera forma importa una clase especfica nombrada, mientras que la se-
gunda forma es una abreviacion conveniente que permite que todas las clases de un
solo paquete sean importadas de una vez.
Para entender como los paquetes son usados es necesario darse cuenta de que los
nombres de los paquetes mapean a nombres de directorios dentro del almacenamien-
to de archivos locales (esto podra extenderse en el futuro a sitios de Internet). Cada
directorio contiene todos los archivos .class para las clases dadas en el paquete.
Las herramientas como el compilador de Java localizan estos directorios en forma
109
relativa a series de puntos fijos en el almacenamiento de archivos, que se almacenan
como rutas de archivos en una variable de ambiente llamada CLASSPATH (diferentes
ambientes de desarrollo pueden proveer mecanismos alternativos para las variables
de ambiente).
adts.containers.sorted
\adts\containers\sorted
o a la ruta en UNIX:
/adts/containers/sorted
.;D:\myclasses
.\adts\containers\sorted
D:\myclasses\adts\containers\sorted
C:\jdk1.1.3\lib\adts\containers\sorted
.:$HOME/lib/Java
resulta los siguientes nombres de directorios generados para el paquete antes men-
cionado:
./adts/containers/sorted
$HOME/lib/Java/adts/containers/sorted
/opt/jdk1.1.3/lib/adts/containers/sorted
110
esto suponiendo que Java ha sido instalado en el directorio /opt/jdk1.1.3.
Cuando una clase es declarada para ser parte de un paquete usando el enunciado
package, se espera que el archivo .class coincidente se localice en el directorio
correspondiente. Durante el desarrollo de la clase, el archivo .java es generalmente
localizado en el mismo directorio por conveniencia. Los nombres de los paquetes
pueden tener tantos subdirectorios como se desee, pero mientras sean mas, mas
profunda sera la estructura del directorio.
Si cualquier clase de otro paquete es usada en el paquete actual, debe ser im-
portada. Para todas las clases, incluyendo las clases de las bibliotecas de Java,
esto debe hacerse explcitamente, excepto para las clases en java.lang, las cuales
siempre se importan en forma implcita.
Para importar de otro paquete, una clase debe ser declarada publica (recuerdese
que solo una clase en el paquete puede ser publica). Las clases no-publicas son locales
a su paquete, dando un grado de control sobre la visibilidad de las clases.
111
Los enunciados package se tratan en forma bastante casual, de tal modo que
una clase puede ser declarada para ser parte de cualquier paquete que el progra-
mador desee, hasta dentro de los paquetes de las bibliotecas de Java. La unica
restriccion en esto es que el directorio coincidente con el nombre del paquete debe
ser accesible y escribible, de tal modo que el archivo .class pueda colocarse ah.
Si una clase no es declarada dentro de un paquete (como son todos los ejemplos
de programas vistos hasta ahora), entonces pertenece a un paquete por omision sin
nombre, as que todas las clases realmente pertenecen a un paquete. Para propositos
de aprendizaje y prueba de un ejemplo, esto resulta muy conveniente, ya que todos
los archivos fuente de un programa pueden estar contenidos en el directorio actual
de trabajo, sin causar ningun conflicto con el proceso de mapeo de paquetes a di-
rectorios. Tan pronto como el programador comienza a usar paquetes, sin embargo,
la estructura apropiada de directorios debe colocarse.
112
4.2. Interfaces
La declaracion de interfaces permite la especificacion de un tipo de referencia
sin proveer una implementacion en la forma en que una clase lo hace. Esto provee
de un mecanismo para declarar tipos que son distintos de las clases, lo que da una
extension importante a la forma en que los objetos y la herencia se usan en Java.
void f();
int g(String s);
void f();
int g(String s);
double h(int x);
entonces se dice que Type2 se conforma con Type1, ya que tiene todos los metodos
que Type1 posee. Esto se escribe frecuentemente como una relacion de la forma:
Type1 Type2
Si Type2 se conforma con Type1, entonces un valor de Type2 puede ser substi-
tuido por un valor de Type1, ya que soporta todos los metodos que un valor Type1
tiene.
113
Clase
Object
Interfaz
Es entonces posible declarar una variable del tipo de la interfaz y tener tal
variable como referencia a cualquier objeto de cualquier clase que implemente la
interfaz. El objeto se dice que se conforma a la interfaz, o se conforma al tipo. Mas
aun, una clase puede implementar varias interfaces de tal modo que los objetos de
tal clase pueden ser usados en cualquier momento en que los tipos de sus interfaces
sean especificados.
Esto, en efecto, provee de una conexion que permite a una clase especificar si
sus objetos pueden ser usados cuando una interfaz coincidente se provea, y puede
hacerlo sin necesidad de que la clase se encuentre en un sitio particular dentro de
la jerarqua de herencia. Esto tiene importantes consecuencias para el codigo del
programa, ya que puede ser escrito para usar un tipo interfaz, en el conocimiento de
que una clase solo tiene que implementar la interfaz para permitir que sus objetos
sean usados. Si el mismo codigo se escribe usando una clase, entonces solo los
objetos de la clase correspondiente y sus subclases pueden ser usados. Esto forzara
a la clase de cualquier otro objeto que pudiera ser utilizado en el futuro a ser una
subclase de la clase especfica.
Una cuestion comun en el diseno que las interfaces tambien atienden es la falta
de herencia multiple entre clases. Una clase puede tener solo una superclase, pero
puede requerir heredar de dos o mas clases repartidas en la jerarqua de herencia.
Esto permitira a los objetos de la clase substituir cualesquiera objetos de diferentes
superclases que les fueran especificados. Anadir interfaces resuelve el problema de
substitutibilidad, ya que una clase puede implementar varias interfaces. La herencia
de metodos y variables se resolvera mediante cambiar las relaciones de herencia
entre clases por asociaciones (lo que no es ideal, pero resulta practico).
Las bibliotecas de clases de Java hacen un gran uso de las interfaces para
especificar un conjunto de metodos que una clase debe proveer para que sus objetos
sean utilizados en ciertas situaciones. Un ejemplo es la interfaz Enumeration, la cual
especifica un tipo y un conjunto de metodos para iterar a traves de una estructura
de datos. Diferentes estructuras de datos requieren diferentes clases que provean los
114
iteradores, pero todas las clases pueden implementar la misma interfaz. Los clientes
de los iteradores deben entonces solo tener que usar una interfaz de Enumeration,
sin la necesidad de saber como un iterador particular funciona, o cual es su clase
real.
Una interfaz puede ser extendida por una o mas interfaces (algo parecido a la
herencia), usando la palabra clave extends.
115
modificadordeinterfaz interface identificador
extends listadenombresdeinterfaces {
declaracion de metodos de la interface
declaracion de variables de la interfaz
}
Una interfaz puede ser anidada dentro de una clase de alto nivel o de otra inter-
faz, e implcitamente sera tratada como estatica, aun cuando puede ser declarada
como public, private o protected como las declaraciones de clases.
interface X {
int f(String s);
boolean g(double d, long l);
Una interfaz puede estar vaca, de tal modo que solo declara el nombre de un
tipo:
interface Empty {
}
116
interfaz publica (o clase publica) en el mismo archivo fuente. Si no es publica, una
interfaz solo podra ser usada dentro del mismo paquete donde esta declarada.
Una interfaz puede extender o heredar de una o mas interfaces (notese la difer-
encia con las clases):
Esto no incluye a los metodos, ya que estos son declarados unicamente para ser
redefinidos, y no tienen cuerpo de metodo.
Una interfaz puede ser anidada dentro de una clase de alto nivel, lo cual permite
que el entorno de su nombre sea controlado por su clase anfitriona. Una interfaz
anidada es parte de su clase anfitriona, y es siempre tratada como estatica, por lo
que no es necesario que sea declarada como tal (una interfaz no-estatica no tiene
caso, ya que las interfaces no son parte de los objetos).
Una interfaz puede tambien ser anidada dentro de otra interface, como se
mostrara en un ejemplo posterior. No es claro si esto es realmente necesario o
no, pero puede hacerse.
4.2.2. implements
La palabra clave implements permite a una clase implementar (o mas bien,
conformarse a) una o mas interfaces.
117
El resto de la declaracion de la clase se hace en forma normal. Si la clase no
redefine todos los metodos declarados en la interfaz o interfaces a implementar,
entonces la clase debe ser declarada como abstracta.
Cuando se implementan dos o mas interfaces puede haber problemas con las
variables con un nombre igual que aparecen en varias interfaces, en cuyo caso
cualquier intento de utilizarlas directamente resultara ambiguo. Esto puede resol-
verse utilizando expresiones de campo:
Notese, sin embargo, que cuando las interfaces son heredadas por otras inter-
faces hay limitaciones para acceder a las variables de interfaz por parte de clases
en un paquete diferente al de la interfaz (vease el ejemplo mas abajo).
No hay ambiguedad con los metodos, ya que las interfaces solo contienen declara-
ciones de metodos abstractos. Sin embargo, si dos interfaces declaran el mismo
metodo, con la misma lista de argumentos y el mismo tipo de retorno, se espera
que el metodo sea redefinido de forma diferente. Por tanto, el metodo en la clase
que implementa a las interfaces necesitara implementar ambas redefiniciones en el
mismo metodo.
Un metodo declarado en una interfaz publica debe ser publico en la clase que
lo implementa, ya que la accesibilidad no puede decrementarse.
118
Clase
extends
Interfaz
implements
Los siguientes archivos fuente usan las interfaces declaradas en el paquete ITest.
El primero declara una clase en el mismo paquete, el cual tiene acceso a las variables
de interfaz ocultas.
package ITest;
System.out.println(X.name);
System.out.println(Y.name);
// Debe de redefinirse g y h
119
}
import ITest.*;
class A implements X {
// System.out.println(name); // ERROR
// System.out.println(X.name); // ERROR
// SIZE = 5; // ERROR
120
class NestedClass implements Example.Nested {
public void z() {
System.out.println("NestedClass:z");
}
}
// Debe redefinirse g y h
class InterfaceTest {
public static void main (String[] args) {
A a = new A();
a.f(10);
121
4.3. Excepciones
Una excepcion ocurre cuando algo falla durante la ejecucion de un programa,
es decir, un evento excepcional ocurre que no permite al programa proceder nor-
malmente. Sin un manejo explcito, un programa en el cual ocurre una excepcion
termina abruptamente, perdiendo el estado de las variables, incluyendo todos los
datos que no han sido guardados en un archivo. Esto es parecido a la situacion de
un documento en el que se ha trabajado por horas y en el ultimo minuto desaparece
sin rastro.
122
// Metodo que retorna una version de String en el lenguaje local
// Este metodo debe ser redefinido por subclases, ya que el
// comportamiento por defecto es retornar el String de error
String getLocalizedMessage();
Las subclases pueden anadir tantas variables y metodos como sea necesario
para almacenar mas informacion acerca de la excepcion que se representa por sus
instancias.
123
Object
Throwable
Exception Error
... ...
otras subclases RuntimeException otras subclases
124
4.3.2. try, catch y finally
Las palabras clave try y catch proveen de un nucleo sintactico para manejar
excepciones. La palabra clave finally provee de un mecanismo para garantizar
que un bloque de codigo se ejecuta sin importar que otra cosa suceda.
try {
secuencia de enunciados
}
catch (parametro) {
secuencia de enunciados
}
finally {
secuencia de enunciados
}
try {
f(); // llamada a un metodo que puede arrojar una excepcion
}
catch (Exception e) {
System.out.println("Llamada a f() ha fallado");
// imprime el mensaje de la excepcion
System.out.println(e.getMessage());
}
El bloque try intenta ejecutar una llamada al metodo f(). Si una excepcion
ocurre mientras se ejecuta f(), entonces se crea y arroja (throw) un objeto excep-
cion. El bloque catch que sigue tiene entonces la oportunidad de atrapar (catch) la
excepcion arrojada, si el tipo del objeto excepcion es compatible por asignacion con
el tipo especificado como parametro del bloque catch. Si la excepcion es atrapada,
los enunciados en el bloque catch respectivo se ejecutan, y la ejecucion prosigue
con el enunciado siguiente al final del bloque try-catch. Si no ocurre ninguna ex-
cepcion, se salta el bloque catch y procede la ejecucion con el siguiente enunciado
125
al final del bloque try-catch. Por lo tanto, lo que sea que ocurra, el programa tiene
una oportunidad de seguir ejecutandose normalmente.
Un bloque try puede ser seguido de varios bloques catch, cada uno tratando
una excepcion diferente:
try {
f(); // llamada al metodo que puede arrojar una excepcion
}
catch(UserException e) {
System.out.println("Llamada al metodo f ha fallado");
System.out.println(e.getMessage());
}
catch(Exception e) {
System.out.println("Llamada al metodo f ha fallado");
System.out.println(e.getMessage());
}
Cuando una excepcion es arrojada, el tipo del objeto se revisa contra el parametro
de cada bloque catch en el orden en que estan escritos. El primer bloque que coinci-
da se selecciona y ejecuta, y el resto son ignorados. En el ejemplo, esto significa que
el objeto excepcion se compara primero para ver si es instancia de UserException
o alguna de sus subclases. Si es as, entonces el bloque catch correspondiente es
ejecutado, o si no, se verifica la coincidencia con el siguiente bloque catch.
Notese que el segundo bloque es mas general, y atrapara mas tipos de excep-
ciones que el primero. Si el orden de los bloque catch se invierte, se da que:
try {
f(); // llamada al metodo que puede arrojar una excepcion
}
catch(Exception e) {
System.out.println("Llamada al metodo f ha fallado");
System.out.println(e.getMessage());
}
126
catch(UserException e) {
System.out.println("Llamada al metodo f ha fallado");
System.out.println(e.getMessage());
}
lo que provoca que el segundo bloque no pueda ser alcanzado nunca, ya que el
primero siempre sera exitoso (UserException es compatible por asignacion con
Exception). El compilador de Java detecta esta situacion y reporta un error.
try {
f(); // llamada al metodo que puede arrojar una excepcion
}
catch(Exception e) {
System.out.println("Llamada al metodo f ha fallado");
System.out.println(e.getMessage());
}
finally {
g(); // Llama siempre a g, no importa lo que pase
}
Un bloque finally siempre se ejecuta, sin importar que pase con el bloque
try, aun si se arroja otra excepcion o se ejecuta un enunciado return. Esto provee
de un lugar para poner enunciados cuya ejecucion esta garantizada.
127
at Exception1.convert(Exception1.java:14)
at Exception1.main(Exception1.java:29)
class Exception1{
public int convert(String s) {
int result = 0;
try {
// Se usa un metodo de la biblioteca de clases Integer
// para convertir un String representando un entero a
// un valor int. parseInt arroja una exception si el
// valor String no es convertible a un int.
result = Integer.parseInt(s);
}
catch(NumberFormatException e) {
System.out.println("Falla en conversion de String: + e");
e.printStackTrace();
return result;
}
class Exception2 {
128
// Este metodo deliberadamente arroja una excepcion seleccionada por el
// argumento. Notese que NullPointerException e InterruptedException son
// clases de excepciones de la biblioteca. InterruptedException es una
// subclase directa de Exception, mientras que NullPointerException es una
// subclase de RuntimeException, la cual es subclase de Exception.
public void g(int x) throws Exception {
switch(x) {
case1 :
throw new MyException("Metodo g ha fallado");
case2 :
throw new NullPointerException("Metodo g ha fallado");
case3 :
throw new InterruptedException("Metodo g ha fallado");
default :
throw new Exception("Metodo g ha fallado");
}
}
catch (MyException e) {
System.out.println("MyException atrapado en metodo f: ");
System.out.println(e.getMesssage());
}
catch (Exception e) {
System.out.println("Exception atrapado en metodo f: ");
System.out.println(e.getMessage());
}
finally {
System.out.println("Se ha ejecutado f");
}
}
129
e2.f(2);
e2.f(3);
e2.f(4);
e2.f(5);
}
}
Todas las demas excepciones deben siempre ser explcitamente tratadas usando
bloques try-catch y declaraciones throws. Cuando se usan estas excepciones, un
metodo puede retornar sin que la excepcion se atrape si el metodo mismo tiene una
declaracion throws y aun si no tiene ningun bloque catch, o que ninguno de los
bloques catch siguientes en el bloque try-catch pueda atrapar la excepcion. La
excepcion no atrapada se propaga de regreso al metodo original. Si hay un bloque
finally presente, este sera ejecutado antes de que la excepcion se propague.
130
4.3.4. Declaracion throws
Se requiere hacer una declaracion throws para cualquier metodo que arroje
o propague una excepcion que deba ser tratada explcitamente (por ejemplo, una
subclase de Exception excepto RuntimeException o alguna de sus subclases). La
declaracion enuncia el tipo o tipos de excepcion que pueden ser arrojadas por el
metodo.
El metodo es invocado desde un bloque try que tiene bloques catch capaces
de atrapar solo algunas de las excepciones que pueden ser arrojadas. El meto-
do que invoca no requiere ninguna declaracion throws respecto a la llamada
(pero puede requerir una declaracion de otras invocaciones).
El metodo se invoca de un bloque try con bloques catch que pueden atrapar
solo algunas de las excepciones que pueden ser arrojadas. Los demas tipos
de excepciones pueden ser propagados y deben aparecer en la declaracion
throws del metodo que invoca.
El metodo es invocado fuera de un bloque try y todos los tipos de excepciones
deben aparecer en el metodo que invoca en una declaracion throws.
Los constructores siguen las reglas anteriores pero las expresiones new, que crean
objetos de la clase del constructor, no tienen que aparecer en el bloque try. Sin
131
embargo, si las excepciones arrojadas por un constructor no son atrapadas, pueden
provocar la terminacion de la hebra actual, o posiblemente del programa.
class Exception3 {
// Un constructor que arroja una excepcion
public Exception3(int x) throws MyException {
throw new MyException("Falla de constructor");
}
132
// El siguiente metodo llama a h y puede tratar cualquier
// excepcion de tipo MyException que h pueda arrojar.
// Sin embargo, no puede atrapar excepciones del tipo
// YourException, propagandose en forma ascendente,
// y requiriendo una declaracion
catch (MyException e) {
System.out.println("Excepcion atrapada en g");
System.out.println(e.getMessage());
}
finally {
System.out.println("finally en g");
}
}
catch(Exception e) {
System.out.println("Excepcion atrapada en f");
System.out.println(e.getMessage());
}
}
133
// Una excepcion de tipo Error puede ser aun atrapada usando un
// bloque try-catch
public void t () {
try {
s();
}
catch (Error e) {
System.out.println("Excepcion atrapada en t");
System.out.println(e.getMessage());
}
}
try {
Exception3 e3b = new Exception3(1);
}
134
4.3.5. El enunciado throw
Un enunciado throw permite que una excepcion sea arrojada. La palabra clave
throw es seguida de una expresion del tipo Throwable, o uno de sus subtipos.
throw ExpresionThrowable;
El enunciado throw arroja una excpecion que sigue las reglas presentadas en
las secciones anteriores. Normalmente, se espera que la excepcion se propague en
forma ascedente al metodo invocado, de tal modo que el metodo que contiene el
throw tiene una declaracion throws. Las excepciones pueden ser arrojadas dentro
de un bloque try y atrapadas dentro del mismo metodo, si es necesario.
Una excepcion se crea usando una expresion new como parte del enunciado
throw, como por ejemplo:
Un enunciado throw puede tambien ser usado para re-arrojar una excepcion que
ya haba sido atrapada. Esto permite que mas de un metodo dentro de la cadena de
metodos activos pueda tratar con la excepcion, tal vez cada uno haciendo su parte
para restaurar el estado del programa para que pueda continuar en forma segura
despues de que la excepcion ha sido tratada.
try {
f();
}
catch (Exception e) {
throw e; // excepcion re-arrojada
}
135
El siguiente ejemplo (y el de la seccion anterior) muestra el uso de los enunciados
throw.
class Exception4 {
// Arroja una excepcion y la atrapa en el mismo metodo.
// Esto puede no ser muy util
public void f() {
try {
throw new Exception("Arrojada en f");
}
catch (Exception e) {
System.out.println(e.getMessage());
}
}
catch (Exception e) {
// Imprime una traza para ver la cadena de metodos
// activos
e.printStackTrace();
136
// Atrapa la excepcion para prevenir mayor propagacion
public void k() {
try {
h();
}
catch (Exception e) {
e.printStackTrace();
}
}
Arrojada en f
java.lang.Exception: Arrojada en g
at Exception4.g(Exception4 java 23)
at Exception4.h(Exception4 java 10)
at Exception4.k(Exception4 java 47)
at Exception4.x(Exception4 java 58)
at Exception4.y(Exception4 java 63)
at Exception4.main(Exception4 java 70)
java.lang.Exception: Arrojada en g
at Exception4.h(Exception4 java 38)
at Exception4.k(Exception4 java 47)
at Exception4.x(Exception4 java 58)
at Exception4.y(Exception4 java 63)
at Exception4.main(Exception4 java 70)
137
El metodo printStackTrace es declarado dos veces, as que hay dos trazas
de la pila comenzando con la lnea java.lang.Exception: Arrojada en g. Cada
traza muestra la cadena de metodos activos hasta el main. Notese que la primera
traza comienza a partir del metodo g, mientras que la segunda comienza desde
el metodo h, como resultado de la llamada al metodo fillInStackTrace, el cual
reinicia la traza en el metodo actual.
138
4.4. Hebras de Control (Threads)
Una hebra o hilo (thread) es un flujo de control en un programa, generalmente
caracterizado como una secuencia de invocaciones a metodos que se hacen cuando
la hebra se ejecuta. Todos los ejemplos anteriores han supuesto hasta ahora una
sola hebra que comienza su ejecucion con el metodo static main. Sin embargo,
un programa puede usar multiples hebras de control mediante tener una sola hebra
que genere nuevas hebras. Esto significa que en cualquier momento puede haber un
numero de secuencias de invocaciones a metodos en ejecucion.
Para ayudar a administrar multiples hebras y decidir cual debe estar activa,
las hebras tienen una prioridad. Una hebra con mayor prioridad tiene precedencia
sobre otra hebra de menor prioridad, y por lo tanto, tiene la oportunidad de eje-
cutarse primero. Cuando una hebra se detiene o se suspende (a lo que a veces se
le llama dormirse), otra hebra tiene la oportunidad de ejecutarse. La seleccion y
administracion de hebras se maneja por un proceso planificador (scheduler), el cual
mantiene una cola de hebras, cada una con su prioridad, y determina cual hebra
debe ejecutarse en un momento y cual debe ejecutarse despues, partiendo de un
conjunto de reglas. La regla principal es, como se menciona anteriormente, escoger
la hebra con mayor prioridad. Pero es posible que haya mas de una hebra con esta
misma prioridad, en cuyo caso se escoge aquella que se encuentre al frente de la
cola.
139
Una consecuencia del mecanismo de priorizacion y planificacion es que las he-
bras con baja prioridad podran no tener la oportunidad de ejecutarse nunca, ya que
siempre es posible que haya una hebra con mayor prioridad esperando en la cola. El
programador debe estar consciente de esto, y tomar las precauciones para asegurar
que todas las hebras tengan la oportunidad de ejecutarse tan frecuentemente como
sea posible para realizar su labor.
Las hebras son utiles para muchos propositos incluyendo el soporte de tecnicas
de programacion concurrente. Un buen numero de bibliotecas de clases de Java usan
hebras de control, tpicamente para permitir actividades lentas, poco frecuentes o
inconexas, que se realizan sin atravesarse en el flujo principal del programa. Por
ejemplo, la biblioteca AWT usa hebras para permitir a la interfaz grafica (GUI) de
un programa el ser manipulada o actualizada mientras que el programa continua
con su labor principal.
Las hebras tambien proveen de soluciones a problemas que de otra manera sera
muy difcil o imposible de programar. Esto incluye el manejo de eventos multiples
asncronos, como leer datos de diversas fuentes en una red, sin que el programa
principal se detenga esperando a que lleguen los datos.
140
4.4.1. La clase Thread
Las hebras de control se representan en Java como instancias de la clase Thread,
que se encuentra en el paquete de bibliotecas java.lang. La clase Thread provee
de una variedad de metodos para crear y manipular hebras. La informacion de la
clase Thread puede encontrarse en la seccion java.lang de la documentacion de
JDK.
Las hebras pueden ser creadas usando una instancia de una clase que imple-
mente la interfaz Runnable, como se describe posteriormente. Esta interfaz declara
un metodo abstracto:
Declarando una subclase de Thread que redefina el metodo run. Cuando una
instancia de la subclase se crea e invoca al metodo start, una nueva hebra
comienza su ejecucion llamando al metodo run.
...
141
Declarando una clase que implemente la interfaz Runnable, de tal modo que
redefina al metodo run declarado en la interfaz. La clase no tiene que ser
una subclase de Thread. Una nueva hebra se inicia mediante primero crear
un objeto Thread, pasando como parametro al constructor de Thread una
instancia de la clase que implementa a Runnable, y se activa mediante invocar
el metodo start del objeto tipo Thread. Esto resulta en la creacion de la
hebra, seguido de la invocacion al metodo run de la clase que implementa
Runnable. La hebra termina cuando run termina.
...
142
despliega ambos conjuntos de mensajes entremezclados. De otra manera, un con-
junto de mensajes aparecera antes que el otro.
143
Una salida entremezclada de este programa puede verse como se muestra en-
seguida. Esto vara de sistema a sistema dependiendo de las propiedades y velocidad
de la Maquina Virtual de Java utilizada:
import java.util.Date;
144
public static void main(String[] args){
Thread t = new Thread(new TestThread2());
t.start();
System.out.println("Fin del programa principal");
}
}
Una vez creadas, todas las hebras se ejecutan dentro del mismo programa y
tienen acceso a cualquier objeto del que se pueda obtener una referencia, lo que
lleva a la posibilidad de conflictos entre hebras como se describe antes. La solucion
a este problema se basa en candados para los objetos. Cada objeto tiene un candado
(un token) que puede retenerse por una sola hebra en cualquier momento dado.
Cuando una hebra ejecuta una invocacion a un metodo que ha sido declarado
como synchronized, primero intenta obtener el candado del objeto al que pertenece
el metodo invocado. Si se obtiene el candado, la hebra continua y ejecuta el meto-
do, reteniendo el candado hasta retornar del metodo. Si el candado no puede ser
obtenido, la hebra se bloquea, y se suspende su ejecucion. Cuando el candado se
libera, una de las hebras bloqueadas tiene la oportunidad de obtener el candado,
pero solo cuando el planificador de hebras se lo permite. Una hebra que retiene
el candado de un objeto puede invocar otros metodos sincronizados en el mismo
objeto, sin necesidad de liberar o re-obtener el candado.
Una vez que el candado de un objeto ha sido solicitado, ninguna otra hebra
de control puede ejecutar metodos sincronizados para tal objeto. Mas aun, una vez
que una hebra retiene el candado de un objeto, solo lo libera cuando la ejecucion
de los metodos sincronizados termina y el metodo que invoca no es sincronizado
para el mismo objeto, o cuando se invoca el metodo wait.
145
ejecutado, la hebra de control que lo invoca debe obtener primero el candado de
clase. Aparte de esto, los metodos sincronizados estaticos se comportan de forma
muy semejante a los metodos sincronizados de instancia.
...
...
// En la hebra 1
a.f(b);
...
// En la hebra 2
b.g(a);
146
el metodo g, obteniendo el candado de b, pero teniendo que suspenderse antes de
alcanzar la llamada al metodo a.y(). En ese punto, ninguna de las hebras puede
proceder, ya que cada una espera la liberacion de un candado que es mantenido
por la otra, lo que implica que un abrazo mortal ha sucedido.
El abrazo mortal puede ser difcil de detectar, y puede involucrar tres o mas
hebras. Ademas, puede ocurrir solo cuando el planificador de hebras produce un
orden especfico de acciones entre hebras en un tiempo particular y relativo de las
llamadas a los metodos. En general, la unica forma de evitar el abrazo mortal es
mediante diseno cuidadoso, y evitando llamadas a metodos que pudieran causar
conflictos. Depurar programas donde ocurren abrazos mortales es notoriamente
difcil, as que es mejor no permitir que el problema ocurra en primera instancia.
Una clase disenada para que sus objetos trabajen apropiadamente en presencia
de multiples hebras se conoce como clase segura para hebras (thread-safe class).
Tpicamente, la mayora de (si no todos) sus metodos publicos son sincronizados, a
menos que un metodo sea tan simple que no se vea afectado por el estado cambiante
del objeto debido a otra hebra. Si hacer un metodo completo sincronizado resulta
poco manejable, entonces puede utilizarse enunciados sincronizados en el metodo,
como se describe en la siguiente seccion.
class ThreadTest6 {
// Declara un conjunto de clases miembro que crean hebras
// que invocan a metodos test diferentes, con diferentes
// pausas
class MyThread1 extends Thread {
public void run() {
for (int i = 0; i < 10; i++) {
g();
}
}
}
147
class MyThread2 extends Thread {
public void run() {
setPriority(8);
for (int i = 0; i < 10; i++) {
g();
pause(700);
}
}
}
148
// Metodo no sincronizado. Las hebras pueden entremezclarse
// al ejecutar esto
public void f(int p) {
i++;
pause(p);
j++;
System.out.println("Metodo f: " + ((i==j)? "Igual" : "Diferente"));
}
i = 0;
j = 0;
t1.start();
t2.start();
try {
t1.join();
t2.join();
}
catch(InterruptedException e) {
}
i = 0;
149
j = 0;
t1.start();
t2.start();
}
private int i = 0;
private int j = 0;
}
class ThreadTest7 {
class MyThread extends Thread {
public void run() {
for (int i = 0; i < 99; i++) {
synchronized(vals) {
vals[i] += vals[i+1];
}
}
}
}
150
public void go() {
Thread t1 = new MyThread();
Thread t2 = new MyThread();
t1.start();
t2.start();
try {
t1.join();
t2.join();
}
catch(InterruptedException e) {
}
151
4.5. Ventanas (Windows)
En la actualidad, muchos programas proveen de un formato grafico de interac-
cion con el usuario: las Interfaces Graficas de Usuario (Graphical User Interface o
GUI). Al usuario se le presentan una o mas ventanas (windows) que contienen
texto, dibujos, diagramas de varios tipos o imagenes. El usuario se comunica con el
programa mediante escribir en ares designadas, o moviendo el raton (mouse) a
una posicion en particular, y presionando un boton del mismo. Las acciones pueden
ser presionar un boton en pantalla, recorrer una ventana a traves de un diagrama
mas grande o area de texto, indicar un lugar en particular dentro de un dibujo
donde una accion o re-dibujado se requiere, etc.
152
pequena, y el sistema debe hacerse cargo de redibujar la interfaz grafica a su nuevo
tamano.
El ciclo de evento esta dentro del codigo que implementa AWT, y no aparece
explcitamente como parte del cogido del programador. El programador debe con-
siderar normalmente el hacer ciertas inicializaciones que preparan a los componentes
graficos para desplegarse, y en seguida se coloca dentro del ciclo de evento de AWT.
El resto del programa es entonces una serie de fragmentos (tpicamente, metodos),
cada uno de los cuales es invocado por el ciclo de evento de AWT para tratar cierto
tipo de evento.
import java.awt.*;
import java.awt.event.*;
public Simple() {
// Prepara la ventana basica
setTitle("Simple");
153
setBackground(Color.green);
setSize(500, 400);
addWindowListener(this);
154
Dimension d = getSize();
int cx = d.width/2,
cy = d.height/2,
faceRadius = 50,
noseLength = 20,
mouthRadius = 30,
mouthAngle = 50,
eyeRadius = 5;
// Dibujar el marco
g.setColor(Color.black);
g.drawRoundRect(2, 2, d.width - 5, d.height - 5, 20, 20);
// Escribe texto
Font f1 = new Font("TimeRoman", Font.PLAIN, 14);
Font f2 = new Font("TimeRoman", Font.ITALIC, 14);
FontMetrics fm1 = g.getFontMetrics(f1);
FontMetrics fm2 = g.getFontMetrics(f2);
String s1 = "Hello, ";
String s2 = "World";
int w1 = fm1.stringWidth(s1);
int w2 = fm2.stringWidth(s2);
g.setColor(Color.black);
g.setFont(f1);
int ctx = cx - ((w1 + w2)/2);
int cty = cy + faceRadius + 30;
g.drawString(s1, ctx, cty);
ctx += w1;
g.setFont(f2);
g.drawString(s2, ctx, cty);
155
}
}
156
de manejadores de presentacion (layout managers), los cuales hacen la la-
bor de posicionar los componentes de una grafica en un contenedor. La
presentacion por omision de las clases derivadas de Frame se conocen como
de borde (border). Se anade entonces un componente de dibujo mediante el
metodo add. Utilizando el manejador de presentacion, las posiciones relativas
se especifican como se muestra en la figura 4.4.
North
South
157
Los componentes se anaden utilizando el metodo add sin argumentos adi-
cionales. Es por esto que los componentes deben anadirse en un orden especfi-
co para llenar renglones y columnas. En el ejemplo anterior, los componentes se
agregan utilizando las invocaciones al metodo add en el siguiente orden:
p.add(renglon0columna0);
p.add(renglon0columna1);
p.add(renglon0columna2);
p.add(renglon1columna0);
p.add(renglon1columna1);
p.add(renglon1columna2);
Se puede ser todava mas especfico sobre como los componentes se acomodan,
por ejemplo, utilizando la presentacion GridBagLayout, o simplemente ir recursiva-
mente anadiendo un GridLayout anidado en otro GridLayout, y as sucesivamente.
158
public class Simple extends Frame implements ActionListener
Por cada boton que se anada, es necesario definir a AWT que clase contiene
el metodo actionPerformed. En general, la clase es aquella que contiene al cons-
tructor que establece al boton en un principio. De este modo, es posible utilizar la
palabra clave this como argumento del metodo addActionListener, aplicado al
boton que se desee utilizar:
quit.addActionListener(this);
Por el momento, este ejemplo solo cuenta con un boton, de tal modo que si
este metodo es invocado, se asume que el boton Quit ha sido presionado. Si esto
sucede, es necesario salir del programa mediante invocar al metodo System.exit.
Ya que es una salida normal del programa, se da un argumento de valor cero. Sin
embargo, antes de hacer esto, es necesario asegurarse que todos los recursos que el
sistema usa en este objeto sean retornados, utilizando el metodo dispose:
dispose();
System.exit(0);
addWindowListener(this);
159
De este modo, el metodo windowClosing solo sale del programa:
En general, para varios otros programas, es posible copiar simplemente las clases
derivadas de Frame (es decir, la clase principal) en cada programa en que se necesite
utilizar ventanas. Esto termina la discusion del codigo para la clase Simple. Ahora,
se comenta el codigo de la clase Canvas0.
160
Cuando se dibujan lneas, se llenan areas de color, se escriben caracteres, etc.,
se invocan los metodos asociados con el objeto g. De este modo, se puede dibujar
un rectangulo con esquinas redondeadas mediante:
g.drawRoundRect(...argumentos...);
Hay una gran coleccion de metodos para dibujar de diferentes formas sobre el
area de dibujo. A continuacion, se listan los mas utiles, y se muestra un codigo para
la clase Canvas0. Cada uno se dibuja utilizando el color actual, que se modifica
cuando se requiera utilizando el metodo setColor.
El metodo drawLine dibuja una lnea del color actual (por ejemplo, la nariz
en el programa anterior). Los cuatro argumentos necesarios son, en orden,
las coordenadas x y y de los extremos de la lnea. Tales argumentos son
de tipo int, utilizando las unidades en pixeles, considerando el origen de
las coordenadas en la esquina superior izquierda del area. De este modo, la
siguiente invocacion dibuja una lnea como se muestra en la figura 4.6.
(0,0)
(30,10)
(10,20)
161
El metodo drawRect dibuja un rectagulo en el color actual. Los primeros
dos argumentos de este metodo son las coordenadas x y y de la esquina
superior izquierda del rectangulo, mientras que el tercer y cuarto argumentos
se refieren respectivamente al ancho y altura del rectangulo en pixeles. As si
el tercer y cuarto argumentos son iguales, el rectangulo sera un cuadrado. El
siguiente codigo dibuja un rectangulo como se muestra en la figura 4.7.
(0,0)
(10,5)
15
20
162
izquierda del rectangulo, y el tercer y cuarto argumentos corresponden al an-
cho y altura respectivamente del rectangulo, de manera semejante al metodo
drawRect. Si el tercer y cuarto argumentos son iguales, el ovalo sera un crcu-
lo. La siguiente llamada genera un ovalo como se muestra en la figura 4.8.
(0,0)
(10,5)
15
20
163
(0,0)
(10,5)
angulo2
angulo1
15
20
g.drawArc(cx - mouthRadius,
cy - mouthRadius,
mouthRadius*2,
mouthRadius*2,
270 - mouthAngle,
mouthAngle*2);
164
el cual escribe una cadena especificada de texto (como primer argumento)
en la posicion que se especifica (con el segundo y tercer argumentos, como
coordenadas). La siguiente llamada escribe un mensaje como se muestra en
la figura 4.10.
(0,0)
Happy Birthday
base
(10,15)
165
El primer argumento de esta invocacion es el nombre del tipo de letra. Otros
tipos de letra comunes son Helvetica, Courier, Dialogue y Symbol. Existen
otra cantidad de tipos de letra dependiendo de la plataforma utilizada.
El segundo argumento es el estilo del tipo de letra. Este debe ser uno de las
siguientes constantes: Font.PLAIN, Font.BOLD o Font.ITALICS. Las cons-
tantes corresponden a los estilos plano, en negrita y cursiva, respectivamente.
Es posible tambien usar expresiones mixtas, como por ejemplo Font.BOLD +
Font.ITALIC, para el segundo argumento, lo que genera un estilo en negrita
y cursiva, siempre y cuando se encuentre esta opcion disponible para el tipo
de letra especificado.
Si se desea posicionar una secuencia de cadenas de caracteres, tal vez con tipos
de letra variables, AWT espera que el programador especifique la posicion
de cada uno de ellos, y para esto es necesario que tanto espacio requiere una
secuencia particular de caracteres de un tipo de letra. La clase FontMetrics
provee tal informacion. Se establece una instancia de esta clase para un objeto
Graphics y para un objeto Font:
166
Supongase el ejemplo de un programa que permite escribir una temperatura en
grados Celsius en un campo de texto etiquetado como Celsius, y que utiliza un
boton que al ser presionado calcula la temperatura equivalente en grados Fahren-
heit en un campo de texto etiquetado como Fahrenheit. Similarmente, es posible
escribir en este campo de texto, y utilizar otro boton para convertirlo en grados
Celsius en el otro campo de texto. Es necesario considerar la validez de la cadena
de caracteres que se escribe.
Para este ejemplo, se utilizan de nuevo una clase Simple2 derivada de Frame,
con seis variables de instancia:
Una variable temp1, de una clase predefinida Temperature, que sirva para la
conversion.
Tres variables de la clase Button, llamadas toF, toC y quit, que permiten al
metodo actionPerformed checar que boton ha sido presionado.
Temperature temp1;
TextField celsiusField, fahrField;
Button toF, toC, quit;
...
}
public Simple2 () {
temp1 = new Temperature();
setTitle = ("Temperatura");
setBackground(Color.green);
setSize(400, 600);
addWindowListener(this);
...
}
167
En la ventana que se define, es necesario crear los componentes graficos puestos
en forma de tres renglones. El primero y segundo renglones son muy similares,
requiriendo:
168
toC = new Button("Convierte a C");
p2.add(toC);
toC.addActionListener(this);
Para poder utilizar estos tres paneles, es necesario declarar un nuevo panel p,
especificando un manejador de borde, y entonces anadir los tres paneles p1, p2 y
p3:
169
Simple2
Panel p
Panel p1
Panel p2
Panel p3
Button
...
else if (event.getSource() == toF) {
String c1 = celsiusField.getText();
double c2 = 0.0;
boolean isValid;
try {
c2 = Double.valueOf(c1).doubleValue();
isValid = true;
}
catch (NumberFormatException e) {
isValid = false;
}
if(isValid) {
temp1.setCelsius(c2);
double f2 = temp1.getFahr();
f2 = ((double) Math.round(f2 * 100)) / 100;
String f1 = Double.toString(f2);
fahrField.setText(f1);
}
else {
celsiusField.setText(" ");
}
}
else ...
170
En el codigo anterior, se realizan las siguientes acciones:
Se extrae la cadena del campo de texto Celsius usando el metodo getText,
y almacenandolo en la variable c1 de tipo String.
Se convierte la cadena en un valor double en un bloque try. Si los caracteres
escritos en el campo no son validos, la conversion falla, atrapandose en el
bloque catch como una excepcion. La variable booleana isValid registra si
la conversion es exitosa.
Si la entrada es valida, se convierte el valor Celsius a su equivalente Fahren-
heit redondeado a dos cifras decimales, y se le convierte de nuevo a una
cadena de caracteres para poder escribirse en el campo de texto utilizando el
metodo setText.
Si la entrada es invalida, se borra el contenido del campo de texto utilizando
una cadena en blanco.
Colocando Menues
En una ventana como la desarrollada en secciones anteriores se anade un sistema
de menu. Este menu contiene dos botones: Cargar, que selecciona un archivo de
imagen a desplegar, y Salir. De hecho, se ha hecho comun en la mayora de los
disenos de interfaz a usuario considerar a este menu de funciones miscelaneas con
el nombre de Archivo (o File), que es lo que se pretende aqu. Para esto, es
necesario declarar variables de instancia de tipo MenuItem para los dos botones, de
tal modo que se sepa cual ha sido seleccionado:
171
public class Simple3 extends Frame
implements WindowListener, ActionListener{
Canvas3 canvas;
MenuItem load, quit;
...
}
172
Hay una serie de otras caractersticas utiles de los menues. Supongase que se
declara las siguientes variables de instancia:
173
Menu menuB = new Menu("MenuB");
Menu menuB1 = new Menu("MenuB1");
buttonB1p1 = new MenuItem("Boton B1.1");
menuB1.add(buttonB1p1);
buttonB1p1.addActionListener(this);
buttonB1p2 = new MenuItem("Boton B1.2");
menuB1.add(buttonB1p2);
buttonB1p2.addActionListener(this);
buttonB1p3 = new MenuItem("Boton B1.3");
menuB1.add(buttonB1p3);
buttonB1p3.addActionListener(this);
menuB.add(menuB1);
Menu menuB2 = new Menu("MenuB2");
buttonB2p1 = new MenuItem("Boton B2.1");
menuB2.add(buttonB2p1);
buttonB2p1.addActionListener(this);
buttonB2p2 = new MenuItem("Boton B2.2");
menuB2.add(buttonB2p2);
buttonB2p2.addActionListener(this);
buttonB2p3 = new MenuItem("Boton B2.3");
menuB2.add(buttonB2p3);
buttonB2p3.addActionListener(this);
buttonB2p4 = new MenuItem("Boton B2.4");
menuB2.add(buttonB2p4);
buttonB2p4.addActionListener(this);
menuB.add(menuB2);
174
En Menu A se tienen varios botones, por lo que usa el metodo addSeparator
para formar tres grupos de botones.
En Menu B se tienen dos sub-menues, Menu B1 y Menu B2, cada uno
con su propio conjunto de botones. Si se presiona el boton para Menu B1,
esto hace que aparezca el sub-menu con sus tres botones.
En Menu C se tienen un numero de checkboxes, cada uno de los cuales in-
dependientemente registra si una opcion ha sido checada (on) o no (off).
Dado que se tienen varios de estos, se declara un arreglo que los contenga,
el cual llama al constructor de cada uno, y los registra con un auditor,
anadiendolos finalmente al menu. Un metodo, el itemStateChanged de la
interfaz ItemListener, se invoca cada vez que el usuario presiona uno de los
checkboxes, cambiando su estado:
Seleccionando un Archivo
Regresando al programa original, notese que el codigo de la opcion Cargar
del menu invoca a un metodo loadFile, lo que permite al usuario seleccionar un
archivo que contiene una imagen para ser desplegada. En seguida, se presenta el
codigo de este metodo:
175
En este metodo se crea una instancia de la clase FileDialog, lo que requiere
tres parametros:
El dueno del dialogo. Esta es una instancia de la clase Simple3, con la cual
este dialogo debe estar asociado, por lo que se utiliza la palabra clave this.
Una cadena de caracteres que aparece como ttulo de la ventana que surge
para el dialogo. Se ha escogido Cargar Archivo.
Una de dos constantes: FileDialog.LOAD, si se planea leer de un archivo
(que es lo que se intenta aqu) o FileDialog.SAVE, si se planea escribir un
archivo.
if (parent.name != null) {
String filename = parent.directory + parent.name;
Image image = Toolkit.getDefaultToolkit().getImage(filename);
g.drawImage(image,
cx - (image.getWidth(this)/2),
cy = (image.getHeight(this)/2),
this);
176
// Escribe el nombre del archivo bajo la imagen
Font f1 = new Font("TimesRoman", Font.PLAIN, 14);
FontMetrics fm1 = g.getFontMetrics(f1);
int w1 = fm1.stringWidth(parent.name);
g.setColor(Color.black);
g.setFont(f1);
int ctx = cx - (w1 / 2);
int cty = cy + (image.getHeight(this) / 2) + 30;
g.drawString(parent.name, ctx, cty);
}
else {
// Escribe el mensaje "No es archivo" en el centro del
// area de dibujo
Font f1 = new Font("TimesRoman", Font.PLAIN, 14);
...
}
}
g.drawImage(image,
cx - (image.getWidth(this)/2),
cy = (image.getHeight(this)/2),
this);
Siguiendo al Raton
Finalmente, se modifica el programa de tal modo que la imagen se posicione en
el punto donde se presione un boton del raton. Existen varios metodos que permiten
177
tomar acciones dependiendo de varias cosas que puede el usuario hace con el raton.
Aqu, se selecciona el metodo mouseClicked de la interfaz MouseListener, ya que
interesa solo si el raton ha sido presionado en el area de dibujo, por lo que se
especifica una clase Canvas5 que implementa tal interface:
public Canvas5(Simple3 f) {
parent = f;
addMouseListener(this);
}
178
Apendice A
import java.io.*;
179
// Lectura de un valor int del teclado. El valor por omision es 0
public final synchronized int readInteger() {
String input = "" ;
int value = 0 ;
try {
input = in.readLine() ;
}
catch (IOException e) { }
if (input != null) {
try {
value = Integer.parseInt(input) ;
}
catch (NumberFormatException e) { }
}
return value ;
}
180
}
catch (NumberFormatException e) { }
}
return value ;
}
181
}
return s ;
}
}
182