Академический Документы
Профессиональный Документы
Культура Документы
Programación
Java
Guía del Estudiante
Mapa del Curso
Caracteristicas
Clases y Objetos avanzadas
Manejo de Errores
Excepciones
Applets
Introduccion
Java
Applets
Multiprogramación
Threads
Comunicaciones
Archivos
Streams I/O Networking
Modulo 1 – Lenguaje Básico de Programación.
Características de Java.
Las características principales que nos ofrece Java respecto a cualquier otro lenguaje de
programación, son:
Simplicidad
Java ofrece toda la funcionalidad de un lenguaje potente, pero sin las características menos
usadas y más confusas de éstos. Java elimina muchas de las características de otros lenguajes
como C++, para mantener reducidas las especificaciones del lenguaje y añadir características
muy útiles como el garbage collector (reciclador de memoria dinámica).
En Java ya no es necesario preocuparse de liberar memoria.
Reduce en un 50% los errores más comunes de programación con lenguajes como C y C++ al
eliminar muchas de las características de éstos, entre las que destacan:
aritmética de punteros
no existen referencias
registros (struct)
definición de tipos (typedef)
macros (#define)
necesidad de liberar memoria (free)
Orientado a Objetos
Java implementa la tecnología básica de C++ con algunas mejoras Java trabaja con sus datos
como objetos y con interfaces a esos objetos. Soporta las tres características propias del
paradigma de la orientación a objetos: encapsulación, herencia y polimorfismo.
Distribuido
Java se ha construido con extensas capacidades de interconexión TCP/IP. Existen librerías de
rutinas para acceder e interactuar con protocolos como http y ftp. Esto permite a los
programadores acceder a la información a través de la red con tanta facilidad como a los
ficheros locales.
Independiente de la Plataforma
Para establecer Java como parte integral de la red, el compilador Java compila su código a un
fichero objeto de formato independiente de la arquitectura de la máquina en que se ejecutará.
Cualquier máquina que tenga el sistema de ejecución (run-time) puede ejecutar ese código
objeto, sin importar en modo alguno la máquina en que ha sido generado.
Actualmente existen sistemas run-time para Solaris 2.x, SunOs 4.1.x, Windows 95, Windows
NT, Linux, Irix, Aix, Mac, Apple y probablemente haya grupos de desarrollo trabajando en el
porting a otras plataformas.
El código fuente Java se "compila" a un código de bytes de alto nivel independiente de la
máquina. Este código (byte-codes) está diseñado para ejecutarse en una máquina hipotética
que es implementada por un sistema run-time, que sí es dependiente de la máquina.
En una representación en que tuviésemos que indicar todos los elementos que forman parte de
la arquitectura de Java sobre una plataforma genérica, obtendríamos una figura como la
siguiente:
En ella podemos ver que lo verdaderamente dependiente del sistema es la Máquina Virtual
Java (JVM) y las librerías fundamentales, que también nos permitirían acceder directamente al
hardware de la máquina. Además, habrá APIs de Java que también entren en contacto directo
con el hardware y serán dependientes de la máquina.
Robusto
Java realiza verificaciones en busca de problemas tanto en tiempo de compilación como en
tiempo de ejecución. La comprobación de tipos en Java ayuda a detectar errores, lo antes
posible, en el ciclo de desarrollo. Java obliga a la declaración explícita de métodos, reduciendo
así las posibilidades de error. Maneja la memoria para eliminar las preocupaciones por parte
del programador de la liberación o corrupción de memoria. También implementa los arrays
auténticos, en vez de listas enlazadas de punteros, con comprobación de límites, para evitar la
posibilidad de sobreescribir o corromper memoria resultado de punteros que señalan a zonas
equivocadas. Estas características reducen drásticamente el tiempo de desarrollo de
aplicaciones en Java.
Además, para asegurar el funcionamiento de la aplicación, realiza una verificación de los byte-
codes, que son el resultado de la compilación de un programa Java. Es un código de máquina
virtual que es interpretado por el intérprete Java. No es el código máquina directamente
entendible por el hardware, pero ya ha pasado todas las fases del compilador: análisis de
instrucciones, orden de operadores, etc., y ya tiene generada la pila de ejecución de órdenes.
Java proporciona, pues:
Comprobación de punteros
Comprobación de límites de arrays
Excepciones
Verificación de byte-codes
Multiprogramación
Al ser multithreaded (multihilvanado, en mala traducción), Java permite muchas actividades
simultáneas en un programa. Los threads (a veces llamados, procesos ligeros), son
básicamente pequeños procesos o piezas independientes de un gran proceso. Al estar los
threads construidos en el lenguaje, son más fáciles de usar y más robustos que sus homólogos
en C o C++.
El beneficio de ser multithreaded consiste en un mejor rendimiento interactivo y mejor
comportamiento en tiempo real. Aunque el comportamiento en tiempo real está limitado a las
capacidades del sistema operativo subyacente (Unix, Windows, etc.), aún supera a los
entornos de flujo único de programa (single-threaded) tanto en facilidad de desarrollo como en
rendimiento.
Carácter simple: se utiliza para caracteres individuales como letras, números, puntuación, y
otros símbolos.
char
Si se declara a un tipo de variable “Object” significa que puede contener cualquier objeto.
Declaración de variables
Ejemplo
int total;
String ciudad, calle, provincia = “Buenos Aires” ;
boolean activo = true;
Nomenclatura de Variables
Los nombres de variables deben comenzar con una letra, un carácter de subrayado ( _ ) o un
signo pesos ($). No pueden empezar con un número. Después del primer carácter pueden
incluir cualquier combinación.
Java es sensible a minúsculas y mayúsculas, esto permite tener una variable llamada X y otra
llamada x.
Comentarios
Hay tres formas de aplicar comentarios en la definición de una clase, que son:
Para comentarios de una línea
//. Todo lo que se encuentre a partir de ahí hasta el final de la línea es considerado comentario.
Para comentarios de mas de una línea: Se utilizan los símbolos /* - */ con los que se encierra el
comentario.
Para los comentarios que son autodocumentables se utilizan los símbolos /** - */.
Ejemplos
// Comentario de una línea
/* Comentario de
mas de
una línea.
*/
/**
Documentación de un método o atributo....
*/
Literales
Un literal es cualquier número, texto o información que representa directamente un valor.
Literales numéricos
Para representar un literal de tipo long anteponga una L al número (long total = 10L;)
Para representar un literal negativo anteponga un – (long total = -10L;)
Para representar un literal con notación octal anteponga un 0 al número (int total = 010;)
Para representar un literal con notación hexadecimal anteponga un 0x al número (int total
= 0xFF;)
Para representar un literal de punto flotante se utiliza un punto (.) para el punto decimal
(double numero = 2.50;), todas estas literales se consideran double.
Para especificar un literal de punto flotante como float anteponga la letra f (f o F) (float
numero = 3.1416F;)
Para representar notación exponencial utilice la letra e (double x = 12e22;)
Literales booleanos
Los valores true y false son literales, y son los únicos valores posibles para las variables
booleanas
Literales de caracteres
Es un carácter sencillo entre comillas sencillas como ‘a’, ‘#’ y ‘3’.
Literales de cadena
Es una cadena de caracteres entre comillas dobles como “Esto es una cadena”.
Como las cadenas en Java son objetos reales, contienen métodos para combinar cadenas,
modificarlas y determinar si dos cadenas poseen el mismo valor.
Java almacena el valor de una cadena como un objeto String, por lo tanto solo se debe
crear un nuevo objeto explícitamente, por lo que son tan fáciles de utilizar como los tipos
de dato básicos.
Operadores Aritméticos
Suma +
Resta -
Multiplicación *
División /
Módulo %
El lado derecho de una expresión siempre se calcula antes de que se dé la asignación. Por
esta razón la siguiente expresión es posible:
int x = 5;
x = x + 2;
Donde x ahora es 7.
Incremento y Decremento
Comparaciones
Son operaciones para hacer comparaciones entre las variables, variables y literales, u
otros tipos de información. Devuelven siempre un valor booleano.
Igual ==
Distinto ¡=
Menor que <
Mayor que >
Menor que o igual a <=
Mayor que o igual a >=
Operadores Lógicos
Las expresiones que producen valores booleanos, como las operaciones de comparación, se
pueden combinar para formar expresiones más complejas. Esto se maneja a través de
operadores lógicos, los cuales para las combinaciones lógicas:
AND: & o && (con && el lado derecho de la expresión nunca se evalúa), devuelve true solo si
las dos expresiones son verdaderas.
OR: | o || (con || el lado derecho de las expresión nunca se evalúa), devuelve true si alguna de
las expresiones son verdaderas.
XOR: ^, devuelve true solo si las dos expresiones booleanas que combina tienen valores
opuestos. Si ambas son true o false, devuelve false.
NOT. !, invierte el valor de una expresión booleana de la misma manera en que un símbolo
menos invierte el signo positivo en un número.
Precedencia de operadores
Es el orden de evaluación que posee Java cuando hay mas de un operador en una expresión.
El orden del primero al último es:
Operaciones de incremento y decremento
Operaciones aritméticas
Comparaciones
Operaciones lógicas
Expresiones de asignación
Para cambiar el orden en que se evalúan las operaciones se deben colocar paréntesis en las
expresiones que se quieren evaluar primero.
Aritmética de cadenas
Controles de Flujo
Instrucciones if – else
Estructura:
if (expresión boolean) {
//Bloque de código para expresiones verdaderas;
}
else {
// Bloque de código para expresiones falsas;
}
Siempre debe tomar una expresión booleana. El else es opcional y puede ser omitido si no
existen acciones a tomar en caso de no cumplirse la condición
Instrucción switch
Estructura:
Switch (expresión 1) {
Case constante2:
Bloque de código;
Break;
Case constante3:
Bloque de código;
Break;
Default:
Bloque de código;
Break;
}
La expresión 1 debe ser compatible con un tipo de dato entero. Los tipos de punto flotante, long
o clases no están permitidas.
Si no hubiera un break como última instrucción dentro del código del case, el programa entrara
al código del próximo case sin evaluar la expresión.
Estructuras de Iteración
Las estructuras de lazo permiten repetir la ejecución del bloque de código que contengan.
En java existen tres tipos: for, while y do.
For y while evalúan la condición de lazo antes de ejecutar el bucle.
Do evalúa después de ejecutar el bucle. En este caso el bucle se ejecuta al menos una vez.
Instrucción for
Estructura:
For (expresión inicial; expresión booleana; alter_expresion3) {
Bloque de código;
}
Instrucción while
Estructura:
While (boolean) {
Bloque de código;
}
Hay que asegurarse de que la variable utilizada en el while esté definida y sea verdadera antes
de la ejecución del mismo.
Instrucción do
Estructura:
Do {
Bloque de código;
} while (expresión booleana);
Hay que asegurarse de que la variable utilizada en el while esté definida y sea verdadera antes
de la ejecución del mismo.
Break [label]; // Es utilizada para salir de las instrucciones del switch, de las estructureas de
lazo y de los bloques con etiqueta de forma prematura.
Continue [label]; // Es usada para saltear el código y llegar hasta el final dentro de las
estructuras de lazo.
Label : instrucción; // Identifica con un nombre una instrucción válida a la que se le tranferirá el
control.
Ejemplo:
Do {
Bloque de código;
If (condición es true)
Break;
}
while (expresión booleana);
Do {
Bloque de código;
If (condición es true)
continue;
}
while (expresión booleana);
loop: //label
Do {
instrucción;
Do {
instrucción;
instrucción;
If (condición es true)
Break loop;
}
while (expresión booleana);
instrucción;
}
while (expresión booleana);
Palabras reservadas
Las siguientes son las palabras reservadas que están definidas en Java y que no se pueden
utilizar como indentificadores:
Declaración:
Se utilizan para agrupar objetos del mismo tipo y nombrarlos con un nombre común.
Ejemplo:
char s[];
Point p[]; //Point es una clase
La declaración de un array crea una referencia que puede ser utilizada para referirlo, o sea que
la memoria del array solo se utiliza cuando éste es “creado” por medio de la instrucción new.
Creación:
Los arrays se crean como el resto de los objetos, utilizando la instrucción new.
Ejemplo:
s = new char[20];
p = new Point [100];
La instrucción p[0] = new Point[]; Indica que se está creando el elemento uno del array,
el sub cero ([0]), es el indice del array.
Inicialización:
Cuando se crea un array se debe inicializar cada uno de sus elementos, no deben
utilizarse antes de este paso.
Ejemplo:
String nombres[] = {
“Juan”,
“Maria”,
“Laura”
};
Arreglos Multidimensionales:
Ejemplo:
Int dosDim [][] = new int [4][];
dosDim[0] = new int [5];
dosDim[1] = new int [5];
En el primer caso se crea un array de 4 elementos, cada elemento es una referencia nula a un
elemento de tipo array de enteros, y será inicializado separadamente. Por esta razón es posible
crear arrays de arrays no rectangulares.
Ejemplo:
int dosDim [][] = new int [4][];
dosDim[0] = new int [5];
dosDim[1] = new int [2];
dosDim[2] = new int [4];
dosDim[3] = new int [8];
El atributo length es utilizado para determinar el largo del array, y es muy útil a la hora de iterar
dentro del mismo.
Ejemplo:
int lista [] = new int [10];
for (int i = 0; i< lista.length; i++) {
System.out.println (lista[i]);
}
Redimensionamiento de arrays
Al crearse un array éste no puede ser redimensionado, pero sí se puede utilizar la misma
variable para referenciar un nuevo array.
Ejemplo:
int miArray[] = new int [6];
miArray = new int [10];
Copia de arrays
Java provee un método especial de la clase System, arraycopy(), para copiar arreglos.
Ejemplo:
System.arraycopy(miArray, 0, otroArray, 0, miArray.length);
Modulo 2 - Programación Orientada a Objetos.
Conceptos Principales
Encapsulacion.
Herencia.
Polimorfismo.
Definición de Clases:
[package nombrepaquete]
[import nombreclase]
[definición de atributos]
[definición de metodos]
}
Ejemplos:
Definición de una clase:
public class Empleado {
String Nombre;
String Apellido;
String Gerencia;
}
Las variables Nombre, Apellido y Gerencia son los atributos de la clase empleado.
Ejemplo
Este método llamado addDays recibe como parámetro la variable de tipo entera (int) days y no
devuelve valores (void).
This
La palabra reservada This es utilizada para hacer referencia al objeto actual.
Ejemplo
public class MyDate{
int day, month, year;
public int mostrarDia(){
return this.day;
}
}
Encapsulamiento
Para encapsular los atributos de una clase se utiliza el modificador private antepuesto al tipo de
dato del mismo. Y se construyen dos métodos (para los casos en que aplique) con el
modificador public, que alteren y muestren el contenido del mismo.
Ejemplo
Sobrecarga de Métodos
En ciertas circunstancias se necesitan desarrollar varios métodos en una misma clase que
básicamente hacen la misma tarea, pero con diferentes argumentos. Podemos considerar un
método simple que solo imprima una línea de texto. Este método lo podemos llamar
imprimirLinea().
Ahora supongamos que necesitamos diferentes métodos de impresión para cada uno de los
siguientes tipos de datos: int, float,String, ya que es razonable que cada uno se imprima de
manera distinta. Se podrían crear tres métodos, uno para cada tipo de datos que se podrían
llamar imprimirLineaInt(); imprimirLineaFloat();imprimirLineaString() respectivamente aunque
esto es tedioso.
Java permite reutilizar los nombres de los métodos solo si, difieren en los argumentos que
recibe o el tipo de dato que retorna, en caso contrario la clase no compilara.
Ejemplo.
Constructores
Los constructores son métodos especiales que se ejecutan por única vez en el momento de
creación de una instancia de una clase (objeto), con el fin de inicializar el contenido de sus
atributos.
Los constructores son identificados por las siguientes características:
a) El nombre del método se corresponde exactamente con el nombre de la clase.
b) El método no devuelve ningún tipo de dato.
Ejemplos
1) Un solo Constructor:
// Declaración de atributos...
public Xyz(){
// Inicialización del objeto
}
2) Sobrecarga de Constructores:
// Declaración de atributos
private String name;
private int salary;
Herencia
En Java solo se puede heredar de una sola clase (herencia simple), esto permite definir una
clase en base a otra ya creada. Esto se define mediante la palabra extends, y se heredan
todos los métodos y los atributos de la superclase. No son heredables los constructores.
Si no se definiera en forma explicita la herencia desde una clase se heredara de la clase Object
por defecto.
Ejemplo.
Super
La palabra super se utiliza para referirse a métodos y/o atributos de la superclase.
Generalmente se la utilizar para reescribir métodos heredados sin reemplazar su
funcionamiento sino agregándole la nueva funcionalidad.
Ejemplo
La salida será:
Interfaces
Los métodos abstractos son útiles cuando se quiere que cada implementación de la clase
parezca y funcione igual, pero necesita que se cree una nueva clase para utilizar los métodos
abstractos.
Los interfaces proporcionan un mecanismo para abstraer los métodos a un nivel superior.
Un interface contiene una colección de métodos que se implementan en otro lugar. Los
métodos de una clase son public, static y final.
La principal diferencia entre interface y abstract es que un interface proporciona un mecanismo
de encapsulación de los protocolos de los métodos sin forzar al usuario a utilizar la herencia.
Por ejemplo:
public interface VideoClip {
// comienza la reproducción del video
void play();
// reproduce el clip en un bucle
void bucle();
// detiene la reproducción
void stop();
}
Las clases que quieran utilizar el interface VideoClip utilizarán la palabra implements y
proporcionarán el código necesario para implementar los métodos que se han definido para el
interface:
class MiClase implements VideoClip {
void play() {
<código>
}
void bucle() {
<código>
}
void stop() {
<código>
}
Al utilizar implements para el interface es como si se hiciese una acción de copiar-y-pegar del
código del interface, con lo cual no se hereda nada, solamente se pueden usar los métodos.
La ventaja principal del uso de interfaces es que una clase interface puede ser implementada
por cualquier número de clases, permitiendo a cada clase compartir el interfaz de programación
sin tener que ser consciente de la implementación que hagan las otras clases que implementen
el interface.
class MiOtraClase implements VideoClip {
void play() {
<código nuevo>
}
void bucle() {
<código nuevo>
}
void stop() {
<código nuevo>
}
Agrupando Clases
Empaquetando
Java provee un mecanismo de agrupación de clases relacionadas en “Paquetes”.
Se puede indicar en la definición de la clase a que paquete o grupo pertenece la misma
utilizando la instrucción package.
Solo una declaración de paquete se permite por clase y por archivo de código fuente.
Los nombres de paquetes deben ser jerárquicos y las jerarquías estar separadas por puntos.
La definición de paquete debe estar al comienzo del archivo de código fuente.
Ejemplo
package telecom.sistemas;
public class Empleado {
//Contenido de la clase.
}
Importación
Para poder utilizar el contenido de los paquetes en nuestras clases debemos decirle al
compilador java donde encontrarlas. De hecho el nombre del paquete forma parte del nombre
de la clase, y uno se puede referir a la clase como telecom.sistemas.Empleado o simplemente
como la clase Empleado.
Puedo agregar tantas importaciones como necesite.
Ejemplos
import java.applet.Applet;
import java.awt.*;
//Metodos Constructores.
public Persona(String nom, String ape, int eda){
this.setNombre(nom);
this.setApellido(ape);
this.setEdad(eda);
}
public Persona(){
this("Sin Nombre");
}
//Metodos de la clase.
public String getDetalles(){
return "Nombre: " + this.getNombre() + " - Apellido: " +
this.getApellido() + " - Edad:" + this.getEdad();
}
//Metodos Constructores
public Empleado(String ger, int sue){
this.setGerencia(ger);
this.setSueldo(sue);
}
public Empleado(String ger){
this(ger,0);
}
public Empleado(){
this("Sin Gerencia");
}
//Metodos de la clase.
public String getDetalles(){
return super.getDetalles() + " - Sueldo: " + this.getSueldo() +
" - Gerencia: " + this.getGerencia();
}
import Persona;
import Empleado;
public class EjemploHerencia {
emp.setNombre("Gustavo");
emp.setApellido("Garcia");
emp.setEdad(27);
emp.setGerencia("CRM Masivo");
emp.setSueldo(1000);
}
}
Modulo 3 – Manejo de Errores
Excepciones
Una clase de tipo “Excepcion” define las condiciones de error leves que pueda contener el
programa. También es posible escribir código que maneje excepciones y haga continuar la
ejecución del programa.
Cualquier condición anormal que pueda trabar la normal ejecución del programa es un error o
una excepción. Estos pueden ocurrir cuando:
Una clase de tipo “Error” define condiciones de error serias que no deben ser recuperadas y
deben permitir que el programa termine.
Java permite que los errores encontrados sean recuperados y utilizados en el programa, de
esta forma se pueden manejar.
Por ejemplo:
while (i < 4) {
System.out.println (greetings [i]);
i++;
}
}
}
Al ejecutarse el ciclo por cuarta vez el programa terminará con el siguiente mensaje de error:
java.lang.ArrayIndexOutOfBoundsException: 3 at HolaMundo.main (HolaMundo.java:12)
Manejo de Excepciones
Para manejar una excepción en particular se debe colocar todo el código que la puede producir
dentro de la instrucción try, y crear una lista de posibles excepciones para cada tipo de error
con catch.
try {
// Código del programa que puede ejecutar alguna excepción.
}
catch (MiTipoDeExcepcion e ) {
//Código a ejecutarse si MiTipoDeExcepcion es disparada.
}
catch (Exception e) {
// Código a ejecutarse si una excpeción general es disparada.
}
Si hay clases que son llamadas por otras, y generan una excepción, ésta es buscada primero
en la clase que la genera, si no la encuentra, busca en la clase que la llamó, y así
sucesivamente hasta encontrarla, si no la encuentra genera un mensaje de error y termina el
programa.
Instrucción finally
try {
ComenzarRiego () ;
AguaRiego ();
}
finally {
TerminarRiego();
}
Categorías de Excepciones
La clase java.lang.Throwable es la superclase que contiene todos los objetos que pueden
utilizar mecanismos de manejo de errores.
Las tres clases más importantes son:
Error: Indica un problema severo difícil de recuperar, por ejemplo cuando el programa corre
fuera de memoria.
Exception: Otras excepciones indican dificultades en tiempo de ejecución que usualmente son
causadas por efectos del entorno y pueden ser manejadas. Por ejemplo archivos no
encontrados o URL invalida.
Excepciones Comunes:
Java provee excepciones predefinidas como:
Jerarquía de Excepciones
OutOfMemory
Error
VirtualMachine
Error
StackOverflow
Error
Error
AWTError
Arithmetic
Excpetion
Throwable
NullPointer
RuntimeExcepti
Exception
on
IndexOu
tOfBound
Exception
EOFException
IOException
FileNotFound
Exception
La excepción Throwable no debe ser usada, en su defecto, se deben utilizar las subclases
antes descriptas.
Ejemplo completo:
Applet
Un applet es "una pequeña aplicación accesible en un servidor, que se transporta por la red, se
instala automáticamente y se ejecuta in situ como parte de un documento web". Es una
aplicación pretendidamente corta basada en un formato gráfico sin representación
independiente: es decir, se trata de un elemento a embeber en otras aplicaciones; es un
componente en su sentido estricto.
El método init() se llama cada vez que el browser carga por primera vez la clase. Si el applet
llamado no lo sobrecarga, init() no hace nada. Fundamentalmente en este método se debe fijar
el tamaño del applet, aunque en el caso de Netscape el tamaño que vale es el que se indique
en la línea del fichero html que cargue el applet. También se deben realizar en este método las
cargas de imágenes y sonidos necesarios para la ejecución del applet. Y, por supuesto, la
asignación de valores a las variables globales a la clase que se utilicen. En el caso de los
applet, este método únicamente es llamado por el sistema al cargar el applet.
El método start() es la llamada para arrancar el applet cada vez que es visitado. La clase
Applet no hace nada en este método. Las clases derivadas deben sobrecargarlo para
comenzar la animación, el sonido, etc. Esta función es llamada automáticamente cada vez que
la zona de visualización en que está ubicado el applet se expone a la visión, a fin de optimizar
en uso de los recursos del sistema y no ejecutar algo que no puede ser apreciado. Esto es,
imaginemos que cargamos un applet en un navegador minimizado; el sistema llamará al
método init(), pero no a start(), que sí será llamado cuando restauremos el navegador a un
tamaño que permita ver el applet. Naturalmente, start() se puede ejecutar varias veces: la
primera tras init() y las siguientes (porque init() se ejecuta solamente una vez) tras haber
aplicado el método stop().
El método stop() es la llamada para detener la ejecución del applet. Se llama cuando el applet
desaparece de la pantalla. La clase Applet tampoco hace nada en este método, que debería
ser sobrecargado por las clases derivadas para detener la animación, el sonido, etc. Esta
función es llamada cuando el navegador no incluye en su campo de visión al applet; por
ejemplo, cuando abandona la página en que está insertado, de forma que el programador
puede paralizar las funciones que no resulten necesarias respecto de un applet no visible, y
luego recuperar su actividad mediante el método start().
El método paint ( Graphics g ) es llamado cada vez que el área de dibujo del applet necesita
ser refrescada. La clase Applet simplemente dibuja un rectángulo gris en el área, es la clase
derivada, obviamente, la que debería sobrecargar este método para representar algo
inteligente en la pantalla. Cada vez que la zona del applet es cubierta por otra ventana, se
desplaza el applet fuera de la visión o el applet cambia de posición debido a un
redimensionamiento del navegador, el sistema llama automáticamente a este método, pasando
como argumento un objeto de tipo Graphics que delimita la zona a ser pintada; en realidad se
pasa una referencia al contexto gráfico en uso, y que representa la ventana del applet en la
página web.
Ejemplo
import java.awt.Graphics;
import java.applet.Applet;
Esta marca html llama al applet HolaMundo.class y establece su ancho y alto inicial.
Los atributos obligatorios que acompañan a la etiqueta <APPLET> son:
Code: Indica el fichero de clase ejecutable, que tiene la extensión .class.
Width: Indica la anchura inicial que el navegador debe reservar para el applet en pixels.
Height: Indica la altura inicial en pixels.
Ejemplo completo
import java.awt.*;
import java.applet.Applet;
//Metodos Constructores.
public void init(){
f = new Font("Arial",Font.BOLD,36);
}
Holamundo.html
<HTML>
<HEAD>
<TITLE>Hola Mundo version Applet</TITLE>
</HEAD>
<BODY>
<APPLET>
<APPLET CODE="HolaMundo.class" WIDTH=600 HEIGHT=100>
</APPLET>
</BODY>
</HTML>
Modulo 5 – Desarrollo de Aplicaciones Graficas
Introducción al AWT
AWT es el acrónimo de Abstract Window Toolkit.Se trata de una biblioteca de clases Java para
el desarrollo de Interfaces de Usuario Gráficas.
La estructura de la versión actual del AWT podemos resumirla en los puntos que exponemos a
continuación:
Los Contenedores contienen Componentes, que son los controles básicos
No se usan posiciones fijas de los Componentes, sino que están situados a través de una
disposición controlada (layouts)
El común denominador de más bajo nivel se acerca al teclado, ratón y manejo de eventos
Alto nivel de abstracción respecto al entorno de ventanas en que se ejecute la aplicación (no
hay áreas cliente, ni llamadas a X, ni hWnds, etc.)
La arquitectura de la aplicación es dependiente del entorno de ventanas, en vez de tener un
tamaño fijo
Componentes y Contenedores
Una interface gráfica está construida en base a elementos gráficos básicos, los componentes.
Típicos ejemplos de estos Componentes son los botones, barras de desplazamiento, etiquetas,
listas, cajas de selección o campos de texto. Los Componentes permiten al usuario interactuar
con la aplicación y proporcionar información desde el programa al usuario sobre el estado del
programa. En el AWT, todos los Componentes de la interface de usuario son instancias de la
clase Component o uno de sus subtipos.
Los Componentes no se encuentran aislados, sino agrupados dentro de Contenedores. Los
Contenedores contienen y organizan la situación de los Componentes; además, los
Contenedores son en sí mismos Componentes y como tales pueden ser situados dentro de
otros Contenedores. También contienen el código necesario para el control de eventos,
cambiar la forma del cursor o modificar el icono de la aplicación. En el AWT, todos los
Contenedores son instancias de la clase Container o uno de sus subtipos.
Los Componentes deben circunscribirse dentro del Contenedor que los contiene. Esto hace
que el anidamiento de Componentes (incluyendo Contenedores) en Contenedores crean
árboles de elementos, comenzando con un Contenedor en la raiz del árbol y expandiéndolo en
sus ramas. A continuación presentamos el árbol que representa la interface que corresponde
con la aplicación gráfica generada anteriormente.
PANELES
La clase Panel es el más simple de los Contenedores de Componentes gráficos. En realidad,
se trataba de crear una clase no-abstracta (Container sí lo es) que sirviera de base a los applet
y a otras pequeñas aplicaciones. La clase Panel consta de dos métodos propios: el constructor,
cuyo fin es crear un nuevo Panel con un LayoutManager de tipo FlowLayout (el de defecto), y el
método addNotify() que, sobrecargando la función del mismo nombre en la clase Container,
llama al método createPanel() del Toolkit adecuado, creando así un PanelPeer. El AWT
enviará así al Panel (y por tanto al applet) todos los eventos que sobre él ocurran. Esto que
puede parecer un poco rebuscado, obedece al esquema arquitectónico del AWT; se trata del
bien conocido esquema de separación interface/implementación que establece por un lado una
clase de interface y por otro distintas clases de implementación para cada una de las
plataformas elegidas.
El uso de Paneles permite que las aplicaciones puedan utilizar múltiples layouts, es decir, que
la disposición de los componentes sobre la ventana de visualización pueda modificarse con
mucha flexibilidad. Permite que cada Contenedor pueda tener su propio esquema de fuentes
de caracteres, color de fondo, zona de diálogo, etc.
herramientas y a la barra de estado con new; al contrario que en C++, en Java todos los
objetos deben ser creados con el operador new:
add( "North",tb = new ToolBar() );
add( "South",sb = new StatusBar() );
También vamos a incorporar un nuevo evento a nuestro controlador, para que maneje los
eventos de tipo ACTION_EVENT que le llegarán cuando se pulsen los botones de la barra de
herramientas o se realice alguna selección, etc.
case Event.ACTION_EVENT:
{
be.verEstado( evt.arg.toString() );
return true;
}
Cuando la aplicación reciba este tipo de evento, alterará el contenido de la barra de estado
para mostrar la información de la selección realizada o el botón pulsado.
Flujo de Programas
Programas de flujo único
Un programa de flujo único o mono-hilvanado (single-thread) utiliza un único flujo de control
(thread) para controlar su ejecución. Muchos programas no necesitan la potencia o utilidad de
múltiples flujos de control. Sin necesidad de especificar explícitamente que se quiere un único
flujo de control, muchos de los applets y aplicaciones son de flujo único.
import MyThread;
public class TesterMyThread {
th1.start();
th2.start();
th3.start();
}
catch (Exception e) {
System.out.println("Error en Main");
}
}
}
CREACION Y CONTROL DE THREADS
Creación de un Thread
Hay dos modos de conseguir threads en Java. Una es implementando la interface Runnable, la
otra es extender la clase Thread.
La implementación de la interface Runnable es la forma habitual de crear threads. Las
interfaces proporcionan al programador una forma de agrupar el trabajo de infraestructura de
una clase. Se utilizan para diseñar los requerimientos comunes al conjunto de clases a
implementar. La interface define el trabajo y la clase, o clases, que implementan la interface
realizan ese trabajo. Los diferentes grupos de clases que implementen la interface tendrán que
seguir las mismas reglas de funcionamiento.
Hay una cuantas diferencias entre interface y clase. Primero, una interface solamente puede
contener métodos abstractos y/o variables estáticas y finales (constantes). Las clases, por otro
lado, pueden implementar métodos y contener variables que no sean constantes. Segundo,
una interface no puede implementar cualquier método. Una clase que implemente una interface
debe implementar todos los métodos definidos en esa interface. Una interface tiene la
posibilidad de poder extenderse de otras interfaces y, al contrario que las clases, puede
extenderse de múltiples interfaces. Además, una interface no puede ser instanciada con el
operador new; por ejemplo, la siguiente sentencia no está permitida:
Runnable a = new Runnable(); // No se permite
El primer método de crear un thread es simplemente extender la clase Thread:
class MiThread extends Thread {
public void run() {
...
}
El ejemplo anterior crea una nueva clase MiThread que extiende la clase Thread y sobrecarga
el método Thread.run() por su propia implementación. El método run() es donde se realizará
todo el trabajo de la clase. Extendiendo la clase Thread, se pueden heredar los métodos y
variables de la clase padre. En este caso, solamente se puede extender o derivar una vez de la
clase padre. Esta limitación de Java puede ser superada a través de la implementación de
Runnable:
public class MiThread implements Runnable {
Thread t;
public void run() {
// Ejecución del thread una vez creado
}
}
En este caso necesitamos crear una instancia de Thread antes de que el sistema pueda
ejecutar el proceso como un thread. Además, el método abstracto run() está definido en la
interface Runnable tiene que ser implementado. La única diferencia entre los dos métodos es
que este último es mucho más flexible. En el ejemplo anterior, todavía tenemos oportunidad de
extender la clase MiThread, si fuese necesario. La mayoría de las clases creadas que
necesiten ejecutarse como un thread , implementarán la interface Runnable, ya que
probablemente extenderán alguna de su funcionalidad a otras clases.
No pensar que la interface Runnable está haciendo alguna cosa cuando la tarea se está
ejecutando. Solamente contiene métodos abstractos, con lo cual es una clase para dar idea
sobre el diseño de la clase Thread. De hecho, si vemos los fuentes de Java, podremos
comprobar que solamente contiene un método abstracto:
package java.lang;
public interface Runnable {
public abstract void run() ;
}
Y esto es todo lo que hay sobre la interface Runnable. Como se ve, una interface sólo
proporciona un diseño para las clases que vayan a ser implementadas. En el caso de
Runnable, fuerza a la definición del método run(), por lo tanto, la mayor parte del trabajo se
hace en la clase Thread. Un vistazo un poco más profundo a la definición de la clase Thread
nos da idea de lo que realmente está pasando:
public class Thread implements Runnable {
...
public void run() {
if( tarea != null )
tarea.run() ;
}
}
...
}
De este trocito de código se desprende que la clase Thread también implemente la interface
Runnable. tarea.run() se asegura de que la clase con que trabaja (la clase que va a ejecutarse
como un thread) no sea nula y ejecuta el método run() de esa clase. Cuando esto suceda, el
método run() de la clase hará que corra como un thread.
Arranque de un Thread
Las aplicaciones ejecutan main() tras arrancar. Esta es la razón de que main() sea el lugar
natural para crear y arrancar otros threads. La línea de código:
t1 = new TestTh( "Thread 1",(int)(Math.random()*2000) );
crea un nuevo thread. Los dos argumentos pasados representan el nombre del thread y el
tiempo que queremos que espere antes de imprimir el mensaje.
Al tener control directo sobre los threads, tenemos que arrancarlos explícitamente. En nuestro
ejemplo con:
t1.start();
start(), en realidad es un método oculto en el thread que llama al método run().
Manipulación de un Thread
Si todo fue bien en la creación del thread, t1 debería contener un thread válido, que
controlaremos en el método run().
Una vez dentro de run(), podemos comenzar las sentencias de ejecución como en otros
programas. run() sirve como rutina main() para los threads; cuando run() termina, también lo
hace el thread. Todo lo que queramos que haga el thread ha de estar dentro de run(), por eso
cuando decimos que un método es Runnable, nos obliga a escribir un método run().
En este ejemplo, intentamos inmediatamente esperar durante una cantidad de tiempo aleatoria
(pasada a través del constructor):
sleep( retardo );
El método sleep() simplemente le dice al thread que duerma durante los milisegundos
especificados. Se debería utilizar sleep() cuando se pretenda retrasar la ejecución del thread.
sleep() no consume recursos del sistema mientras el thread duerme. De esta forma otros
threads pueden seguir funcionando. Una vez hecho el retardo, se imprime el mensaje "Hola
Mundo!" con el nombre del thread y el retardo.
Suspensión de un Thread
Puede resultar útil suspender la ejecución de un thread sin marcar un límite de tiempo. Si, por
ejemplo, está construyendo un applet con un thread de animación, querrá permitir al usuario la
opción de detener la animación hasta que quiera continuar. No se trata de terminar la
animación, sino desactivarla. Para este tipo de control de thread se puede utilizar el método
suspend().
t1.suspend();
Este método no detiene la ejecución permanentemente. El thread es suspendido
indefinidamente y para volver a activarlo de nuevo necesitamos realizar una invocación al
método resume():
t1.resume();
Parada de un Thread
El último elemento de control que se necesita sobre threads es el método stop(). Se utiliza para
terminar la ejecución de un thread:
t1.stop();
Esta llamada no destruye el thread, sino que detiene su ejecución. La ejecución no se puede
reanudar ya con t1.start(). Cuando se desasignen las variables que se usan en el thread, el
objeto thread (creado con new) quedará marcado para eliminarlo y el garbage collector se
encargará de liberar la memoria que utilizaba.
En nuestro ejemplo, no necesitamos detener explícitamente el thread. Simplemente se le deja
terminar. Los programas más complejos necesitarán un control sobre cada uno de los threads
que lancen, el método stop() puede utilizarse en esas situaciones.
Si se necesita, se puede comprobar si un thread está vivo o no; considerando vivo un thread
que ha comenzado y no ha sido detenido.
t1.isAlive();
Este método devolverá true en caso de que el thread t1 esté vivo, es decir, ya se haya llamado
a su método run() y no haya sido parado con un stop() ni haya terminado el método run() en su
ejecución.
...
return( true );
}
...
Para controlar el estado del applet, hemos introducido la variable suspendido. Diferenciar los
distintos estados de ejecución del applet es importante porque algunos métodos pueden
generar excepciones si se llaman desde un estado erróneo. Por ejemplo, si el applet ha sido
arrancado y se detiene con stop(), si se intenta ejecutar el método start(), se generará una
excepción IllegalThreadStateException.
ESTADOS DE UN THREAD
Durante el ciclo de vida de un thread, éste se puede encontrar en diferentes estados. La figura
siguiente muestra estos estados y los métodos que provocan el paso de un estado a otro. Este
diagrama no es una máquina de estados finita, pero es lo que más se aproxima al
funcionamiento real de un thread .
Modulo 7 – Comunicaciones
Todos los lenguajes de programación tienen alguna forma de interactuar con los sistemas de
ficheros locales; Java no es una excepción.
Cuando se desarrollan applets para utilizar en red, hay que tener en cuenta que la
entrada/salida directa a fichero es una violación de seguridad de acceso. Muchos usuarios
configurarán sus navegadores para permitir el acceso al sistema de ficheros, pero otros no.
Por otro lado, si se está desarrollando una aplicación Java para uso interno, probablemente
será necesario el acceso directo a ficheros.
Ficheros
Antes de realizar acciones sobre un fichero, necesitamos un poco de información sobre ese
fichero. La clase File proporciona muchas utilidades relacionadas con ficheros y con la
obtención de información básica sobre esos ficheros.
Creación de un objeto File
Para crear un objeto File nuevo, se puede utilizar cualquiera de los tres constructores
siguientes:
File miFichero;
miFichero = new File( "/etc/kk" );
o
miFichero = new File( "/etc","kk" );
o
File miDirectorio = new File( "/etc" );
miFichero = new File( miDirectorio,"kk" );
El constructor utilizado depende a menudo de otros objetos File necesarios para el acceso. Por
ejemplo, si sólo se utiliza un fichero en la aplicación, el primer constructor es el mejor. Si en
cambio, se utilizan muchos ficheros desde un mismo directorio, el segundo o tercer constructor
serán más cómodos. Y si el directorio o el fichero es una variable, el segundo constructor será
el más útil.
Comprobaciones y Utilidades
Una vez creado un objeto File, se puede utilizar uno de los siguientes métodos para reunir
información sobre el fichero:
Nombres de fichero
String getName()
String getPath()
String getAbsolutePath()
String getParent()
boolean renameTo( File nuevoNombre )
Comprobaciones
boolean exists()
boolean canWrite()
boolean canRead()
boolean isFile()
boolean isDirectory()
boolean isAbsolute()
Información general del fichero
long lastModified()
long length()
Utilidades de directorio
boolean mkdir()
String[] list()
Vamos a desarrollar una pequeña aplicación que muestra información sobre los ficheros
pasados como argumentos en la línea de comandos, InfoFichero.java:
import java.io.*;
class InfoFichero {
STREAMS DE ENTRADA
Hay muchas clases dedicadas a la obtención de entrada desde un fichero. Este es el esquema
de la jerarquía de clases de entrada por fichero:
Objetos FileInputStream
Los objetos FileInputStream típicamente representan ficheros de texto accedidos en orden
secuencial, byte a byte. Con FileInputStream, se puede elegir acceder a un byte, varios bytes o
al fichero completo.
Apertura de un FileInputStream
Para abrir un FileInputStream sobre un fichero, se le da al constructor un String o un objeto
File:
FileInputStream mi FicheroSt;
miFicheroSt = new FileInputStream( "/etc/kk" );
También se puede utilizar:
File miFichero FileInputStream miFicheroSt;
miFichero = new File( "/etc/kk" );
miFicheroSt = new FileInputStream(
miFichero );
Lectura de un FileInputStream
Una vez abierto el FileInputStream, se puede leer de él. El método read() tiene muchas
opciones:
int read();
Lee un byte y devuelve -1 al final del stream.
int read( byte b[] );
Llena todo el array, si es posible. Devuelve el número de bytes leídos o -1 si se alcanzó el final
del stream.
int read( byte b[],int offset,int longitud );
Lee longitud bytes en b comenzando por b[offset]. Devuelve el número de bytes leídos o -1 si
se alcanzó el final del stream.
Cierre de FileInputStream
Cuando se termina con un fichero, existen dos opciones para cerrarlo: explícitamente, o
implícitamente cuando se recicla el objeto (el garbage collector se encarga de ello).
Para cerrarlo explícitamente, se utiliza el método close():
miFicheroSt.close();
Ejemplo: Visualización de un fichero
Si la configuración de la seguridad de Java permite el acceso a ficheros, se puede ver el
contenido de un fichero en un objeto TextArea. El código siguiente contiene los elementos
necesarios para mostrar un fichero:
FileInputStream fis;
TextArea ta;
try {
fis = new FileInputStream( "/etc/kk" );
} catch( FileNotFoundException e ) {
/* Hacer algo */
}
try {
i = fis.read( b );
} catch( IOException e ) {
/* Hacer algo */
}
class Telefonos {
static FileOutputStream fos;
public static final int longLinea = 81;
APERTURA DE SOCKETS
Si estamos programando un cliente, el socket se abre de la forma:
Socket miCliente;
miCliente = new Socket( "maquina",numeroPuerto );
Donde maquina es el nombre de la máquina en donde estamos intentando abrir la conexión y
numeroPuerto es el puerto (un número) del servidor que está corriendo sobre el cual nos
queremos conectar. Cuando se selecciona un número de puerto, se debe tener en cuenta que
los puertos en el rango 0-1023 están reservados para usuarios con muchos privilegios
(superusuarios o root). Estos puertos son los que utilizan los servicios estándar del sistema
como email, ftp o http. Para las aplicaciones que se desarrollen, asegurarse de seleccionar un
puerto por encima del 1023.
En el ejemplo anterior no se usan excepciones; sin embargo, es una gran idea la captura de
excepciones cuando se está trabajando con sockets. El mismo ejemplo quedaría como:
Socket miCliente;
try {
miCliente = new Socket( "maquina",numeroPuerto );
} catch( IOException e ) {
System.out.println( e );
}
Si estamos programando un servidor, la forma de apertura del socket es la que muestra el
siguiente ejemplo:
Socket miServicio;
try {
miServicio = new ServerSocket( numeroPuerto );
} catch( IOException e ) {
System.out.println( e );
}
A la hora de la implementación de un servidor también necesitamos crear un objeto socket
desde el ServerSocket para que esté atento a las conexiones que le puedan realizar clientes
potenciales y poder aceptar esas conexiones:
Socket socketServicio = null;
try {
socketServicio = miServicio.accept();
} catch( IOException e ) {
System.out.println( e );
}
CREACION DE STREAMS
Creación de Streams de Entrada
En la parte cliente de la aplicación, se puede utilizar la clase DataInputStream para crear un
stream de entrada que esté listo a recibir todas las respuestas que el servidor le envíe.
DataInputStream entrada;
try {
entrada = new DataInputStream( miCliente.getInputStream() );
} catch( IOException e ) {
System.out.println( e );
}
La clase DataInputStream permite la lectura de líneas de texto y tipos de datos primitivos de
Java de un modo altamente portable; dispone de métodos para leer todos esos tipos como:
read(), readChar(), readInt(), readDouble() y readLine(). Deberemos utilizar la función que
creamos necesaria dependiendo del tipo de dato que esperemos recibir del servidor.
En el lado del servidor, también usaremos DataInputStream, pero en este caso para recibir las
entradas que se produzcan de los clientes que se hayan conectado:
DataInputStream entrada;
try {
entrada =
new DataInputStream( socketServicio.getInputStream() );
} catch( IOException e ) {
System.out.println( e );
}
Creación de Streams de Salida
En el lado del cliente, podemos crear un stream de salida para enviar información al socket del
servidor utilizando las clases PrintStream o DataOutputStream:
PrintStream salida;
try {
salida = new PrintStream( miCliente.getOutputStream() );
} catch( IOException e ) {
System.out.println( e );
}
La clase PrintStream tiene métodos para la representación textual de todos los datos
primitivos de Java. Sus métodos write y println() tienen una especial importancia en este
aspecto. No obstante, para el envío de información al servidor también podemos utilizar
DataOutputStream:
DataOutputStream salida;
try {
salida = new DataOutputStream( miCliente.getOutputStream() );
} catch( IOException e ) {
System.out.println( e );
}
La clase DataOutputStream permite escribir cualquiera de los tipos primitivos de Java, muchos
de sus métodos escriben un tipo de dato primitivo en el stream de salida. De todos esos
métodos, el más útil quizás sea writeBytes().
En el lado del servidor, podemos utilizar la clase PrintStream para enviar información al
cliente:
PrintStream salida;
try {
salida = new PrintStream( socketServicio.getOutputStream() );
} catch( IOException e ) {
System.out.println( e );
}
Pero también podemos utilizar la clase DataOutputStream como en el caso de envío de
información desde el cliente.
CIERRE DE SOCKETS
Siempre deberemos cerrar los canales de entrada y salida que se hayan abierto durante la
ejecución de la aplicación. En la parte del cliente:
try {
salida.close();
entrada.close();
miCliente.close();
} catch( IOException e ) {
System.out.println( e );
}
Y en la parte del servidor:
try {
salida.close();
entrada.close();
socketServicio.close();
miServicio.close();
} catch( IOException e ) {
System.out.println( e );
}
SERVIDOR DE ECO
En el siguiente ejemplo, vamos a desarrollar un servidor similar al que se ejecuta sobre el
puerto 7 de las máquinas Unix, el servidor echo. Básicamente, este servidor recibe texto desde
un cliente y reenvía ese mismo texto al cliente. Desde luego, este es el servidor más simple de
los simples que se pueden escribir. El ejemplo que presentamos, ecoServidor.java, maneja
solamente un cliente. Una modificación interesante sería adecuarlo para que aceptase
múltiples clientes simultáneos mediante el uso de threads.
import java.net.*;
import java.io.*;
class ecoServidor {
public static void main( String args[] ) {
ServerSocket s = null;
DataInputStream sIn;
PrintStream sOut;
Socket cliente = null;
String texto;
CLIENTE/SERVIDOR TCP/IP
Mínimo Servidor TCP/IP
Veamos el código que presentamos en el siguiente ejemplo, minimoServidor.java, donde
desarrollamos un mínimo servidor TCP/IP, para el cual desarrollaremos después su
contrapartida cliente TCP/IP. La aplicación servidor TCP/IP depende de una clase de
comunicaciones proporcionada por Java: ServerSocket. Esta clase realiza la mayor parte del
trabajo de crear un servidor.
import java.awt.*;
import java.net.*;
import java.io.*;
class minimoServidor {
public static void main( String args[] ) {
ServerSocket s = (ServerSocket)null;
Socket s1;
String cadena = "Tutorial de Java!";
int longCad;
OutputStream s1out;
class minimoCliente {
public static void main( String args[] ) throws IOException {
int c;
Socket s;
InputStream sIn;