You are on page 1of 21

Instituto Tecnológico Metropolitano

Programación en Java
Parte 2: Uso de la POO y las interfaces gráficas básicas
Objetivo General
Aplicar el estilo de la programación orientada a objetos a los programas
en Java y aprovechar el uso de la interfaces gráficas para hacer
programas más amigables.

Introducción
La clase es el núcleo de Java. Es la construcción lógica sobre la que se construye el lenguaje Java, ya que
define la forma y la naturaleza de un objeto. La clase constituye la base de la programación orientada a
objetos en Java. Cualquier concepto que se desee implementar en un programa Java debe ser encapsulado
en una clase.

TEMA 1 La POO en Java


En los programas realizados hasta el momento se han utilizado clases, pero de la manera más simple: Una
clase que encapsula al método main(), lo que ha permitido ilustrar las bases de la sintaxis de Java. Pero las
clases son más poderosas de lo visto hasta ahora.

Lo más importante acerca de una clase es que define un nuevo tipo de dato. Una vez definido, este nuevo
tipo de dato se puede utilizar para crear objetos de ese tipo. Una clase es una plantilla para un objeto y un
objeto es una instancia de una clase.

Clases
Cuando se define una clase, se declara su forma y naturaleza exacta. Esto se hace especificando los datos
que contiene la clase y el código que opera sobre ellos.

Una clase se declara utilizando la palabra clave class. Una clase tiene la siguiente forma:
Class NombreClase {
tipo variable1;
tipo variable2;
...
tipo variableN;

tipo metodo1(listaParametros){
...
}
tipo metodo2(listaParametros){
...
}
...
tipo metodoN(listaParametros){
...
}
}

Las variable definidas en una clase se llaman variables de instancia. Los subprogramas se denominan
métodos.

Para objetos de una clase es necesario realizar dos pasos:


• Declarar una variable del tipo de la clase. La variable no define un objeto, sino una referencia a él.
1
Instituto Tecnológico Metropolitano

• Obtener una copia física y real objeto y asignarla a la variable. Para ello se utiliza el operador new.
Este operador asigna dinámicamente en tiempo de ejecución, memoria para un objeto y devuelve
una referencia a este.

Por ejemplo, cuando decidimos utilizar la clase InputStreamReader, las instrucción fue:
InputStreamReader isr=new InputStreamReader(System.in);

La cual se puede dividir así


InputStreamReader isr; //Se declara la referencia al objeto
isr=new InputStreamReader(System.in); //Reserva espacio para el objeto

Métodos
Java le da mucho poder y flexibilidad a los métodos. Una de las características es que pueden devolver un
tipo específico de dato o de clase. Si no devuelve ningún valor el tipo devuelto debe ser void. La lista de
parámetros es una secuencia de parejas de tipo e identificador separadas por coma. Los parámetros son
variables que reciben el valor de los argumentos que se pasan al método. Si el método no tiene parámetros,
entonces la lista estará vacía.

Cuando el método devuelve un tipo distinto de void, deben retornar un valor utilizando la instrucción return:
return valor;

Los métodos definen la interfaz de la mayoría de las clases. Esto permite que la clase oculte sus estructuras
de datos internas detrás de un conjunto de métodos. Además de definir método que proporcionan acceso a
los datos, también pueden definirse métodos que serán utilizados internamente por la propia clase.

Un programa ejemplo: Rescribiendo una aplicación utilizando POO

El código de la derecha permite calcular valor de import java.io.*;


una cuota en una deuda con base en el valor de public class Cuota{
préstamo, la tasa de interés y el número de public static void main(String args[]){
períodos. (Fue descrito con anterioridad). InputStreamReader isr=new InputStreamReader(System.in);
BufferedReader br=new BufferedReader(isr);
String texto;
En él se observa que no sólo se atiende el double p,i,n,a;
problema del cálculo de los resultados sino System.out.println("Calculo del valor de una cuota:");
también de la entrada de datos. Esto dificulta un try{
poco entender el programa. System.out.print("Valor del Préstamo? ");
texto=br.readLine();
Aplicando la POO se podría pensar en dividir el Double d1=new Double(texto);
problema en dos: p=d1.doubleValue();
System.out.print("Interés por período? ");
• La entrada de datos por consola
texto=br.readLine();
• El calculo del valor de la cuota. Double d2=new Double(texto);
i=d2.doubleValue()/100;
Para ello sería conveniente definir dos clases: System.out.print("Períodos? ");
• Una que contenga los métodos para la texto=br.readLine();
lectura de variables, no sólo de valores Double d3=new Double(texto);
n=d3.doubleValue();
reales, sino también de enteros y double factor=Math.pow(1+i,n);
cadenas (para poder reutilizarla a=p*(factor*i)/(factor-1);
después en otro programa). System.out.print("Valor Cuota="+a);
• Una que contenga el método principal, }
donde se calcule el valor de la cuota. catch (Exception e) {
System.out.println("Error "+e);
}
}
}
El código para la primer clase sería:
2
Instituto Tecnológico Metropolitano

import java.io.*;

class LeerVariable{
InputStreamReader isr=new InputStreamReader(System.in);
BufferedReader br=new BufferedReader(isr);
public int leerEntero(String mensaje){
int num=0;
try{
System.out.print(mensaje);
num=Integer.parseInt(br.readLine());
} catch (Exception e) {
System.out.println("Error "+e);
}
return num;
}
public double leerReal(String mensaje){
double num=0;
try{
System.out.print(mensaje);
Double d=Inew Double(br.readLine());
num=d.doubleValue();
} catch (Exception e) {
System.out.println("Error "+e);
}
return num;
}
}

y el código para la segunda:

public class Cuota{

public static void main(String args[]){


System.out.println("Calculo del valor de una cuota:");
LeerVariable lv=new LeerVariable();
double p=lv.leerReal("Valor del Préstamo? ");
double i=lv.leerReal("Interés por período? ")/100;
double n=lv.leerReal("Períodos? ");
double factor=Math.pow(1+i,n);
double a=p*(factor*i)/(factor-1);
System.out.print("Valor Cuota="+a);
}
}

Cómo se podrá observar, se simplifica mucho el código para la segunda clase, y la primera clase puede ser
reutilizada en otras aplicaciones.

La Herencia
La herencia es una de las piedras angulares de la POO ya que permite la creación de clasificaciones
jerárquicas. Gracias a la herencia se puede definir una clase general que define características comunes a
un conjunto de elementos relacionados.

En la terminología Java, a la clase general se le llama superclase y a las clases más específicas que
heredan de la general se les llama subclases. Una subclase es una versión especializada de una
3
Instituto Tecnológico Metropolitano

superclase, que hereda todas las variables de instancia y los métodos definidos por la superclase y que
añade sus propios elementos.

La forma general de la declaración de una clase que hereda de otra es la siguiente:

Class nombreSubClase extends nombreSuperClase {



}

Una subclase no puede acceder a aquellos miembros de la superclase que han sido declarados como
private.

Un programa ejemplo: Uso de la herencia


Se requiere una aplicación que permita crear objetos círculo y cilindro para calcular las magnitudes
correspondientes a cada uno de ellos, tales como área, perímetro y volumen.

Se requiere por tanto, diseñar la jerarquía de clases Círculo y Cilindro. En esencia se puede decir que un
objeto de la clase cilindro se compone de un objeto de la clase círculo y una altura.

Las magnitudes se calcularían así:


Perímetro = 2 π r
Área = π r²
Volumen = π r² h

La clase Circulo la definiríamos así:


class Circulo {
double radio;
public double perimetro(){
return 2*Math.PI*radio;
}
public double area(){
return Math.PI*Math.pow(radio,2);
}
}

La clase Cilindro heredaría la clase Circulo y quedaría así:


class Cilindro extends Circulo {
double altura;
public double volumen(){
return altura*area();
}
}

Observe como para calcular el volumen se utiliza el método area() como si estuviera en la propia clase.

El siguiente sería el código para calcular el volumen de un cilindro, leyendo su altura y radio:
public class PruebaCilindro{

public static void main(String args[]){

4
Instituto Tecnológico Metropolitano

LeerVariable lv=new LeerVariable();


Cilindro c=new Cilindro();
c.radio=lv.leerReal("radio?");
c.altura=lv.leerReal("altura?");
System.out.println("Volumen cilindro:"+c.volumen());
}
}
Esta aplicación instancia un objeto de la clase LeerVariable la cual ha sido previamente compilada y está en
la misma carpeta, así como un objeto de la clase Cilindro.

La Sobrecarga de constructores
En java es posible definir dos o más métodos dentro de la misma clase que tengan el mismo nombre, pero
con su lista de parámetros distintas. Cuando ocurre esto, se dice que los métodos están sobrecargados. La
sobrecarga es lo que utiliza Java para implementar el polimorfismo.

Cuando se invoca un método sobrecargado, Java utiliza el tipo y número de argumentos como guía para
determinar la versión del método sobrecargado. Por eso, los métodos sobrecargados deben diferenciarse en
el tipo y número de parámetros.

Además de sobrecargar los métodos, también se pueden sobrecargar los constructores. Los constructores
son métodos especiales que permiten que los objetos se inicialicen cuando son creados. Tienen el mismo
nombre que la clase en que reside. Una vez definido, se llama automáticamente al constructor después de
crear el objeto, antes de que termine el operador new. Los constructores parecen un poco extraños porque
no devuelven ningún tipo, ni siquiera void. Esto se debe a que el tipo implícito que devuelve un constructor
de clase es el propio tipo de la clase.

Podemos aplicar la sobrecarga de constructores para redefinir la manera como podemos instanciar los
objetos de la clase Cilindro.

En el siguiente código la clase Circulo contiene dos constructores para la instancia de los objetos. En el
primero pide un argumento para inicializar el radio y en el segundo no pide ningún parámetro e inicializa el
radio en 0.
class Circulo {
double radio;

public double perimetro(){


return 2*Math.PI*radio;
}
public double area(){
return Math.PI*Math.pow(radio,2);
}
Circulo(double r){
radio=r;
}
Circulo(){
radio=0;
}
}

Aplicamos lo mismo a la clase Cilindro: Un constructor que pide argumentos para inicializar el radio y la
altura, y otro constructor que no pide argumentos y los inicializa en 0.
class Cilindro extends Circulo {
double altura;

public double volumen(){


5
Instituto Tecnológico Metropolitano

return altura*area();
}
Cilindro(double r,double h){
radio=r;
altura=h;
}
Cilindro(){
radio=0;
altura=0;
}
}

Una aplicación que instancie objetos de estas dos clases podría ser:
public class Prueba{

public static void main(String args[]){


LeerVariable lv=new LeerVariable();
Cilindro cil=new Cilindro(5,4);
System.out.println("Volumen cilindro:"+cil.volumen());
Circulo cir=new Circulo();
cir.altura=lv.leerReal("radio?");
System.out.println("Area circulo:"+cir.area());
}
}
En este ejemplo se define un objeto de la clase Cilindro con radio=5 y altura=4 y se le calcula el volumen. Se
instancia además un objeto de la clase Circulo a la cual se le lee el radio y se le calcula el área.

TEMA 2 Applets
Un Applet, como su nombre lo indica, es una especie de miniaplicación, diseñada para ser ejecutada
mediante un navegador del Web. Las applets se diferencian de las aplicaciones regulares en numerosas
formas. Una de las más imprtantes es que los applet tienen restricciones de seguridad sobre lo que está
permitido realizar. A menudo, un applet consiste de un código poco fiable, por lo que se les impide tener
acceso al sistema de archivo local.

Todas las applets son subclases de Applet, por lo que deben importar el paquete java.applet. Además, las
applets tienen que importar java.awt. AWT es un conjunto de herramientas para diseñar interfaces gráficas
de usuario. Como todas las applets se ejecutan sobre una ventana, es necesario incluir el soporte para
trabajar con esa ventana. Las applets no son ejecutadas por el intérprete de Java a través de una consola,
sino que son ejecutadas por un navegador compatible con Java o por un visualizador de applets.

Para un programador, una de las diferencias más grandes entre los applets y las aplicaciones es que los
primeros no tienen método main() u otro simple punto de entrada desde el cual comience a ejecutarse. En
vez de eso, para escribir un applet, se hace una subclase de la clase Applet y anula los métodos estándares.

Ahora bien, la tarea de escribir un applet se reduce a definir los métodos apropiados. Veamos:

Método Descripción
void init() Este método se ejecuta cuando una applet comienza su ejecución. Es el primer método
que se ejecuta en una applet.
void destroy Es llamado cuando el applet está a punto de ser descargadp del navegador. Este debe
liberar cualquier recurso o memoria que el applet tenga asignado
void start() Es llamado cuando al applet se vuelve visible y debe comenzar a hacer lo que debe. A
menudo se usa con la animación y con los hilos.
void stop() Es llamado cuando el applet se vuelve temporalmente invisible, por ejemplo, cuando el
usuario lo ha arrastrado fuera de la pantalla. Le ordena al applet detener la ejecución de
6
Instituto Tecnológico Metropolitano

una animación o cualquier otra tarea.

Aparte de estos métodos, hay otros heredados de las superclases de Applet, que el navegador invoca en el
momento apropiado y que un applet debe invalidar. El más conocido es paint(), al que el navegador invoca
para pedirle al applet que se dibuje a si mismo en la pantalla.

La clase Applet también define algunos métodos comúnmente usados por los applet:

Método Descripción
Image getImage() Carga un archivo de imagen desde la estación de trabajo y devuelve un objeto
Image.
AudioClip getAudioClip() Carga un sonido desde la estación de trabajo y devuelve un objeto AudioClip.
String[] getParameter() Localiza y devuelve el valor de un parámetro nombrado, especificado en el
archivo HTML que hace referencia al applet con la etiqueta <PARAM>.
URL getCodeBase() Devuelve el URL base, desde el cual el archivo de clase applet fue cargado.
URL getDocumentBase() Devuelve el URL base del archivo HTML que hace referencia al applet.
void showStatus() Muestra un mensaje en la línea de estado del navegador.

Un ejemplo sencillo de applet sería:


import java.applet.*;
import java.awt.*;
public class AppletHola extends Applet {
public void paint(Graphics g){
g.drawString("Hola",10,20);
}
}
Este ejemplo utiliza el método paint(), invocado por el navegador del Web cuando el applet necesita ser
dibujado. Este método ejecuta una salida gráfica, que en este caso es el texto “hola”. paint() utiliza un
argumento que es un objeto de la clase Graphics. La clase Graphics es todo lo que se necesita para dibujar
en Java.

Para desplegar el applet , se necesita un archivo HTML que le haga referencia.


<html>
<head>
<title>Applet</title>
</head>
<body>
<applet code=AppletHola.class width=200 height=100>
</applet>
</body>
</html>

La etiqueta utilizada es la <APPLET>…</APPLET> la cual pide 3 parámetros:


• El nombre del archivo compilado (*.class)
• El ancho del área de despliegue (WIDTH)
• El alto del área de despliegue (HEIGHT)

En el navegador, así se vería el applet:

7
Instituto Tecnológico Metropolitano

Métodos gráficos básicos

Como se mencionó anteriormente, un applet utiliza el método drawString(), que es miembro de la clase
Graphics. Normalmente este método se utiliza en los métodos update() y paint() del applet y tiene la
siguiente sintaxis:
public void drawString(String mensaje, int x, int y)

Para darle color al fondo del applet se utiliza el método setBakcground(), mientras que setForeGround()
permite asignarle color al texto y a las líneas. La clase Color define las siguientes constantes para
especificar colores:
Color.black Color.blue Color.cyan Color.darkGray
Color.gray Color.green Color.lightGray Color.magenta
Color.orange Color.pink Color.red Color.white
Color.yellow

Las siguientes instrucciones asignan un fondo de color cyan y el texto de color azul.
setBackGround(Color.cyan);
setForeGround(Color.blue);

Para obtener los colores actuales, se utilizan los métodos getBackGround() y getForeGround().

Cuando un applet necesita actualizar la información mostrada en su ventana, se utiliza el método repaint().

Cuando se necesita mostrar un mensaje, un applet puede utilizar la barra de estado del navegador mediante
una llamada al método showStatus

Para el trazado de figuras geométricas se tienen los siguientes métodos:


Método Descripción
void drawLine(int x1, int y1, int x2, int y2) Dibuja una línea desde las coordenadas x1,y1 hasta
x2,y2
void drawRect(int x1, int y1, int ancho, int alto) Dibuja un rectángulo desde las coordenadas x1,y1 con
un ancho y un alto específicos.
void drawRoundRect(int x1, int y1, int ancho, int Dibuja un rectángulo redondeado desde las
alto, int xDiametro, int yDiametro) coordenadas x1,y1 con un ancho y un alto específicos,
y los diámetros de los arcos de las esquinas xDiametro
y yDiametro.
void fillRect(int x1, int y1, int ancho, int alto) Rellena un rectángulo desde las coordenadas x1,y1
con un ancho y un alto específicos.
8
Instituto Tecnológico Metropolitano

void fillRoundRect(int x1, int y1, int ancho, int Rellana un rectángulo redondeado desde las
alto, int xDiametro, int yDiametro) coordenadas x1,y1 con un ancho y un alto específicos,
y los diámetros de los arcos de las esquinas xDiametro
y yDiametro.
void drawOval(int x, int y, int ancho, int alto) Dibuja la elipse comprendida por el rectángulo que va
desde las coordenadas x1,y1 con un ancho y un alto
específicos.
void fillOval(int x, int y, int ancho, int alto) Rellena la elipse comprendida por el rectángulo que va
desde las coordenadas x1,y1 con un ancho y un alto
específicos.
void drawArc(int x1, int y1, int ancho, int alto, Dibuja el arco comprendido por el rectángulo que va
int anguloInicial, int anguloBarrido) desde las coordenadas x1,y1 con un ancho y un alto
específicos. El arco comienza en un anguloInicial y
recorre la distancia dada por anguloBarrido.
void fillArc(int x1, int y1, int ancho, int alto, int Rellena el arco comprendido por el rectángulo que va
anguloInicial, int anguloBarrido) desde las coordenadas x1,y1 con un ancho y un alto
específicos. El arco comienza en un anguloInicial y
recorre la distancia dada por anguloBarrido.
void drawPolygon(int x[], int y[], int puntos) Dibuja el polígono definido por los vértices
correspondientes a los vectores x[] y y[], con un total de
vértices definido por puntos.
void fillPolygon(int x[], int y[], int puntos) Rellena el polígono definido por los vértices
correspondientes a los vectores x[] y y[], con un total de
vértices definido por puntos.

Para ajustar el tamaño de un gráfico al tamaño actual del applet, se utilizan los métodos getWidth() y
getHeight() para consultar el ancho y el alto del applet respectivamente y hacer los cálculos necesarios.

Un applet de ejemplo: Gráfica de una función


A continuación emplearemos un applet para graficar una función matemática.

Una función matemática permite calcular un valor como resultado de aplicar una expresión a una variable.
Por ejemplo, dada la función f(x)=x3, para cada valor de x, donde x es un número real, f(x) corresponde al
valor de x elevado al cubo. Con base en esto, se puede obtener la siguiente tabla:

X -10 -9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9 10
f(x) 100 81 64 49 36 25 16 9 4 1 0 1 4 9 16 25 36 49 64 81 100

Esta tabla permite definir una gráfica en un plano


cartesiano de dos ejes, uno para los valores de la
variable x (horizontal) y otro para los valores de la
función (vertical). La gráfica se realiza trazando
líneas entre dos puntos cada uno correspondiente a
la pareja compuesta por un valor de la variable y el
respectivo valor de la función (x, f(x)). Por ejemplo:

Punto 1=(-10,-100) al Punto 2=(-9,-81)


Punto 2=(-9,-81) al Punto 2=(-8,-64)

9
Instituto Tecnológico Metropolitano

Se va a implementar entonces un applet que grafique la función f(x)=x3. Para ello se debe tener en cuenta
que las coordenadas para graficar la función no corresponden a las coordenadas utilizadas para hacer
trazos en el applet, por lo que hay que hacer los respectivos cálculos para hacer la adaptación.

Estos cálculos comprenden el manejo de las siguientes variables:


• xi: Valor inicial de x. Corresponde al pixel 0
• xf: Valor final de x. Corresponde al pixel del tamaño del applet dado por getWidth().
• yi: Valor máximo de la función. Corresponde al pixel 0
• yf: Valor mínimo de la función. Corresponde al pixel del tamaño del applet dado por getHeight().
• dx: Valor de incremento de x para un pixel. Equivalente a (xf – xi)/getWidth().
• dy: Valor de incremento de la función para un pixel. Equivalente a (yi – yf)/getHeight()

Para un valor de x determinado, el pixel correspondiente sería: (x – xi)/dx


Para un valor de y determinado, el pixel correspondiente sería: (yi - y)/dx

La gráfica de la función se hará trazando pequeñas líneas con una distancia horizontal de sólo 1 pixel lo cual
dará el efecto de curva de la función. Para ello se calculan dos valores de la variable x contiguos (con una
diferencia de dx) y sus respectivos valores f(x) y se traza una línea. Seguido, se traza la línea siguiente y así
sucesivamente de izquierda a derecha hasta llegar al último píxel que corresponde al valor xf.

El siguiente código resuelve el problema:


import java.awt.*;
import java.applet.*;
public class Funcion extends Applet {

float f(float x)
{
Float f=new Float(Math.pow(x,3));
return f.floatValue();
}

public void paint(Graphics g)


{
g.setColor(Color.blue);
//Límites de X
float xi=-10;float xf=10;
//Incremento de X por pixel
float dx=(xf-xi)/getSize().width;

10
Instituto Tecnológico Metropolitano

//Máximo y mínimo de Y
float yi=f(xi);float yf=f(xi);
float x=xi;
while(x<=xf)
{
if(f(x)>yi) yi=f(x);
if(f(x)<yf) yf=f(x);
x+=dx;
}
//Incremento de Y por Pixel
float dy=(yi-yf)/getSize().height;
//Eje Vertical
int px;
if((xi<=0)&&(0<=xf))
{
px=Math.round(-xi/dx);
g.drawLine(px, 0, px, getSize().height);
}
//Eje Horizontal
int py;
if((yf<=0)&&(0<=yi))
{
py=Math.round(yi/dy);
g.drawLine(0, py, getSize().width,py);
}
//Hallar punto inicial
g.setColor(Color.red);
float x1=xi;
float y1=f(x1);
int py1=Math.round((yi-y1)/dy);
float x2,y2;
int py2;
for (px=0 ; px<getSize().width ; px++)
{
//Hallar punto final
x2=x1+dx;
y2=f(x2);
py2=Math.round((yi-y2)/dy);
g.drawLine(px, py1, px+1, py2);
//Siguiente línea
x1=x2;
y1=y2;
py1=py2;
}
}
}

En el código se trazan además las líneas correspondientes a los ejes horizontal (cuando y=0) y vertical
(cuando x=0)

La ejecución en un navegador se vería así:

11
Instituto Tecnológico Metropolitano

Imágenes
Una imagen es básicamente un objeto gráfico rectangular. Inicialmente las gráficas aceptadas en el Web
eran en formato GIF que fue creado por Compuserve en 1987 para hacer posible que las imágenes se
viesen mientras cargaban pero sólo podían tener hasta 256 colores. Luego apareció el formato JEPG el cual
fue creado por un grupo experto en fotografías para almacenar imágenes en color real. El formato JEPG
tiene un mayor grado de compresión pero no soporta transparencia ni animación.

Cuando se trabaja con imágenes, se pueden realizar las siguientes operaciones:


Método Descripción
Image getImage(URL url, String archivoImagen) Carga la imagen desde fuentes externas.
boolean drawImage(Image objetoImagen, int x, int y, Muestra una imagen cargada. Es un método de
ImageObserver objetoImagen) la clase Graphics. El objeto de la clase
ImafeObserver generalmente es null.
Image createImage(ImageProducer generadorImagen) Crea una imagen a partir de un generador de
imágenes, como por ejemplo
FilteredImageSource, la cual es una clase que
permite filtrar imágenes.

El siguiente ejemplo permite mostrar la imagen de un dibujo tridimensional del relieve de la ciudad de Medllín
almacenado en el archivo “Medellín.jpg”:

import java.applet.*;
import java.awt.*;

public class ImagenMedellin extends Applet{


Image imagen;

public void init()


{
imagen=getImage(getDocumentBase(),"Medellin.jpg");
}

public void paint(Graphics g)


{
12
Instituto Tecnológico Metropolitano

g.drawImage(imagen,0,0,null);
}
}

En un navegador lo veríamos así:

Recorte de imágenes
Basado en el trabajo con imágenes, es posible realizar applets que muestren una secuencia de imágenes
para generar una animación. Las tramas de la animación se obtienen de una única imagen que se puede
dividir en una especie de rejilla utilizando coordenadas.

La clase CropImageFilter filter filtra una imagen origen para extraer una región rectangular, lo cual es útil
cuando se desean utilizar varias imágenes pequeñas obtenidas a partir de una imagen origen más grande.
Cargar 20 imágenes de 2K es mucho más lento que cargar un único archivo de 40K.

Para que se efectúe el filtrado de la imagen se requiere un objeto de la clase FilteredImageSource la cual
es una subclase de ImageProducer que es una interfaz que genera datos de imagen.
13
Instituto Tecnológico Metropolitano

La clase CropImageFilter es una subclase de ImageFilter y sus métodos se usan para la comunicación entre
el filtro y su FilteredImageSource, y nunca se deben llamar directamente.

Un filtrado sencillo de imagen sería:

El código en Java es el siguiente:


import java.applet.*;
import java.awt.*;
import java.awt.image.*;

public class RecorteImagen extends Applet


{
Image imagen, imagenFiltrada;

public void init()


{
imagen=getImage(getDocumentBase(),"Monalisa.jpg");
CropImageFilter cif=new CropImageFilter(0,0,50,50);
FilteredImageSource fis=new FilteredImageSource(imagen.getSource(),cif);
imagenFiltrada=createImage(fis);
}

public void paint(Graphics g)


{
g.drawImage(imagenFiltrada,0,0,null);
}
public void update(Graphics g)
{
paint(g);
}
}

Cuya ejecución se vería así:

14
Instituto Tecnológico Metropolitano

Hilos
Un programa multihilo contiene dos o más partes que pueden ejecutarse de forma concurrente. Cada parte
de ese programa se llama hilo (Thread) y cada hilo establece un camino de ejecución independiente. Es un
tipo de multitarea distinta a la multitarea por procesos, la cual ejecuta varios programas a la vez. En Java, es
un solo programa con varios hilos a la vez.

La programación multihilo permite escribir programas muy eficientes que utilizan al máximo la CPU,
reduciendo al mínimo el tiempo que está libre. Esto es importante en los entornos interactivos y en red en los
que se trabaja con Java.

Los hilos en Java son algo común. Dado que Java hace tan sencillo emplear los hilos, las bibliotecas de
clase de Java requieren su uso en cierto número de casos. Por ejemplo, todo applet que ejecute animación
lo logra mediante un hilo.

En un entorno de programación de un hilo único, cuando el hilo se bloquea, deteniendo la ejecución porque
está esperando algún recurso, detiene por completo el programa. En el entorno de programación multihilo de
Java, un hilo puede detenerse sin parar las otras partes del programa. Por ejemplo, el tiempo que queda
libre cuando un hilo lee datos de una red o espera datos de entrada de un usuario, se puede utilizar en
cualquier otra parte. Cuando un hilo se bloquea en un programa Java, sólo ese hilo se detiene y todos los
demás continúan su ejecución.

Los hilos pueden estar en distintos estados. Un hilo puede estar ejecutándose. También puede estar
preparado para ejecutarse tan pronto como disponga de tiempo de CPU. Un hilo que está ejecutándose
puede suspenderse, lo que equivale a detener temporalmente su actividad. El hilo suspendido puede
reanudarse permitiendo que continue su tarea allí donde la dejó. Un hilo puede estar bloqueado cuando
espera un recurso. En cualquier momento, un hilo puede detenerse, finalizando su ejecución de manera
inmediata. Una vez detenido, un hilo no puede reanudarse.

El intérprete de Java utiliza prioridades para determinar como debe tratar cada hilo con respecto a los
demás. Es un valor entero que se utiliza para decidir cuándo se pasa a ejecutar otro hilo, lo cual se conoce
como cambio de contexto. Un hilo puede ceder el control voluntariamente al bloquearse o en espera de
una E/S pendiente. La CPU ejecuta a continuación, el hilo que tenga mayor prioridad y esté preparado para
ejecución. Un hilo también puede ser desalojado por otro de mayor prioridad.

15
Instituto Tecnológico Metropolitano

Debido a que los hilos permiten y potencian el comportamiento asíncrono de los programas, debe existir
alguna manera de forzar el sincronismo allí donde sea necesario. Por ejemplo, si se desea que dos hilos se
comuniquen para compartir una estructura de datos, necesitará alguna manera de garantizar que no exista
conflicto entre dichos hilos, es decir, es necesario evitar que un hilo escriba datos mientras el otro está
leyéndolos.

Una vez que el programa se ha dividido en hilos, es preciso definir cómo se comunicarán entre sí dichos
hilos. Java proporciona un sistema de mensajes que permite que un hilo entre en un método sincronizado de
un objeto y espere ahí hasta que otro hilo le notifique explícitamente que debe salir de ahí.

El sistema multihilo de Java está construido en torno a la clase Thread, sus métodos y su interfaz de apoyo
Runnable. La clase Thread involucra la noción hilo de ejecución. Para crear un hilo, se debe transferir un
objeto Runnable (esto es, un objeto que instrumenta la interfaz Runnable al definir un método run()) al
constructor Thread para que defina su propio método run().

El método run() del Thread o el objeto Runnable especificados es el cuerpo del hilo. Empieza a ejecutarse
cuando se llama al método start() del objeto Thread. El hilo funciona hasta que regresa el método run() o
hasta que se llama al método stop() del objeto Thread. Los métodos estáticos de esta clase operan sobre el
hilo que funciona en ese momento. Pueden llamarse métodos de una instancia de un hilo para que operen
en un hilo diferente.

Método Descripción
void start() Inicia el funcionamiento del hilo.
voif stop() Detiene el hilo al activar un error Thread Death.
void suspend() Detiene temporalmente un hilo.
void resume() Reanuda un hilo suspendido.
void sleep(long milesimas) Hace que el hilo se detenga durante una cantidad de tiempo.
void yield() Hace que el hilo actual entregue el control a otros hilos de igual prioridad en
espera de ejecutarse.
void join() Espera que se extinga un hilo.
void interrupt() Activa un hilo en espera o inactivo.
boolean isAlive() Determina si un hilo está ejecutándose todavía.
int getPriority Obtiene la prioridad del hilo.
String getName Obtiene el nombre del hilo

Al comenzar la ejecución de un programa en Java, ya hay un hilo ejecutándose. Es el llamado hilo principal
del programa. Este es muy importante porque es el hilo a partir del cual se crean el resto de hilos y debe ser
el último que termine su ejecución.

Aunque el hilo principal se crea automáticamente cuando comienza el programa, se puede controlar a través
de un objeto Thread. Para hacer esto, es necesario obtener una referencia a él, llamando al método
currentThread().

El código que aparece a continuación permite simular el funcionamiento de un reloj de manecillas. Para ello
se realiza la gráfica del reloj compuesta de: circunferencia, números y manecillas. Esta gráfica se debe estar
actualizando constantemente para brindar el efecto del desplazamiento de las manecillas. Esto se logra
redibujando el applet en instantes de tiempo cortos, para lo cual se utiliza un hilo que se encargará de hacer
las pausas. Este hilo hará referencia al hilo principal.

import java.util.*;
import java.awt.*;
import java.applet.*;
import java.text.*;

public class Reloj extends Applet implements Runnable


16
Instituto Tecnológico Metropolitano

{
/* Un objeto Runnable es requerido para crear un hilo a través del método run()
el cual se convierte en el 'cuerpo' del hilo.
El hilo comienza a ejecutarse cuando se llama el método start() del objeto Thread
*/

Thread reloj;
int ultxs, ultys, ultxm, ultym, ultxh, ultyh; // Dimensiones últimas manecillas
Color colorManecilla,colorNumero; //Colores para los elementos del reloj

public void init()


{
ultxs = ultys = ultxm = ultym = ultxh = ultyh = 0;
colorManecilla=Color.blue;
colorNumero = Color.red;
}

public void paint(Graphics g)


{
int radio=50; // radio de la circuferencia del reloj
// Dimensiones actuales manecillas
int xh, yh, xm, ym, xs, ys, s = 0, m = 0, h = 0, xcentro, ycentro ;

// Obtiene la fecha del sistema


Date fecha = new Date();
// Obtiene el valor de los segundos aplicándole un formato de fecha sólo con
segundos
SimpleDateFormat formatofecha = new SimpleDateFormat("s",Locale.getDefault());
try
{s=Integer.parseInt(formatofecha.format(fecha));}
catch (NumberFormatException n)
{s=0;}
// Obtiene el valor de los minutos aplicándole un formato de fecha sólo con minutos
formatofecha.applyPattern("m");
try
{m=Integer.parseInt(formatofecha.format(fecha));}
catch (NumberFormatException n)
{m=0;}
// Obtiene el valor de las horas aplicándole un formato de fecha sólo con horas
formatofecha.applyPattern("h");
try
{h=Integer.parseInt(formatofecha.format(fecha));}
catch (NumberFormatException n)
{h=0;}
xcentro=radio+10;
ycentro=radio+10;
// Halla las coordenadas correspondientes a las líneas de las manecillas del reloj
xs = (int)(Math.cos(s * 3.14f/30 - 3.14f/2) * (radio-5) + xcentro);
ys = (int)(Math.sin(s * 3.14f/30 - 3.14f/2) * (radio-5) + ycentro);
xm = (int)(Math.cos(m * 3.14f/30 - 3.14f/2) * (radio-10) + xcentro);
ym = (int)(Math.sin(m * 3.14f/30 - 3.14f/2) * (radio-10) + ycentro);
xh = (int)(Math.cos((h*30 + m/2) * 3.14f/180 - 3.14f/2) * (radio-20) + xcentro);
yh = (int)(Math.sin((h*30 + m/2) * 3.14f/180 - 3.14f/2) * (radio-20) + ycentro);

// Dibuja el círculo y los números


17
Instituto Tecnológico Metropolitano

g.setColor(colorManecilla);
g.drawOval(xcentro-radio,ycentro-radio,2*radio,2*radio);
g.setColor(colorNumero);
g.drawString("9",xcentro-(radio-5),ycentro+3);
g.drawString("3",xcentro+(radio-10),ycentro+3);
g.drawString("12",xcentro-5,ycentro-(radio-13));
g.drawString("6",xcentro-3,ycentro+(radio-5));
// Borra las anteriores líneas si es necesario y dibuja las nuevas.
g.setColor(getBackground()); // Obtener color del fondo
// dibujar la anterior línea del segundero si es necesario
if (xs != ultxs || ys != ultys)
{
g.drawLine(xcentro, ycentro, ultxs, ultys);
}
// dibujar la anterior línea del minutero si es necesario
if (xm != ultxm || ym != ultym)
{
g.drawLine(xcentro, ycentro-1, ultxm, ultym);
g.drawLine(xcentro-1, ycentro, ultxm, ultym);
}
// dibujar la anterior línea del horario si es necesario
if (xh != ultxh || yh != ultyh)
{
g.drawLine(xcentro, ycentro-1, ultxh, ultyh);
g.drawLine(xcentro-1, ycentro, ultxh, ultyh);
}
// dibujar la nueva línea del segundero
g.setColor(colorManecilla);
g.drawLine(xcentro, ycentro, xs, ys);
// dibujar la nueva línea del minutero
g.drawLine(xcentro, ycentro-1, xm, ym);
g.drawLine(xcentro-1, ycentro, xm, ym);
// dibujar la nueva línea del horario
g.drawLine(xcentro, ycentro-1, xh, yh);
g.drawLine(xcentro-1, ycentro, xh, yh);
//Conservar las últimas coordenadas
ultxs=xs; ultys=ys;
ultxm=xm; ultym=ym;
ultxh=xh; ultyh=yh;
fecha=null;
}

public void start()


{
// Crea un hilo para la ejecución del reloj
reloj = new Thread(this);
reloj.start();
}

public void run() {


// Establece el actual hilo en ejecución
Thread hilo = Thread.currentThread();
while (reloj == hilo) {
// Realiza una pausa durante unas milésimas de segundo
try
18
Instituto Tecnológico Metropolitano

{Thread.currentThread().sleep(100);}
catch (InterruptedException e) {}
// Redibuja el applet
repaint();
}
}

En el navegador se vería así:

Animaciones
Ahora bien, para poder realizar una animación, es imprescindible hacer una buena gestión de las imágenes
cargadas simultáneamente. Para ello se tiene la clase MediaTracker la cual define un objeto que comprueba
el estado de un número arbitrario de imágenes en paralelo. Para utilizar MediaTracker hay que crear una
nueva instancia y usar su método addImage() para añadir una imagen al control del estado de la carga. Este
método tiene dos argumentos: el objeto Image y un número entero para identificar la imagen.

Cuando se utiliza MediaTracker, una vez que se ha llamado al método addImage() sobre una imagen, su
referencia en MediaTracker evita que el sistema la elimine a través del mecanismo de recogida de basura.
Una vez que se ha cargado la imagen original, ésta se corta en un número determinado de pequeñas
imágenes. Entonces se comienza la ejecución de un hilo que va mostrando las imágenes en el orden que se
considera conveniente. El hilo debe dormir un tiempo suficiente para mantener la velocidad en la que se
muestran las tramas.

import java.applet.*;
import java.awt.*;
import java.awt.image.*;

public class Animacion extends Applet implements Runnable{


Image imagen,fondo;
Image diapositiva[]=new Image[22];

public void init() {


try
{
fondo=getImage(getDocumentBase(),"FondoCasa.jpg");

19
Instituto Tecnológico Metropolitano

imagen=getImage(getDocumentBase(),"animacion.gif");
MediaTracker mt=new MediaTracker(this);
CropImageFilter cif;
FilteredImageSource fis;
mt=new MediaTracker(this);
for(int i=0;i<=21;i++)
{
cif=new CropImageFilter(i*45+1,1,44,95);
fis=new FilteredImageSource(imagen.getSource(),cif);
diapositiva[i]=createImage(fis);
mt.addImage(diapositiva[i],i);
}
mt.waitForAll();
}
catch (InterruptedException e) {}
}

public void update(Graphics g)


{
paint(g);
}

public void paint(Graphics g)


{
g.drawImage(fondo,0,0,null);
g.drawImage(diapositiva[numeroDiapositiva],x,55,null);
}

Thread t;
int numeroDiapositiva;
int x;
public void start()
{
t=new Thread(this);
t.start();
}

public void stop()


{
t.stop();
}

public void run()


{
numeroDiapositiva=0;
x=0;
for(numeroDiapositiva=0;numeroDiapositiva<=21;numeroDiapositiva++)
{
paint(getGraphics());
if(numeroDiapositiva<16)
{
x+=10;
try
{
Thread.sleep(200);
20
Instituto Tecnológico Metropolitano

}
catch (Exception e) {}
}
else
{
x+=3;
try
{
Thread.sleep(400);
}
catch (Exception e) {}
}
}
}
}

La ejecución del applet se puede ver así:

Donde se puede observar una especie de desplazamiento de la figura.

TEMA 3 Ejercicios de aplicación

1. Elaborar una animación en un applet que permita hacer una transición de una imagen a otra en varios
barridos. Cada barrido va mostrando más de la figura siguiente y menos de la segunda, como se
observa en la siguiente gráfica:

21

You might also like