Curso Java
Curso Java
PROGRAMACIÓN EN
JAVA
Dirección de Operación
Centro Nacional de Capacitación Sureste
INDICE
OBJETIVOS GENERALES 1
INTRODUCCIÓN 1
Módulo 1: Fundamentos y Características del lenguaje Java 2
Objetivo 2
Introducción 2
Java frente a los demás lenguajes 3
Java 2 Standard Development Kit 3
Mecanismos de seguridad 4
Indiferente a la arquitectura 5
Tipos de Datos, Identificadores e Instrucciones 6
Tipos de Datos 7
Identificadores 8
Palabras clave 8
Expresiones y Operadores 9
Módulo 2. Estructuras de Flujo y Control de Datos 12
Objetivo 12
Introducción 12
Estructuras de Control de Flujo 13
Instrucciones de Estructura de Datos y Bucles 14
Laboratorio Modulo 2. Estructuras de Control y Flujo de Datos 16
Módulo 3. Programación Orientada a Objetos 17
Objetivos 17
Introducción 17
Clases y objetos 17
Mensajes y métodos 18
Encapsulamiento 19
Herencia 19
Superclase y Subclases 20
Polimorfismo 20
Abstracción 20
Módulo 4. Trabajando con Clases y Objetos en Java 22
Objetivo 22
Introducción 22
Definición de clases 22
Constructores y creación de objetos 24
Variables y métodos de clase 27
Heredando clases en Java 31
Sobrecarga de métodos y de constructores 32
Sobreescritura de métodos 34
Clases abstractas 35
Control de acceso a miembros de una clase 35
Interfaces 36
Laboratorio Modulo 4. Clases y Objetos en Java 38
Módulo 5. Manejo de Excepciones y Recolección de Basura (Garbage Collector) 39
Objetivo 39
Introducción 39
Excepciones 39
Try...Catch...finally 40
Clase Exception 41
Capturar excepciones 41
Excepciones Predefinidas 42
Generar Excepciones 44
El Recolector de Basura ( Garbage Collector) 45
Laboratorio Modulo 5. Gestión de Excepciones 48
Módulo 6. Implementación de Objetos del Paquete Java Lang 49
Objetivo 49
Introducción 49
Clases del paquete [Link] 49
Interfaces 49
Clases 50
Boolean 50
Double 51
Float 51
Integer 51
Math 51
String 51
Concepto de inmutabilidad 51
Stringbuffer 52
System 52
Excepciones 52
Modulo 7. GUI de Java 54
Objetivo 54
Introducción 54
AWT Y SWING 54
Frames 54
Manejo de Eventos 55
Cómo Implementar un Manejador de Eventos 55
Reglas Generales del Uso de Componentes 57
Crear y configurar un Frame 58
Ejemplos Varios de Aplicacion de elementos de SWING 60
Laboratorio Modulo 7. GUI de Java 66
Módulo 8. Paquete [Link] 67
Objetivo 67
Introducción 67
Flujos de entrada y salida 67
Lectura y Escritura 68
Entrada y Salida estándar 69
[Link] 69
Manejo de Archivos 70
Ejemplo con File 71
Flujos (Streams) 72
Archivos de Acceso Aleatorio 74
Filtros 74
Clases que trabajan con caracteres 75
Serializacion de objetos 75
Laboratorio Modulo 8. Usando la librería [Link] 78
Módulo 9. Programación Multithreaded 79
Objetivo 79
Introducción 79
Hilos 79
Programas de flujo único 80
Programas de flujo múltiple 80
La clase Thread 82
Creación de un Thread 84
Manipulación de un Thread 88
Suspensión de un Thread 89
Parada de un Thread 89
Grupo de Hilos 90
Suspender y Reanudar Threads 91
Estados de un Threat ( sincronización 92
Nuevo Thread 92
Laboratorio Modulo 9. Uso de Hilos en Java 96
Modulo 10. JDBC 97
Objetivo 97
Introducción 97
El driver con la base de datos 97
Conectarnos con la base de datos 98
Creamos la tabla en Base de Datos ... y la borramos. 99
Insertar datos en la base de datos 100
Consultar datos de la base de datos 100
Update en la base de datos 101
Borrar datos de la base de datos 101
Cerrar conexiones 102
Laboratorio Modulo 10. JDBC 103
Conclusion 104
APENDICE . RESOLUCION DE LABORATORIOS 105
Respuesta Laboratorio Modulo 2. Estructuras de Flujo y Control de Datos 106
Respuesta Laboratorio Modulo 4. Clases y Métodos 108
Respuesta Laboratorio Modulo 5. Gestión de Excepciones 110
Respuesta Laboratorio Modulo 7. GUI de Java 111
Respuestas Laboratorio 8. Uso de la librería [Link] 117
Respuesta Laboratorio 9. Programación MultiThread 119
Respuestas Laboratorio 10. JDBC 121
OBJETIVOS GENERALES
Este curso está diseñado para programadores con conocimientos suficientes de metodología de la
programación y/o Programación Orientada a Objetos que deseen introducirse en este lenguaje
multiplataforma con el que se pueden realizar las más diversas aplicaciones, tanto para PC como para
Internet o dispositivos móviles , ya que la plataforma para dichas tecnologías es la misma
INTRODUCCIÓN
Objetivo
Mostrar y enseñar las características principales del lenguaje así como de las partes básicas de las cuales se
compone
Introducción
Java es un lenguaje orientado a objetos, eso implica que su concepción es muy próxima a la forma de pensar
humana. También posee otras características muy importantes:
Es un lenguaje que es compilado, generando archivos de clases compilados, pero estas clases compiladas,
son en realidad interpretadas por la máquina virtual de java. Siendo la máquina virtual de java la que
mantiene el control sobre las clases que se estén ejecutando.
Es un lenguaje multiplataforma: El mismo código java que funciona en un sistema operativo, funcionará en
cualquier otro sistema operativo que tenga instalada la máquina virtual java.
Es un lenguaje seguro: La máquina virtual, al ejecutar el código java, realiza comprobaciones de seguridad,
además el propio lenguaje carece de características inseguras, como por ejemplo los punteros.
Gracias al API de java podemos ampliar el lenguaje para que sea capaz de, por ejemplo, comunicarse con
equipos mediante red, acceder a bases de datos, crear páginas HTML dinámicas, crear aplicaciones visuales
al estilo Windows
Para poder trabajar con java es necesario emplear un software que permita desarrollar en java. Existen
varias alternativas comerciales en el mercado: JBuilder, Visual Age, Visual Café,... y un conjunto de
herramientas shareware, e incluso freeware, que permiten trabajar con java.
Pero todas estas herramientas en realidad se basan en el uso de una herramienta proporcionada por Sun
actualmente absorbido por ORACLE, el creador de java, que es el Java Development Kit (JDK). Nosotros nos
centraremos en el uso de dicha herramienta.
Existen diversas versiones del JDK, siendo posible obtener cualquiera de dichas versiones desde la propia
página de JAVA-ORACLE: [Link]
También es esto lo que ha hecho que soporte de modo nativo (no mediante el uso de librerías, como C) los
threads, siendo posible aprovechar las ventajas de los sistemas
multiprocesadores.
Las ventajas fundamentales de Java frente a otros lenguajes son el menor periodo de aprendizaje por parte
del programador, llegando a ser un programador productivo en menos tiempo (sencillez) y siendo posible
desarrollar aplicaciones más rápido que en otros lenguajes (sencillez y robustez), lo cual se traduce en el
mundo empresarial en un ahorro de costes.
Sus cualidades de distribuido, seguro e independencia de la plataforma lo hacen ideal para aplicaciones
relacionadas con el mundo web; precisamente a esto es a lo que Java debe su gran difusión y fama. El hecho
de que sea independiente de la máquina y del sistema operativo permite que distintas máquinas con
distintos sistemas operativos se conecten a una misma página web y ejecuten los mismos applets. Además
la seguridad que garantiza Java para los applets impiden que alguien trate de averiguar información sobre
los usuarios que se conectan a la página web o intente dañar sus máquinas.
Dicho entorno de programación es suministrado por ORACLE de forma gratuita, pudiéndose encontrar en la
dirección web: [Link]
Es de consenso que el entorno jdk no es el más adecuado para el desarrollo de aplicaciones Java, debido a
funcionar única y exclusivamente mediante comandos de consola, ya que hoy en día la programación se
suele ayudar de entornos visuales, como JBuilder, JCreator o muchos otros, que facilitan enormemente la
tarea (ver apéndice B). Sin embargo, puede ser un entorno bastante útil para aprender el lenguaje, ya que
aunque los entornos visuales nos hagan mucho trabajo siempre es necesario ir al código para modificarlo y
obtener el comportamiento deseado, lo cual quiere decir que necesitamos dominar el lenguaje y es más fácil
llegar a este dominio escribiendo códigos completos en un entorno “hostil” que no nos ayuda, que
simplemente remodelando códigos ya generados por entornos visuales.
Lenguaje simple
Java posee una curva de aprendizaje muy rápida. Resulta relativamente sencillo escribir Applets interesantes
desde el principio. Todos aquellos familiarizados con C++ encontrarán que Java es más sencillo, ya que se
han eliminado ciertas características, como los punteros. Debido a su semejanza con C y C++, y dado que la
mayoría de la gente los conoce aunque sea de forma elemental, resulta muy fácil aprender Java. Los
programadores experimentados en C++ pueden migrar muy rápidamente a Java y ser productivos en poco
tiempo.
Orientado a objetos
Java fue diseñado como un lenguaje orientado a objetos desde el principio. Los objetos agrupan en
estructuras encapsuladas tanto sus datos como los métodos (o funciones) que manipulan esos datos. La
tendencia del futuro, a la que Java se suma, apunta hacia la programación orientada a objetos,
especialmente en entornos cada vez más complejos y basados en red.
Distribuido
Java proporciona una colección de clases para su uso en aplicaciones de red, que permiten abrir sockets y
establecer y aceptar conexiones con servidores o clientes remotos, facilitando así la creación de aplicaciones
distribuidas.
Java es compilado, en la medida en que su código fuente se transforma en una especie de código máquina,
los bytecodes, semejantes a las instrucciones de [Link] otra parte, es interpretado, ya que los
bytecodes se pueden ejecutar directamente sobre cualquier máquina a la cual se hayan portado el
intérprete y el sistema de ejecución en tiempo real (run-time).
Robusto
Java fue diseñado para crear software altamente fiable. Para ello proporciona numerosas comprobaciones
en compilación y en tiempo de ejecución. Sus características de memoria liberan a los programadores de
una familia entera de errores (la aritmética de punteros), ya que se ha prescindido por completo los
punteros, y la recolección de basura elimina la necesidad de liberación explícita de memoria.
Seguro
Dada la naturaleza distribuida de Java, donde las applets se bajan desde cualquier punto de la Red, la
seguridad se impuso como una necesidad de vital importancia. A nadie le gustaría ejecutar en su
computadora programas con acceso total a su sistema, procedentes de fuentes desconocidas. Así que se
implementaron barreras de seguridad en el lenguaje y en el sistema de ejecución en tiempo real.
Mecanismos de seguridad
Java fue diseñado para ofrecer las siguientes medidas de seguridad básicas:
Uso de un lenguaje de programación seguro. El lenguaje de programación Java está diseñado para
ser seguro, como hemos comentado en el apartado anterior.
Integración de un sistema de control de permisos para los programas. Java define un mecanismo
(denominado mecanismo del cajón de arena) que permite controlar que se le permite hacer a un
programa y controlar como accede a los recursos.(GESTOR DE RECURSOS)
Encriptación y uso de certificados. Se definen mecanismos para que los programadores puedan
firmar el código, de manera que los usuarios puedan verificar quien es el propietario del código y
que este no ha sido modificado después de ser firmado.
1. El cargador de clases (Class Loader), que determina como y cuando pueden cargar código los
programas y garantiza que los componentes del sistema no han sido remplazados.
2. El verificador de archivos de clases (Class file verifier), que garantiza que el código tiene el formato
correcto, que el bytecode no viola las restricciones de seguridad de tipos de la JVM, que las pilas
internas no puedan desbordarse ni por arriba ni por abajo y que las instrucciones en bytecode
tengan parámetros de tipos correctos.
3. El gestor de seguridad (Security Manager), que controla el acceso a los recursos en tiempo de
ejecución. Los recursos sobre los que tiene control son múltiples: E/S de red y ficheros, creación de
cargadores de clases, manipulación de hilos de ejecución, ejecución de programas externos (del
SO), detener la JVM, cargar código nativo en la máquina virtual, realizar determinadas operaciones
en el entorno de ventanas o cargar ciertos tipos de clases.
Indiferente a la arquitectura
Java está diseñado para soportar aplicaciones que serán ejecutadas en los más variados entornos de red,
desde Unix a Windows Nt, pasando por Mac y estaciones de trabajo, sobre arquitecturas distintas y con
sistemas operativos diversos. Para acomodar requisitos de ejecución tan variopintos, el compilador de Java
genera bytecodes: un formato intermedio indiferente a la arquitectura diseñada para transportar el código
eficientemente a múltiples plataformas hardware y software. El resto de problemas los soluciona el
intérprete de Java.
Portable
La indiferencia a la arquitectura representa sólo una parte de su portabilidad. Además, Java especifica los
tamaños de sus tipos de datos básicos y el comportamiento de sus operadores aritméticos, de manera que
los programas son iguales en todas las plataformas. Estas dos últimas características se conocen como la
Máquina Virtual Java (JVM).
Multihebra
Hoy en día ya se ven como terriblemente limitadas las aplicaciones que sólo pueden ejecutar una acción a la
vez. Java soporta sincronización de múltiples hilos de ejecución (multithreading) a nivel de lenguaje,
especialmente útiles en la creación de aplicaciones de red distribuidas. Así, mientras un hilo se encarga de la
comunicación, otro puede interactuar con el usuario mientras otro presenta una animación en pantalla y
otro realiza cálculos.
Dinámico
El lenguaje Java y su sistema de ejecución en tiempo real son dinámicos en la fase de enlazado. Las clases
sólo se enlazan a medida que son necesitadas. Se pueden enlazar nuevos módulos de código bajo demanda,
procedente de fuentes muy variadas, incluso desde la Red.
Aplicaciones
Las aplicaciones Java están típicamente compiladas en un bytecode, aunque la compilación en código
máquina nativo también es posible. En el tiempo de ejecución, el bytecode es normalmente interpretado o
compilado a código nativo para la ejecución, aunque la ejecución directa por hardware del bytecode por un
procesador Java también es posible.
La implementación original y de referencia del compilador, la máquina virtual y las bibliotecas de clases de
Java fueron desarrolladas por Sun Microsystems en 1995. Desde entonces, Sun ha controlado las
especificaciones, el desarrollo y evolución del lenguaje a través del Java Community Process, si bien otros
han desarrollado también implementaciones alternativas de estas tecnologías de Sun, algunas incluso bajo
licencias de software libre. Actualmente Sun pertenece a ORACLE.
Las variables son localidades de memoria en las que pueden almacenarse datos. Cada una tiene un nombre,
un tipo y valor. Java tiene tres tipos de variables: de instancia, de clase y locales.
Variables de instancia.
Variables de clase.
Son similares a las variables de instancia, con la excepción de que sus valores son los mismos para
todas las instancias de la clase.
Variable locales.
* A diferencia de otros lenguajes, Java no tiene variables globales, es decir , variables que son vistas en
cualquier parte del programa.
Java es un lenguaje "fuertemente tipeado o tipificado" por lo que es necesario especificar el tipo de dato
para cada una de las variables que se vayan a utilizar en un programa. El nombre de la variable debe de ser
un identificador válido, y se usa para referirse a los datos que contiene una variable.
El tipo de una variable determina los valores que puede almacenar y las operaciones que se pueden hacer
sobre ella. Para dar a una variable un tipo y un nombre, se escribe una declaración de una variable, que
tiene la siguiente forma:
TipoDato nombreVariable;
Se pueden escribir varios nombres de variables del mismo tipo en una sola línea,
int x, y, z;
Tipos de Datos
Tipo primitivo.
Una variable de tipo primitivo contiene un solo valor del tamaño y formato apropiado de su tipo: un
número, un carácter, o un valor booleano.
La tabla siguiente lista los tipos de datos primitivos soportados por Java.
Números enteros
Números reales
Otros tipos
En Java, cualquier numérico con punto flotante automáticamente se considera double. Para que sea
considerado float se agrega una letra "f" o "F" al final del valor.
double d = 10.50;
float f = 10.50F;
Referencia.
Los arreglos, las clases y las interfaces son del tipo referencia. El valor de una variable del tipo referencia es
una dirección de un conjunto de valores representados por una variable.
Las referencias en Java no son como en C++, éstas son identificadores de instancias de alguna clase en
particular.
Ej.
Identificadores
Un identificador es una secuencia de caracteres comenzando por una letra y conteniendo letras y números.
Los identificadores no se limitan a los caracteres ASCII, si el editor de texto lo soporta, se pueden escribir
identificadores utilizando caracteres Unicode.
Las letras Java incluyen los caracteres ASCII A-Z y a-z. Los digitos Java incluyen los digitos ASCII 0-9. Para
propósitos de construir identificadores, los caracteres ASCII $ y _ son también considerados letras Java. No
hay un límite en lo concerniente al número de caracteres que pueden tener los identificadores.
Palabras clave
La siguiente tabla muestra las palabras claves de Java, éstas son reservadas y no pueden ser utilizadas como
identificadores.
true, false, and null no son palabras claves pero son palabras reservadas, así que tampoco pueden ser
utilizadas como identificadores.
Expresiones y Operadores
Expresión
Una expresión es una combinación de variables, operadores y llamadas de métodos construida de acuerdo a
la sintaxis del lenguaje que devuelve un valor.
El tipo de dato del valor regresado por una expresión depende de los elementos usados en la expresión.
Operadores
Los operadores son símbolos especiales que por lo común se utilizan en expresiones.
La tabla siguiente muestra los distintos tipos de operadores que utiliza Java.
Operadores aritméticos
+ Suma a+b
- Resta a-b
* Multiplicación a*b
/ División a/b
% Módulo a%b
Operadores de asignación
= Asignación a=b
Operadores relacionales
== Igualdad a == b
!= Distinto a != b
Operadores especiales
() Agrupación de expresiones a = (a + b) * c
La tabla siguiente muestra la precedencia asignada a los operadores, éstos son listados en orden de
precedencia.
Operador Notas
* / % Multiplicativos
+- Aditivos
== != Igualdad
|| OR lógico
?: Condicional
Todos los operadores binarios que tienen la misma prioridad (excepto los operadores de asignación) son
evaluados de izquierda a derecha.
Objetivo
Mostrar los diferentes mecanismos de flujos de control y bucles que el lenguaje Java le da Soporte y como
utilizarlos
Introducción
El control del flujo es la manera que tiene un lenguaje de programación de provocar que el flujo de la
ejecución avance y se ramifique en función de los cambios de estado de los datos. Java, en este aspecto, no
utiliza los principios de diseño orientado a objetos, sino que las sentencias de control del flujo del programa
se han tomado del C/C++. A continuación se tratan todos los mecanismos que proporciona Java para
conseguir este control y decidir qué partes del código ejecutar.
Sentencia
Una instrucción o sentencia representa la tarea más sencilla que se puede realizar en un programa.
Sentencias de expresión
Los siguientes tipos de expresiones pueden ser hechas dentro de una sentencia terminando la expresión con
punto y coma (;):
Expresiones de asignación
Cualquier uso de los operadores ++ y –
Llamada de métodos
Expresiones de creación de objetos
Ej.
int bValue;
double aValue = 8933.234;
String varCad;
Las sentencias de control de flujo determinan el orden en el cual serán ejecutadas otro grupo de sentencias.
Las sentencias if y for son ejemplos de sentencias de control de flujo.
Bloque de sentencias
Un bloque es un grupo de cero o más sentencias encerradas entre llaves ( { y } ). Se puede poner un bloque
de sentencias en cualquier lugar en donde se pueda poner una sentencia individual.
La sentencia if
if ( Expresion ){
sentencias;
}
if ( Expresion ){
sentenciasA;
}
else{
sentenciasB;
}
Si al evaluar la expresión se obtiene un valor booleano true se ejecutarán las sentenciasA, en caso contrario
se ejecutarán las sentenciasB.
La sentencia switch
Cuando se requiere comparar una variable con una serie de valores diferentes, puede utilizarse la sentencia
switch, en la que se indican los posibles valores que puede tomar la variable y las sentencias que se tienen
que ejecutar sí es que la variable coincide con alguno de dichos valores.
switch( variable ){
case valor1:
sentencias;
break;
case valor2:
sentencias;
break;
...
case valorN:
sentencias;
break;
default:
sentencias;
}
Cada case ejecutará las sentencias correspondientes, con base en el valor de la variable, que deberá de
evaluarse con valores de tipo byte, char, short o int.
Si el valor de la variable no coincide con ningún valor, entonces se ejecutan las sentencias por default, sí es
que las hay.
La sentencia break al final de cada case transfiere el control al final de la sentencia switch; de esta manera,
cada vez que se ejecuta un case todos los enunciados case restantes son ignorados y termina la operación
del switch.
El ciclo for
El ciclo for repite una sentencia, o un bloque de sentencias, mientras una condición se cumpla. Se utiliza la
mayoría de las veces cuando se desea repetir una sentencia un determinado número de veces.
for(inicialización;condición;incremento){
sentencias;
}
En su forma más simple, la inicialización es una sentencia de asignación que se utiliza para
establecer una variable que controle el ciclo.
La condición es una expresión que comprueba la variable que controla el ciclo y determinar cuando
salir del ciclo.
Los ciclos while y do-while, al igual que los ciclos for repiten la ejecución de un bloque de sentencias
mientras se cumpla una condición específica.
La sentencia while
La condición es una condición booleana, que mientras tenga el valor true permite que se ejecuten las
sentencias correspondientes.
La sentencia do-while
Al contrario de los ciclos for y while que comprueban una condición en lo alto del ciclo, el ciclo do-while la
examina en la parte más baja del mismo. Esta característica provoca que un ciclo do-while siempre se
ejecute por lo menos una vez.
do{
sentencias;
}while (condición);
break
La sentencia break tiene dos usos. El primer uso es terminar un case en la sentencia switch. El segundo es
forzar la terminación inmediata de un ciclo, saltando la prueba condicional normal del ciclo.
continue
La sentencia continue es similar a la sentencia break. Sin embargo, en vez de forzar la terminación del ciclo,
continue forza la siguiente iteración y salta cualquier código entre medias.
return
Se utiliza la sentencia return para provocar la salida del método actual; es decir, return provocará que el
programa vuelva al código que llamó al método.
La sentencia return puede regresar o no un valor. Para devolver un valor, se pone el valor después de la
palabra clave return.
return valor;
El tipo de dato del valor regresado debe ser el mismo que el que se especifica en la declaración del método.
return;
Objetivo. Utilizar las instrucciones de control de Java para generar los programas solicitados
1. Escribir un programa que solicite ingresar 10 notas de alumnos y nos informe cuántos tienen notas
mayores o iguales a 7 y cuántos menores.
2. Realizar un programa que permita cargar dos listas de 15 valores cada una. Informar con un mensaje cual
de las dos listas tiene un valor acumulado mayor (mensajes "Lista 1 mayor", "Lista 2 mayor", "Listas iguales")
Tener en cuenta que puede haber dos o más estructuras repetitivas en un algoritmo.
Objetivos
Introducción
El elemento básico de la programación orientada a objetos en Java es una clase. Una clase define la forma y
comportamiento de un objeto. Todo en Java gira alrededor de estos conceptos, por ello, una vez que se han
repasado los mecanismos básicos del lenguaje en cuestiones ya conocidas; esta sección se adentra en la
ampliación de los conceptos teóricos enfocados hacia el estilo de programación orientada a objetos de Java.
Clases y objetos
Objeto
Al igual que los objetos del mundo real, los objetos de software tienen un estado y un comportamiento. El
estado de los objetos se determina a partir de una o más variables y el comportamiento con la
implementación de métodos.
Como se observa en la figura, todos los objetos tienen una parte pública (su comportamiento) y una parte
privada (su estado). En este caso, hicimos una vista transversal pero desde el mundo exterior, el objeto se
observará como una esfera.
Clase
Una clase está formada por los métodos y las variables que definen las características cómunes a todos los
objetos de esa clase. Precisamente la clave de la OOP está en abstraer los métodos y los datos comunes a un
conjunto de objetos y almacenarlos en una clase.
Una clase equivale a la generalización de un tipo específico de objetos. Una instancia es la concreción de
una clase.
Clase X
Cada uno de los objetos tiene su propia copia de las variables definidas en la clase de la cual son
instanciados y comparten la misma implementación de los métodos.
Mensajes y métodos
El modelado de objetos no sólo tiene en consideración los objetos de un sistema, sino también sus
interrelaciones.
Mensaje
Los objetos interactúan enviándose mensajes unos a otros. Tras la recepción de un mensaje el objeto
actuará. La acción puede ser el envío de otros mensajes, el cambio de su estado, o la ejecución de cualquier
otra tarea que se requiera que haga el objeto.
Método
Un método se implementa en una clase, y determina cómo tiene que actuar el objeto cuando recibe un
mensaje.
Cuando un objeto A necesita que el objeto B ejecute alguno de sus métodos, el objeto A le manda un
mensaje al objeto B.
Al recibir el mensaje del objeto A, el objeto B ejecutará el método adecuado para el mensaje recibido.
Encapsulamiento
Como se puede observar de los diagramas, las variables del objeto se localizan en el centro o núcleo del
objeto. Los métodos rodean y esconden el núcleo del objeto de otros objetos en el programa. Al
empaquetamiento de las variables de un objeto con la protección de sus métodos se le llama
encapsulamiento. Típicamente, el encapsulamiento es utilizado para esconder detalles de la puesta en
práctica no importantes de otros objetos. Entonces, los detalles de la puesta en práctica pueden cambiar en
cualquier tiempo sin afectar otras partes del programa.
Modularidad, esto es, el código fuente de un objeto puede ser escrito, así como darle
mantenimiento, independientemente del código fuente de otros objetos. Así mismo, un objeto
puede ser transferido alrededor del sistema sin alterar su estado y conducta.
Ocultamiento de la información, es decir, un objeto tiene una "interfaz publica" que otros objetos
pueden utilizar para comunicarse con él. Pero el objeto puede mantener información y métodos
privados que pueden ser cambiados en cualquier tiempo sin afectar a los otros objetos que
dependan de ello.
Los objetos proveen el beneficio de la modularidad y el ocultamiento de la información. Las clases proveen
el beneficio de la reutilización. Los programadores de software utilizan la misma clase, y por lo tanto el
mismo código, una y otra vez para crear muchos objetos.
Herencia
La herencia es un mecanismo que permite la definición de una clase a partir de la definición de otra ya
existente. La herencia permite compartir automáticamente métodos y datos entre clases, subclases y
objetos.
La herencia está fuertemente ligada a la reutilización del código en la OOP. Esto es, el código de cualquiera
de las clases puede ser utilizado sin más que crear una clase derivada de ella, o bien una subclase.
Hay dos tipos de herencia: Herencia Simple y Herencia Múltiple. La primera indica que se pueden definir
nuevas clases solamente a partir de una clase inicial mientras que la segunda indica que se pueden definir
nuevas clases a partir de dos o más clases iniciales. Java sólo permite herencia simple.
Superclase y Subclases
El concepto de herencia conduce a una estructura jerárquica de clases o estructura de árbol, lo cual significa
que en la OOP todas las relaciones entre clases deben ajustarse a dicha estructura.
En esta estructura jerárquica, cada clase tiene sólo una clase padre. La clase padre de cualquier clase es
conocida como su superclase. La clase hija de una superclase es llamada una subclase.
o A es la superclase de B, C y D.
o D es la superclase de E.
o B, C y D son subclases de A.
o E es una subclase de D.
Polimorfismo
Otro concepto de la OOP es el polimorfismo. Un objeto solamente tiene una forma (la que se le asigna
cuando se construye ese objeto) pero la referencia a objeto es polimórfica porque puede referirse a objetos
de diferentes clases (es decir, la referencia toma múltiples formas). Para que esto sea posible debe haber
una relación de herencia entre esas clases. Por ejemplo, considerando la figura anterior de herencia se
tiene que:
Una referencia a un objeto de la clase B también puede ser una referencia a un objeto de la clase A.
Una referencia a un objeto de la clase C también puede ser una referencia a un objeto de la clase A.
Una referencia a un objeto de la clase D también puede ser una referencia a un objeto de la clase A.
Una referencia a un objeto de la clase E también puede ser una referencia a un objeto de la clase D.
Una referencia a un objeto de la clase E también puede ser una referencia a un objeto de la clase A.
Abstracción
Volviendo a la figura anterior de la relación de herencia entre clases, se puede pensar en una jerarquía de
clase como la definición de conceptos demasiado abstractos en lo alto de la jerarquía y esas ideas se
convierten en algo más concreto conforme se desciende por la cadena de la superclase.
Sin embargo, las clases hijas no están limitadas al estado y conducta provistos por sus superclases; pueden
agregar variables y métodos además de los que ya heredan de sus clases padres. Las clases hijas pueden,
también, sobreescribir los métodos que heredan por implementaciones especializadas para esos métodos.
De igual manera, no hay limitación a un sólo nivel de herencia por lo que se tiene un árbol de herencia en el
que se puede heredar varios niveles hacia abajo y mientras más niveles descienda una clase, más
especializada será su conducta.
Objetivo
Codificar en lenguaje Java los conceptos de Programación Orientada a Objetos
Introducción
Una vez que hemos entendido la parte teórica de la programación orientada a objetos , es fundamental
poder aterrizarla en código, ya que de lo contrario no nos serviría de nada. Dicha aplicación de la teoría en
código es realmente sencilla y como vera a continuación el participante
Definición de clases
La definición de una clase especifica cómo serán los objetos de dicha clase, esto es, de que variables y de
que métodos constarán.
}
Como se puede observar, la definición de una clase consta de dos partes fundamentales:
La declaración de la clase
El cuerpo de la clase
El cuerpo de la clase sigue a la declaración de la clase y está contenido entre la pareja de llaves ({ y }). El
cuerpo de la clase contiene las declaraciones de las variables de la clase, y también la declaración y la
implementación de los métodos que operan sobre dichas variables.
El estado de un objeto está representado por sus variables (variables de instancia). Las variables de instancia
se declaran dentro del cuerpo de la clase. Típicamente, las variables de instancia se declaran antes de la
declaración de los métodos, pero esto no es necesariamente requerido.
Implementación de métodos
Los métodos de una clase determinan los mensajes que un objeto puede recibir.
Las partes fundamentales de un método son el valor de retorno, el nombre, los argumentos (opcionales) y
su cuerpo. Además, un método puede llevar otros modificadores opcionales que van al inicio de la
declaración del método y que se analizarán más adelante. La sintaxis de un método es la siguiente:
sentencias;
Los métodos en Java pueden ser creados únicamente como parte de una clase. Cuando se llama a un
método de un objeto se dice comúnmente que se envia un mensaje al objeto.
/* [Link] */
class Usuario
{
String nombre;
int edad;
String direccion;
void setNombre(String n)
{
nombre = n;
}
String getNombre()
{
return nombre;
}
void setEdad(int e)
{
edad = e;
}
int getEdad()
{
return edad;
}
void setDireccion(String d)
{
direccion = d;
}
String getDireccion()
{
return direccion;
}
}
Una vez que se tiene definida la clase a partir de la cual se crearán los objetos se está en la posibilidad de
instanciar los objetos requeridos.
Para la clase Usuario del ejemplo anterior podemos crear un objeto de la siguiente manera:
La primera línea corresponde a la declaración del objeto, es decir, se declara una variable del tipo de objeto
deseado.
El operador new
El operador new crea una instancia de una clase asignando la cantidad de memoria necesaria de acuerdo al
tipo de objeto. El operador new se utiliza en conjunto con un constructor. El operador new regresa una
referencia a un nuevo objeto.
Constructores
Un constructor es un tipo específico de método que siempre tiene el mismo nombre que la clase, y que se
utiliza cuando se desean crear objetos de dicha clase, es decir, se utiliza al crear e iniciar un objeto de una
clase.
Constructores múltiples
Cuando se declara una clase en Java, se pueden declarar uno o más constructores (constructores múltiples)
opcionales que realizan la iniciación cuando se instancia un objeto de dicha clase.
Para la clase Usuario del ejemplo anterior no se especificó ningún constructor, sin embargo, Java
proporciona un constructor por omisión que inicia las variables del objeto a sus valores predeterminados.
Ej.
/* [Link] */
class ProgUsuario
{
public static void main(String args[])
{
Usuario usr1, usr2; /* Se declaran dos objetos de la clase Usuario */
boolean si_no;
if(si_no == true)
[Link]("\nEl objeto usr1 SI es instancia de Usuario.");
else
[Link]("\nEl objeto usr1 NO es instancia de Usuario.");
if(si_no == true)
[Link]("\nEl objeto usr2 SI es instancia de Usuario.");
else
[Link]("\nEl objeto usr2 NO es instancia de Usuario.");
}
}
Una vez que se ha creado un objeto, seguramente se querrá hacer algo con él. Tal vez se requiera obtener
información de éste, se quiera cambiar su estado, o se necesite que realice alguna tarea.
Para accesar a las variables de un objeto se utiliza el operador punto ( . ). La sintaxis es la siguiente:
[Link];
Para llamar a los métodos de un objeto, se utiliza también el operador punto ( . ). La sintaxis es la siguiente:
Ejemplo
/* [Link] */
class Usuario2
{
String nombre;
int edad;
String direccion;
direccion = null;
}
Usuario2(Usuario2 usr)
{
nombre = [Link]();
edad = [Link]();
direccion = [Link]();
}
void setNombre(String n)
{
nombre = n;
}
String getNombre()
{
return nombre;
}
void setEdad(int e)
{
edad = e;
}
int getEdad()
{
return edad;
}
void setDireccion(String d)
{
direccion = d;
}
String getDireccion()
{
return direccion;
}
}
Ejemplo
/* [Link] */
class ProgUsuario2
{
[Link](usr1);
[Link](usr2);
}
}
Variables de clase
Las variables de clase son variables cuyos valores son los mismos para la clase y para todas sus instancias.
Para indicar que una variable es una variable de clase se utiliza la palabra clave static en la declaración de la
variables:
static tipoVariable nombreVariable;
Métodos de clase
Los métodos de clase al igual que las variables de clase, se aplican a la clase como un todo y no a sus
instancias.
Se utiliza de igual manera la palabra clave static para indicar que un método es un método de clase:
{
/* cuerpo del método */
}
Para acceder a las variables o métodos de clase se utiliza el mismo operador punto ( . ).
Aunque se puede acceder a las variables y métodos de clase a través de un objeto, está permitido y se
recomienda utilizar mejor el nombre de la clase,
/* Utilizar esto */
[Link];
[Link]();
/* en lugar de esto */
[Link];
[Link]();
Ejemplo
/* [Link] */
class Usuario3
{
static char MAS = 'm';
static char FEM = 'f';
String nombre;
int edad;
String direccion;
char sexo;
Usuario3( )
{
nombre = null;
edad = 0;
direccion = null;
sexo = '\0';
}
Usuario3(Usuario3 usr)
{
nombre = [Link]();
edad = [Link]();
direccion = [Link]();
sexo = [Link]();
}
void setNombre(String n)
{
nombre = n;
}
String getNombre( )
{
return nombre;
}
void setEdad(int e)
{
edad = e;
}
int getEdad()
{
return edad;
}
void setDireccion(String d)
{
direccion = d;
}
String getDireccion( )
{
return direccion;
}
void setSexo(char s)
{
sexo = s;
}
char getSexo( )
{
return sexo;
}
/* [Link] */
class ProgUsuario3
{
ProgUsuario3(String nombre)
{
[Link] = nombre;
}
Usuario3 usr1,usr2,usr3,usr4;
[Link](usr1);
[Link]( "\t Total: " +[Link]() );
[Link](usr2);
[Link]( "\t Total: " +[Link]() );
[Link](usr3);
[Link]( "\t Total: " +ProgUsuario3.NUM_USUARIOS );
[Link](usr4);
[Link]( "\t Total: " +getNumUsuarios() +"\n");
[Link](usr4);
[Link]( "\t Total: " +[Link]() );
[Link](usr3);
[Link]( "\t Total: " +[Link]() );
[Link](usr2);
[Link]( "\t Total: " +ProgUsuario3.NUM_USUARIOS );
[Link](usr1);
[Link]( "\t Total: " +getNumUsuarios() +"\n");
}
}
El concepto de herencia conduce a una estructura jerárquica de clases o estructura de árbol, lo cual significa
que en la OOP todas las relaciones entre clases deben ajustarse a dicha estructura.
En esta estructura jerárquica, cada clase tiene sólo una clase padre. La clase padre de cualquier clase es
conocida como su superclase. La clase hija de una superclase es llamada una subclase.
De manera automática, una subclase hereda las variables y métodos de su superclase (más adelante se
explica que pueden existir variables y métodos de la superclase que la subclase no puede heredar. Véase
Modificadores de Acceso). Además, una subclase puede agregar nueva funcionalidad (variables y métodos)
que la superclase no tenía.
Para crear una subclase, se incluye la palabra clave extends en la declaración de la clase.
En Java, la clase padre de todas las clases es la clase Object y cuando una clase no tiene una superclase
explícita, su superclase es Object.
La sobrecarga de métodos es la creación de varios métodos con el mismo nombre pero con diferentes
firmas y definiciones. Java utiliza el número y tipo de argumentos para seleccionar cuál definición de método
ejecutar.
Java diferencia los métodos sobrecargados con base en el número y tipo de argumentos que tiene el
método y no por el tipo que devuelve.
Tambien existe la sobrecarga de constructores: Cuando en una clase existen constructores múltiples, se dice
que hay sobrecarga de constructores.
Ejemplo
/* Métodos sobrecargados */
int calculaSuma(int x, int y, int z){
...
}
int calculaSuma(double x, double y, double z){
...
}
Ejemplo
/* [Link] */
class Usuario4
{
String nombre;
int edad;
String direccion;
{
[Link] = nombre;
[Link] = edad;
[Link] = direccion;
}
Usuario4(Usuario4 usr)
{
nombre = [Link]();
edad = [Link]();
direccion = [Link]();
}
void setNombre(String n)
{
nombre = n;
}
String getNombre()
{
return nombre;
}
void setEdad(float e)
{
edad = (int)e;
}
int getEdad()
{
return edad;
}
void setDireccion(String d)
{
direccion = d;
}
String getDireccion()
{
return direccion;
}
}
Ejemplo
/* [Link] */
class ProgUsuario4
{
void imprimeUsuario(Usuario4 usr)
{
// [Link] equivale en este caso a [Link]()
[Link]("\nNombre: " + [Link] );
[Link]("Edad: " + [Link]() );
[Link]("Direccion: " + [Link]() +"\n");
}
[Link](50);
[Link](30.45f);
[Link](usr1);
[Link](usr2);
}
}
Sobreescritura de métodos
Una subclase hereda todos los métodos de su superclase que son accesibles a dicha subclase a menos que la
subclase sobreescriba los métodos.
Una subclase sobreescribe un método de su superclase cuando define un método con las mismas
características ( nombre, número y tipo de argumentos) que el método de la superclase.
Las subclases emplean la sobreescritura de métodos la mayoría de las veces para agregar o modificar la
funcionalidad del método heredado de la clase padre.
Ejemplo
class ClaseA
{
void miMetodo(int var1, int var2)
{ ... }
String miOtroMetodo( )
{ ... }
}
String miOtroMetodo( )
{ ... }
}
Clases abstractas
Una clase que declara la existencia de métodos pero no la implementación de dichos métodos (o sea, las
llaves { } y las sentencias entre ellas), se considera una clase abstracta.
Una clase abstracta puede contener métodos no-abstractos pero al menos uno de los métodos debe ser
declarado abstracto.
Para declarar una clase o un método como abstractos, se utiliza la palabra reservada abstract.
Una clase abstracta no se puede instanciar pero si se puede heredar y las clases hijas serán las encargadas
de agregar la funcionalidad a los métodos abstractos. Si no lo hacen así, las clases hijas deben ser también
abstractas.
Los modificadores más importantes desde el punto de vista del diseño de clases y objetos, son los que
permiten controlar la visibilidad y acceso a los métodos y variables que están dentro de una clase.
Uno de los beneficios de las clases, es que pueden proteger a sus variables y métodos (tanto de instancia
como de clase) del acceso desde otras clases.
Java soporta cuatro niveles de acceso a variables y métodos. En orden, del más público al menos público
son: público (public), protegido (protected), sin modificador (también conocido como package) y privado
(private).
(*) Los miembros (variables y metodos) de clase (static) si son visibles. Los miembros de instancia no son
visibles.
Como se observa de la tabla anterior, una clase se ve a ella misma todo tipo de variables y métodos (desde
los public hasta los private); las demas clases del mismo paquete (ya sean subclases o no) tienen acceso a los
miembros desde los public hasta los sin-modificador. Las subclases de otros paquetes pueden ver los
miembros public y a los miembros protected, éstos últimos siempre que sean static ya de no ser así no serán
visibles en la subclase (Esto se explica en la siguiente página). El resto del universo de clases (que no sean ni
del mismo paquete ni subclases) pueden ver sólo los miembros public.
Interfaces
Una interface es una variante de una clase abstracta con la condición de que todos sus métodos deben ser
asbtractos. Si la interface va a tener atributos, éstos deben llevar las palabras reservadas static final y con un
valor inicial ya que funcionan como constantes por lo que, por convención, su nombre va en mayúsculas.
interface Nomina
{
public static final String EMPRESA = "Patito, S. A.";
public void detalleDeEmpleado(Nomina obj);
}
Una clase implementa una o más interfaces (separadas con comas ",") con la palabra reservada implements.
Con el uso de interfaces se puede "simular" la herencia múltiple que Java no soporta.
En este ejemplo, la clase Empleado tiene una clase padre llamada Object (implícitamente) e implementa a la
interface Nomina, quedando el diagrama de clases de la siguiente manera:
1. Confeccionar una clase que represente un empleado. Definir como atributos su nombre y su
sueldo. Confeccionar los métodos para la carga, otro para imprimir sus datos y por último uno que
imprima un mensaje si debe pagar impuestos (si el sueldo supera a 3000)
2. Implementar la clase operaciones. Se deben cargar dos valores enteros, calcular su suma, resta,
multiplicación y división, cada una en un método, imprimir dichos resultados.
Objetivo
Introducción
Una excepción es un evento que ocurre durante la ejecución de un programa y detiene el flujo normal de la
secuencia de instrucciones de ese programa; en otras palabras, una excepción es una condición anormal que
surge en una secuencia de código durante su ejecución.
Las excepciones en Java están destinadas, al igual que en el resto de los lenguajes que las soportan, para la
detección y corrección de errores. Si hay un error, la aplicación no debería morirse y generar un core (o un
crash en caso del DOS). Se debería lanzar (throw) una excepción que a su vez debería capturar (catch) y
resolver la situación de error, o poder ser tratada finalmente (finally) por un gestor por defecto u omisión.
Java sigue el mismo modelo de excepciones que se utiliza en C++. Utilizadas en forma adecuada, las
excepciones aumentan en gran medida la robustez de las aplicaciones.
Excepciones
Las excepciones son la manera que ofrece Java de manejar los errores en tiempo de ejecución. Muchos
lenguajes imperativos, cuando tenían un error de este clase lo que hacían era detener la ejecución del
programa. Las excepciones nos permiten escribir código que nos permita manejar ese error y continuar (si lo
estimamos conveniente) con la ejecución del programa.
El error en ejecución más clásico en Java es el de desbordamiento. Es decir, el intento de acceso a una
posición de un vector que no existe. Por ejemplo:
[Link]
public class Desbordamiento {
static String mensajes[] = {"Primero", "Segundo", "Tercero" };
public static void main(String[] args) {
for (int i=0; i<=3; i++)
[Link](mensajes[i]);
[Link]("Ha finalizado la ejecución");
}
}
Este programa tendrá un serio problema cuando intente acceder a mensajes[3], pues no existe dicho valor.
Al ejecutarlo nos dirá lo siguiente (o algo parecido):
Primero
Segundo
Tercero
Nos da un error de ejecución (en esta terminología se diría que se lanzó una excepción) al intentar acceder a
dicho valor inexistente. Vemos que, por ahora, el comportamiento de nuestro código es el mismo que en los
lenguajes imperativos. Cuando encuentra el error, se para la ejecución. Ahora veremos cómo evitar esto.
Try...Catch...finally
Existe una estructura que nos permitirá capturar excepciones, es decir, reaccionar a un error de ejecución.
De este modo podremos imprimir mensajes de error "a la medida" y continuar con la ejecución del
programa si consideramos que el error no es demasiado grave. Para ver cómo funcionan vamos a modificar
el ejemplo anterior, pero asegurándonos ahora de que capturamos las excepciones:
[Link]
Dentro del bloque que sigue a try colocamos el código a ejecutar. Es como si dijeramos que vamos a intentar
ejecutar dicho código a ver qué pasa. Después de try deberemos colocar al menos un bloque catch o un
bloque finally, pudiendo tener ambos e incluso más de un bloque catch.
En el bloque finally ponemos el código que se ejecutará siempre, tanto si se lanza la excepción como si no.
Su utilidad no es mucha, ya que si se permite continuar con la ejecución del programa basta con poner el
código después del bloque try...catch. En nuestro ejemplo podríamos haber puesto lo siguiente:
try {
for (int i=0; i<=3; i++)
[Link](mensajes[i]);
}
catch ( ArrayIndexOutOfBoundsException e ) {
[Link]("El asunto se nos ha desbordado");
}
[Link]("Ha finalizado la ejecución");
Clase Exception
Cuando se lanza una excepción, en nuestro mundo orientado objetos lo que se hace es lanzar una instancia
de Exception o de una clase derivada de él. Normalmente las clases derivadas no añaden mucha
funcionalidad (muchas veces ninguna en absoluto), pero al ser distintas nos permiten distinguir entre los
distintos tipos de excepciones.
En el programa anterior, por ejemplo, en el bloque catch capturábamos una excepción del tipo
ArrayIndexOutOfBoundsException, ignorando cualquier otro tipo de excepción.
Esta clase tiene como cositas interesantes dos constructores y dos métodos (tiene más métodos pero sólo
vamos a ver éstos):
String [Link]()
void [Link]()
Capturar excepciones
Ahora sabemos lo suficiente como para entender cómo funcionan los catch. Entre paréntesis, cual
parámetro se pone la declaración de una excepción, es decir, el nombre de una clase derivada de Exception
(o la misma Exception) seguido por el nombre de la variable. Si se lanza una excepción que es la que
deseamos capturar o una derivada de la misma se ejecutará el código que contiene el bloque. Así, por
ejemplo:
catch (Exception e) {
...
Se ejecutará siempre que se produzca una excepción del tipo que sea, ya que todas las excepciones se
derivan de Exception. No es recomendable utilizar algo así ya que estamos capturando cualquier tipo de
excepciones sin saber si eso afectará a la ejecución del programa o no.
Se pueden colocar varios bloques catch. En ese caso, se comprobará en orden si la excepción lanzada es la
que deseamos capturar y si no pasa a comprobar la siguiente. Eso sí, sólo se ejecuta un bloque catch. En
cuanto captura la excepción deja de comprobar los demás bloques. Por eso, lo siguiente:
catch (Exception e) {
...
catch (DerivadaDeException e) {
...
Excepciones Predefinidas
Los nombres de las excepciones indican la condición de error que representan. Las siguientes son las
excepciones predefinidas más frecuentes que se pueden encontrar:
ArithmeticException
int i = 12 / 0;
NullPointerException
Se produce cuando se intenta acceder a una variable o método antes de ser definido:
Image img;
paint( Graphics g ) {
[Link]( img,25,25,this );
IncompatibleClassChangeException
El intento de cambiar una clase afectada por referencias en otros objetos, específicamente cuando esos
objetos todavía no han sido recompilados.
ClassCastException
y = (Prueba)x; // donde
x no es de tipo Prueba
NegativeArraySizeException
OutOfMemoryException
¡No debería producirse nunca! El intento de crear un objeto con el operador new ha fallado por falta de
memoria. Y siempre tendría que haber memoria suficiente porque el garbage collector se encarga de
proporcionarla al ir liberando objetos que no se usan y devolviendo memoria al sistema.
NoClassDefFoundException
ArrayIndexOutOfBoundsException
UnsatisfiedLinkException
Se hizo el intento de acceder a un método nativo que no existe. Aquí no existe un método [Link]
class A {
InternalException
Este error se reserva para eventos que no deberían ocurrir. Por definición, el usuario nunca debería ver este
error y esta excepción no debería lanzarse.
Generar Excepciones
Cuando se produce un error se debería generar, o lanzar, una excepción. Para que un método en Java,
pueda lanzar excepciones, hay que indicarlo expresamente.
Se pueden definir excepciones propias, no hay por qué limitarse a las predefinidas; bastará con extender la
clase Exception y proporcionar la funcionalidad extra que requiera el tratamiento de esa excepción.
También pueden producirse excepciones no de forma explícita como en el caso anterior, sino de forma
implícita cuando se realiza alguna acción ilegal o no válida.
Las excepciones, pues, pueden originarse de dos modos: el programa hace algo ilegal (caso normal), o el
programa explícitamente genera una excepción ejecutando la sentencia throw (caso menos normal). La
sentencia throw tiene la siguiente forma:
throw ObtejoExcepction;
class melon {
Si compilamos y ejecutamos esta aplicación Java, obtendremos la siguiente salida por pantalla:
[Link]: / by zero
at [Link]([Link])
Una de los aportes más relevantes de Java es la inclusión del recolector de basura ( garbage collector) tanto
que hoy en día es un estándar y ni se menciona como una característica del lenguaje. Java no lo inventó,
pero si aportó mucho para su adopción.
El GC corre automáticamente en la JVM cada cierto tiempo. No hay forma de proveer cuando lo hará (o si lo
hará) ni de forzar a que corra (o que no lo haga). Existen varias estrategias de implementación y es todo un
tema que sigue en desarrollo e investigación. Lo mejor de todo es que como usuarios de la plataforma
tenemos muy poco que hacer para utilizarlo.
Referencias
La regla para que un objeto pueda ser recolectado es sencilla, no debe de haber referencias al objeto.
....
Empleado a = new Empleado("God"); // a es la 1ra referencia
Empleado b = a; // b es la segunda referencia al mismo objeto "God"
...
En el ejemplo anterior se creó un objeto de tipo Empleado y hay dos referencias a él (a y b). Este objeto no
será recolectado por el gc y su memoria seguirá en uso.
a = new Empleado("inez"); // "a" ahora apunta a otro objeto solo queda una: b
b = null; // b ahora no apunta a nada, ya no quedan referencias al objeto "God"
Entonces el espacio de memoria de nuestro primer empleado puede ser reclamado por el gc y liberado.
Cuando se trata de variables creadas en un método ( ya sea en el cuerpo o en los parámetros ) las
referencias se limpian al terminar la ejecución del método.
Ejemplo
Un ejemplo muy ilustrador del uso de memoria, del garbage collector y los memory leaks es la siguiente
implementación de una pila.
class Stack {
}
/** Devuelve el elemento del tope de la pila */
public Object pop() {
checkLowerLimit();
data[index--];
}
Lo malo es que pop solamente decrementa el apuntador, pero lo que se haya almacenado ahí, sigue ahí. Si
se pone un objeto y luego este objeto se elimina, y el buen programador se encarga de eliminar todas las
referencias, el gc no podrá hacer su trabajo por que aún existe una referencia en el arreglo. Solamente se
perderá hasta que otro objeto ocupe ese espacio, pero mientras tanto el objeto ahí queda inaccesible y no
reclamable.
....
public void produce() {
Request request = [Link]();
[Link]( request ); // 2 referencias al objeto request...
}
public void consume() {
RequestExecutor executor = ...
[Link]( [Link]() ); // deja un request en la memoria interna del stack que no puede ser
reclamable
}
...
...
public void pop() {
Object value = peek();
checkLowerLimit();
data[index--] = null; // eliminamos la referencia y decrementamos el indice
return value;
Se crea una referencia "value" pero esta se pierde al terminar el método. La referencia que estaba dentro
del arreglo se pierde también al apuntarlo a otro lado ( null en este caso ) y no más memory leaks.
Objetivo. Generar la captura de excepciones de una aplicación de Java y generar las propias
2. Cree su propia clase de excepción utilizando la palabra clave extends. Escriba un constructor para dicha
clase que tome un argumento String y lo almacene dentro del objeto como una referencia de tipo String.
Escriba un método que muestre la cadena de caracteres almacenada. Cree una cláusula try-catch para
probar la nueva excepción.
Objetivo
Mostrar como utilizar algunas de los objetos mas utilizados dentro del paquete [Link] que ofrece el
lenguaje de Java
Introducción
Antes de nada queremos recordar que el paquete [Link] se importa automáticamente. Esto quiere decir
que no es necesario poner en la clase donde se vaya a hacer uso de este paquete la cláusula o instrucción
correspondiente “import [Link]”. Dentro de este paquete están gran parte de las clases más utilizadas
dentro de las aplicaciones o programas creados con tecnología Java.
[Link](cadena);
onde vemos que en el código del Programa no ha sido necesario hacer uso de la clausula import [Link]
para poder utilizar la clase String. Obtenemos la siguiente salida:
Ahora vamos a proceder a repasar muy rápidamente un poco tanto las interfaces, como las clases
principales del paquete, así como las posibles excepciones y errores que se pueden producir. En los
siguientes epígrafes haremos un repaso rápido y teórico para luego hacer ejercicios y ejemplos.
Interfaces
Las interfaces que consideramos principales del paquete [Link] son las siguientes:
Cloneable
Comparable
Iterable
Estas 3 interfaces nos definen los métodos necesarios a definir en nuestra clase para que puedan
implementar dicha interfaz. Por un lado la interfaz Cloneable nos obliga a implementar el método
denominado clone() que aparece en la clase Object y que sirve para crear una copia o clon de un objeto.
Y que lo que debe hacer es comparar 2 objetos de la misma clase claseK y se debe devolver -1, 0 o 1 si el
objeto que llama al método es respectivamente menor, igual o mayor que el objeto especificado en el
método por objeton.
Por último la interfaz Iterable <T> nos obligaría a definir el método siguiente:
Iterator<T> iterator()
Clases
En cuanto a lo que respecta a clases, todas son casi imprescindibles de conocer, ya que son básicas y por
eso están en el paquete [Link] pero vamos a destacar unas cuantas por considerarlas principales.
Boolean Math
Double String
Float StringBuffer
Integer System
Boolean
La clase Boolean ya la conocemos. Es lo que se llama un wrap o envoltorio, que es una clase que permite
manejar los datos equivalentes de tipo primitivo. En este caso la clase Boolean es un wrap del tipo primitivo
boolean. Los métodos de esta clase permiten el manejo de los valores primitivos true o false, su
modificación o su comparación ya que implementa la interfaz Comparable.
Double
Es la clase wrap correspondiente al tipo primitivo double, por lo que lo métodos son muy parecidos a los de
la clase Boolean, pero manejando los tipos primitivos para double. Permitiendo obtener, modificar,
comparar, etc valores de tipo double.
Float
Al igual que las anteriores también es un wrap pero para el tipo básico o primitivo float.
Integer
Esta es quizás la clase de todos los wrappers más utilizada con diferencia y por tanto maneja tipos primitivos
de tipo int. Tiene una gran cantidad de métodos sobre todo para poder convertir el entero a otros tipos
como long, float, double, etc.
Math
La clase Math tiene una gran cantidad de métodos para poder hacer operaciones matemáticas, como las
funciones sin (double a) que calcula el seno del valor a, tan (double a) que calcula la tangente de a, etc.
String
La clase String, quizás hasta más utilizada que la Integer, es una clase que permite la definición y manejo de
cadenas de caracteres. Pero un inconveniente posible es que se define como constante y tras su creación no
puede ser cambiada (se dice que es inmutable, como explicaremos más adelante).
Por ejemplo si quisiéramos tener una cadena con valores “abc” bastaría con definir una variable de la Clase
String de la siguiente manera:
La amplia funcionalidad y manejo de String se puede ver en la gran cantidad de métodos que disponemos
en esta clase para su manejo, donde podemos hacer comparaciones entre cadenas, comparaciones
ignorando mayúsculas (muy útil cuando por ejemplo queremos comparar si la contraseña de un usuario es
la correcta), concatenación de cadenas, consulta de carácter en una posición determinada de la cadena,
reemplazar caracteres, convertir a mayúsculas, o minúsculas, etc.
Concepto de inmutabilidad
Veamos el significado de inmutable, que no es precisamente sencillo de explicar. Consideremos que un
objeto ocupa un espacio de memoria. Ahora diremos que hay dos tipos de objetos:
a) Objetos mutables: son aquellos cuyo espacio de memoria puede ser reemplazado por un contenido
definido con posterioridad a la creación del objeto. En este caso, cuando usamos métodos
modificadores es el objeto original el que sufre el cambio
b) Objetos inmutables: son aquellos cuyo espacio de memoria permanece ocupado con el objeto tal y
como se creó inicialmente. ¿Significa esto que no podemos usar métodos modificadores? No, no
significa eso. Significa que cuando usamos métodos modificadores en vez de redefinirse el objeto
original, la variable apuntadora pasa a apuntar a un nuevo objeto que se crea, permaneciendo el
original en su espacio de memoria, podemos decir que en algunos casos “inaccesible”, pero
estando.
En Java, la mayoría de los objetos "esenciales" son inmutables: Long, Integer, Boolean, String, , etc. Un
objeto se considera inmutable si su estado no puede cambiarse luego de ser construido. Este
comportamiento está ampliamente aceptado como una estrategia robusta para crear código simple y
confiable. La inmutabilidad se considera efectiva en aplicaciones concurrentes (hablamos de concurrencia
para referirnos a dos tareas que transcurren durante el mismo intervalo de tiempo) donde la mutabilidad
(cambiar los objetos su estado), podría dar lugar a que los objetos se corrompieran o llegaran a tener un
estado inconsistente.
Nosotros en principio no debemos preocuparnos por el costo de crear nuevos objetos frente a actualizar el
mismo objeto. En general usar objetos inmutables es eficiente y solo en casos muy específicos tendríamos
que preocuparnos por soluciones que ahorraran la multiplicación de objetos.
Aunque no “nos preocupemos” por ella, sí es deseable que tengamos el concepto de inmutabilidad claro.
Stringbuffer
Es una versión mejorada o ampliada de la clase String, ya que permite su modificación después de su
creación, es decir, los objetos de tipo StringBuffer son objeto mutables. El método más utilizado de esta
clase es insert, que permite insertar cualquier objeto de cualquier tipo como String a la secuencia de
StringBuffer.
System
Por último la clase System es una de las clases más usuales. Define tres campos err, in y out que
respectivamente son la salida estándar de error, entrada estándar y salida estándar. Los métodos para el
manejo de estas entradas y salidas estándares de datos son bastante utilizados, al igual que el método
currentTimeMillis() que devuelve la fecha actual en milisegundos. También es muy usual el uso del
métodogetenv() para acceder a variables de entorno o propiedades del sistema. Cuando se invoca este
método, se nos devuelve información sobre el sistema con que estamos trabajando.
Excepciones
Aunque más adelante trataremos el uso y gestión de excepciones, queremos mencionar las más usuales de
este paquete. Adelantar que una excepción se produce cuando ocurre un evento, un cálculo, o una
ejecución de una instrucción falla produciéndose un problema por el que esa instrucción, cálculo o evento
no se puede realizar.
ArithmeticException: se produce cuando hay un problema aritmético, como por ejemplo una
división por cero.
Objetivo
Mostrar como crear aplicaciones en Java que tengan una interfaz grafica por medio de las librerías AWT y
SWING
Introducción
Java incluye como parte de su biblioteca de clases estándar un conjunto de componentes para crear
interfaces gráficas de usuario, los cuales permiten crear interfaces portables.
AWT Y SWING
Los componentes que se utilizan en Java para crear interfaces gráficas de usuario se agrupan en dos
paquetes :
Los componentes awt dependen de las facilidades gráficas ofrecidas por cada sistema operativo : los
programas escritos con AWT tendrán un « look and feel » distinto en windows y en UNIX.
[Link]
SWING es 100% java y por tanto es independiente de la plataforma, los componentes gráficos se pintan en
tiempo de ejecución por lo que las aplicaciones SWING suelen ser más lentas que las de AWT.
En la práctica las aplicaciones Java con interfaces gráficas de usuario suelen mezclar AWT y SWING, asi AWT
se encarga de toda la gestión de eventos y SWING ofrece componentes más sofisticados.
Frames
Los frames o marcos son ventanas en las que se pueden colocar otros controles de los que se utilizan para
crear las intefaces gráficas de usuario (botones, etiquetas, cajas de texto, listas desplegables, menús, etc).
Los frames son ventanas que puede tener borde y barra de título con botones de maximizar / minimizar /
cerrar
AWT SWING
[Link] [Link]
Manejo de Eventos
Cada vez que el usuario teclea un caracter o pulsa un botón del ratón, ocurre un evento. Todo lo que tiene
que hacer es implementar el interface apropiado y ser registrado como un oyente de evento del evento
fuente apropiado. Los componentes Swing puede generar muchas clases de evento.
1. Donde se declare la clase del manejador de eventos, el código especifica que la clase o implementa
un interface de oyente, o desciende una clase que implementa un interface de oyente. Por
ejemplo.
2. El código que registra un ejemplar de la clase de manejo de eventos de un oyeNte sobre uno o más
componentes. Por ejemplo.
[Link](instanceOfMyClass);
Un escenario de manejo de eventos típico ocurre con los botones (JButton). Para detectar cuando el usuario
pulsa un botón de la pantalla (o pulsa la tecla equivalente), un programa debe tener un objeto que
implementa el interface ActionListener.
El programa debe registrar este objeto como un oyente de actión del botón (la fuente del evento), usando el
método addActionListener. Cuando el usuario pulsa el botón de la pantalla, éste dispara el evento action,
Esto resulta en una llamada al método actionPerformed del oyente de action, el único método del interface
ActionListener). El único argumento del método es un objeto ActionEvent que ofrece información sobre el
evento y su fuente.
ActionEvent
Los contenedores de alto nivel proporcionan el marco de trabajo en el que existen los componentes de
peso ligero. Específicamente, un contenedor de alto nivel Swing proporciona un área en el que los
componentes de peso ligero Swing pueden dibujarse a sí mismos. Los contenedores de alto nivel Swing
también proporcionan otras características Swing como el espacio para una barra de menú, manejo
avanzado de eventos y dibujo, y soporte de accesibilidad.
En general, todo componente Swing debería tener un contenedor Swing de alto nivel por encima en el árbol
de contenidos. Por ejemplo, todo applet que contenga componentes Swing debería estar implementado
como una subclase de JApplet (que es una subclase de Applet). De forma similar, toda ventana principal que
contenga componentes Swing debería ser implementada con un JFrame.
Aquí tenemos una imagen del árbol de contenido de un GUI de un típico programa Swing que implementa
una ventana que contiene dos botones, un campo de texto y una lista.
El panel de contenido de las figuras anteriores es el Contenedor normal que hay debajo de cada contenedor
Swing.
Aquí está el código que construye el GUI mostrado en las figuras anteriores.
[Link](button1);
[Link](button2);
[Link](panel);
Método Propósito
JFrame(String)
DISPOSE_ON_CLOSE
Existen diferentes formas de instanciar el Jframe, veamos otros ejemplos de hola mundo con swing y awt :
En el siguiente ejemplo la clase que hereda de JFrame, en ella se crea una instancia de JFrame, se configura
y se muestra; todo el código va incluido dentro del método main() (único método de la clase).
En este otro ejemplo la clase que hereda de JFrame, con un método constructor de la propia clase que es el
que crea y muestra la ventana, el método main() simplemente instancia un ejemplar de la clase.
La salida para ambos ejemplos será una Ventana como la que se muestra a continuación en la imagen
[Link](300, 300);
[Link](true);
}
//Constructor de la clase
public DosBotones() {
[Link]( al );
[Link]( "Soy el JBoton 1" );
add( boton1 );
[Link]( al );
[Link]( "Soy el JBoton 2" );
add( boton2 );
[Link]( "Soy el JCampoDeTexto" );
add( texto );
}
[Link]( true );
}
}
Ejemplo 3 : [Link]
getContentPane().setLayout(new FlowLayout());
getContentPane().add(entrada);
getContentPane().add(lno);
getContentPane().add(tno);
getContentPane().add(lca);
getContentPane().add(tca);
getContentPane().add(lpa);
getContentPane().add(tpa);
getContentPane().add(b);
getContentPane().add(salida);
getContentPane().add(sno);
getContentPane().add(tsno);
getContentPane().add(sca);
getContentPane().add(tsca);
getContentPane().add(spa);
getContentPane().add(tspa);
[Link](false);
[Link](false);
[Link](false);
[Link](false);
[Link](false);
[Link](false);
// acción por defecto al cerrar la ventana: salir del programa
[Link](JFrame.EXIT_ON_CLOSE);
}
public void actionPerformed(ActionEvent e)
{
String nombres = [Link]();
[Link](nombres);
[Link]("");
[Link]("");
[Link]("");
}
public static void main(String ar[])
{
AplicacionDatosSwingSetVisible fr =
new AplicacionDatosSwingSetVisible("Entrada Swing");
[Link](1000, 300);
[Link](true);
}
}
Ejemplo 4 : [Link]
//Constructor de la clase
public InterfaceGrafica( String titulo)
{
//Se invoca el constructor del padre (JFrame) el cual recibe el titulo del frame
super(titulo);
getContentPane().setLayout(new FlowLayout());
if([Link]() == b1){
[Link]("Hola Mundo");
[Link](true);
}
if([Link]() == b2){
[Link]([Link]());
[Link](true);
}
}
//Función principal
public static void main(String ar[])
{
//Se instancia la clase
InterfaceGrafica gui = new InterfaceGrafica ("Hola Mundo Swing");
[Link](1000, 300);
[Link](true);
}
}
Cuando se desplace la posición del marcador de la escala con el ratón, la otra escala se debe de actualizar
inmediatamente para reflejar la misma temperatura en la otra unidad de medida.
Objetivo
Mostrar como utilizar los objetos contenidos dentro del paquete [Link] para el manejo de archivos y
obtención de datos a través del buffer
Introducción
Todo dato tiene un origen de entrada o un destino de salida (convenientemente en inglés: I/O). Un
programa trabaja con datos que provienen de una entrada la cual puede ser un teclado, un archivo en disco
o un socket a través de la red. Así mismo un programa genera resultados enviándolos a una salida la cual
puede ser la pantalla, un archivo en disco o un socket a través de la red. Como podemos comprender hay
distintos orígenes de entrada y destinos de salida para los datos, en Java todos estos se consideran flujos (en
inglés: streams) de datos para los cuales disponemos, en la API estándar, del paquete [Link] especializado
en la tarea de poder controlar estos flujos con cierta abstracción sobre el origen de los mismos, gracias a
esto siempre podemos recurrir al mismo grupo de clases independientemente del caso a tratar.
Así que en Java podemos tener un flujo de entrada (en ingles: input stream) por el cual recibimos datos
desde el exterior de nuestro programa, o un flujo de salida (en ingles: output stream) el cual envía nuestros
datos hacia un cierto destino.
Flujos binarios – byte (8-bit): Es el tipo de flujo mas primitivo y portable, de echo cualquier otro
tipo de flujo esta construido sobre este porque hablando a bajo nivel todas las operaciones de I/O
son flujos de bytes. Nos permitirá trabajar adecuadamente con datos binarios tales como archivos
de imagen, sonido, etc. Las clases principales para manejar estos flujos son las clases abstractas
InputStream y OutputStream de las cuales heredan otras sub-clases que implementan en formas
mas concretas la misma tarea.
Flujos de caracteres – char (16-bit): Es un tipo de flujo de caracteres en codificación Unicode, listo
para la internacionalización, ideal para trabajar con texto plano. Las clases principales para manejar
estos flujos son las clases abstractas Reader y Writer de las cuales heredan otras sub-clases que
implementan en formas mas concretas la misma tarea. Cualquiera de estas clases realiza la
conversión correspondiente de byte a char para leer o de char a byte para escribir.
Esquema con las clases principales del paquete [Link]: InputStream, OutputStream, Reader, Writer, y sus
sub-clases directas:
Lectura y Escritura
Lectura
La clase InputStream:
int read() - Lee el próximo byte del flujo representado en un entero. Devuelve -1 si no quedan mas
datos que leer.
int read(byte[] b, int off, int len) – Lee un arreglo de bytes del flujo, desde y hasta la posición
indicada.
La clase Reader:
int read() – Lee el próximo carácter del flujo representado en un entero. Devuelve -1 si no quedan
mas datos que leer.
int read(char[] cbuf, int off, int len) – Lee un arreglo de caracteres del flujo, desde y hasta la
posición indicada.
Escritura
La clase OutputStream:
void write(byte[] b, int off, int len) – Escribe una porción de un arreglo de bytes en el flujo.
La clase Writer:
void write(char[] cbuf, int off, int len) - Escribe una porción de un arreglo de caracteres en el flujo.
La clase System, en el paquete [Link] de la API estándar, posee referencias estáticas hacia tres objetos
que representan flujos de datos ya abiertos y preparados para poder usarse. Estos flujos son:
Cualquiera de estos flujos pueden ser redirigidos hacia otras fuentes o destinos.
[Link]
Es un tipo de excepción generalizada que es lanzada cuando algún tipo de operación I/O ah fallado. Esta
excepción debe de ser tratada ya sea manejándola con un bloque de código try-catch en el mismo momento
que se realice la operación o dejando que se propague hacia arriba en la pila de llamadas utilizando throws
IOException.
Manejo de Archivos
Clases de archivos
Los archivos también se distinguen por:
Por su contenido:
- Archivos tipo texto.
- Archivos binarios.
Por su modo de acceso:
- Archivos secuenciales.
- Archivos Random.
Clase File:
File: Representa un archivo y las operaciones que se pueden hacer con él. No incluye las operaciones de
lectura y escritura en archivo.
Constructores
Métodos
import [Link].*;
}
else
{
[Link]("El Archivo " + ar[0] + " NO tiene permiso de escritura...");
}
//[Link]();
[Link]("Ruta Absoluta: " + [Link]());
}
}
Flujos (Streams)
Flujo: Un flujo representa un objeto que se puede utilizar para realizar una entrada o una salida de datos,
representa un canal de información de donde se puede leer o escribir información.
Existen dos tipos de flujo en Java: unos que trabajan con bytes y otros que trabajan con caracteres.
FileInputStream CharArrayReader
FilterInputStream FilterReader
ObjectInputStream InputStreamReader
PipedInputStream PipeReader
StringBufferInputStream StringReader
ByteArrayOutputStream BufferedWriter
FileOutputStream CharArrayWriter
FilterOutputStream FilterWriter
ObjectOutputStream OutputStreamWriter
PipedOutputStream PipedWriter
PrintWriter
StringWriter
import [Link].*;
public class PruebaFlujoCaracteres{
public static void main(String ar[])
throws IOException
{
String nombreArchivo="[Link]";
FileWriter escribir;
try{
escribir = new FileWriter(nombreArchivo);
for(char c='a'; c<='z'; c++){
[Link](c);
}
[Link]();
}catch (IOException e){
[Link]("Imposible abrir el archivo para escribir");
}
FileReader leer;
int c;
try{
leer = new FileReader(nombreArchivo);
c= [Link]();
while (c != -1){
[Link]((char)c);
c= [Link]();
}
[Link]();
}catch(IOException e){
[Link]("Imposible abrir el archivo para leer");
}
}
}
Son archivos que permiten acceder a ellos de forma aleatoria indicando la posición en la que se quiere
operar.
Clase RandomAccessFile: clase para el acceso aleatorio a archivos. Permite utilizar los archivos en modo
lectura y escritura simultáneamente.
Ejemplo
import [Link].*;
try{
}
}
Filtros
Son clases que representan flujos que se conectan a otros flujos que ya existen para, leyendo a través de los
mismos, transformar los datos proporcionando métodos de lectura o escritura más apropiados para un
programador.
import [Link].*;
public class LeaArchivoTexto
{
public static void main(String ar[]) throws IOException
{
String entrada;
if( [Link] == 0)
{
[Link]("Falta el archivo a desplegar.....");
[Link](-1);
}
Serializacion de objetos
Este mecanismo extiende las clases Java para I/O que vienen en su núcleo básico para que brinden soporte
para objetos.
Soporta la codificación de objetos como Stream de bytes y soporta la reconstrucción complementaria del
grado del objeto desde el Stream (decodificación).
Los objetos a ser almacenados y recuperados tienen que implementar las interfaces Serializable. Los
atributos no deben ser static.
Los objetos a ser almacenados y recuperados pueden referirse a otros objetos; estos objetos TIENEN QUE
SER almacenados y recuperados al mismo tiempo para mantener la relación entre los objetos: Cuando un
objeto es almacenado, TODOS los objetos que son invocados desde este objeto son almacenados también.
Se requiere lo siguiente:
Después de grabar los objetos cerramos los archivos ObjectOutputStream y FileOutputStream así:
[Link]();
[Link]();
[Link];
[Link];
Ejemplo
import [Link].*;
import [Link].*;
Objetivo. Crear un programa que utilice los objetos de la librería [Link] según los requerimientos
pedidos
1. Crear un archivo que capture 2 números que capture el usuario y hacer una suma. El programa debe
de usar objeto Stream para la captura de los datos por parte del usuario
2. Crear un archivo de texto en C: que contenga la cadena ABCD y crear un programa en java que pueda
abrir y leer el contenido de dicho archivo. El contenido del archivo debe de ser desplegado en consola.
Mostrar como crear una aplicación en java que maneje múltiples subprocesos y poderlos controlar y
comunicarse entre ellos
Introducción
Considerando el entorno multithread (multihilo), 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 hilo 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.
Hilos
Todos los hilos 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 hilos (threads) se parecen en su
funcionamiento a lo que muestra la figura siguiente:
Hay que distinguir multihilo (multithread) de multiproceso. El multiproceso se refiere a dos programas que
se ejecutan "aparentemente" a la vez, bajo el control del Sistema Operativo. Los programas no necesitan
tener relación unos con otros, simplemente el hecho de que el usuario desee que se ejecuten a la vez.
Multihilo se refiere a que dos o más tareas se ejecutan "aparentemente" a la vez, dentro de un mismo
programa.
Se usa "aparentemente" en ambos casos, porque normalmente las plataformas tienen una sola CPU, con lo
cual, los procesos se ejecutan en realidad "concurrentemente", sino que comparten la CPU. En plataformas
con varias CPU, sí es posible que los procesos se ejecuten realmente a la vez.
en el caso del multihilo, como el programa debe ser diseñado expresamente para que pueda soportar esta
característica, es imprescindible que el autor tenga que planificar adecuadamente la ejecución de cada hilo,
o tarea.
Actualmente hay diferencias en la especificación del intérprete de Java, porque el intérprete de Windows
’95 conmuta los hilos de igual prioridad mediante un algoritmo circular (round-robin), mientras que el de
Solaris 2.X deja que un hilo ocupe la CPU indefinidamente, lo que implica la inanición de los demás.
Aquí, cuando se llama a main(), la aplicación imprime el mensaje y termina. Esto ocurre dentro de un único
hilo de ejecución (thread).
Debido a que la mayor parte de los entornos operativos no solían ofrecer un soporte razonable para
múltiples hilos de control, los lenguajes de programación tradicionales, tales como C++, no incorporaron
mecanismos para describir de manera elegante situaciones de este tipo. La sincronización entre las múltiples
partes de un programa se llevaba a cabo mediante un bucle de suceso único. Estos entornos son de tipo
síncrono, gestionados por sucesos. Entornos tales como el de Macintosh de Apple, Windows de Microsoft y
X11/Motif fueron diseñados en torno al modelo de bucle de suceso.
En la aplicación de saludo, no se ve el hilo de ejecución que corre el programa. Sin embargo, Java posibilita
la creación y control de hilos de ejecución explícitamente. La utilización de hilos (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 hilos de ejecución, 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 hilos en Java. Habrá
observado que dos applets 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 hiloss,
sino que el navegador es multihilo, multihilvanado o multithreaded.
Los navegadores utilizan diferentes hilos ejecutándose en paralelo para realizar varias tareas,
"aparentemente" concurrentemente. Por ejemplo, en muchas páginas web, se puede desplazar la página e ir
leyendo el texto antes de que todas las imágenes estén presentes en la pantalla. En este caso, el navegador
está trayéndose las imágenes en un hilo de ejecución y soportando el desplazamiento de la página en otro
hilo diferente.
Las aplicaciones (y applets) multihilo 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
hilo de ejecución para cada subtarea.
Mientras que los programas de flujo único pueden realizar su tarea ejecutando las subtareas
secuencialmente, un programa multihilo permite que cada thread comience y termine tan pronto como sea
posible. Este comportamiento presenta una mejor respuesta a la entrada en tiempo real.
Vamos a crear un programa llamado saludo creando tres hilos de ejecución individuales, que imprimen cada
uno de ellos su propio mensaje de saludo:
Antes de entrar en más profundidades en los hilos de ejecución, se propone una referencia rápida de la
clase Thread.
La clase Thread
Es la clase que encapsula todo el control necesario sobre los hilos de ejecución (threads). Hay que distinguir
claramente un objeto Thread de un hilo de ejecución o thread. Esta distinción resulta complicada, aunque se
puede simplificar si se considera al objeto Thread como el panel de control de un hilo de ejecución (thread).
La clase Thread es la única forma de controlar el comportamiento de los hilos y para ello se sirve de los
métodos que se exponen en las secciones siguientes.
Métodos de Clase
Estos son los métodos estáticos que deben llamarse de manera directa en la clase Thread.
currentThread()
Este método devuelve el objeto thread que representa al hilo de ejecución que se está ejecutando
actualmente.
yield()
Este método hace que el intérprete cambie de contexto entre el hilo actual y el siguiente hilo ejecutable
disponible. Es una manera de asegurar que nos hilos de menor prioridad no sufran inanición.
sleep( long )
El método sleep() provoca que el intérprete ponga al hilo en curso a dormir durante el número de
milisegundos que se indiquen en el parámetro de invocación. Una vez transcurridos esos milisegundos,
dicho hilo volverá a estar disponible para su ejecución. Los relojes asociados a la mayor parte de los
intérpretes de Java no serán capaces de obtener precisiones mayores de 10 milisegundos, por mucho que se
permita indicar hasta nanosegundos en la llamada alternativa a este método.
Métodos de Instancia
Aquí no están recogidos todos los métodos de la clase Thread, sino solamente los más interesantes, porque
los demás corresponden a áreas en donde el estándar de Java no está completo, y puede que se queden
obsoletos en la próxima versión del JDK, por ello, si se desea completar la información que aquí se expone se
ha de recurrir a la documentación del interfaz de programación de aplicación (API) del JDK.
start()
Este método indica al intérprete de Java que cree un contexto del hilo del sistema y comience a ejecutarlo. A
continuación, el método run() de este hilo será invocado en el nuevo contexto del hilo. Hay que tener
precaución de no llamar al método start() más de una vez sobre un hilo determinado.
run()
El método run() constituye el cuerpo de un hilo en ejecución. Este es el único método del interfaz Runnable.
Es llamado por el método start() después de que el hilo apropiado del sistema se haya inicializado. Siempre
que el método run() devuelva el control, el hilo actual se detendrá.
stop()
Este método provoca que el hilo se detenga de manera inmediata. A menudo constituye una manera brusca
de detener un hilo, especialmente si este método se ejecuta sobre el hilo en curso. En tal caso, la línea
inmediatamente posterior a la llamada al método stop() no llega a ejecutarse jamás, pues el contexto del
hilo muere antes de que stop() devuelva el control. Una forma más elegante de detener un hilo es utilizar
alguna variable que ocasione que el método run() termine de manera ordenada. En realidad, nunca se
debería recurrir al uso de este método.
suspend()
El método suspend() es distinto de stop(). suspend() toma el hilo y provoca que se detenga su ejecución sin
destruir el hilo de sistema subyacente, ni el estado del hilo anteriormente en ejecución. Si la ejecución de un
hilo se suspende, puede llamarse a resume() sobre el mismo hilo para lograr que vuelva a ejecutarse de
nuevo.
resume()
El método resume() se utiliza para revivir un hilo suspendido. No hay garantías de que el hilo comience a
ejecutarse inmediatamente, ya que puede haber un hilo de mayor prioridad en ejecución actualmente, pero
resume() ocasiona que el hilo vuelva a ser un candidato a ser ejecutado.
setPriority( int )
El método setPriority() asigna al hilo la prioridad indicada por el valor pasado como parámetro. Hay
bastantes constantes predefinidas para la prioridad, definidas en la clase Thread, tales como MIN_PRIORITY,
NORM_PRIORITY y MAX_PRIORITY, que toman los valores 1, 5 y 10, respectivamente. Como guía
aproximada de utilización, se puede establecer que la mayor parte de los procesos a nivel de usuario
deberían tomar una prioridad en torno a NORM_PRIORITY. Las tareas en segundo plano, como una
entrada/salida a red o el nuevo dibujo de la pantalla, deberían tener una prioridad cercana a MIN_PRIORITY.
Con las tareas a las que se fije la máxima prioridad, en torno a MAX_PRIORITY, hay que ser especialmente
cuidadosos, porque si no se hacen llamadas a sleep() o yield(), se puede provocar que el intérprete Java
quede totalmente fuera de control.
getPriority()
Este método devuelve la prioridad del hilo de ejecución en curso, que es un valor comprendido entre uno y
diez.
setName( String )
Este método permite identificar al hilo con un nombre menmónico. De esta manera se facilita la depuración
de programas multihilo. El nombre mnemónico aparecerá en todas las líneas de trazado que se muestran
cada vez que el intérprete Java imprime excepciones no capturadas.
getName()
Este método devuelve el valor actual, de tipo cadena, asignado como nombre al hilo en ejecución mediante
setName().
Creación de un Thread
Hay dos modos de conseguir hilos de ejecución (threads) en Java. Una es implementando el interfaz
Runnable, la otra es extender la clase Thread.
La implementación del interfaz Runnable es la forma habitual de crear hilos. Los 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. El interfaz define el trabajo y la clase, o
clases, que implementan el interfaz para realizar ese trabajo. Los diferentes grupos de clases que
implementen el interfaz tendrán que seguir las mismas reglas de funcionamiento.
Hay una cuantas diferencias entre interfaz y clase, que ya son conocidas y aquí solamente se resumen.
Primero, un interfaz 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, un interfaz no puede implementar cualquier método. Una clase que implemente un
interfaz debe implementar todos los métodos definidos en ese interfaz. Un interfaz tiene la posibilidad de
poder extenderse de otros interfaces y, al contrario que las clases, puede extenderse de múltiples
interfaces. Además, un interfaz no puede ser instanciado con el operador new; por ejemplo, la siguiente
sentencia no está permitida:
...
El ejemplo anterior crea una nueva clase MiThread que extiende la clase Thread y sobreescribe el método
[Link]() 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:
Thread t;
En este caso necesitamos crear una instancia de Thread antes de que el sistema pueda ejecutar el proceso
como un hilo. Además, el método abstracto run() está definido en el interfaz Runnable y 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 está la oportunidad de extender la clase MiThread, si fuese necesario. La mayoría
de las clases creadas que necesiten ejecutarse como un hilo, implementarán el interfaz Runnable, ya que
probablemente extenderán alguna de su funcionalidad a otras clases.
No pensar que el interfaz 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 se observan los fuentes de Java, se puede comprobar que solamente contiene un
método abstracto:
package [Link];
Y esto es todo lo que hay sobre el interfaz Runnable. Como se ve, un interfaz 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 da idea de lo que realmente está pasando:
...
[Link]() ;
...
De este trocito de código se desprende que la clase Thread también implemente el interfaz Runnable.
[Link]() se asegura de que la clase con que trabaja (la clase que va a ejecutarse como un hilo) 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 hilo.
A continuación se presenta un ejemplo, que implementa el interfaz Runnable para crear un programa
multihilo.
class java1001 {
static public void main( String args[] ) {
// Se instancian dos nuevos objetos Thread
Thread hiloA = new Thread( new MiHilo(),"hiloA" );
Thread hiloB = new Thread( new MiHilo(),"hiloB" );
// Esta clase existe solamente para que sea heredada por la clase
// MiHilo, para evitar que esta clase sea capaz de heredar la clase
// lugar
[Link]( [Link]() );
Como se puede observar, el programa define una clase MiHilo que extiende a la clase NoHaceNada e
implementa el interfaz Runnable. Se redefine el método run() en la clase MiHilo para presentar información
sobre el hilo.
La única razón de extender la clase NoHaceNada es proporcionar un ejemplo de situación en que haya que
extender alguna otra clase, además de implementar el interfaz.
En el ejemplo muestra el mismo programa básicamente, pero en este caso extendiendo la clase Thread, en
lugar de implementar el interfaz Runnable para crear el programa multihilo.
class java1002 {
static public void main( String args[] ) {
// Se instancian dos nuevos objetos Thread
Thread hiloA = new Thread( new MiHilo(),"hiloA" );
Thread hiloB = new Thread( new MiHilo(),"hiloB" );
[Link]();
[Link]();
En ese caso, la nueva clase MiHilo extiende la clase Thread y no implementa el interfaz Runnable
directamente (la clase Thread implementa el interfaz Runnable, por lo que indirectamente MiHilo también
está implementando ese interfaz). El resto del programa es similar al anterior.
Y todavía se puede presentar un ejemplo más simple, utilizando un constructor de la clase Thread que no
necesita parámetros, tal como se presenta en el ejemplo siguiente. En los ejemplos anteriores, el
constructor utilizado para Thread necesitaba dos parámetros, el primero un objeto de cualquier clase que
implemente el interfaz Runnable y el segundo una cadena que indica el nombre del hilo (este nombre es
independiente del nombre de la variable que referencia al objeto Thread).
class java1003 {
static public void main( String args[] ) {
// Se instancian dos nuevos objetos Thread
Thread hiloA = new MiHilo();
Thread hiloB = new MiHilo();
Las sentencias en este ejemplo para instanciar objetos Thread, son mucho menos complejas, siendo el
programa, en esencia, el mismo de los ejemplos anteriores.
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 hilos. La línea de código:
crea un nuevo hilo de ejecución. Los dos argumentos pasados representan el nombre del hilo y el tiempo
que se desea que espere antes de imprimir el mensaje.
Al tener control directo sobre los hilos, hay que arrancarlos explícitamente. En el ejemplo con:
[Link]();
Manipulación de un Thread
Si todo fue bien en la creación del hilo, t1 debería contener un thread válido, que controlaremos en el
método run().
Una vez dentro de run(), se pueden comenzar las sentencias de ejecución como en otros programas. run()
sirve como rutina main() para los hilos; cuando run() termina, también lo hace el hilo. Todo lo que se quiera
que haga el hilo de ejecución ha de estar dentro de run(), por eso cuando se dice que un método es
Runnable, es obligatorio escribir un método run().
En este ejemplo, se intenta inmediatamente esperar durante una cantidad de tiempo aleatoria (pasada a
través del constructor):
sleep( retardo );
El método sleep() simplemente le dice al hilo de ejecución que duerma durante los milisegundos
especificados. Se debería utilizar sleep() cuando se pretenda retrasar la ejecución del hilo. sleep() no
consume recursos del sistema mientras el hilo duerme. De esta forma otros hilos pueden seguir
funcionando. Una vez hecho el retardo, se imprime el mensaje "Hola Mundo!" con el nombre del hilo y el
retardo.
Suspensión de un Thread
Puede resultar útil suspender la ejecución de un hilo sin marcar un límite de tiempo. Si, por ejemplo, está
construyendo un applet con un hilo de animación, seguramente se 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 los hilos de ejecución se puede utilizar el método suspend().
[Link]();
Este método no detiene la ejecución permanentemente. El hilo es suspendido indefinidamente y para volver
a activarlo de nuevo se necesita realizar una invocación al método resume():
[Link]();
Parada de un Thread
El último elemento de control que se necesita sobre los hilos de ejecución es el método stop(). Se utiliza
para terminar la ejecución de un hilo:
[Link]();
Esta llamada no destruye el hilo, sino que detiene su ejecución. La ejecución no se puede reanudar ya con
[Link](). Cuando se desasignen las variables que se usan en el hilo, el objeto Thread (creado con new)
quedará marcado para eliminarlo y el garbage collector se encargará de liberar la memoria que utilizaba.
Si se necesita, se puede comprobar si un hilo está vivo o no; considerando vivo un hilo que ha comenzado y
no ha sido detenido.
[Link]();
Este método devolverá true en caso de que el hilo 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.
En el ejemplo no hay problemas de realizar una parada incondicional, al estar todos los hilos vivos. Pero si a
un hilo de ejecución, que puede no estar vivo, se le invoca su método stop(), se generará una excepción. En
este caso, en los que el estado del hilo no puede conocerse de antemano es donde se requiere el uso del
método isAlive().
Grupo de Hilos
Todo hilo de ejecución en Java debe formar parte de un grupo. La clase ThreadGroup define e implementa
la capacidad de un grupo de hilos.
Los grupos de hilos permiten que sea posible recoger varios hilos de ejecución en un solo objeto y
manipularlo como un grupo, en vez de individualmente. Por ejemplo, se pueden regenerar los hilos de un
grupo mediante una sola sentencia.
Cuando se crea un nuevo hilo, se coloca en un grupo, bien indicándolo explícitamente, o bien dejando que el
sistema lo coloque en el grupo por defecto. Una vez creado el hilo y asignado a un grupo, ya no se podrá
cambiar a otro grupo.
En la ejecución de los ejemplos de esta sección, se ha podido observar la circunstancia anterior. Por
ejemplo, el resultado en pantalla de uno de esos ejemplos es el que se reproduce a continuación:
% java java1002
Thread[hiloA,5,main]
Thread[hiloB,5,main]
Thread[main,5,main]
Como resultado de la ejecución de sentencias del tipo:
[Link]( [Link]() );
Para presentar la información sobre el hilo de ejecución. Se puede observar que aparece el nombre del hilo,
su prioridad y el nombre del grupo en que se encuentra englobado.
La clase Thread proporciona constructores en los que se puede especificar el grupo del hilo que se esta
creando en el mismo momento de instanciarlo, y también métodos como setThreadGroup(), que permiten
determinar el grupo en que se encuentra un hilo de ejecución.
Ahora que ya se ha visto por encima como se arrancan, paran, manipulan y agrupan los hilos de ejecución, el
ejemplo un poco más gráfico,vamos a implementa un contador.
En este caso, la clase java1004 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 hilo ya conocerá al objeto que va a correr. En este caso this es una referencia a java1004. Después de
que se haya creado el hilo, necesitamos arrancarlo. La llamada a start(), llamará a su vez al método run() de
la clase, es decir, a [Link](). La llamada a start() retornará con éxito y el hilo 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 hilo 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 hilo, porque sino, el hilo consumirá todo el tiempo de la CPU
para su proceso y no permitirá que entren métodos de otros hilos a ejecutarse. Otra forma de detener la
ejecución del hilo sería hacer una llamada al método stop(). En el contador, el hilo 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á en un único refresco. Así,
mientras los refrescos se van encolando, la variable contador se estará todavía incrementando, pero no se
visualiza en pantalla.
El uso y la conveniencia de utilización del método stop() es un poco dudoso y algo que quizá debería
evitarse, porque puede haber objetos que dependan de la ejecución de varios hilos, y si se detiene uno de
ellos, puede que el objeto en cuestión estuviese en un estado no demasiado consistente, y si se le mata el
hilo de control puede que definitivamente ese objeto se dañe. Una solución alternativa es el uso de una
variable de control que permita saber si el hilo se encuentra en ejecución o no, por ello, en el ejemplo se
utiliza la variable miThread que controla cuando el hilo está en ejecución o parado.
La clase anidada ProcesoRaton es la que se encarga de implementar un objeto receptor de los eventos de
ratón, para detectar cuando el usuario pulsa alguno de los botones sobre la zona de influencia del applet.
El uso de suspend() es crítico en ocasiones, sobre todo cuando el hilo que se va a suspender está utilizando
recursos del sistema, porque en el momento de la suspensión los va a bloquear, y esos recursos seguirán
bloqueados hasta que no se reanude la ejecución del hilo con resume(). Por ello, deben utilizarse métodos
alternativos a estos, por ejemplo, implementando el uso de variables de control que vigiles periódicamente
el estado en que se encuentra el hilo actual y obren el consecuencia.
...
suspendido = !suspendido;
...
Para controlar el estado del applet, se ha modificado el funcionamiento del objeto Listener que recibe los
eventos del ratón, en donde se ha 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.
Aquí podemos poner de nuevo en cuarentena la idoneidad del uso de estos métodos para el control del
estado del hilo de ejecución, tanto por lo comentado del posible bloqueo de recursos vitales del sistema,
como porque se puede generar un punto muerto en el sistema si el hilo de ejecución que va a intentar
revivir el hilo suspendido necesita del recurso bloqueado. Por ello, es más seguro el uso de una variable de
control como suspendido, de tal forma que sea ella quien controle el estado del hilo y utilizar el método
notify() para indicar cuando el hilo vuelve a la vida.
Durante el ciclo de vida de un hilo, é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 hilo.
Nuevo Thread
La siguiente sentencia crea un nuevo hilo de ejecución pero no lo arranca, lo deja en el estado de Nuevo
Thread:
Cuando un hilo está en este estado, es simplemente un objeto Thread vacío. El sistema no ha destinado
ningún recurso para él. Desde este estado solamente puede arrancarse llamando al método start(), o
detenerse definitivamente, llamando al método stop(); la llamada a cualquier otro método carece de
sentido y lo único que provocará será la generación de una excepción de tipo IllegalThreadStateException.
Ejecutable
Ahora obsérvense las dos líneas de código que se presentan a continuación:
[Link]();
La llamada al método start() creará los recursos del sistema necesarios para que el hilo puede ejecutarse, lo
incorpora a la lista de procesos disponibles para ejecución del sistema y llama al método run() del hilo de
ejecución. En este momento se encuentra en el estado Ejecutable del diagrama. Y este estado es Ejecutable
y no En Ejecución, porque cuando el hilo está aquí no esta corriendo. Muchos ordenadores tienen solamente
un procesador lo que hace imposible que todos los hilos estén corriendo al mismo tiempo. Java implementa
un tipo de scheduling o lista de procesos, que permite que el procesador sea compartido entre todos los
procesos o hilos que se encuentran en la lista. Sin embargo, para el propósito que aquí se persigue, y en la
mayoría de los casos, se puede considerar que este estado es realmente un estado En Ejecución, porque la
impresión que produce ante el usuario es que todos los procesos se ejecutan al mismo tiempo.
Cuando el hilo se encuentra en este estado, todas las instrucciones de código que se encuentren dentro del
bloque declarado para el método run(), se ejecutarán secuencialmente.
Parado
El hilo de ejecución entra en estado Parado cuando alguien llama al método suspend(), cuando se llama al
método sleep(), cuando el hilo está bloqueado en un proceso de entrada/salida o cuando el hilo utiliza su
método wait() para esperar a que se cumpla una determinada condición. Cuando ocurra cualquiera de las
cuatro cosas anteriores, el hilo estará Parado.
[Link]();
try {
[Link]( 10000 );
} catch( InterruptedException e ) {
[Link]( 10000 );
hace que el hilo se duerma durante 10 segundos. Durante ese tiempo, incluso aunque el procesador
estuviese totalmente libre, MiThread no correría. Después de esos 10 segundos. MiThread volvería a estar
en estado Ejecutable y ahora sí que el procesador podría hacerle caso cuando se encuentre disponible.
Para cada una de los cuatro modos de entrada en estado Parado, hay una forma específica de volver a
estado Ejecutable. Cada forma de recuperar ese estado es exclusiva; por ejemplo, si el hilo ha sido puesto a
dormir, una vez transcurridos los milisegundos que se especifiquen, él solo se despierta y vuelve a estar en
estado Ejecutable. Llamar al método resume() mientras esté el hilo durmiendo no serviría para nada.
Los métodos de recuperación del estado Ejecutable, en función de la forma de llegar al estado Parado del
hilo, son los siguientes:
Si un hilo está bloqueado en una entrada/salida, una vez que el comando de entrada/salida
concluya su ejecución
Si un hilo está esperando por una condición, cada vez que la variable que controla esa condición
varíe debe llamarse al método notify() o notifyAll()
Muerto
Un hilo de ejecución se puede morir de dos formas: por causas naturales o porque lo maten (con stop()). Un
hilo muere normalmente cuando concluye de forma habitual su método run(). Por ejemplo, en el siguiente
trozo de código, el bucle while es un bucle finito -realiza la iteración 20 veces y termina-:
También se puede matar en cualquier momento un hilo, invocando a su método stop(). En el trozo de código
siguiente:
[Link]();
try {
[Link]( 10000 );
} catch( InterruptedException e ) {
[Link]();
El método stop() envía un objeto ThreadDeath al hilo de ejecución que quiere detener. Así, cuando un hilo
es parado de este modo, muere asíncronamente. El hilo morirá en el momento en que reciba ese objeto
ThreadDeath.
Los applets utilizarán el método stop() para matar a todos sus hilos cuando el navegador con soporte Java en
el que se están ejecutando le indica al applet que se detengan, por ejemplo, cuando se minimiza la ventana
del navegador o cuando se cambia de página.
El método isAlive()
El interfaz de programación de la clase Thread incluye el método isAlive(), que devuelve true si el hilo ha
sido arrancado (con start()) y no ha sido detenido (con stop()). Por ello, si el método isAlive() devuelve false,
sabemos que estamos ante un Nuevo Thread o ante un thread Muerto. Si devuelve true, se sabe que el hilo
se encuentra en estado Ejecutable o Parado. No se puede diferenciar entre Nuevo Thread y Muerto, ni entre
un hilo Ejecutable o Parado.
Objetivo. Crear un programa en Java que sea capaz de controlar por intervalos varios procesos
1. Crear un programa que controle 3 subprocesos, los cuales se ejecuten en diferentes intervalos y que
manden a imprimir su estatus a pantalla.
Mostrar como conectar una aplicación de Java a una base de datos y generar procesos en conjunto
Introducción
JDBC son las siglas en ingles de Java Database Connectivity. Es un conjunto de clases que nos permite
acceder a diversos gestores de bases de datos en forma transparente.
Para cuestiones practicas, en este modulo nos enfocaremos a como conectarnos con el motor de base de
datos MySQL.
Una vez instalada la base de datos (tema que es aparte de este curso), podremos ver podemos conectarnos
con ella y ejecutar los comandos SQL básicos: SELECT, INSERT, UPDATE y DELETE.
También, por si acaso, crearemos desde Java la tabla de base de datos que vamos a usar y la borraremos.
[Link]
Tendremos que poner ese jar accesible en nuestro proyecto. Dependiendo de qué utilicemos para
programar en java hay muchas opciones.
Ponerlo en la variable de entorno CLASSPATH, siempre que nuestro IDE (eclipse, netbeans, etc)
haga caso de esa variable. Si trabajas con una ventana de ms-dos (símbolo del sistema), puedes
poner esto
Añadirlo en nuestro IDE. En el ejemplo de eclipse, damos con el botón derecho del ratón sobre el
proyecto, para sacar el menú y elegimos "properties"->"java build path"-
import [Link];
import [Link];
...
try
{
[Link]("[Link]");
Connection conexion = [Link]("jdbc:mysql://localhost/agenda", "root",
"LA_PASSWORD");
...
En primer lugar hemos hecho los import adecuados para poder obtener la conexión.
Luego hay que asegurarse de que java carga el Driver. Para ello hay dos opciones. Hacer un new de él o bien
instanciarlo con [Link]("nombre de clase"), que es como hacer el new, pero de una forma rara.
La ventaja de hacerlo con [Link]() es que sólo necesitaremos el Driver de una base de datos si lo
usamos. Me explico. Imagina que haces un programa que permite permite conectarse con varias bases de
datos distintas: MySQL, PostGres, Oracle, etc. En algún sitio damos a elegir al usuario qué tipo de base de
datos tiene.
Si hacemos new de los Driver, tendremos que hacer unos if para en función de la elegida, hacer new de uno
o de otro. El problema es que en nuestro código estarán los import y los new de todos los Driver de todas las
bases de datos necesaras, los vayamos a usar o no. El programa no compilará y no correrá si no tiene todos
los Driver accesibles.
/* Todos estos import son necesarios para que compilen los news correspondientes */
import driver_de_mysql;
import driver_de_postgres;
import driver_de_oracle;
...
if (ha_elegido_mysql)
new driver_de_mysql();
if (ha_elegido_postgres)
new driver_de_postgres();
if (ha_elegido_oracle)
new driver_de_oracle();
Sin embargo, con [Link]() no necesitamos el import. Haremos los mismos if de antes, pero
usaremos simplemente String distintos para llamar a [Link](). No necesitamos los import y no
necesitamos los jar con los Driver, salvo el jar con el Driver que realmente elija el usuario. Es cosa suya tener
instalado SU driver de SU base de datos.
[Link]("driver_de_mysql");
if (ha_elegido_postgres)
[Link]("driver_de_postgres");
if (ha_elegido_oracle)
[Link]("driver_de_oracle");
Una vez que nos hemos asegurado que java tiene el Driver cargado, simplemente pediremos conexión con la
base de datos a la clase DriverManager.
DriverManager tiene muchos métodos getConnection() con parámetros variados. Todos son variantes de lo
mismo y la información que suministramos es la misma. Aquí hemos utilizado uno con tres parámetros
String, que vamos a explicar.
url: Es una cadena que nos permite localizar la base de datos. Para mysql, el formato es
"jdbc:mysql://computadora_donde_corre_la_base_de_datos/nombre_base_datos". Donde se
pone el nombre o IP dla computadora en el que se encuentra nuestro servidor de base de datos y el
nombre de la base de datos. En nuestro ejemplo, tenemos el servidor de base de datos corriendo
en el mismo computadora que el programa java, por lo que ponemos localhost. La base de datos la
he llamado agenda. El comando SQL para crear la base de datos agenda sería
Si todo va bien, tendremos nuestra conexión a la base de datos. Si va mal, saltará una excepción. Por eso es
importante (y obligatorio para que compile) meter todo esto en un try-catch.
Para enviar comandos SQL a la base de datos, se usa la clase Statement de java. Esta clase se obtiene a
partir de la conexión, de esta forma:
Statement st = [Link]();
Statement tiene muchos métodos, pero hay dos interesentantes: executeUpdate() y executeQuery(). El
primero se usa para sentencias SQL que impliquen modificaciones en la base de datos (INSERT, UPDATE,
DELETE, etc). El segundo sólo para consultas (SELECT y similares).
Nuestra creación de tabla afecta a la base de datos, así que con executeUpdate().
Esto crea una tabla contacto con cuatro campos: id, nombre, apellidos y telefono.
Para borrar la tablita esta, lo mismo, pero con DROP en vez de CREATE.
String nombres[]={"Juan","Pedro","Antonio"};
String apellidos[]={"Gomez","Lopez","Alvarez"};
String telefonos[]={"123","456","789"};
...
for (int i=0;i<[Link];i++)
[Link]("INSERT INTO contacto (nombre, apellidos, telefono) VALUES
('"+nombres[i]+"','"+apellidos[i]+"','"+telefonos[i]+"' )");
también en un try-catch.
Pondremos los datos a insertar en unos arrays. Tú puedes recogerlos de un sitio con más sentido. Por
ejemplo, si es una agenda de contactos, lo mejor es pedírselos al usuario. Por simplicidad y porque para el
ejemplo sea comprobable de forma mas facil, se colocaran en un array y con un bucle y se iran insertando.
La sentencia INSERT se va componiendo a base de sumar cadenas. Sería más eficiente con un StringBuffer,
pero así se simplifica algo el ejemplo.
Fíjate que en el INSERT metemos comillas simples para los valores de cadenas, es decir, en nombre, apellidos
y telefono. El INSERT sería, de verdad, así
INSERT INTO contacto (nombre, apellidos, telefono) VALUES ('Juan', 'Gomez', '123');
Ahí se ven más claras las comillas simples para los datos que definimos como VARCHAR en la creación de la
tabla, es decir, Juan, Gomez y 123. Por eso en el código las hemos puesto
...VALUES ('"+nombres[i]+"','"...
Si fuesen datos numéricos no harían falta, son cosas de SQL, pero tenemos que tener cuidado de
componerlas bien.
El ResultSet de alguna forma representa una conexión hacia los datos. En el ResultSet NO están todavía los
datos. Según se los vayamos pidiendo, los irá trayendo de base de datos. Esto quiere decir que si una
consulta devuelve muchos resultados, no se nos va a llenar la memoria por el hecho de hacer la consulta.
Para traer el primer resultado, debemos llamar el método next() del ResulSet. Para el siguiente otro next() y
así sucesivamente hasta que next() devuelva false, indicando que ya no quedaban datos. El código puede ser
así
[Link]();
Hemos hecho un SELECT para obtener los datos. Luego un bucle while([Link]()), es decir, mientras next()
vaya trayendo resultados.
Después de next(), el resultado recién traido está disponible en el ResulSet. La forma de recoger los campos
es pedirlos con algún método get(). Si sabemos de qué tipo es el dato, podemos pedirlo con getInt(),
getString(), etc. Si no lo sabemos o nos da igual (como en este caso), bastará con un getObject(), capaz de
traer cualquier tipo de dato.
En estos métodos get() podemos pasar como parámetro un entero, empezando en 1, que es el número del
campo en el SELECT. Es decir, si hacemos SELECT campo1, campo2, campo3 ..., si pedimos getObject(1)
obtenemos el valor de campo1, para getObject(2) el de campo2, etc.
Otra opción que a mi me gusta más, es pasar el nombre del campo, como se muestra en el código. Así, en el
ejemplo de antes, getObject("campo1") nos devuelve el valor del campo1.
Para UPDATE usamos executeUpdate(). Vamos a cambiar el número de teléfono de Juan. Ya que en la tabla
hemos puesto un campo id, vamos primero a obtener el id de Juan para luego usarlo en el UPDATE. Esto no
tiene por qué ser así, es sólo el ejemplo.
Es un SELECT para obtener el campo id de Juan. Con [Link]() hacemos que venga el primer resultado de la
consulta (y único en nuestro ejemplo). Como sabemos que es un INT, lo recogemos con getInt() y lo
guardamos.
Para hacerlo bien, deberíamos ver si [Link]() devuelve o no resultado (quizás no haya nadie que se llame
Juan en la base de datos) o si devuelve más de uno (más de un Juan en la base de datos), pero no vamos a
complicarnos la vida en este ejemplo sencillo.
Esta vez, como el campo id es numérico, no hemos puesto las comillas simples.
El borrado se hace con DELETE y executeUpdate(). Vamos a borrar a Pedro, que nos hemos peleado con él.
Obtenemos primero su id, igual que antes.
y ahora lo borramos
Cerrar conexiones
Los Connection, Statement y ResultSet con conexiones abiertas con base de datos. Debemos cerrarlas.
ResultSet se cierra solo cuando hacemos otra llamada execute() al Statement del que obtuvimos este
ResultSet o bien cuando el recolector de basura "recolecta" al ResultSet. No nos preocupa en principio que
se quede abierto, porque se acabará cerrando solo.
Eso sí, no podemos hacer nada con el Statement hasta que hayamos terminado con el ResultSet o se nos
cerrará. Si necesitamos realizar otra cosa con base de datos, debemos crear otro Statement distinto.
Objetivo. Conectar una aplicación de Java a una base de Datos de MySQL y generar operaciones con ella.
1. Crear una aplicación de Java para probar una conexión a una base de datos de [Link] base de datos
llamada prueba y que tiene una tabla llamada persona. Dicha tabla tiene 3 campos. Id, nombre,
nacimiento.
Conclusion
Java tiene importantes ventajas, no sólo como un lenguaje comercial, sino también como lenguaje de
enseñanza. Permite a los estudiantes a aprender programación orientada a objetos sin exponerlos a la
complejidad de C + +. Proporciona una base robusta de control de error en tiempo de compilación algo que
normalmente carecen lenguajes de programación. Permite introducir al alumno en la programación GUI,
redes, hilos, y otros conceptos importantes que se utilizan en el software de hoy en día.
Java se ha transformado en uno de los lenguajes de programación más utilizados de los departamentos de
informática y sus aplicaciones parecen ser casi infinitas.
APENDICE
RESOLUCION DE LABORATORIOS
Ejercicio 1
import [Link];
Ejercicio2
import [Link];
suma1=suma1 + valor;
x=x + 1;
}
[Link]("Segunda lista");
x=1;
while (x<=15) {
[Link]("Ingrese valor:");
valor=[Link]();
suma2=suma2 + valor;
x=x + 1;
}
if (suma1>suma2) {
[Link]("Lista 1 mayor.");
} else {
if (suma2>suma1) {
[Link]("Lista2 mayor.");
} else {
[Link]("Listas iguales.");
}
}
}
}
import [Link];
public class Empleado {
private Scanner teclado;
String nombre;
float sueldo;
import [Link];
public class Operaciones {
private Scanner teclado;
int valor1,valor2;
Ejercicio 1
Ejercicio 2
import [Link].*;
import [Link].*;
import [Link].*;
import [Link].*;
import [Link].* ;
// Modo inicial
int modo = MODO_F2C;
JPanel panelFH;
JPanel panelCG;
[Link]( fahrenheit );
[Link]( [Link]( vpad10 ) );
valorF = new JLabel( "00000",CENTER );
[Link]( valorF );
[Link]( centigrade );
[Link]( [Link]( vpad10 ) );
valorC = new JLabel( "00000",CENTER );
[Link]( valorC );
[Link]( [Link](valor) );
actcualizaBarraCG( valor );
}
else if( [Link](centigrade) && modo == MODO_C2F ) {
[Link]( [Link](valor) );
actualizaBarraFH( valor );
}
}
f = (int)( (c * 9) / 5 + 32 + 0.5 );
[Link]( [Link](f) );
[Link]( f );
}
[Link]() );
}
// Finalmente, se recupera el cursor por defecto del sistema
[Link]( [Link]( Cursor.DEFAULT_CURSOR ) );
}
}
Ejercicio 1
import [Link].*;
Ejercicio 2
Se debe de crear previamente un archivo de texto en C: con el contenido “ABCDE”
import [Link];
import [Link];
import [Link];
import [Link];
InputStream is = null;
InputStreamReader isr = null;
BufferedReader br = null;
try{
int value=0;
// prints character
[Link](c);
}
}catch(Exception e){
[Link]();
}finally{
} // fin de main
[Link]( tiempoInactividad );
}
String nombres[]={"Juan","Pedro","Antonio"};
String apellidos[]={"Gomez","Lopez","Alvarez"};
String telefonos[]={"123","456","789"};
...
for (int i=0;i<[Link];i++)
[Link]("INSERT INTO contacto (nombre, apellidos, telefono) VALUES
('"+nombres[i]+"','"+apellidos[i]+"','"+telefonos[i]+"' )");
public PruebaMySQL()
{
// Se mete todo en un try por los posibles errores de MySQL
try
{
// Se registra el Driver de MySQL
[Link](new [Link]());
Connection conexion = [Link] (
"jdbc:mysql://localhost/prueba","root", "la_clave");
Statement s = [Link]();
while ([Link]())
{
[Link] ([Link] ("Id") + " " + [Link] (2)+
" " + [Link](3));
}
[Link]();
}
catch (Exception e)
{
[Link]();
}
}