Lenguaje Programación Java

Guía del Estudiante

Mapa del Curso

Lenguaje Básico de Programación
Caracteristicas De Java Identificadores Palabras Reserv. Tipos de datos Expresiones Control de Flujo de programas Manejo de Arreglos

Programación Orientada a Objetos
Clases y Objetos Caracteristicas avanzadas

Manejo de Errores
Excepciones

Applets
Introduccion Java Applets

Desarrollo de Aplicaciones Graficas
Construyendo GUIs Modelo de Eventos AWT Librería de Componentes AWT Java Foundation Classes

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 bytecodes, 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.

Identificadores – Palabras Reservadas - Tipos de Datos
Tipos de Datos Hay ocho tipos básicos de datos llamados “primitivos”. Enteros con signo. byte (8 bits). short (16 bits). int (32 bits). long (64 bits). Numéricos de punto flotante: float double Carácter simple: se utiliza para caracteres individuales como letras, números, puntuación, y otros símbolos. char Booleanos: que pueden albergar los valores “true” ó “false”. boolean

Tipos de Clases String: Es un tipo de clase usado para almacenar texto Ejemplo colorDePelo = “castaño”; Cuando una variable tiene una clase como su tipo, la variable se refiere a un objeto de esa clase o a una de sus subclases. Si se declara a un tipo de variable “Object” significa que puede contener cualquier objeto. Declaración de variables Se declaran anteponiendo el tipo de dato o clase a la cual pertenecen. Para las variables del mismo tipo, se pueden declarar en la misma línea separándolas por comas.. 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. El estándar recomendado es para el nombramiento de variables es: La primera letra minúscula. Cada palabra después del nombre de la variable empieza con mayúscula. (Ej. areaCodigo) Todas las demás letras son minúsculas 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.

Expresiones – Control de Flujo - Arreglos
Expresiones y Operadores Una expresión es una instrucción que produce un valor. Por ejemplo: int x = 3; int y = 4; int z = x * y; // Expresión Un operador es un símbolo especial utilizado para funciones matemáticas, algunos tipos de instrucciones de asignación y comparaciones lógicas. Por ejemplo el * de la expresión anterior. 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 Sirven para añadir o sustraer 1 a una variable. Para incrementar se utiliza ++ (int x = 7; x = x++; ahora x vale 8). Para decrementar se utiliza - - (int x = 7; x = x - -; ahora x vale 6). 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 El operador + también puede ser utilizado para concatenar cadenas. El operador + combina cadenas, objetos y variables para formar una cadena sencilla. También esta el operador corto += que añade algo al final de la cadena. Ejemplo miNombre += “Señor”; Controles de Flujo Estructuras de decisión o salto 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. El default es opcional, y es utilizado para especificar un segmento de código que es ejecutado si la variable o expresión no matchea con ninguno de los case. 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; } En esta estructura está permitido utilizar comas para separar variables, Ejemplo for (int i = 0, j = 0; j < 10; i++, j++) { Bloque de código; } La variable i es solo accesible dentro del bloque del for. 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. Instrucciones especiales dentro de las estructuras de lazo: 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: Do { //label

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: abstract boolean break byte byvalue case catch char class const continue default Do double Else extends False Final Finally Float for goto if implements import instanceof int interface long native new null package private protected public return short static super switch synchronized this threadsafe throw transient true try void while

Arreglos 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: En java se pueden crear arrays de arrays, y arrays de arrays de arrays. 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]; Hay otra forma más corta de inicializar un array de array: int dosDim [] [] = new int [4] [5]; 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]; El primer array es perdido a menos que otra referencia lo haya guardado. 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.

Clase: Definicion de atributos y comportamientos de un objeto de la realidad. Cada clase se define en un solo archivo de código fuente (.java) que lleva el nombre de la misma. Objeto: Instancia particular de una clase. Definición de Clases: [package nombrepaquete] [import nombreclase] [Modificadores] class NombreClase [extends SuperClase] [implements Interfase] { [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. Definición de un Objeto y utilización de sus atributos. Empleado emp = new Empleado(); emp.Nombre = “Juan”; emp.Apellido = “Perez”; emp.Gerencia = “Administración”;

Definición de Método: [Modificadores] tiporetorno NombreMetodo ( [Lista de Argumentos] ) { Bloque de Código; } Ejemplo public class String String String Empleado { Nombre; Apellido; Gerencia;

public void addDays (int days) { Bloque de código del método; } } Este método llamado addDays recibe como parámetro la variable de tipo entera (int) days y no devuelve valores (void). Empleado emp = new Empleado(); emp.Nombre = “Juan”; emp.Apellido = “Perez”; emp.Gerencia = “Administración”; emp.addDays(4); 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 public class MyDate { private int day; private int month; private int year; // Método de alteración del contenido del atributo. public void setDay(int newvalueofday){ //validaciones del nuevo valor. this.day = newvalueofday; } // Método para mostrar el valor. public int getDay(){ return this.day; }

}

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. public void imprimirLinea (int i); public void imprimirLinea (float f); public void imprimirLinea (String s); 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: public class Xyx{ // Declaración de atributos... public Xyz(){ // Inicialización del objeto } // resto de los métodos. } 2) Sobrecarga de Constructores: public class Employee{ // Declaración de atributos private String name; private int salary; public Employee(String n, int s) { this.name = n; this.salary = s; } public Employee(String n) { this(n,0); } public Employee() { this(“Unknow”); } } // resto de los métodos.

Los constructores son invocados solo con la utilización de la instrucción new. Si la clase no tiene una definición explicita de constructor, el lenguaje de programación le provee uno en tiempo de ejecución. Este constructor no recibe ningún parámetro y no efectúa ninguna operación, solo se utiliza para poder instanciar nuevos objetos con la instrucción new. 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. public class Persona { private String nombre; private String apellido; public void cambiarNombre( String nuevonombre) { nombre = nuevonombre; } } public class Empleado extends Persona { private String cargo; public String getCargo(){ return cargo; } } Empleado emp = new Empleado(); emp.cambiarNombre(“Carlos”); 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 public class Empleado { private string nombre; private int sueldo; public String getDetalles(){ return “Nombre: “ + nombre + “ – Sueldo: “ + } sueldo;

}

public class Gerente extends Empleado { private String gerencia; public String getDetalles() { return super.getDetalles() + “ – Gerencia: ” + gerencia; } } Gerente ger = new Gerente(); ger.nombre = “Jose”; ger.sueldo = 3000; ger.gerencia = “Administración”; System.out.println(ger.getDetalles()); La salida será: Nombre: Jose – Sueldo: 3000 – Gerencia: Administracion

Clases Abstractas Una de las características más útiles de cualquier lenguaje orientado a objetos es la posibilidad de declarar clases que definen como se utiliza solamente, sin tener que implementar métodos. Esto es muy útil cuando la implementación es específica para cada usuario, pero todos los usuarios tienen que utilizar los mismos métodos. Un ejemplo de clase abstracta en Java es la clase Graphics: public abstract class Graphics { public abstract void drawLine( int x1,int y1,int x2, int y2 ); public abstract void drawOval( int x,int y,int width, int height ); public abstract void drawArc( int x,int y,int width, int height,int startAngle,int arcAngle ); . . . } Los métodos se declaran en la clase Graphics, pero el código que ejecutará el método está en algún otro sitio: public class MiClase extends Graphics { public void drawLine( int x1,int y1,int x2,int y2 ) { <código para pintar líneas -específico de la arquitectura-> } } Cuando una clase contiene un método abstracto tiene que declararse abstracta. No obstante, no todos los métodos de una clase abstracta tienen que ser abstractos. Las clases abstractas no pueden tener métodos privados (no se podrían implementar). Una clase abstracta tiene que derivarse obligatoriamente, no se puede hacer un new de una clase abstracta. 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.*; public class Empleado extends Applet { contenido de la clase: }

Ejemplo Completo public class Persona { //Atributos. private String nombre; private String apellido; private int edad; //Metodos Constructores. public Persona(String nom, String ape, int eda){ this.setNombre(nom); this.setApellido(ape); this.setEdad(eda); } public Persona(String nom, String ape){ this(nom,ape,1); } public Persona(String nom){ this(nom,"Sin Apellido"); } public Persona(){ this("Sin Nombre"); } //Metodos de la clase. public String getDetalles(){ return "Nombre: " + this.getNombre() + " - Apellido: " + this.getApellido() + " - Edad:" + this.getEdad(); } //Metodos para acceder a la clase. public int getEdad(){ return this.edad; } public boolean setEdad(int nuevaEdad){ if (nuevaEdad > 90) { return false; } else { this.edad = nuevaEdad; return true; } } public String getNombre(){ return this.nombre; } public boolean setNombre(String nuevoNombre){ if (nuevoNombre == "") { return false; } else { this.nombre = nuevoNombre; return true; } }

public String getApellido(){ return this.apellido; } public boolean setApellido(String nuevoApellido){ if (nuevoApellido == "") { return false; } else { this.apellido = nuevoApellido; return true; } } } public class Empleado extends Persona { // Atributos. private int sueldo; private String gerencia; //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(); } //Metodos para Acceder a los atributos. public int getSueldo(){ return this.sueldo; } public boolean setSueldo(int nuevoSueldo){ if (nuevoSueldo == 0) { return false; } else { this.sueldo = nuevoSueldo; return true; } } public String getGerencia(){ return this.gerencia; } public boolean setGerencia(String nuevaGerencia){ if (nuevaGerencia == "") { return false; }

} }

else { this.gerencia = nuevaGerencia; return true; }

import Persona; import Empleado; public class EjemploHerencia { public static void main(String args[]){ Empleado emp = new Empleado(); System.out.println("Detalles: " + emp.getDetalles()); emp.setNombre("Gustavo"); emp.setApellido("Garcia"); emp.setEdad(27); emp.setGerencia("CRM Masivo"); emp.setSueldo(1000); System.out.println("Detalles: " + emp.getDetalles()); } }

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: El archivo que se está tratando de abrir no existe. La conexión a la red se interrumpe. La clase buscada no se encuentra. Etc. 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: public class HolaMundo { public static void main (String args[]) { int i = 0; String greetings [] = { “Hola mundo!”, “No, me equivoque!”, “HOLA MUNDO!!” }; 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) El manejo de excepciones permite al programa tomar la excepción, manejarla y continuar con la ejecución del mismo. Manejo de Excepciones Instrucciones try y catch 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. } El mecanismo del llamado por pila: 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 Define un bloque de código que siempre se ejecuta, independientemente de si el programa efectuó una excepción. Ejemplo: try { ComenzarRiego () ; AguaRiego (); } finally { TerminarRiego(); } En el ejemplo anterior TerminarRiego es ejecutada aunque haya existido alguna exceptión mientras ComenzarRiego se ejecutaba. El código dentro de las llaves del try es llamado “código protegido”. La instrucción finally pudiera no ser ejecutada solo en el caso de que el método System.exit () fuera ejecutado dentro del código protegido, ya que ésta deriva la ejecución normal de programa, terminándolo. Si la instrucción return fuera ejecutada dentro del try, el código de finally sería ejecutado antes de ésta. 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. RuntimeException: Indica un problema de diseño o implementación, por ejemplo, ArrayIndexOutOfBoundsException, nunca debería darse si los índices del array no pasaran el largo del mismo. 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: ArithmeticException – El resultado de dividir por cero una operación de enteros.

NullPointerException – Intentar acceder a algún método o atributo de un objeto sin que éste esté instanciado. NegativeArraySizeException – Intentar crear un array son un tamaño negativo. ArrayIndexOutOfBoundsException – Intentar acceder a un elemento del array mayor al tamaño del mismo. ScurityException – Se suele generar en un browser, la clase SecurityManager genera esa excepción por applets que intentan : Acceder a una fila local, Abrir un socket en el servidor que no es el mismo que sirve el applet, Ejecutar otro programa en el entorno de ejecución.

Jerarquía de Excepciones
OutOfMemory Error VirtualMachine Error Error StackOverflow Error

AWTError Arithmetic Excpetion Throwable RuntimeExcepti on NullPointer Exception

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: public class HolaMundo { public static void main (String args[]) { int i = 0; String greetings [] = { “Hola mundo!”, “No, me equivoque!”, “HOLA MUNDO!!” }; while (i < 4) { try { System.out.println (greetings [i]); } catch (ArrayIndexOutOfBoundsException e) { System.out.println (“Actualice los índices”); I = -1; } finally { System.out.println (“Esto siempre se imprime”); } i++; } } }

Modulo 4 – Applets
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. Ciclo de vida de un Applet Cuando un applet se carga en el browser, comienza su ciclo de vida, que pasaría por las siguientes fases: Se crea una instancia de la clase que controla el applet.. El applet se inicializa. El applet comienza a ejecutarse. El applet empieza a recibir llamadas. Primero recibe una llamada init (inicializar), seguida de un mensaje start (empezar) y paint (pintar). Estas llamadas pueden ser recibidas asincrónicamente. 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 destroy() se llama cuando ya no se va a utilizar más el applet, cuando se necesita que sean liberados todos los recursos dispuestos por el applet, por ejemplo, cuando se cierra el navegador. La clase Applet no hace nada en este método. Las clases derivadas deberían sobrecargarlo para hacer una limpieza final. 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. El metodo update( Graphics g ) es el que realmente se llama cuando se necesita una actualización de la pantalla. La clase Applet simplemente limpia el área y llama al método paint(). Esta funcionalidad es suficiente para la mayoría de los casos; aunque, de cualquier forma, las clases derivadas pueden sustituir esta funcionalidad para sus propósitos especiales. Llamando al metodo repaint() se podrá forzar la actualización de un applet. Ejemplo import java.awt.Graphics; import java.applet.Applet; public class HolaMundo extends Applet { public void paint( Graphics g ) { g.drawString( "Hola Curso Java!",25,25 ) ; } } Ejemplo de archivo contenedor Holamundo.html <APPLET>: <APPLET CODE="HolaMundo.class" WIDTH=100 HEIGHT=50> </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; public class HolaMundo extends Applet { //Atributos de la clase. Font f; //Metodos Constructores. public void init(){ f = new Font("Arial",Font.BOLD,36); } // Sobreescribo Metodos de Applet. public void paint( Graphics screen ) { screen.setFont(f); screen.setColor(Color.red); screen.drawString( "Hola Mundo Applet!",5,40 ) ; } } 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 básica del AWT se basa en Componentes y Contenedores. Estos últimos contienen Componentes posicionados a su respecto y son Componentes a su vez, de forma que los eventos pueden tratarse tanto en Contenedores como en Componentes, corriendo por cuenta del programador el encaje de todas las piezas, así como la seguridad de tratamiento de los eventos adecuados. Debido a que el lenguaje de programación Java es independiente de la plataforma en que se ejecuten sus aplicaciones, el AWT también es independiente de la plataforma en que se ejecute. El AWT proporciona un conjunto de herramientas para la construcción de interfaces gráficas que tienen una apariencia y se comportan de forma semejante en todas las plataformas en que se ejecute. Los elementos de interface proporcionados por el AWT están implementados utilizando toolkits nativos de las plataformas, preservando una apariencia semejante a todas las aplicaciones que se creen para esa plataforma. 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.

Al final, la apariencia de la aplicación en pantalla es la que presenta la figura anterior. LAYOUTS Los layout managers o manejadores de composición, en traducción literal, ayudan a adaptar los diversos Componentes que se desean incorporar a un Panel, es decir, especifican la apariencia que tendrán los Componentes a la hora de colocarlos sobre un Contenedor. Java dispone de varios, en la actual versión, tal como se muestra en la imagen:

¿Por qué Java proporciona estos esquemas predefinidos de disposición de componentes? La razón es simple: imaginemos que deseamos agrupar objetos de distinto tamaño en celdas de una rejilla virtual: si confiados en nuestro conocimiento de un sistema gráfico determinado, codificamos a mano tal disposición, deberemos preveer el redimensionamiento del applet, su repintado cuando sea cubierto por otra ventana, etc., además de todas las cuestiones relacionadas con un posible cambio de plataforma (uno nunca sabe a donde van a ir a parar los propios hijos, o los applets).

Modulo 6 – MultiProgramacion
Considerando el entorno multithread, cada thread (hilo, flujo de control del programa) representa un proceso individual ejecutándose en un sistema. A veces se les llama procesos ligeros o contextos de ejecución. Típicamente, cada thread controla un único aspecto dentro de un programa, como puede ser supervisar la entrada en un determinado periférico o controlar toda la entrada/salida del disco. Todos los threads comparten los mismos recursos, al contrario que los procesos en donde cada uno tiene su propia copia de código y datos (separados unos de otros). Gráficamente, los threads se parecen en su funcionamiento a lo que muestra la figura siguiente:

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. Por ejemplo, en nuestra aplicación estándar de saludo: public class HolaMundo { static public void main( String args[] ) { System.out.println( "Hola Mundo!" ); } } Aquí, cuando se llama a main(), la aplicación imprime el mensaje y termina. Esto ocurre dentro de un único thread. Programas de flujo múltiple En nuestra aplicación de saludo, no vemos el thread que ejecuta nuestro programa. Sin embargo, Java posibilita la creación y control de threads explícitamente. La utilización de threads en Java, permite una enorme flexibilidad a los programadores a la hora de plantearse el desarrollo de aplicaciones. La simplicidad para crear, configurar y ejecutar threads, permite que se puedan implementar muy poderosas y portables aplicaciones/applets que no se puede con otros lenguajes de tercera generación. En un lenguaje orientado a Internet como es Java, esta herramienta es vital. Si se ha utilizado un navegador con soporte Java, ya se habrá visto el uso de múltiples threads en Java. Habrá observado que dos applet se pueden ejecutar al mismo tiempo, o que puede desplazar la página del navegador mientras el applet continúa ejecutándose. Esto no significa que el applet utilice múltiples threads, sino que el navegador es multithreaded. Las aplicaciones (y applets) multithreaded utilizan muchos contextos de ejecución para cumplir su trabajo. Hacen uso del hecho de que muchas tareas contienen subtareas distintas e independientes. Se puede utilizar un thread para cada subtarea. Mientras que los programas de flujo único pueden realizar su tarea ejecutando las subtareas secuencialmente, un programa multithreaded permite que cada thread comience y termine tan pronto como sea posible. Este comportamiento presenta una mejor respuesta a la entrada en tiempo real.

Ejemplo de Hola Mundo version Threads public class MyThread extends Thread { private int intervalo; private String descripcion; public void setIntervalo(int nuevointervalo){ intervalo = nuevointervalo; } public int getIntervalo(){ return intervalo; } public void setDescripcion(String nuevadescripcion){ descripcion = nuevadescripcion; } public String getDescripcion(){ return descripcion; } public MyThread(String nuevadescripcion){ this.setDescripcion(nuevadescripcion); } public void run(){ while(true){ try { this.setIntervalo((int)(Math.random()*1000)); this.sleep(this.getIntervalo()); System.out.println("Descripcion: " + this.getDescripcion() + " - Intervalo:" + this.getIntervalo()); } catch (Exception e) { System.out.println("Error en Tbread"); } } } } import MyThread; public class TesterMyThread { public static void try { MyThread th1 MyThread th2 MyThread th3 th1.start(); th2.start(); th3.start(); } catch (Exception e) { System.out.println("Error en Main"); } } } main (String args[]){ = new MyThread("Hola Mundo - Thread 1"); = new MyThread("Hola Mundo - Thread 2"); = new MyThread("Hola Mundo - Thread 3");

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. ARRANCAR Y PARAR THREADS Ahora que ya hemos visto por encima como se arrancan, paran y manipulan threads, vamos a mostrar un ejemplo un poco más gráfico, se trata de un contador, cuyo código (App1Thread.java) es el siguiente: import java.awt.*; import java.applet.Applet; public class App1Thread extends Applet implements Runnable { Thread t; int contador; public void init() { contador = 0; t = new Thread( this ); t.start(); } public void run() { while( true ) { contador++; repaint(); try { t.sleep( 10 ); } catch( InterruptedException e ) { ; }; } } public boolean mouseDown( Event evt,int x,int y ) { t.stop(); return( true ); } public void paint( Graphics g ) { g.drawString( Integer.toString( contador ),10,10 ); System.out.println( "Contador = "+contador ); } public void stop() { t.stop(); } } Este applet arranca un contador en 0 y lo incrementa, presentando su salida tanto en la pantalla gráfica como en la consola. Una primera ojeada al código puede dar la impresión de que el programa empezará a contar y presentará cada número, pero no es así. Una revisión más profunda del flujo de ejecución del applet, nos revelará su verdadera identidad. En este caso, la clase App1Thread está forzada a implementar Runnable sobre la clase Applet que extiende. Como en todos los applets, el método init() es el primero que se ejecuta. En init(), la variable contador se inicializa a cero y se crea una nueva instancia de la clase Thread. Pasándole this al constructor de Thread, el nuevo thread ya conocerá al objeto que va a correr. En este caso this es una referencia a App1Thread. Después de que hayamos creado el thread, necesitamos arrancarlo. La llamada a start(), llamará a su vez al método run() de nuestra clase, es decir, a App1Thread.run(). La llamada a start() retornará con éxito y el thread comenzará a ejecutarse en ese instante. Observar que el método run() es un bucle infinito. Es

infinito porque una vez que se sale de él, la ejecución del thread se detiene. En este método se incrementará la variable contador, se duerme 10 milisegundos y envía una petición de refresco del nuevo valor al applet. Es muy importante dormirse en algún lugar del thread, porque sino, el thread consumirá todo el tiempo de la CPU para su proceso y no permitirá que entren otros métodos de otros threads a ejecutarse. Otra forma de detener la ejecución del thread es hacer una llamada al método stop(). En el contador, el thread se detiene cuando se pulsa el ratón mientras el cursor se encuentre sobre el applet. Dependiendo de la velocidad del ordenador, se presentarán los números consecutivos o no, porque el incremento de la variable contador es independiente del refresco en pantalla. El applet no se refresca a cada petición que se le hace, sino que el sistema operativo encolará las peticiones y las que sean sucesivas las convertirán en un único refresco. Así, mientras los refescos se van encolando, la variable contador se estará todavía incrementando, pero no se visualiza en pantalla. SUSPENDER Y REANUDAR THREADS Una vez que se para un thread, ya no se puede rearrancar con el comando start(), debido a que stop() concluirá la ejecución del thread. Por ello, en ver de parar el thread, lo que podemos hacer es dormirlo, llamando al método sleep(). El thread estará suspendido un cierto tiempo y luego reanudará su ejecución cuando el límite fijado se alcance. Pero esto no es útil cuando se necesite que el thread reanude su ejecución ante la presencia de ciertos eventos. En estos casos, el método suspend() permite que cese la ejecución del thread y el método resume() permite que un método suspendido reanude su ejecución. En la siguiente versión de nuestra clase contador, App2Thread.java, modificamos el applet para que utilice los métodos suspend() y resume(): public class App2Thread extends Applet implements Runnable { Thread t; int contador; boolean suspendido; ... public boolean mouseDown( Event evt,int x,int y ) { if( suspendido ) t.resume(); else t.suspend(); suspendido = !suspendido; 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 { public static void main( String args[] ) throws IOException { if( args.length > 0 ) {

for( int i=0; i < args.length; i++ ) { File f = new File( args[i] ); System.out.println( "Nombre: "+f.getName() ); System.out.println( "Camino: "+f.getPath() ); if( f.exists() ) { System.out.print( "Fichero existente " ); System.out.print( (f.canRead() ? " y se puede Leer" : "" ) ); System.out.print( (f.canWrite() ? " y se puese Escribir" : "" ) ); System.out.println( "." ); System.out.println( "La longitud del fichero son "+ f.length()+" bytes" ); } else System.out.println( "El fichero no existe." ); } } else System.out.println( "Debe indicar un fichero." ); } } 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; public void init() { byte b[] = new byte[1024]; int i; // El buffer de lectura se debe hacer lo suficientemente grande // o esperar a saber el tamaño del fichero String s; try { fis = new FileInputStream( "/etc/kk" ); } catch( FileNotFoundException e ) { /* Hacer algo */ } try { i = fis.read( b ); } catch( IOException e ) { /* Hacer algo */ } s = new String( b,0 ); ta = new TextArea( s,5,40 ); add( ta ); } Hemos desarrollado un ejemplo, Agenda.java, en el que partimos de un fichero agenda que dispone de los datos que nosotros deseamos de nuestros amigos, como son: nombre, teléfono y dirección. Si tecleamos un nombre, buscará en el fichero de datos si existe ese nombre y presentará la información que se haya introducido. Para probar, intentar que aparezca la información de Pepe. Objetos DataInputStream Los objetos DataInputStream se comportan como los FileInputStream. Los streams de datos pueden leer cualquiera de las variables de tipo nativo, como floats, ints o chars. Generalmente se utilizan DataInputStream con ficheros binarios. Apertura y cierre de DataInputStream Para abrir y cerrar un objeto DataInputStream, se utilizan los mismos métodos que para FileInputStream: DataInputStream miDStream; FileInputStream miFStream; // Obtiene un controlador de fichero

miFStream = new FileInputStream "/etc/ejemplo.dbf" ); //Encadena un fichero de entrada de datos miDStream = new DataInputStream( miFStream ); // Ahora se pueden utilizar los dos streams de entrada para // acceder al fichero (si se quiere...) miFStream.read( b ); i = miDStream.readInt(); // Cierra el fichero de datos explícitamente //Siempre se cierra primero el fichero stream de mayor nivel miDStream.close(); miFStream.close(); Lectura de un DataInputStream Al acceder a un fichero como DataInputStream, se pueden utilizar los mismos métodos read() de los objetos FileInputStream. No obstante, también se tiene acceso a otros métodos diseñados para leer cada uno de los tipos de datos: byte readByte() int readUnsignedByte() short readShort() int readUnsignedShort() char readChar() int readInt() long readLong() float readFloat() double readDouble() String readLine() Cada método leerá un objeto del tipo pedido. Para el método String readLine(), se marca el final de la cadena con \n, \r, \r\n o con EOF. Para leer un long, por ejemplo: long numeroSerie; ... numeroSerie = miDStream.readLong(); Streams de entrada de URLs Además del acceso a ficheros, Java proporciona la posibilidad de acceder a URLs como una forma de acceder a objetos a través de la red. Se utiliza implícitamente un objeto URL al acceder a sonidos e imágenes, con el método getDocumentBase() en los applets: String imagenFich = new String( "imagenes/pepe.gif" ); imagenes[0] = getImage( getDocumentBase(),imagenFich ); No obstante, se puede proporcionar directamente un URL, si se quiere: URL imagenSrc; imagenSrc = new URL( "http://enterprise.com/~info" ); imagenes[0] = getImage( imagenSrc,"imagenes/pepe.gif" ); Apertura de un Stream de entrada de URL También se puede abrir un stream de entrada a partir de un URL. Por ejemplo, se puede utilizar un fichero de datos para un applet: ImputStream is; byte buffer[] = new byte[24]; is = new URL( getDocumentBase(),datos).openStream(); Ahora se puede utilizar is para leer información de la misma forma que se hace con un objeto FileInputStream: is.read( buffer,0,buffer.length ); NOTA: Debe tenerse muy en cuenta que algunos usuarios pueden haber configurado la seguridad de sus navegadores para que los applets no accedan a ficheros. STREAMS DE SALIDA La contrapartida necesaria de la lectura de datos es la escritura de datos. Como con los Streams de entrada, las clases de salida están ordenadas jerárquicamente:

Examinaremos las clases FileOutputStream y DataOutputStream para complementar los streams de entrada que se han visto. En los ficheros fuente del directorio $JAVA_HOME/src/java/io se puede ver el uso y métodos de estas clases, así como de los streams de entrada ($JAVA_HOME es el directorio donde se haya instalado el Java Development Kit, en sistemas UNIX). Objetos FileOutputStream Los objetos FileOutputStream son útiles para la escritura de ficheros de texto. Como con los ficheros de entrada, primero se necesita abrir el fichero para luego escribir en él. Apertura de un FileOutputStream Para abrir un objeto FileOutputStream, se tienen las mismas posibilidades que para abrir un fichero stream de entrada. Se le da al constructor un String o un objeto File. FileOutputStream miFicheroSt; miFicheroSt = new FileOutputStream( "/etc/kk" ); Como con los streams de entrada, también se puede utilizar: File miFichero FileOutputStream miFicheroSt; miFichero = new File( "/etc/kk" ); miFicheroSt = new FileOutputStream( miFichero ); Escritura en un FileOutputStream Una vez abierto el fichero, se pueden escribir bytes de datos utilizando el método write(). Como con el método read() de los streams de entrada, tenemos tres posibilidades: void write( int b ); Escribe un byte. void write( byte b[] ); Escribe todo el array, si es posible. void write( byte b[],int offset,int longitud ); Escribe longitud bytes en b comenzando por b[offset]. Cierre de FileOutputStream Cerrar un stream de salida es similar a cerrar streams de entrada. Se puede utilizar el método explícito: miFicheroSt.close(); O, se puede dejar que el sistema cierre el fichero cuando se recicle miFicheroSt. Ejemplo: Almacenamiento de Información Este programa, Telefonos.java, pregunta al usuario una lista de nombres y números de teléfono. Cada nombre y número se añade a un fichero situado en una localización fija. Para indicar que se ha introducido toda la lista, el usuario especifica "Fin" ante la solicitud de entrada del nombre. Una vez que el usuario ha terminado de teclear la lista, el programa creará un fichero de salida que se mostrará en pantalla o se imprimirá. Por ejemplo: 95-4751232,Juanito 564878,Luisa 123456,Pepe 347698,Antonio 91-3547621,Maria El código fuente del programa es el siguiente: import java.io.*; class Telefonos { static FileOutputStream fos; public static final int longLinea = 81; public static void main( String args[] ) throws IOException {

byte tfno[] = new byte[longLinea]; byte nombre[] = new byte[longLinea]; fos = new FileOutputStream( "telefono.dat" ); while( true ) { System.err.println( "Teclee un nombre ('Fin' termina)" ); leeLinea( nombre ); if( "fin".equalsIgnoreCase( new String( nombre,0,0,3 ) ) ) break; System.err.println( "Teclee el numero de telefono" ); leeLinea( tfno ); for( int i=0; tfno[i] != 0; i++ ) fos.write( tfno[i] ); fos.write( ',' ); for( int i=0; nombre[i] != 0; i++ ) fos.write( nombre[i] ); fos.write( '\n' ); } fos.close(); } private static void leeLinea( byte linea[] ) throws IOException { int b = 0; int i = 0; while( (i < ( longLinea-1) ) && ( ( b = System.in.read() ) != '\n' ) ) linea[i++] = (byte)b; linea[i] = (byte)0; } } Streams de salida con buffer Si se trabaja con gran cantidad de datos, o se escriben muchos elementos pequeños, será una buena idea utilizar un stream de salida con buffer. Los streams con buffer ofrecen los mismos métodos de la clase FileOutputStream, pero toda salida se almacena en un buffer. Cuando se llena el buffer, se envía a disco con una única operación de escritura; o, en caso necesario, se puede enviar el buffer a disco en cualquier momento. Creación de Streams de salida con buffer Para crear un stream BufferedOutput, primero se necesita un stream FileOutput normal; entonces se le añade un buffer al stream: FileOutputStream miFileStream; BufferdOutpurStream miBufferStream; // Obtiene un controlador de fichero miFileStream = new FileOutputStream( "/tmp/kk" ); // Encadena un stream de salida con buffer miBufferStream = new BufferedOutputStream( miFileStream ); Volcado y Cierre de Streams de salida con buffer Al contrario que los streams FileOutput, cada escritura al buffer no se corresponde con una escritura en disco. A menos que se llene el buffer antes de que termine el programa, cuando se quiera volcar el buffer explícitamente se debe hacer mediante una llamada a flush(): // Se fuerza el volcado del buffer a disco miBufferStream.flush(); // Cerramos el fichero de datos. Siempre se ha de cerrar primero el // fichero stream de mayor nivel miBufferStream.close(); miFileStream.close(); Streams DataOutput

Java también implementa una clase de salida complementaria a la clase DataInputStream. Con la clase DataOutputStream, se pueden escribir datos binarios en un fichero. Apertura y cierre de objetos DataOutputStream Para abrir y cerrar objetos DataOutputStream, se utilizan los mismos métodos que para los objetos FileOutputStream: DataOutputStream miDataStream; FileOutputStream miFileStream; BufferedOutputStream miBufferStream; // Obtiene un controlador de fichero miFileStream = new FileOutputStream( "/tmp/kk" ); // Encadena un stream de salida con buffer (por eficiencia) miBufferStream = new BufferedOutputStream( miFileStream ); // Encadena un fichero de salida de datos miDataStream = new DataOutputStream( miBufferStream ); // Ahora se pueden utilizar los dos streams de entrada para // acceder al fichero (si se quiere) miBufferStream.write( b ); miDataStream.writeInt( i ); // Cierra el fichero de datos explícitamente. Siempre se cierra // primero el fichero stream de mayor nivel miDataStream.close(); miBufferStream.close(); miFileStream.close(); Escritura en un objeto DataOutputStream Cada uno de los métodos write() accesibles por los FileOutputStream también lo son a través de los DataOutputStream. También encontrará métodos complementarios a los de DataInputStream: void writeBoolean( boolean b ); void writeByte( int i ); void writeShort( int i ); void writeChar( int i ); void writeInt( int i ); void writeFloat( float f ); void writeDouble( double d ); void writeBytes( String s ); void writeChars( string s ); Para las cadenas, se tienen dos posibilidades: bytes y caracteres. Hay que recordar que los bytes son objetos de 8 bits y los caracteres lo son de 16 bits. Si nuestras cadenas utilizan caracteres Unicode, debemos escribirlas con writeChars(). Contabilidad de la salida Otra función necesaria durante la salida es el método size(). Este método simplemente devuelve el número total de bytes escritos en el fichero. Se puede utilizar size() para ajustar el tamaño de un fichero a múltiplo de cuatro. Por ejemplo, de la forma siguiente: ... int numBytes = miDataStream.size() % 4; for( int i=0; i < numBytes; i++ ) miDataStream.write( 0 ); ...

En este capítulo no nos vamos a extender demasiado en profundidades sobre la comunicación y funcionamiento de redes, aunque sí proporcionaremos un breve baño inicial para sentar, o recordar, los fundamentos de la comunicación en red, tomando como base Unix.

Presentaremos un ejemplo básico de cliente/servidor sobre sockets TCP/IP, proporcionando un punto de partida para el desarrollo de otras aplicaciones cliente/servidor basadas en sockets, que posteriormente implementaremos. SOCKETS Los sockets son puntos finales de enlaces de comunicaciones entre procesos. Los procesos los tratan como descriptores de ficheros, de forma que se pueden intercambiar datos con otros procesos transmitiendo y recibiendo a través de sockets. El tipo de sockets describe la forma en la que se transfiere información a través de ese socket. Sockets Stream (TCP, Transport Control Protocol) Son un servicio orientado a conexión donde los datos se transfieren sin encuadrarlos en registros o bloques. Si se rompe la conexión entre los procesos, éstos serán informados. El protocolo de comunicaciones con streams es un protocolo orientado a conexión, ya que para establecer una comunicación utilizando el protocolo TCP, hay que establecer en primer lugar una conexión entre un par de sockets. Mientras uno de los sockets atiende peticiones de conexión (servidor), el otro solicita una conexión (cliente). Una vez que los dos sockets estén conectados, se pueden utilizar para transmitir datos en ambas direcciones. Sockets Datagrama (UDP, User Datagram Protocol) Son un servicio de transporte sin conexión. Son más eficientes que TCP, pero no está garantizada la fiabilidad. Los datos se envían y reciben en paquetes, cuya entrega no está garantizada. Los paquetes pueden ser duplicados, perdidos o llegar en un orden diferente al que se envió. El protocolo de comunicaciones con datagramas es un protocolo sin conexión, es decir, cada vez que se envíen datagramas es necesario enviar el descriptor del socket local y la dirección del socket que debe recibir el datagrama. Como se puede ver, hay que enviar datos adicionales cada vez que se realice una comunicación. Sockets Raw Son sockets que dan acceso directo a la capa de software de red subyacente o a protocolos de más bajo nivel. Se utilizan sobre todo para la depuración del código de los protocolos. Diferencias entre Sockets Stream y Datagrama Ahora se nos presenta un problema, ¿qué protocolo, o tipo de sockets, debemos usar - UDP o TCP? La decisión depende de la aplicación cliente/servidor que estemos escribiendo. Vamos a ver algunas diferencias entre los protocolos para ayudar en la decisión. En UDP, cada vez que se envía un datagrama, hay que enviar también el descriptor del socket local y la dirección del socket que va a recibir el datagrama, luego éstos son más grandes que los TCP. Como el protocolo TCP está orientado a conexión, tenemos que establecer esta conexión entre los dos sockets antes de nada, lo que implica un cierto tiempo empleado en el establecimiento de la conexión, que no existe en UDP. En UDP hay un límite de tamaño de los datagramas, establecido en 64 kilobytes, que se pueden enviar a una localización determinada, mientras que TCP no tiene límite; una vez que se ha establecido la conexión, el par de sockets funciona como los streams: todos los datos se leen inmediatamente, en el mismo orden en que se van recibiendo. UDP es un protocolo desordenado, no garantiza que los datagramas que se hayan enviado sean recibidos en el mismo orden por el socket de recepción. Al contrario, TCP es un protocolo ordenado, garantiza que todos los paquetes que se envíen serán recibidos en el socket destino en el mismo orden en que se han enviado. Los datagramas son bloques de información del tipo lanzar y olvidar. Para la mayoría de los programas que utilicen la red, el usar un flujo TCP en vez de un datagrama UDP es más sencillo y hay menos posibilidades de tener problemas. Sin embargo, cuando se requiere un rendimiento óptimo, y está justificado el tiempo adicional que supone realizar la verificación de los datos, los datagramas son un mecanismo realmente útil. En resumen, TCP parece más indicado para la implementación de servicios de red como un control remoto (rlogin, telnet) y transmisión de ficheros (ftp); que necesitan transmitir datos de longitud indefinida. UDP es menos complejo y tiene una menor sobrecarga sobre la conexión; esto hace que sea el indicado en la implementación de aplicaciones cliente/servidor en sistemas distribuidos montados sobre redes de área local. USO DE SOCKETS Podemos pensar que un Servidor Internet es un conjunto de sockets que proporciona capacidades adicionales del sistema, los llamados servicios. Puertos y Servicios

Cada servicio está asociado a un puerto. Un puerto es una dirección numérica a través de la cual se procesa el servicio. Sobre un sistema Unix, los servicios que proporciona ese sistema se indican en el fichero /etc/services, y algunos ejemplos son: daytime 13/udp ftp 21/tcp telnet 23/tcp telnet smtp 25/tcp mail http 80/tcp La primera columna indica el nombre del servicio. La segunda columna indica el puerto y el protocolo que está asociado al servicio. La tercera columna es un alias del servicio; por ejemplo, el servicio smtp, también conocido como mail, es la implementación del servicio de correo electrónico. Las comunicaciones de información relacionada con Web tienen lugar a través del puerto 80 mediante protocolo TCP. Para emular esto en Java, usaremos la clase Socket. La fecha (daytime). Sin embargo, el servicio que coge la fecha y la hora del sistema, está ligado al puerto 13 utilizando el protocolo UDP. Un servidor que lo emule en Java usaría un objeto DatagramSocket. LA CLASE URL La clase URL contiene contructores y métodos para la manipulación de URL (Universal Resource Locator): un objeto o servicio en Internet. El protocolo TCP necesita dos tipos de información: la dirección IP y el número de puerto. Vamos a ver como podemos recibir pues la página Web principal de nuestro buscador favorito al teclear: http://www.yahoo.com En primer lugar, Yahoo tiene registrado su nombre, permitiendo que se use yahoo.com como su dirección IP, o lo que es lo mismo, cuando indicamos yahoo.com es como si hubiesemos indicado 205.216.146.71, su dirección IP real. La verdad es que la cosa es un poco más complicada que eso. Hay un servicio, el DNS (Domain Name Service), que traslada www.yahoo.com a 205.216.146.71, lo que nos permite teclear www.yahoo.com, en lugar de tener que recordar su dirección IP. Si queremos obtener la dirección IP real de la red en que estamos corriendo, podemos realizar llamadas a los métodos getLocalHost() y getAddress(). Primero, getLocalHost() nos devuelve un objeto iNetAddress, que si usamos con getAddress() generará un array con los cuatro bytes de la dirección IP, por ejemplo: InetAddress direccion = InetAddress.getLocalHost(); byte direccionIp[] = direccion.getAddress(); Si la dirección de la máquina en que estamos corriendo es 150.150.112.145, entonces: direccionIp[0] = 150 direccionIp[1] = 150 direccionIp[2] = 112 direccionIp[3] = 145 Una cosa interesante en este punto es que una red puede mapear muchas direcciones IP. Esto puede ser necesario para un Servidor Web, como Yahoo, que tiene que soportar grandes cantidades de tráfico y necesita más de una dirección IP para poder atender a todo ese tráfico. El nombre interno para la dirección 205.216.146.71, por ejemplo, es www7.yahoo.com. El DNS puede trasladar una lista de direcciones IP asignadas a Yahoo en www.yahoo.com. Esto es una cualidad útil, pero por ahora abre un agujero en cuestión de seguridad. Ya conocemos la dirección IP, nos falta el número del puerto. Si no se indica nada, se utilizará el que se haya definido por defecto en el fichero de configuración de los servicios del sistema. En Unix se indican en el fichero /etc/services, en Windows-NT en el fichero services y en otros sistemas puede ser diferente. El puerto habitual de los servicios Web es el 80, así que si no indicamos nada, entraremos en el servidor de Yahoo por el puerto 80. Si tecleamos la URL siguiente en un navegador: http://www.yahoo.com:80 también recibiremos la página principal de Yahoo. No hay nada que nos impida cambiar el puerto en el que residirá el servidor Web; sin embargo, el uso del puerto 80 es casi estándar, porque elimina pulsaciones en el teclado y, además, las direcciones URL son lo suficientemente difíciles de recordar como para añadirle encima el número del puerto. Si necesitamos otro protocolo, como: ftp://ftp.microsoft.com

el puerto se derivará de ese protocolo. Así el puerto FTP de Microsoft es el 21, según su fichero services. La primera parte, antes de los dos puntos, de la URL, indica el protocolo que se quiere utilizar en la conexión con el servidor. El protocolo http (HyperText Transmission Protocol), es el utilizado para manipular documentos Web. Y si no se especifica ningún documento, muchos servidores están configurados para devolver un documento de nombre index.html. Con todo esto, Java permite los siguientes cuatro constructores para la clase URL: public URL( String spec ) throws MalformedURLException; public URL( String protocol,String host,int port,String file ) throws MalformedURLException; public URL( String protocol,String host,String file ) throws MalformedURLException; public URL( URL context,String spec ) throws MalformedURLException; Así que podríamos especificar todos los componenetes del URL como en: URL( "http","www.yahoo.com","80","index.html" ); o dejar que los sistemas utilicen todos los valores por defecto que tienen definidos, como en: URL( "http://www.yahoo.com" ); y en los dos casos obtendríamos la visualización de la página principal de Yahoo en nuestro navegador. DOMINIOS DE COMUNICACIONES El mecanismo de sockets está diseñado para ser todo lo genérico posible. El socket por sí mismo no contiene información suficiente para describir la comunicación entre procesos. Los sockets operan dentro de dominios de comunicación, entre ellos se define si los dos procesos que se comunican se encuentran en el mismo sistema o en sistemas diferentes y cómo pueden ser direccionados. Dominio Unix Bajo Unix, hay dos dominios, uno para comunicaciones internas al sistema y otro para comunicaciones entre sistemas. Las comunicaciones intrasistema (entre dos procesos en el mismo sistema) ocurren (en una máquina Unix) en el dominio Unix. Se permiten tanto los sockets stream como los datagrama. Los sockets de dominio Unix bajo Solaris 2.x se implementan sobre TLI (Transport Level Interface). En el dominio Unix no se permiten sockets de tipo Raw. Dominio Internet Las comunicaciones intersistemas proporcionan acceso a TCP, ejecutando sobre IP (Internet Protocol). De la misma forma que el dominio Unix, el dominio Internet permite tanto sockets stream como datagrama, pero además permite sockets de tipo Raw. Los sockets stream permiten a los procesos comunicarse a través de TCP. Una vez establecidas las conexiones, los datos se pueden leer y escribir a/desde los sockets como un flujo (stream) de bytes. Algunas aplicaciones de servicios TCP son: File Tranfer Protocol, FTP Simple Mail Transfer Protocol, SMTP TELNET, servicio de conexión de terminal remoto Los sockets datagrama permiten a los procesos utilizar el protocolo UDP para comunicarse a y desde esos sockets por medio de bloques. UDP es un protocolo no fiable y la entrega de los paquetes no está garantizada. Servicios UDP son: Simple Network Management Protocol, SNMP Trivial File Transfer Protocol, TFTP (versión de FTP sin conexión) Versatile Message Transaction Protocol, VMTP (servicio fiable de entrega punto a punto de datagramas independiente de TCP) Los sockets raw proporcionan acceso al Internet Control Message Protocol, ICMP, y se utiliza para comunicarse entre varias entidades IP. MODELO DE COMUNICACIONES CON JAVA En Java, crear una conexión socket TCP/IP se realiza directamente con el paquete java.net. A continuación mostramos un diagrama de lo que ocurre en el lado del cliente y del servidor:

El modelo de sockets más simple es: El servidor establece un puerto y espera durante un cierto tiempo (timeout segundos), a que el cliente establezca la conexión. Cuando el cliente solicite una conexión, el servidor abrirá la conexión socket con el método accept(). El cliente establece una conexión con la máquina host a través del puerto que se designe en puerto# El cliente y el servidor se comunican con manejadores InputStream y OutputStream Hay una cuestión al respecto de los sockets, que viene impuesta por la implementación del sistema de seguridad de Java. Actualmente, los applets sólo pueden establecer conexiones con el nodo desde el cual se transfirió su código. Esto está implementado en el JDK y en el intérprete de Java de Netscape. Esto reduce en gran manera la flexibilidad de las fuentes de datos disponibles para los applets. El problema si se permite que un applet se conecte a cualquier máquina de la red, es que entonces se podrían utilizar los applets para inundar la red desde un ordenador con un cliente Netscape del que no se sospecha y sin ninguna posibilidad de rastreo. 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; // Abrimos una conexión con breogan en el puerto 9999 // No podemos elegir un puerto por debajo del 1023 si no somos // usuarios con los máximos privilegios (root) try { s = new ServerSocket( 9999 ); } catch( IOException e ) { } // Creamos el objeto desde el cual atenderemos y aceptaremos // las conexiones de los clientes y abrimos los canales de // comunicación de entrada y salida try { cliente = s.accept(); sIn = new DataInputStream( cliente.getInputStream() ); sOut = new PrintStream( cliente.getOutputStream() ); // Cuando recibamos datos, se los devolvemos al cliente

}

// que los haya enviado while( true ) { texto = sIn.readLine(); sOut.println( texto ); } } catch( IOException e ) { System.out.println( e ); } }

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; // Establece el servidor en el socket 4321 (espera 300 segundos) try { s = new ServerSocket( 4321,300 ); } catch( IOException e ) { System.out.println( e ); } // Ejecuta un bucle infinito de listen/accept while( true ) { try { // Espera para aceptar una conexión s1 = s.accept(); // Obtiene un controlador de fichero de salida // con el socket s1out = s1.getOutputStream(); // Enviamos nuestro texto longCad = sendString.length(); for( int i=0; i < longCad; i++ ) s1out.write( (int)sendString.charAt( i ) ); // Cierra la conexión, pero no el socket del servidor s1.close(); } catch( IOException e ) { System.out.println( e ); } } }

asociado

} Mínimo Cliente TCP/IP El lado cliente de una aplicación TCP/IP descansa en la clase Socket. De nuevo, mucho del trabajo necesario para establecer la conexión lo ha realizado la clase Socket. Vamos a presentar ahora el código de nuestro cliente más simple, minimoCliente.java, que encaja con el servidor presentado antes. El trabajo que realiza este cliente es que todo lo que recibe del servidor lo imprime por la salida estándar del sistema. import java.awt.*; import java.net.*; import java.io.*; class minimoCliente { public static void main( String args[] ) throws IOException { int c; Socket s; InputStream sIn; // Abrimos una conexión con breogan en el puerto 4321 try { s = new Socket( "breogan",4321 ); } catch( IOException e ) { System.out.println( e ); } // Obtenemos un controlador de fichero de entrada del socket y // leemos esa entrada sIn = s.getInputStream(); while( ( c = sIn.read() ) != -1 ) System.out.print( (char)c ); // Cuando se alcance el fin de fichero, cerramos la conexión y // abandonamos s.close(); } }

EJECUTAR TCP/IP EN Windows '95 Las indicaciones que se proporcionan a continuación, van a permitirnos fijar los parámetros de Windows '95 para que se puedan ejecutar programas cliente y servidor, sin necesidad de que el ordenador en el cual se está ejecutando Windows '95 esté conectado a una red de área local. Esto puede resultar útil para mucha gente que está probando Java en su casa y no dispone de red, o incluso para aquellos programadores o estudiosos que quieren probar sus nuevos programas distribuidos pero no disponen de red o de Internet. Advertencia: Si los siguientes parámetros se van a fijar en un ordenador portátil que en ocasiones sí se conecte a una red, sería conveniente anotar los parámetros actuales de la

configuración de Windows '95, para que sean fácilmente recuperables cuando este ordenador se vuelva a conectar a la red. Configuración del TCP/IP de Windows '95 Hay que seguir los pasos que vamos a relatar a continuación, suponemos que se está ejecutando la versión española de Windows '95, en otras versiones puede que las opciones tengan nombre diferente: En el Panel de Control, seleccionar Red Picar Agregar, luego Protocolo y luego Agregar Seleccionar Microsoft y luego TCP/IP y picar en Aceptar En este momento probablemente se solicite la introducción de los discos de Windows '95, o del CD-ROM Seleccionar la pestaña Configuración de la ventana Red Seleccionar TCP/IP en la lista que aparece y picar Propiedades Seleccionar la pestaña Dirección IP Crearse una dirección IP, como por ejemplo: 102.102.102.102 Crearse una Máscara de subred, como: 255.255.255.0 Seleccionar la pestaña Configuración DNS y desactivar DNS Los valores correspondientes a las otras cuatro pestañas pueden dejarse los que hay por defecto Picar Aceptar (Opcional) Seleccionar la pestaña Identificación de la ventana Red (Opcional) Introducir un nombre para el ordenador, como por ejemplo: breogan (Opcional) Picar Aceptar Crear una entrada en la "red" Editar el fichero hosts.sam que está en el directorio de Windows Al final del fichero incorporar la dirección IP y el nombre del ordenador que se han introducido antes, en nuestro caso: 102.102.102.102 breogan Asegurarse de que la dirección IP y el nombre coinciden con la dirección IP que se ha fijado en el paso 7a de antes y que el nombre es el mismo que el indicado en el paso 12 anterior Salvar el fichero con el nombre "hosts" y reiniciar Windows '95 Comprobación de la red Abrir una sesión MS-DOS Teclear "ping breogan" Debería aparecer: Pinging breogan [102.102.102.102] with 32 bytes of data: Reply from 102.102.102.102: bytes=32 time=1ms TTL=32 Reply from 102.102.102.102: bytes=32 time<10ms TTL=32 Reply from 102.102.102.102: bytes=32 time<10ms TTL=32 Reply from 102.102.102.102: bytes=32 time<10ms TTL=32 Teclear "tracert 102.102.102.102" Debería aparecer: Tracing route to 102.102.102.102 over a maximum of 30 hops 1 <10 ms 1 ms <10 ms 102.102.102.102 Trace complete. En este instante, si todo ha ido bien, el ordenador está listo para funcionar como si estuviera en red. Dos o más programas que se comuniquen en red a través de sockets debería poder ejecutarse ahora dentro de los dominios del ordenador que acabamos de configurar Problemas más frecuentes Los tres problemas que pueden presentarse cuando intentemos comprobar el funcionamiento correcto de la red interna que acabamos de montar son: Cuando hacemos "ping" obtenemos "Bad IP address breogan" Intentar teclear "ping 102.102.102.102". Si ahora sí se obtiene réplica, la causa del problema es que el los pasos 12 de la Configuración y 3 de la Creación de la entrada en la tabla de hosts, no se ha introducido correctamente el nombre de la máquina. Comprobar esos pasos y que todo coincide. El programa cliente o el servidor fallan al intentar el "connect" La causa podría estar en que se produzca un fallo por fichero no encontrado en el directorio Windows/System de las librerías WINSOCK.DLL o WSOCK32.DLL. Muchos programas que se utilizan en Internet reemplazan estos ficheros cuando se instalan. Asegurarse de que están estos ficheros y que son los originales que vienen con la distribución de Windows '95.

El programa servidor dice que no puede "bind" a un socket Esto sucede porque tiene el DNS activado y no puede encontrar ese DNS o servidor de direcciones, porque estamos solos en la red. Asegurarse de que en el paso 8 de la Configuración la opción de DNS está deshabilitada.