Anda di halaman 1dari 70

Introducción.

Cómo compilar
aplicaciones y applets.
• La aplicación "HelloWord"
o 1) Crear un Archivo Java fuente
o 2) Compilar el Archivo fuente
o 3) Ejecutar la Aplicación
• El applet "HelloWord"
o 1) Crear un Archivo Java fuente
o 2) Compilar Archivo fuente con el compilador de Java
o 3) Crear un archivo HTML que incluya el Applet
o 4) Visualizar el archivo HTML

Los programas más comunes de Java son las standalone programs (aplicaciones) y los
applets. Las standalone programs son aplicaciones que se pueden ejecutar como
cualquier otro lenguaje de programación, sin necesidad de ningún elemento soporte.
Los applets son pequeñas aplicaciones en Java, que se transfiere a través de la red y que
necesita para su ejecución un browser (navegador o visualizador) de red compatible con
Java.
Aquí le introduciremos con un sencillo ejemplo cómo deberá compilar sus aplicaciones
y applets Java creados con cualquier procesador de texto.

Para este ejemplo así como para el resto del manual hemos utilizado el JDK (Java
Developers Kit), Kit para desarrolladores en Java, que contiene todo lo necesario para
desarrollar todo tipo de programas en Java.

La Aplicación "Hello world"

Siguiendo los pasos de esta página, usted podrá crear y ejecutar una sencilla aplicación
en Java.

1) Crear un Archivo Java fuente.

Java requiere que todo el código resida dentro de una clase con nombre. Cree un
archivo llamado HelloWorldApp.java con el siguiente código de Java mostrado aquí,
asegurándose que las mayúsculas del nombre del archivo coincidan con el de la clase.

/**
* The HelloWorldApp clase implementa una aplicación que
* simplemente muestra "Hello World!" en la salida estándar.
*/

class HelloWorldApp {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
2) Compilar el Archivo fuente.

Para compilar el programa fuente de Java se utiliza el compilador de Java, javac, dando
el nombre del archivo fuente. Según la plataforma en la que trabaje:

UNIX: javac HelloWorldApp.java

DOS shell (Windows 95/NT): javac HelloWorldApp.java

MacOS: Arrastre el icono del fichero HelloWorldApp.java al icono del compilador


javac.

El compilador javac crea un archivo llamado HelloWorldApp.class que contiene el


código de byte compilado del programa independientemente del procesador. Si la
compilación fracasó, asegúrese de haber escrito y nombrado correctamente el programa,
tal y como se mostró arriba.

3) Ejecutar la Aplicación.

Para ejecutar la aplicación es necesario disponer de un intérprete de Java que cargue


esta nueva clase y la ejecute. Esto se hace pasando al intérprete el nombre de la clase
HelloWolrdApp. Según la plataforma en la que trabaje:

UNIX: java HelloWorldApp

DOS shell (Windows 95/NT): java HelloWorldApp

MacOS: Doble-click en el icono del fichero HelloWorldApp.class

Como resultado, usted debería ver en la pantalla el mensaje: "Hello world!"

El Applet "Hello World"

Siguiendo estos pasos usted podrá crear un applet en Java.

1) Crear un Archivo Java fuente.

Cree un archivo llamado HelloWorld.java con el siguiente código de Java mostrado


aquí, asegurándose que las mayúsculas del nombre del archivo coincidan con el de la
clase.

import java.applet.Applet;
import java.awt.Graphics;

public class HelloWorld extends Applet {


public void paint(Graphics g) {
g.drawString("Hello world!", 50, 25);
}
}

2) Compilar Archivo fuente con el compilador de Java


UNIX: javac HelloWorld.java

DOS shell (Windows 95/NT): javac HelloWorld.java

MacOS: Arrastre el icono de el fichero HelloWorld.java al icono del compilador javac.

El compilador javac crea un archivo llamado HelloWorldApp.class que contiene el


código de byte compilado del programa independientemente del procesador. Si la
compilación fracasa, asegurese de haber escrito y nombrado correctamente el programa,
tal y como se mostró arriba.

3) Crear un archivo HTML que Incluya el Applet

Cree un archivo en el mismo directorio con el nombre Hello.html, que deberá contener
la HelloWorld.class creada anteriormente.

El texto contenido del archivo HTML es el siguiente:

<HTML>
<HEAD>
<TITLE> A Simple Program </TITLE>
</HEAD>
<BODY>

Hello World!

<APPLET CODE="HelloWorld.class" WIDTH=150 HEIGHT=25>


</APPLET>
</BODY>
</HTML>

4) Visualizar el archivo HTML.

Cargue el Archivo HTML en una aplicación que admita applets de Java (un browser
compatible Java como por ejemplo el Appletviewer que se proporciona con el JDK
(Java Developers Kit).

UNIX: appletviewer file:/home/kwalrath/java/Hello.html

DOS shell (Windows 95/NT): appletviewer file:/home/kwalrath/java/Hello.html

MacOS: Ejecute el AppletViewer desde el menú File, eligiendo abrir URL y entre el
URL del fichero HTML que ha creado, por ejemplo:

file:/home/kwalrath/java/Hello.html.

Para cargar el archivo HTML, usted normalmente necesitará especificar a la aplicación


(browser) el URL del archivo HTML que usted ha creado. Por ejemplo, usted podría
entrar algo así en su URL o campo de Ubicación de su browser:

file:/home/kwalrath/HTML/Hello.html
Una vez que haya ha completado correctamente estos pasos, debería ver esto en la
ventana del browser:

Hello World!

Aquí se acaba la pequeña introducción de cómo debe compilar sus aplicaciones o


applets en Java. A partir de aquí nos adentraremos en propio lenguaje Java y como crear
sus própias aplicaciones y applets.

Capítulo 1. Variables y tipos de datos.


• Identificadores
• Palabras claves reservadas
o Variables
o Declaración de una variable
o ámbito de una variable
• Tipos simples
o Tipos numéricos
o Enteros
o Números en coma flotante
o Conversión de tipos
o Caracteres
o Booleanos
• Matrices
o Matrices multidimensionales

Un tipo define una expresión o variable de manera que se puede predecir su


comportamiento. Este capítulo trata todos los tipos principales de Java, mostrando cómo
declarar variables, asignar valores y combinar tipos en expresiones.

Identificadores

Los identificadores se utilizan como nombres de clase, método y variable. Un


identificador puede ser cualquier sentencia descriptiva de letras en mayúscula o
minúscula , números y los caracteres subrayado (_) y signo de dólar ($). No se deban
comenzar por número. Java diferencia entre mayúsculas/minúsculas, lo que significa
que VALOR es un identificador diferente de Valor.

Palabras clave reservadas

Las palabras clave reservadas son identificadores especiales que el lenguaje Java se ha
reservado para controlar cómo está definido su programa. Se utilizan para identificar los
tipos, modificadores y mecanismos para control de secuencia incorporados. Estas
palabras clave sólo se pueden utilizar para su propósito original y no se pueden utilizar
como identificadores de nombres de variable, clase o método. Hay 59 palabras clave
reservadas definidas en la versión Java 1.0, que se muestran en la siguiente tabla.

Variables
La variable es la unidad básica de almacenamiento en un programa en Java. Una
variable se define mediante la combinación de un identificador, un tipo y un ámbito.

Declaración de un variable

La forma básica de una declaración de variable es:

tipo identificador [ = valor ] [, identificador [ = valor ]


... ] ;

El tipo puede ser: byte, short, int, long, char, float, double, boolean o el nombre de una
clase o interfaz. Conceptos todos que describiremos más adelante.

Ámbito de una variable

Los bloques de sentencias compuestas en Java se delimitan con dos llaves { }. Las
variables de Java sólo son válidas desde el punto donde están declaradas hasta el final
de la sentencia compuesta que la engloba. Se pueden anidar estas sentencias compuestas
y cada una puede contener su propio conjunto de declaraciones de variables locales. Sin
embargo, no se puede declarar una variable con el mismo nombre que una de un ámbito
superior.

Tipos simples

En Java, que es un lenguaje de programación orientado a objetos, se ha renunciado a la


abstracción uniforme del concepto "todo es un objeto" por cuestiones de eficiencia. El
rendimiento del sistema fue una meta fundamental en el desarrollo de Java. Esta
decisión condujo a la creación de los tipos simples de Java. No están en absoluto
orientados a objeto, y son análogos a los tipos simples de la mayoría de los otros
lenguajes que no utilizan objetos. Los tipos simples representan expresiones atómicas,
de un solo valor, como números enteros, de coma flotante, caracteres y valores
booleanos. Java también tiene un tipo para matrices, que son conjuntos de tamaño fijo
de un único tipo. También tiene los tipos compuestos, clases e interfaces que veremos
en un próximo capítulo. Parte de la seguridad y robustez de Java viene del hecho de que
Java es un lenguaje fuertemente tipado. Cada expresión tiene un tipo. Cada variable
tiene un tipo y cada tipo está definido estrictamente. En todas las asignaciones de
expresiones a variables, tanto explícitas como a través de paso de parámetros en
llamadas a método, se comprueba la compatibilidad de los tipos. El compilador de Java
comprueba estáticamente todo el código que compila para asegurar que los tipos sean
correctos. Si no coinciden los tipos no se generan avisos de compilador, sino que se
generan errores que se deben corregir antes de que el compilador termine de compilar la
clase. Para cada tipo se define explícitamente el único conjunto de valores que puede
expresar, junto con un conjunto definido de operaciones que están permitidas. Por
ejemplo, los tipos numéricos int y float se pueden sumar entre ellos, pero los tipos
boolean no.

Tipos numéricos

Los tipos numéricos son los que contienen números y pueden ser de dos clases, los que
guardan números de valor completo sin parte fraccionaria, llamados enteros, y los que
pueden almacenar una parte fraccionaria, llamados números en coma flotante. La
magnitud del rango o grado de precisión de una componente fraccionaria que se va a
necesitar dependerá de su aplicación.
Todos estos tipos en Java tienen definido un rango explícito y un comportamiento
matemático. La mayor parte del código no portable de otros lenguajes está repleto de
problemas debidos al comportamiento no especificado del tipo int. La base de este
problema reside en un concepto llamado tamaño de palabra de la máquina. El tamaño de
palabra de una CPU dada está determinado por el número de bits que utiliza
internamente para representar sus registros más básicos, que se utilizan para almacenar
y manipular los números. Debido a la evolución de los PC y al tamaño de palabra
variable de estos, algunos compiladores implementan int como un entero de tamaño de
palabra de 32 bits, otros puede que sólo tengan 16 bits, y los compiladores de los
sistemas más modernos puede que incluso utilicen 64 bits. En Java, no hay una
conexión entre el tamaño de palabra de la máquina y el rango de un tipo numérico. Un
valor int siempre tiene 32 bits en todos los intérpretes Java, independientemente de la
plataforma de la que se trate. Esto permite que los programas que se escriban tengan
garantizada su ejecución en cualquier arquitectura sin tener que transportarlos.

Enteros

Todos los tipos numéricos de Java son valores con signo. Esta ausencia de signo reduce
el número de tipos de entero a cuatro, cada uno de los cuales representa 1, 2, 4 y 8 bytes
de almacenamiento. Veamos en detalle cada uno de los cuatro tipos, byte, short, int y
long.

byte

byte es un tipo de 8 bits con signo. Su rango comprende desde -128 a 127. Es
especialmente útil cuando se tiene un flujo de bytes externos recibidos desde una red o
archivo. Si se necesita analizar gramaticalmente un protocolo de red o un formato de
archivo, o resolver problemas de ordenamiento de bytes, el tipo byte es el apropiado.
Las variables byte se declaran utilizando la palabra clave byte. Por ejemplo, el código
siguiente declara dos variables byte llamadas b y c, inicializando c con el valor
hexadecimal 0x55.

byte b;
byte c = 0x55;

En general, se debería evitar la utilización del tipo byte excepto cuando se trabaje con
manipulación de bits. Para los enteros normales, que se utilizan para contar y operar,
int, que se describe más adelante, es una elección mucho más adecuada.

short

short es un tipo de 16 bits con signo. Su rango comprende desde -32768 a 32767.
Probablemente es el tipo de Java menos utilizado. Ahora que las computadoras de 16
bits empiezan a estar en desuso ya no hay muchos valores short con los que trabajar.
Ejemplos de declaraciones de variables short:
short s;
short t = 0x55aa;

int

int es un tipo de 32 bits con signo. su rango comprende desde -2.147.483.648 a


2.147.483.647. Es el tipo más utilizado habitualmente para almacenar valores enteros
simples. Con un rango de miles de millones, es ideal para la mayoría de las iteraciones
con matrices y para contar. Siempre que se tenga una expresión con enteros que incluya
byte, short, int y números literales, la expresión completa se promociona a int antes de
realizar el cálculo.
Ejemplos de declaraciones de variables int:

int i;
int j = 0x55aa0000;

long

long es un tipo de 64 bits con signo. Hay algunas ocasiones en las que un tipo int no es
lo suficientemente grande como para guardar un valor deseado. Cuando se calcular
expresiones enteras con números grandes, una multiplicación puede generar algunos
valores intermedios de miles de billones. También, cuando se calcula el tiempo, el
número de milisegundos en un año es de cerca de 30.000 millones y se desbordará un
int de 32 bits. En estos casos se necesita utilizar un long.
Ejemplos de declaraciones de variables long:

long m;
long n = 0x55aa000055aa0000;

Tabla de anchuras y rangos para cada uno de los tipos enteros.

Nombre Anchura Rango


long 64 -9.223.372.036.854.775.808..9.223.372.036.854.775.807
int 32 -2.147.483.648..2.147.483.647
short 16 -32.768..32.767
byte 8 -128..127

Números en coma flotante

Los números en coma flotante, también conocidos como números reales en otros
lenguajes, se utilizan cuando se calculan funciones que requieren precisión fraccionaria.
Los cálculos complejos, como la raíz cuadrada, o trigonométricas, como el seno y el
coseno, tienen como resultado un valor cuya precisión requiere un tipo en coma
flotante. Hay dos clases de tipos en coma flotante, float y double, como se describen:

Nombre Anchura Rango


double 64 1.7e-308..1.7e+308
float 32 3.4e-038..3.4e+038

float

La precisión simple, especificada por la palabra clave float, utiliza 32 bits para
almacenar un valor. La precisión simple es más rápida en algunos procesadores y ocupa
la mitad de espacio, pero comenzará a ser imprecisa cuando los valores sean muy
grandes o muy pequeños.
Ejemplos de declaraciones de variables float:

float f;
float f2 = 3.14f;

double

La precisión doble, especificada por la palabra clave double, utiliza 64 bits para
almacenar un valor. Realmente la precisión doble es más rápida que la simple en
algunos procesadores modernos que han sido optimizados para cálculos matemáticos a
alta velocidad. Cuando se necesita mantener la precisión tras muchos cálculos iterativos,
o está manipulando números de gran valor, double es la mejor opción.
Ejemplos de declaraciones de variables double:

double d;
double pi = 3.14159365358979323846;

Conversión de tipos

Hay situaciones en las cuales se tiene un valor de un tipo dado y se desea almacenar ese
valor en una variable de un tipo diferente. En algunos tipos es posible almacenar
simplemente el valor sin una conversión de tipos; lo que se denomina conversión
automática. Esto sólo es posible en Java si el compilador reconoce que la variable
destino tiene la suficiente precisión para contener el valor origen, como almacenar un
valor byte en una variable int. A esto se le llama ensanchamiento o promoción, dado
que el tipo más pequeño se ensancha o promociona al tipo compatible más grande. Si
por el contrario, se desea asignar un valor de variable int a una variable byte se necesita
realizar una conversión de tipos explícita. A esto se le llama estrechamiento, dado que
se estrecha explícitamente el valor para que quepa en el destino. La conversión de un
tipo se realiza poniendo delante un nombre de tipo entre paréntesis, por ejemplo, (tipo)
valor. El código siguiente demuestra la conversión de tipos de int a byte. Si el valor del
entero fuese mayor que el rango de un byte, se reduciría al módulo (resto de la división)
del rango de byte.

int a = 100;
byte b = (byte) a;

Caracteres

Dado que Java utiliza Unicode para representar los caracteres de una cadena, el tipo
char es de 16 bits sin signo. El rango de un carácter es de 0 a 65536. No hay caracteres
negativos. Unicode es una unificación de docenas de conjuntos de caracteres,
incluyendo el latín, griego, arábigo, cirílico, hebreo, katakana, hangul y muchos más.
Ejemplos de declaraciones char:

char c;
char c2 = 0xf132;
char c3 = 'a';
char c4 = '\n';

Aunque no se utilicen los caracteres como enteros, puede operar con ellos como si lo
fueran. Esto permite sumar dos caracteres o incrementar el valor de una variable
carácter.

int tres = 3;
char uno = '1';
char cuatro = (char) (tres + uno);

La variable cuatro termina con un '4' almacenado en ella. Observe que uno fue
promocionado a int en la expresión, por lo que se requiere la conversión de tipos para
volver a char antes de la asignación a cuatro.

Booleanos

Java tiene un tipo simple para los valores lógicos, llamado boolean. Sólo puede tomar
uno de estos dos posibles valores, true (verdadero) o false (falso) que son palabras
reservadas. Este es el tipo que devuelven todos los operadores de comparación o que se
requiere en todos los operadores de control de flujo que se explicarán en capítulos
posteriores.
Ejemplo de una declaración de tipo boolean:

boolean terminado = false;

Matrices

Las matrices son un tipo especial que agrupa un conjunto de variables del mismo tipo.
Si se desea crear una matriz de doce enteros, se crea un tipo especial, que es una "matriz
de int". Este ejemplo muestra la declaración de una variable month_day con el tipo
"matriz de int":

int month_days[];

Para las matrices, hay un valor especial llamado null, que representa una matriz sin
ningún valor. Se debe utilizar un operador especial, new (nuevo), para asignar el
espacio de una matriz. Para utilizar el operador new se debe promocionar un tipo y un
número entero no negativo de elementos a asignar. En este ejemplo, se asignan 12
enteros a una matriz a la que se referencia como month_days.

month_days = new int[12];

Matrices multidimensionales
No hay realmente matrices multidimensionales en Java. Hay matrices de matrices, que
se parecen mucho a matrices multidimensionales, con un par de diferencias. En Java, se
puede declarar que una variable sea tridimensional, pero no definir las dimensiones
segunda y tercera, y después asignar las direcciones Y y Z de manera separada. El
código siguiente crea una matriz tradicional de 16 doubles. Internamente esta matriz se
implementa como una matriz de matrices double.

double matrix[][] = new double[4][4];

El código que sigue inicializa la misma cantidad de memoria pero haciendo las últimas
inicializaciones a mano para mostrar cómo las distintas dimensiones son en realidad
matrices anidadas.

double matrix[][] = new double[4][];


matrix[0] = new double[4];
matrix[1] = new double[4];
matrix[2] = new double[4];
matrix[3] = { 0, 1, 2, 3 };

Capítulo 2. Operadores.
• Operadores aritméticos
o Operadores de calculadora
• Operador módulo
o Asignaciones con operadores aritméticos
o Incremento y decremento
• Operadores a nivel de bit enteros
o Desplazamiento a la izquierda
o Desplazamiento a la derecha
o Desplazamiento a la derecha sin signo
• Operadores relacionales
• Operadores lógicos booleanos
• Operadores lógicos en cortocircuito
• Operador if-then-else ternario
• Precedencia de operadores
o Precedencia explícita

Los operadores de Java son caracteres especiales que le dicen al compilador que se
desea realizar una operación sobre algunos operandos. A los operadores que toman un
único operando se les llama operadores unarios. A los que aparecen antes del operando
se les llama operadores prefijo y los que van después se les llama operadores sufijo. La
mayoría de los operadores están entre dos operandos y se les llama operadores binarios
infijo. E incluso hay un operador que toma tres operandos y se le llama operador
ternario.
Java tiene 44 operadores incorporados divididos en cuatro clases básicas: aritméticos, a
nivel de bit, relaciónales y lógicos.

Operadores aritméticos
Los operadores aritméticos se utilizan para operaciones matemáticas, exactamente de la
misma manera en la que están definidos en álgebra. Los operandos deben ser tipo
numérico. No se pueden utilizar estos operadores con tipos boolean, pero se pueden
utilizar con tipos char, dado que el tipo char en Java es un subconjunto de int.

Operadores de calculadora

La suma, reta, multiplicación y división se comporta todas como se espera de todos los
tipos numéricos. El operador menos unario niega al operando único al que precede.

Operador módulo

El operador módulo, %, devuelve el resto de una división. En Java la función módulo


funciona con tipos en coma flotante además de con tipos enteros.

Asignaciones con operadores aritméticos

Cada uno de los operadores aritméticos tiene una forma asociada, cuando se tiene una
asignación tras la operación. Esto sirve para todos los operaciones que se utilizan de la
siguiente forma

var = var op expresión;

que, por lo tanto, se puede reescribir como

var op= expresión;

Aquí tenemos algunos ejemplos que muestran varias asignaciones y sus


correspondientes con el operador op=.

a = a + 4; también se puede utilizar a += 4;


a = a % 2; se puede expresar como a %=2;

Incremento y decremento

Los operadores incremento y decremento (++ y --) son una notación abreviada para
añadir o restar 1 de un operando. La forma completa es

x = x + 1; que como se ha mostrado anteriormente, es lo mismo que

x += 1; que, utilizando este nuevo operador, es igual a

++x;

del mismo modo x = x -1 equivale a --x

Estos operadores pueden aparecer en forma de prefijo, como lo mostrado hasta ahora, y
también en forma de sufijo cuando siguen al operando. La diferencia entre las dos
formas es importante. En la forma de prefijo, el operando se modifica antes de obtener
el valor. En la forma sufijo, se obtiene el valor, y a continuación el operando se
incrementa o decrementa. Por ejemplo:

x = 42;
y = ++x; en este caso, y se establece a 43; sin embargo:

x = 42;
y = x++; se toma el valor de x antes del incremento;
y se establece a 42, y x a 43.

Operadores a nivel de bit enteros

Los tipos numéricos enteros, long, int, short, char y byte tienen un conjunto adicional de
operadores que pueden modificar e inspeccionar los bits que componen sus valores.
Estos operadores se resumen a continuación.

Todos los tipos enteros se representan mediante números binarios de anchura variable.
Por ejemplo, el valor de byte de 42 en binario es 00101010. Los operadores a nivel de
bit operan independientemente sobre cada uno de los bits de un valor.

NOT

El operador NOT unario, ~, invierte todos los bits de su operando. Por ejemplo, en
número 42, que tiene el siguiente patrón de bits 00101010 se convierte en 11010101
después de aplicar el operador NOT.

AND

El operador AND, &, combina los bits de manera que se obtiene un 1 si ambos
operandos son 1, obteniendo 0 en cualquier otro caso.

00101010 42
& 00001111 15
= 00001010 10

OR

El operador OR, |, combina los bits de manera que se obtiene un 1 si cualquiera de los
operandos es un 1.

00101010 42
| 00001111 15
= 00101111 47

XOR

El operador XOR, ^, combina los bits de manera que se obtiene un 1 si cualquiera de los
operandos es un 1, pero no ambos, y cero en caso contrario.
00101010 42
^ 00001111 15
= 00100101 37

La tabla siguiente muestra cómo actúa cada operador a nivel de bit sobre cada
combinación de bits de operando.

A B OR AND XOR NOT


0 0 0 0 0 1
1 0 1 0 1 1
0 1 1 0 1 1
1 1 1 1 0 0

Desplazamiento a la izquierda

El operador desplazamiento a la izquierda, <<, mueve hacia la izquierda todos los bits
del operando de la izquierda un número de posiciones de bit especificado en el
operando de la derecha. Al realizarse el desplazamiento se pierden por el extremo
izquierdo del operando el número de bits desplazados y se rellena el operando con ceros
por la derecha el mismo número de bits.

Desplazamiento a la derecha

El operador desplazamiento a la derecha, >>, mueve hacia la derecha todos los bits del
operando de la izquierda un número de posiciones de bit especificado por el operando
de la derecha. Este ejemplo desplaza el valor 32 a la derecha dos posiciones de bit,
obteniendo como resultado que el valor de a sea 8.

int a = 32;
a = a >> 2;

Cuando un valor tiene bits que se desplazan fuera por la parte izquierda o derecha de
una palabra, esos bits se pierden. Este ejemplo desplaza el valor 35 a la derecha los dos
bits inferiores por la derecha, y el resultado vuelve a ser que el valor de a sea 8.

int a = 35;
a = a >> 2;

Si se estudian en binario estas operaciones se observa con mayor claridad.

00100000 32 00100011 35
>> 2 >> 2
00001000 8 00001000 8

Desplazamiento a la derecha sin signo

El operador de desplazamiento a la derecha rellenando con ceros de Java, >>>, desplaza


introduciendo ceros en los bits más significativos, a lo que se llama desplazamiento sin
signo. En este ejemplo, a se establece a -1, lo que supone en binario que los 32 bits sean
1. Este valor se desplaza después a la derecha 24 bits, rellenando los 24 bits superiores
con ceros, ignorando la extensión de signo normal, a pasa a valer 255.

int a = -1;
a = a >>> 24;

Esta misma operación en formato binario:

11111111111111111111111111111111 -1
>>> 24
00000000000000000000000011111111 255

Esto es especialmente útil en la creación de máscaras para operaciones gráficas.

Todos los operadores a nivel de bit binarios tienen una forma similar a la de los
operadores algebraicos, que hacen una asignación automática del resultado al operador
de la izquierda. Por ejemplo:

a = a >> 4; equivale a: a >>= 4; desplazan el valor de a cuatro posiciones

a = a | b; equivale a: a |= b; se asigna a a el resultado de a OR b.

Operadores relacionales

Para comparar dos valores, Java tiene el siguiente conjunto de operadores relaciónales
que describen igualdad y ordenamiento.

Operador Resultado
== igual a
!= distinto de
> mayor que
< menor que
>= mayor o igual que
<= menor o igual que

Sólo se pueden comparar los tipos numéricos utilizando los operadores de ordenación.
Se pueden comparar operandos enteros, en coma flotante y caracteres para ver cual es
mayor o menor que otro. Cada uno de los operadores devuelve como resultado un tipo
boolean.

Operadores lógicos booleanos

Todos los operadores lógicos booleanos combinan dos valores boolean para dar como
resultado un valor boolean.

Operador Resultado Operador Resultado


& AND lógico &= Asignación AND
| OR lógico |= Asignación OR
^ XOR lógico (OR exclusivo) ^= Asignación XOR
|| OR en cortocircuito == igual a
&& AND en cortocircuito != distinto de
! NOT unario lógico ?: if-then-else ternario

Los operadores booleanos lógicos, AND, OR, XOR operan sobre valores booleanos de
la misma manera que operaban sobre los bits de un entero. El operador lógico NOT
invierte el estado booleano.

A B OR AND XOR NOT


false false false false false true
true false true false true false
false true true false true true
true true true true false false

Operadores lógicos en cortocircuito

Existen versiones secundarias de los operadores lógicos AND y OR, a los que se llama
operadores lógicos en cortocircuito. La utilización de estos operadores en una expresión
provoca que la evaluación se detenga inmediatamente en el momento en que se conoce
el resultado de la expresión que se está evaluando. Así, por ejemplo, este fragmento de
código muestra la manera de aprovechar la evaluación lógica en cortocircuito para
asegurarse que una operación de división es válida antes de evaluarla.

if (denom != 0 && num / denom > 10)

Dado que se utilizó la forma en cortocircuito de AND, &&, no se corre el riesgo de


provocar una excepción (error) en tiempo de ejecución al intentar dividir por denom
cuando éste fuese igual a cero

Operador if-then-else ternario

Java incluye el mismo operador ternario que C y C++. La forma general es

expresión ? sentencia1 : sentencia2

Donde expresión puede ser cualquier expresión que dé como resultado un valor
boolean. Si el resultado es true entonces se ejecuta sentencia1, en caso contrario se
ejecuta sentencia2. La limitación es que sentencia1 y sentencia2 deben devolver el
mismo tipo, que no puede ser void.

Precedencia de operadores
La tabla siguiente muestra el orden de todas las posibles operaciones en Java, desde la
precedencia más alta a la más baja.

La más alta
() [] .
++ -- ~ !
* / %
+ -
>> >>> <<
> >= < <=
== !=
&
^
|
&&
||
?:
= op=
La más baja

Precedencia explícita

Dado que los paréntesis son la precedencia más alta, siempre puede poner una pareja de
paréntesis extra si no se está seguro de las reglas de precedencia implícitas o quiere que
su código sea legible. En expresiones complicadas, incluso si se está seguro de la
precedencia, pueden ayudar al programador unos paréntesis clarificadores.
Por ejemplo, si no está seguro de lo que significa la siguiente expresión,

a | 4 + c >> b & 7 || b > a % 3

intente poner unos cuantos paréntesis que la aclaren, por ejemplo:

(a | (((4 + c) >> b) & 7)) || (b > (a % 3))

Capítulo 3. Control del flujo.


• Ramificación
o if-else
o break
o switch
o return
• Bucles
o while
o do-while
o for
o Sentencias separadas por comas
o continue
• Excepciones

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. La ramificación, iteración, selección y llamadas a subrutina son formas de control
de flujo.

Ramificación

En los programas se necesitará que algunas sentencias se ejecuten condicionalmente y


se omitan otras. Java proporciona varios mecanismos para conseguir este control y
decidir qué partes del código ejecutar, mediante sus sentencias de ramificación.

if-else

La construcción if-else provoca que la ejecución atraviese un conjunto de estados


boolean que determinan que se ejecuten distintos fragmentos de código.

if ( expresión-booleana ) sentencia1; [ else sentencia2; ]

La cláusula else es opcional. Cada una de las sentencias puede ser una sentencia
compuesta encerrada entre llaves, { }. Una expresión-booleana es cualquier expresión
que devuelve un valor boolean. Podría ser una variable simple declarada como boolean.

boolean datosdisponibles; int bytesdisponibles;


// ... // ...
if (datosdisponibles) if (bytesdisponibles > 0)
ProcesarDatos(); ProcesarDatos();
else else
esperarAMasDatos(); esperarAMasDatos();

Si se desea incluir más sentencias después del if o else, hay que utilizar las llaves como
en este código ejemplo.

int bytesDisponibles;
// ...
if (bytesDisponibles > 0) {
ProcesarDatos();
bytesDisponibles -= n;
} else
esperarAMasDatos();

break

La sentencia break de Java está diseñada para cubrir aquellos casos en los que saltar
arbitrariamente a una porción de código es una constucción valiosa y legítima para el
control del flujo. El término break se refiere al acto de salirse de un bloque de código.
Le dice al intérprete que retome la ejecución pasado el final del bloque.
switch

La sentencia switch proporciona una forma limpia de dirigir la ejecución a partes


diferentes del código en base al valor de una variable o expresión. Esta es la forma
general de la sentencia switch:

switch ( expresión ) {
case valor1:
break;
case valor2:
break;
case valorN:
break;
default:
}

El valor de expresión se compara con cada uno de los valores literales de las sentencias
case. Si coincide con alguno, se ejecuta el código que sigue a la sentencia case. Si no
coincide con ninguno de ellos, entonces se ejecuta la sentencia default (por defecto). La
sentencia default es opcional. La sentencia break, comentada anteriormente, hace, en
este caso, que la ejecución salte al final del switch. Si no se pone el break, la ejecución
continuará en el siguiente case.

return

Como se explicará en el siguiente capítulo, Java utiliza una forma de subrutina llamada
método para implementar una interfaz de procedimiento a las clases de objetos. En
cualquier momento dentro de un método, se puede utilizar la sentencia return para que
la ejecución salte y vuelva al punto donde se llamó al método.

Bucles

Un bucle es lo que llamamos ejecutar repetidamente el mismo bloque de código hasta


que cumpla una condición de terminación. Hay cuatro partes en cualquier bucle,
inicialización, cuerpo, iteración y terminación . La inicialización es el código que
establece las condiciones iniciales de un bucle. El cuerpo es la sentencia que queremos
repetir. La iteración es el código que queremos ejecutar después de cuerpo, pero antes
de entrar de nuevo en el bucle. Se utiliza a menudo para incrementar o decrementar
contadores e índices. La terminación es la expresión booleana que comprueba cada vez
a lo largo de un bucle para ver si ha llegado el momento de parar de iterar. Java tiene
tres construcciones para bucles: while, do-while y for.

while

Ejecuta una sentencia repetidamente mientras una expresión booleana sea verdadera.
Esta es su forma general:

[ inicialización; ]
while ( terminación ) {
cuerpo;
[ iteración; ]
}
Las partes inicialización e iteración son opcionales, y mientras la expresión terminación
devuelva un valor true, la sentencia cuerpo continuará ejecutándose.

do-while

La contrucción do-while se utiliza cuando se desea ejecutar el cuerpo de un bucle while


al menos una vez, incluso si la expresión booleana tiene el valor false la primera vez. Es
decir si se desea evaluar la expresión de terminación al final del bucle en vez de al
principio como en el while.

[ inicialización; ]
do { cuerpo; [ iteración; ] } while ( terminación );

for

La sentencia for es una forma compacta de expresar un bucle.

for ( inicalización; terminación; iteración ) cuerpo;

Si las condiciones iniciales no provocan que la terminación devuelva true la primera


vez, entonces la sentencia cuerpo e iteración no se ejecutarán nunca. Ejemplo de un
bucle for:

for (int i = 1; i <= 10; i++)


System.out.println("i = " + i);

Aunque no hemos estudiado todavía las clases de Java, comentar brevemente que este
bucle imprime en pantalla desde 1 hasta 10 el índice ( i ) del bucle mediante el método
println.

Sentencias separadas por comas

A veces se podría desear incluir más de un sentencia en las sentencias de inicialización


y de iteración. Java proporciona una manera de expresar sentencias múltiples mediante
la utilización de comas ( , ) en el interior de los paréntesis de un bucle for. Por lo tanto
puede ponerse tantas sentencias como se desee, siempre y cuando estén separadas por
comas.

int a, b;
for (a = 1, b = 4, a < b, a++, b--) {
System.out.println("a = " + a);
System.out.println("b = " + b);
}

El bucle anterior sólo se ejecuta dos veces y su salida es la siguiente:

a=1
b=4
a=2
b=3

continue
Del mismo modo que se desea salir prematuramente de un bucle, se podría desear
continuar en el bucle, pero dejar de procesar el resto de código en esta interación en
concreto. La sentencia continue de Java salta del cuerpo de bucle, pero permaneciendo
en el bucle.

for (int i = 0; i < 10; i++) {


System.out.print(i + " ");
if (i % 2 == 0)
continue;
System.out.println("");
}

Este bucle utiliza continue para provocar que se impriman dos números en cada línea.

Excepciones

La última manera de provocar un salto


en el flujo de un código es utilizando el
mecanismo de gestión de excepciones
de Java. Las sentencias try, catch,
throw y finally se utilizan para
expresar este modelo potente para
ramificación no local. La gestión de
excepciones se utiliza muy a menudo
en las clases de Java fundamentales y
le dedicaremos más Capítulo 4. Clases.
• Referencias a objeto
• Variables de instancia
• El operador new
• El operador punto (.)
• Declaración de método
o this
 ocultar variables de instancia
• El método main()
• Llamada a método
• Constructores
• Sobrecarga de método
• this en los constructores
• Herencia
• super
• Selección de método dinámica
• final
• finalize
• static
• abstract

El elemento básico de la programación orientada a objetos en Java es un clase. Una


clase define la forma y el comportamiento de un objeto. Cualquier concepto que desee
representar en su programa en Java está encapsulado en una clase. En este capítulo
abordaremos en profundidad los conceptos teóricos de la programación orientada a
objetos: encapsulado, herencia, y polimorfismo. Aprenderemos a crear, ampliar y crear
instancias de nuestras propias clases y comenzaremos a utilizar la potencia real del
estilo orientado a objetos de Java. Para crear una clase sólo se necesita un archivo fuente
que contenga la palabra clave class seguida de un identificador legal y un par de llaves
para el cuerpo.

class Point {
}

Las clases de Java típicas incluirán variables y métodos de instancia. Los programas en
Java completos constarán por lo general de varias clases de Java de distintos archivos
fuente.
Una clase define la estructura de un objeto y su interfaz funcional, conocida como
métodos. Cuando se ejecuta un programa en Java, el sistema utiliza definiciones de
clase para crear instancias de las clases, que son objetos reales. La forma general de una
clase se muestra a continuación.

class nombre_de_clase extends nombre_de_superclase {


type variable_de_instancia1;
type variable_de_instancia2;
type variable_de_instanciaN;
type nombre_de_método1 (lista_de_parámetros) {
cuerpo_del_método;
}
type nombre_de_método2 (lista_de_parámetros) {
cuerpo_del_método;
}
type nombre_de_métodoN (lista_de_parámetros) {
cuerpo_del_método;
}
}

Aquí, nombre_de_clase y nombre_de_superclase son identificadores. La palabra clave


extends se utiliza para indicar que nombre_de_clase será una subclase de
nombre_de_superclase. Hay una clase incorporada, llamada Object (objeto), que está en
la raíz de la jerarquía de clases de Java. Si se desea realizar una subclase de Object
directamente, se puede omitir la cláusula extends.

Referencias a objeto
Cada nueva clase que se crea añade otro tipo que se puede utilizar igual que los tipos
simples. Por lo tanto, cuando se declara una nueva variable, se puede utilizar un nombre
de clase como tipo. A estas variables se las conoce como referencias a objeto.
A cada instancia se le puede llamar también objeto. Cuando se declara que el tipo de
una variable es una clase, tiene como valor por omisión el null, que es una referencia al
tipo Object, y, por lo tanto, es compatible en tipo con todas las otras clases. El objeto
null no tiene valor; es distinto del entero 0, igual que el false de boolean. Este ejemplo
declara una variable p cuyo tipo es de la clase Point.

Point p; Aquí la variable p tiene un valor null.

Variables de instancia

Los datos se encapsulan dentro de una clase declarando las variables dentro de las llaves
de apertura y cierre de la declaración de la clase. A las variables que se declaran en este
ámbito y fuera del ámbito de un método concreto se las conoce como variables de
instancia. Este ejemplo declara una clase de nombre Point, con dos variables de
instancia enteras llamadas x e y.

class Point {
int x, y;
}

El operador new

El operador new crea una única instancia de una clase y devuelve una referencia a ese
objeto. Aquí se crea una nueva instancia de Point y se almacena en una variable p.

Point p = new Point();

Aquí p referencia a una instancia de Point, pero realmente no lo contiene. Se pueden


crear múltiples referencias al mismo objeto.

Point p = new Point();


Point p2 = p;

Cualquier cambio realizado en el objeto refernciado por p2 afectará al mismo objeto al


cual se refiere p. La asignación de p a p2 no asignó memoria ni copió nada en el objeto
original. De hecho las asignaciones posteriores a p simplemente desengancharán p del
objeto original sin afectar al propio objeto, como se muestra a continuación.

Point p = new Point();


Point p2 = p;
p = null;

Aunque se haya asignado p a null, p2 todavía apunta al objeto creado por el operador
new.

El operador punto (.)


El operador punto se utiliza para acceder a las variables de instancia y los métodos
contenidos en un objeto. Esta es la forma general de acceder a las variables de instancia
utilizando el operador punto.

referencia_a_objeto.nombre_de_variable

Aquí referencia_a_objeto es una referencia a un objeto y nombre_de_variable es el


nombre de la variable de instancia contenida en el objeto al que se desea acceder. El
siguiente fragmento de código muestra cómo se puede utilizar el operador punto para
almacenar valores en variables de instancia y para referirnos a estos.

p.x = 10;
p.y = 20;
System.out.println("x = " + p.x + " y = " + p.y);

Declaración de método

Los métodos son subrutinas unidas a una definición de una clase específica. Se declaran
dentro de una definición de clase al mismo nivel que las variables de instancia. Se debe
llamar a los métodos en el contexto de una instancia concreta de esa clase.
En la declaración de los métodos se define que devuelve un valor de un tipo concreto y
que tiene un conjunto de parámetros de entrada.

tipo nombre_de_método ( lista_formal_de_parámetros ) {


cuerpo_del_método;
}

El nombre_de_método es cualquier identificador legal distinto de los ya utilizados en el


ámbito actual. La lista_forma_de_parámetros es una secuencia de parejas de tipo e
identificador separadas por comas. Si no se desean parámetros, la declaración del
método deberá incluir un par de paréntesis vacío.
Podríamos crear un método en nuestra clase Point que inicialice las variables de
instancia que quedaría de la forma siguiente:

clase Point {
int x, y;
void init(int x, int y) {
this.x = x;
this.y = y;
}
}

this

En Java es ilegal declarar dos variables locales con el mismo nombre dentro del mismo
ámbito o uno que lo incluya. Observará que hemos utilizado x e y como parámetros
para el método init y en el interior del método hemos utilizado un valor de referencia
especial llamado this para referirnos directamente a las variables de instancia. Si no
hubiéramos utilizado this entonces x e y se hubieran referido al parámetro formal y no a
las variables de instancia lo que se conoce como ocultar variables de instancia.

El método main()
Dado que no hay funciones globales en Java, se debía idear alguna manera de iniciar un
programa, de ahí el sentido del método main. Puesto que en otros lenguajes (p.e. C y C+
+) main se utilizaba a menudo para pasar parámetros desde la línea de órdenes, un
concepto perdido en los usuarios de interfaces de usuario gráficas, el main de Java
también pasa esos argumentos.
El compilador de Java compilará clases que no tengan el método main. El intérprete
Java, sin embargo, no tiene ningún modo de ejecutar esas clases. El método main es
simplemente un lugar de inicio para que el intérprete comience. Un programa complejo
tendrá docenas de clases, y sólo una de ellas necesitará tener un método main. Para los
applets (programas Java que están incrustados en los visualizadores de red) no se utiliza
el método main, ya que los visualizadores (o navegadores) de red siguen un convenio
distinto para inicializar applets.

Llamada a método

Se llama a los métodos dentro de una instancia de un clase utilizando el operador punto
(.). La forma general de una llamada:

referencia_a_objeto . nombre_de_método ( lista_de_parámetros );

Aquí, referencia_a_objeto es cualquier variable que se refiere a un objeto,


nombre_de_método es el nombre de un método de la clase con la que se declaró
referencia_a_objeto y lista_de_parámetros es una lista de valores o expresiones
separados por comas que coinciden en número y tipo con cualquiera de los métodos
declarados como nombre_de_método en la clase.
En este caso, podríamos llamar al método init sobre cualquier objeto Point.

Point p = new Point();


p.init (10, 20);

Constructores

Las clases pueden implementar un método especial llamado constructor. Un constructor


es un método que inicializa un objeto inmediatamente después de su creación. Tienen
exactamente el mismo nombre de la clase en la que residen; de hecho no se puede tener
ningún otro método que comparta su nombre con su clase. Una vez definido, se llama
automáticamente al constructor después de crear el objeto, antes de que termine el
operador new.
Como extensión a nuestro ejemplo Point, podemos inicializar las dos variables de
instancia cuando construimos el objeto.

class Point {
int x, y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
}
class PointCreate {
public static void main (String args[]) {
Point p = new Point(10, 20);
System.out.println("x = " + p.x + " y = " + p.y);
}
}

Se llama al método del constructor justo después de crear la instancia y antes de que
new vuelva al punto de la llamada.

Sobrecarga de método

Es posible y a menudo deseable crear más de un método con el mismo nombre, pero
con listas de parámetros distintas. A esto se le llama sobrecarga de método. Se
sobrecarga un método siempre que se crea un método en una clase que ya tiene un
método con el mismo nombre. Aquí presentamos una versión de la clase Point que
utiliza sobrecarga de método para crear un constructor alternativo.

class Point {
int x, y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
Point() {
x = -1;
y = -1;
}
}
class PointCreateAlt {
public static void main (String args[]) {
Point p = new Point();
System.out.println("x = " + p.x + " y = " + p.y);
}
}

Este ejemplo crea un objeto Point que llama al segundo constructor sin parámetros en
vez de al primero.

this en los constructores

Un refinamiento adicional es que un constructor llame a otro para construir la instancia


correctamente. A menudo es una buena idea crear constructores relacionados para que
las clases puedan evolucionar con más suavidad a lo largo del ciclo de desarrollo. Por
ejemplo.

class Point {
int x, y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
Point() {
this(-1, -1);
}
}

Herencia
La herencia es un concepto que relaciona clases una encima de otra de un manera
jerárquica. Esto permite que los descendientes de una clase hereden todas las variables y
métodos de sus ascendientes, además de crear los suyos propios. A estos descendientes
se les llama subclases. Al padre inmediato de una clase se le llama su superclase. En
este ejemplo, extendemos la clase Point para que incluya un tercer componente llamado
z.

class Point3D extends Point {


int z;
Point3D(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
Point3D() {
this(-1, -1, -1);
}
}

La palabra clave extends se utiliza para indicar que se desea crear una subclase de Point.
No se necesita declarar las variables x e y en Point3D porque se habían heredado de
Point. Todas las variables x, y, z están en el mismo ámbito desde la perspectiva de una
instancia de Point3D.

super

Hay una variable especial en Java llamada super, que se refiere directamente a los
constructores de la superclase. Este ejemplo define una nueva versión de Point3D que
utiliza el constructor super para inicializar x e y.

class Point3D extends Point {


int z;
Point3D(int x, int y, int z) {
super(x, y); // aquí se llama al constructor Point(x,
y)
this.z = z;
}
}

Selección de método dinámica

Durante la ejecución, la referencia a un objeto se puede referir a una instancia de alguna


subclase del tipo de referencia declarado. En estos casos, Java utiliza la instancia real
para decidir a qué método llamar en el caso que la subclase sobrescriba el método al que
se llama. Por ejemplo, estas dos clases tienen una relación subclase/superclase simple
con un único método que se sobrescribe en la subclase.

class A {
void callme() {
System.out.println("En el método callme de A");
}
}
class B extends A {
void callme() {
System.out.println("En el método callme de B");
}
}
class Dispatch {
public static void main(String args[]) {
A a = new B();
a.callme();
}
}

Hemos declarado que la variable es de tipo A y después hemos almacenado ella una
referencia a una instancia de la clase B. Cuando llamamos al método callme de a, el
compilador de Java verifica que realmente A tiene un método llamado callme, pero el
interprete de Java observa que la referencia es realmente una instancia de B, por lo que
llama al método de B, en vez de al método de A. Esta forma de polimorfismo dinámico
durante la ejecución es uno de los mecanismos más poderosos que ofrece el diseño
orientado a objetos para soportar la reutilización de código y la robustez.

final

Todos los métodos y las variables de instancia se pueden sobrescribir por defecto. Si se
desea declarar que ya no se quiere permitir que las subclases sobrescriban las variables
o métodos, éstos se pueden declarar como final. El modificador de tipo final implica
que todas las referencias futuras a este elemento se basarán en esta definición.
Se puede utilizar final como modificador en declaraciones de método cuando se desea
no permitir que las subclases sobrescriban un método concreto.

finalize

Por lo general, en Java, no hay que preocuparse por liberar memoria. Sin embargo, hay
circunstancias en las que se podría desear ejecutar algún código especial cuando el
sistema de recogida de basura reclama un objeto.
Se añade un método a cualquier clase con el nombre finalize y el intérprete de Java
llama al método cuando vaya a reclamar el espacio de ese objeto.

static

A veces se desea crear un método que se utiliza fuera del contexto de cualquier
instancia. Todo lo que se tiene que hacer es declarar estos métodos como static
(estático). Los métodos estáticos sólo pueden llamar a otros métodos static
directamente, y no se pueden referir a this o super de ninguna manera. Las variables
también se pueden declarar como static, pero debe ser consciente que es equivalente a
declararlas como variables globales, que son accesibles desde cualquier fragmento de
código. Se puede declarar un bloque static que se ejecuta una sola vez si se necesitan
realizar cálculos para inializar las variables static. El ejemplo siguiente muestra una
clase que tiene un método static, algunas variables static y un bloque de inicialización
static.

class Static {
static int a = 3;
static int b;
static void method(int x) {
System.out.println("x = " + x);
System.out.println("a = " + a);
System.out.println("b = " + b);
}
static {
System.out.println("bloque static inicializado");
b = a * 4;
}
public static void main(String args[]) {
method(42);
}
}

Esta es la salida del programa.

bloque static inicializado


x = 42
a=3
b = 12

abstract

Hay situaciones que se necesita definir una clase que declara la estructura de una
abstracción dada sin promocionar una implementación completa de cada método. Se
puede indicar que se necesita que ciertos métodos se sobrescriban en subclases
utilizando el modificador abstract. A estos métodos se les llama a veces responsabilidad
de subclase. Cualquier clase que contenga métodos declarados como abstract también se
tiene que declarar como abstract. No se pueden crear instancias de dichas clases
directamente con el operador new, dado que su implementación completa no está
definida. No se pueden declarar constructores abstract o métodos abstract static.
Cualquier subclase de una clase abstract debe implementar todos los métodos abstract
de la superclase o ser declarada también como abstract.

adelante un capítulo completo a su estudio.

Capítulo 5. Paquetes e interfaces.


• Paquetes
o La sentencia package (paquete)
o Compilación de clases en paquetes
o La sentencia import
o Protección de accesos
• Interfaces
o La sentencia interface
o La sentencia implements

Paquetes

Los paquetes son los módulos de Java. Recipientes que contienen clases y que se
utilizan tanto para mantener el espacio de nombres dividido en compartimentos más
manejables como un mecanismo de restricción de visibilidad. Se pueden poner clases
dentro de los paquetes restringiendo la accesibilidad a éstas en función de la
localización de los otros miembros del paquete. Cada archivo .java tiene las mismas
cuatro partes internas y hasta ahora sólo hemos utilizado una de ellas en nuestros
ejemplos. Esta es la forma general de un archivo fuente de Java.

una única sentencia de paquete (opcional) las sentencias de importación deseadas


(opcional) una única declaración de clase pública las clases privadas de paquete
deseadas (opcional)

La sentencia package (paquete)

Lo primero que se permite en un archivo Java es una sentencia package, que le dice al
compilador en qué paquete se deberían definir las clases incluidas. Si se omite la
sentencia package, las clase terminan en el paquete por defecto, que no tiene nombre. El
compilador Java utiliza directorios de sistema de archivos para almacenar paquetes.
Si se declara que una clase está en dentro de un paquete llamado MiPaquete, entonces el
archivo fuente de esa clase se debe almacenar en un directorio llamado MiPaquete.
Recuerde que se diferencia entre mayúsculas y minúsculas y que el nombre del
directorio debe coincidir con el nombre exactamente. Esta es la forma general de la
sentencia package.

package paq1[ .paq2 [ .paq3 ]];

Observe que se puede crear una jerarquía de paquetes dentro de paquetes separando los
niveles por puntos. Esta jerarquía se debe reflejar en el sistema de archivos de desarrollo
de Java. Un paquete declarado como

package java.awt.imagen;

se debe almacenar en java/awt/imagen, en java\awt\imagen o en java:awt:imagen en los


sistemas de archivo UNIX, Windows o Macintosh, respectivamente.

Compilación de clases en paquetes

Cuando se intenta poner una clase en un paquete, el primer problema es que la


estructura de directorios debe coincidir con la jerarquía de paquetes exactamente. No se
puede renombrar un paquete sin renombrar el directorio en el cual están almacenadas
las clases. El otro problema que presenta la compilación es más sutil.
Cuando desee llamar a una clase que se encuentre en un paquete deberá prestar atención
a su ubicación en la jerarquía de paquetes. Por ejemplo, se crea una clase llamada
PaquetePrueba en un paquete llamado prueba. Para ello se crea el directorio llamado
prueba y se pone en él PaquetePrueba.java y se compila. Si intenta ejecutarlo, el
intérprete no lo encontrará. Esto se debe a que esta clase se encuentra ahora en un
paquete llamado prueba y no nos podemos referir a ella simplemente con
PaquetePrueba. sin embargo si es posible referirse a cualquier paquete enumerando su
jerarquía de paquetes, separando los paquetes por puntos. Por lo tanto, si intenta ahora
ejecutar ahora Java con prueba.PaquetePrueba, el intérprete tampoco lo encontrará.
El motivo de este nuevo fracaso se encuentra en la variable CLASSPATH.
Probablemente contenga algo parecido a ".;C:\java\classes" que le dice al intérprete que
busque en el directorio de trabajo actual y en el directorio de instalación del Equipo de
herramientas de desarrollo de Java estándar. El problema es que no hay un directorio
prueba en el directorio de trabajo actual porque usted ya está en el directorio prueba.
Tiene dos opciones en este momento: cambiar de directorio un nivel arriba y ejecutar
"java prueba.PaquetePrueba", o añadir el extremo de su jerarquía de clases de desarrollo
a la variable de entorno CLASSPATH. Después podrá utilizar "java
prueba.PaquetePrueba" desde cualquier directorio y Java encontrará el archivo .class
adecuado. Si trabaja con su código fuente en un directorio llamado C:\mijava, dé a
CLASSPATH el valor ".;C:\mijava;C:\java\classes".

La sentencia import

Lo siguiente que se pone después de una sentencia package y antes de las definiciones
de clase en un archivo fuente en Java puede ser una lista de sentencias import. Todas las
clases interesantes están almacenadas en algún paquete con nombre. Para no tener que
introducir el largo nombre de trayecto de paquete para cada clase, Java incluye la
sentencia import para que se puedan ver ciertas clases o paquetes enteros. La forma
general de la sentencia import:

import paquete1.[ paquete2 ].( nombre_clase | * );

paquete1 es el nombre de un paquete de alto nivel, paquete2 es el nombre de un paquete


opcional contenido en el paquete exterior separado por un punto (.). No hay ningún
límite práctico a la profundidad de la jerarquía de paquetes.
Finalmente, nombre_clase explícito o un asterisco (*) que indica que el compilador Java
debería buscar este paquete completo.

import java.util.Date;
import java.io.*;

Protección de accesos

Java proporciona unos cuantos mecanismos para permitir un control de acceso entre
clase en circunstancias diferentes.
Dentro de un clase todas las variables y métodos son visibles para todas las otras partes
de la misma clase. Por la existencia de paquetes, Java debe distinguir cuatro categorías
de visibilidad entre elementos de clase:

- Subclases del mismo paquete.


- No subclases del mismo paquete.
- Subclases en paquetes distintos.
- Clases que no están ni en el mismo paquete ni en las
subclases.

Las tres palabras clave, private, public y protected se combinan de varias maneras para
generar muchos niveles de acceso. La tabla siguiente resume las interacciones.

sin private
private protected public
modificador protected
misma
sí sí sí sí sí
clase
misma no sí sí sí sí
subclase de
paquete
misma no
subclase de no sí no sí sí
paquete
subclase de
diferente no no sí sí sí
paquete
no subclase
de diferente no no no no sí
paquete

Interfaces

Las interfaces de Java están diseñadas para admitir resolución de método dinámica
durante la ejecución. Para que un método sea llamado desde una clase a otra, ambas
clases tienen que estar presentes durante la compilación, para que el compilador de Java
pueda comprobar que las signaturas de método son compatibles. Debe haber un
mecanismo para desconectar la definición de un método o conjunto de métodos de la
jerarquía de herencias. Las interfaces son como las clases, pero sin variables de
instancia y con métodos declarados sin cuerpo. Las clases pueden inplementar varias
interfaces. Para implementar una interfaz, todo lo que necesita una clase es una
implementación del conjunto completo de métodos de la interfaz. Las interfaces están
en una jerarquía distinta de la de las clases, por lo que es posible que varias clases que
no tengan la más mínima relación en cuanto a la jerarquía de clases implementen la
misma interfaz.

La sentencia interface

La forma general de una interfaz es

interface nombre {
tipo_devuelto nombre_de_método1(lista_de_parámetros);
type nombre_var_final = valor;
}

Aquí nombre es cualquier identificador legal. Observe que los métodos que se declaran
no tienen sentencias de cuerpo. Todos los métodos que están implementando interfaces
se deben declarar como public. Las variables se pueden declarar dentro de las
declaraciones de interfaz y son final implícitamente, lo que significa que no las puede
cambiar la clase que las implementa y además deben ser inicializadas con un valor
constante. Un ejemplo de declaración de interfaz que contiene un método que toma un
único parámetro.

interface Callback {
void callback(int param);
}

La sentencia implements
Ampliando nuestra definición anterior de clase, una clase que implementa algunas
interfaces tiene la forma general siguiente.

class nombre_de_clase [extends superclase] [implements interfaz0


[,interfaz1...]] {
cuerpo_de_clase;

Capítulo 6. Gestión de cadenas.


• Constructores
• Sintaxis de cadena especial
o Creación de cadenas
o Concatenación de cadenas
o Aspectos de precedencia de operadores
o Conversión de cadenas
• Extracción de caracteres
• Comparación
o Igualdad
o Ordenación
• valueOf
• StringBuffer
• append

Una cadena es una secuencia de caracteres. Las cadenas son una parte fundamental de la
mayoría de los programas, así pues Java tiene varias características incorporadas que
facilitan la manipulación de cadenas.

Java tiene una clase incorporada en el paquete java.lang que encapsula las estructuras de
datos de una cadena. Esta clase, llamada String es la representación como objeto de una
matriz de caracteres que no se puede cambiar.

Hay una clase que la acompaóa, llamada StringBuffer, que se utiliza para crear cadenas
que pueden ser manipuladas después de ser creadas.

Constructores

Como con todas las otros clases, se pueden crear instancias de String con el operador
new.

String s = new String();

Este ejemplo creara una instancia de String sin caracteres en ella. Para crear un String
inicializado con caracteres hay que pasarle una matriz de char al constructor. Veamos
un ejemplo:

char chars[] = { 'a','b','c'};


String s = new String(chars); // s es la cadena "abc"
Si se tiene una matriz de la que solo un rango nos interesa existe un constructor que
permite especificar el índice de comienzo y el n§. de caracteres a utilizar.

char chars[] = {'a','b','c','d','e','f'};


String s = new String(chars, 2, 3); // s es la cadena "cde"

También existen constructores para caracteres ASCII (caracteres de 8 bits) frente a los
caracteres Unicode de Java (caracteres de 16 bits).

Sintaxis de cadenas especial

Java incluye algunas ayudas sintácticas con el fin de ayudar a los programadores a
realizar las operaciones mas habituales con cadenas.

Creación de cadenas

Dado que los Strings son valores constantes, Java incluye un atajo para un literal de
cadena estándar, en el que un valor de cadena se puede encerrar entre comillas dobles:

String s = "abad";

uno de los métodos mas habituales que se utilizan en un String es length, que devuelve
el n§. de caracteres de una cadena:

String s = "abc";
System.out.println(s.length()); // imprimiría 3

Un punto interesante en Java es que se crea una instancia de objeto para cada literal
String, por lo que se puede llamar a los métodos directamente con una cadena entre
comillas, como si fuera una referencia a objeto, con este ejemplo se volvería a imprimir
un 3:

String s = "abc";
System.out.println("abc".lenght());

Concatenación de cadenas

El único operador que utiliza Java es + , y en los objetos String. El + actúa como
operador de concatenación en este caso en concreto para mejorar la legibilidad, por ser
operación muy común.

String s = "El tiene " + edad + " años";

esta mucho más claro que

String s = new StringBuffer("El tiene ")


.append (edad)
.append (" años")
.toString();
que es lo que sucede cuando se ejecuta este código. append aóade cosas al final de
StringBuffer, y toString convierte a cadenas el StringBuffer. Trataremos con detalle
append y toString mas adelante en este capítulo.

Aspectos de precedencia de operadores

Debe tener cuidado cuando mezcle expresiones enteras con expresiones de


concatenación de cadenas por que puede obtener resultados sorprendentes.

String s = "cuatro: " + 2 + 2;

Se podría esperar que el valor de s sea "cuatro: 4", pero la procedencia de operadores
provoco que se evaluase primero la subexpresión "cuatro: " + 2, y después "cuatro: 2" +
2, donde como resultado "cuatro: 22". Si se desea realizar primero la expresión entera,
hay que utilizar paréntesis, como aquí:

String s = "cuatro: " + (2+2);

Conversión de cadenas

StringBuffer tiene una versión sobrecargada de append para cada tipo posible. Por lo
tanto, cuando se utiliza `+' para concatenar una variable, se llama a la versión adecuada
de append para esa variable. El método append realmente llama a un método estático de
String llamado valueOf para construir la representación tipo cadena. Para tipos simples,
valueOf crea simplemente una representación de cada int o float. Para objetos, valueOf
llama al método to String con ese objeto. Cada clase implementa toString, con una
implementación por defecto que se encuentra en la clase Object. Es bueno el
sobrescribir toString y presentar una versión propia de cadena para las clases. El
ejemplo siguiente muestra una clase que sobrescribe toString para mostrar los valores
de sus variables de instancia.

class Point {
int x, y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
public String toString() {
return "Punto[" + x + "," + y + "]";
}
}
class toStringDemo {
public static void main(String args[]) {
Point p = new Point(10, 20);
System.out.println("p = " + p);
}
}

Esta versión de la clase Point de los capítulos anteriores incluye una versión con la que
se sobrescribe el método toString del objeto, y que da formato a la cadena que contiene
los valores de x y de y de cada instancia de Point. La salida de este programa es la
siguiente:
c:\> java toStringDemo
p = Punto[10, 20 ]

Extracción de caracteres

Para extraer un único carácter de una cadena, se puede referir a un carácter indexado
mediante el método charAt:

"abc".charAt(1) // devolverá 'b'

Si se necesita extraer más de un carácter a la vez, puede utilizar el método gerChars, que
le permite especificar el índice del primer carácter y del último más uno que se desean
copiar, además de la matriz char donde se desean colocar dichos caracteres.

String s = "Esto no es una canción";


char buf[] = new char[2];
s.getChars(5, 7, buf, 0); // buf ahora tendrá el valor 'no'

También existe una función útil llamada toCharArray, que devuelve una matriz de char
que contiene la cadena completa.

Comparación

Si se desean comparar dos cadenas para ver si son iguales, puede utilizar el método
equals de String. Devolverá true si el único parámetro está compuesto de los mismos
caracteres que el objeto con el que se llama a equals. Una forma alternativa de equals
llamada equalsIgnoreCase ignora si los caracteres de las cadenas que se comparan están
en mayúsculas o minúsculas.

La clase String ofrece un par de métodos útiles que son versiones especializadas de
equals. El método reginMatches se utiliza para comparar una región especifica que se
parte de una cadena con otra región de otra cadena. Hay dos variantes de
regionMatches, una le permite controlar si es importante la diferenciación entre
mayúsculas/minúsculas; la otra asume que si lo es.

boolean regionMatches (int toffset, String otra,


int ooffset, int longitud); // si importa la
diferencia
boolean regionMatches (boolean ignorarMaysc,
int toffset, String otra,
int ooffset, int longitud); // no importa la
diferencia

En estas dos versiones de regionMatches, el parámetro toffset indica el desplazamiento


en caracteres en el objeto String sobre el que estamos llamando el método. La cadena
con la que estamos comparando se llama otra, y el desplazamiento dentro de esa cadena
se llama ooffset. Se comparan longitud caracteres de las dos cadenas comenzando a
partir de los dos desplazamientos.

Igualdad
El método equals y el operador = = hacen dos pruebas completamente diferentes para la
igualdad. Mientras que el método equals compara los caracteres contenidos en una
String, el operador = = compara dos referencias de objeto para ver si se refieren a la
misma instancia.

Ordenación

A menudo no basta con conocer si dos cadenas son idénticas o no. Para aplicaciones de
ordenación, necesitamos conocer cuál es menor que, igual que o mayor que la siguiente.
El método de String compareTo se puede utilizar para determinar la ordenación. Si el
resultado entero de compareTo es negativo, la cadena es menor que el parámetro, y si es
positivo, la cadena es mayor. Si compareTo devuelve 0, entonces las dos cadenas son
iguales. Ahora ordenaremos una matriz de cadenas utilizando compareTo para
determinar el criterio de ordenación mediante una Ordenación en burbuja.

class SortString {
static String arr[] = {
"Ahora", "es", "el ", "momento", "de", "actuar"
};
public static void main(String args[]) {
for (int j = 0; j < arr.length; j++) {
for (int i = j + 1; i < arr.length; y++) {
if (arr[i].compareTo(arr[j] < 0) {
String t = arr[j];
arr[j] = arr[i];
arr[i] = t;
}
}
System.out.println(arr[j]);
}
}
}

valueOf

Si se tiene algún tipo de datos y se desea imprimir su valor de una forma legible,
primero hay que convertirlo a String. El método valueOf está sobrecargado en todos los
tipos posibles de Java, por lo que cada tipo se puede convertir correctamente en una
String. Cualquier objeto que se le pase a valueOf devolverá el resultado de llamar al
método toString del objeto. De hecho, se podría llamar directamente a toString y
obtener el mismo resultado.

StringBuffer

StringBuffer es una clase gemela de String que proporciona gran parte de la


funcionalidad de la utilización habitual de las cadenas. StringBuffer representa
secuencias de caracteres que se pueden ampliar y modificar. Java utiliza ambas clases
con frecuencia, pero muchos programadores sólo tratan con String y permiten que Java
manipule StringBuffer por su cuenta mediante el operador sobrecargado '+'.

append
Al método append de StringBuffer se le llama a menudo a través del operador +. Tiene
versiones sobrecargadas para todos los tipos. Se llama a String.valueOf para cada
parámetro y el resultado se aóade al StringBuffer actual. Cada versión de append
devuelve el propio buffer.

Capítulo 7. Gestión de excepciones.


• Excepciones no capturadas
• try y catch
• Cláusulas catch múltiples
• throw
• finally

Una excepción es una condición anormal que surge en una secuencia de código durante
la ejecución.

La gestión de excepciones de Java lleva la gestión del error en tiempo de ejecución al


mundo orientado a objetos. Una excepción de Java es un objeto que describe una
condición excepcional que se ha producido en un fragmento de código.

Excepciones no capturadas

Los objetos de excepción los crea automáticamente el intérprete de Java como respuesta
a alguna condición excepcional. Como ejemplo tomamos una división por cero. Cuando
el intérprete de Java intenta ejecutar la división, observa que el denominador es cero y
construye un nuevo objeto de excepción para que se detenga este código y se trate esta
condición de error. Una vez detenido el flujo del código en el operador de división, se
buscará en la pila de llamadas actual cualquier gestor de excepciones (pila que contiene
un registro de las llamadas a método).
Un gestor de excepciones es algo establecido para tratar inmediatamente la condición
excepcional. Si no codificamos un gestor de excepciones, se ejecutara el gestor en
tiempo de ejecución por defecto. El gestor por defecto imprime el valor String de la
excepción y el trazado de la pila del lugar donde se produjo la excepción.

try y catch

A menudo es más elegante y practico manejar nosotros mismos la excepción. Se puede


utilizar la palabra clave try para especificar un bloque de código que se debería proteger
frente a todas las excepciones. A continuación inmediatamente del bloque try, se
incluye la cláusula catch que especifica el tipo de excepción que se desea captar.
Veamos estas construcciones sobre el ejemplo anterior de la división:

class Exc {
public static void main(String args[]) {
try {
int d = 0;
int a = 42;
} catch (ArithmeticException e) {
System.out.println("división por cero");
}
}
}

ArithmeticException es una subclase especial de Exception, que describe más


específicamente el tipo de error que se ha producido. El ámbito de la cláusula catch está
restringido a las sentencias especificadas por la sentencia try precedente.

Cláusulas catch múltiples

En algunos casos, la misma secuencia de código puede activar más de una condición
excepcional. Se pueden tener varias cláusulas catch en una fila. Se inspecciona cada uno
de estos tipos de excepción en el orden en que están y el primero que coincida se
ejecuta. Las clases de excepción más especificas se colocaran primero, dado que no se
alcanzarán las subclases si están después de unas superclase.

throw

La sentencia throw se utiliza para lanzar explícitamente una excepción. En primer lugar,
debe obtener un descriptor de una instancia de Throwable, mediante un parámetro en
una cláusula catch, o cerrar una utilizando el operador new. esta es la forma general de
una sentencia throw:

throw InstanciaThrowable;

El flujo de la ejecución se detiene inmediatamente después de la sentencia throw, y no


se llega a la sentencia siguiente. Se inspecciona el bloque try que la engloba más
cercano para ver si tiene una cláusula catch cuyo tipo coincida con el de la instancia
Throwable. Si la encuentra, el control se transfiere a esa sentencia. Si no, se inspecciona
el siguiente bloque try que la engloba, y así sucesivamente, hasta que le gestor de
excepción más externo detiene el programa e imprime el trazado de la pila hasta la
sentencia throw.

finally

A veces es necesario estar seguro de que se ejecutará un fragmento de código dado


independientemente de que excepciones se provocan y capturan. Se puede utilizar la
palabra clave finally par identificar dicho bloque de código. Incluso aunque so coincida
ninguna de las cláusulas catch, se ejecutará el bloque finally antes del código que está
después del final del bloque try completo

Capítulo 8. Hilo y Sincronización.


• El modelo de hilo de Java
o Prioridades de hilo
o Sincronización
o Intercambio de mensajes
• Thread
• Runnable
• Prioridades de los hilos
• Sincronización
o La sentencia synchronized
• Comunicación entre hilos
o Bloqueos
• Resumen de la interfaz de programación (API) de hilos
o Métodos de clase
o Métodos de instancia

La programación multihilo es un paradigma conceptual de la programación pro el cual


se dividen los programs en dos o más procesos que se pueden ejecutar en paralelo. En
un momento dado pueden haber datos de entrada de usuario a los que responder,
animaciones y visualizaciones de interfaz de usuario, también cálculos grandes que
podrían tardar varios segundos en terminar, y nuestros programas tendrán que tratar con
estos temas sin provocar retrasos desagradables al usuario.

Lo interesante de todos estos procesos en paralelo es que la mayor parte de ellos


realmente no necesitan los recursos completos de la computadora durante su vida
operativa. El problema en los entornos de hilo único tradicionales es que se tiene que
esperar a que se terminen cada una de estas tareas antes de proseguir con la siguiente.
Aunque la CPU esté libre la mayor parte del tiempo, tiene que colocar las tareas en la
cola ordenadamente.

El modelo de hilo de Java

Los sistemas multihilo aprovechan la circunstancia de que la mayoría de los hilos


computacionales invierten la mayor parte del tiempo esperando a que un recurso quede
disponible, o bien esperando a que se cumpla alguna condición de temporización. Si
fuésemos capaces de describir todas las tareas como hilos de control independientes,
conmutando de manera automática entre una tarea que esté lista para pasar a un modo
de espera, y otra que sí tenga algo que hacer, conseguiríamos realizar una cantidad
mayor de trabajo en le mismo intervalo de tiempo.

Java se diseño partiendo de cero, en un mundo en el que el entorno multihilo, a nivel de


sistema operativo, era una realidad. El intérprete de Java hace uso intensivo de hilos
para multitud de propósitos, y todas las bibliotecas de clases se diseñaron teniendo en
mente el modelo multihilo. Una vez que un hilo comienza su tarea, puede suspenderse,
lo que equivale a detener temporalmente su actividad. El hilo suspendido puede
reanudarse, lo que supone que continúa su tarea allí donde la dejó. En cualquier
momento, un hilo puede deteriores, finalizando su ejecución de manera inmediata. Una
vez detenido, el proceso no puede reiniciarse.

Prioridades de hilo

El intérprete de Java utiliza prioridades para determinar cómo debe comportarse cada
hilo con respecto a los demás. Las prioridades de hilo son valores entre 1 y 10 que
indican la prioridad relativa de un hilo con respecto a los demás.

Sincronización
Ya que los hilos permiten y potencian el comportamiento asíncrono de los programas,
debe existir alguna manera de forzar el sincronismo allí donde sea necesario. Por
ejemplo, si desease que dos hilos se comunicasen para compartir una estructura de datos
compleja (como una lista enlazada), necesitará alguna manera de garantizar que cada
uno se aparte del camino del otro. Java incorpora una versión rebuscada de un modelo
clásico para la sincronización, el monitor. La mayor parte de los sistemas multihilo
implementan los monitores a modo de objetos, pero Java proporciona una solución más
elegante: no existe la clase monitor, cada objeto lleva asociado su propio monitor
implícito, en el que puede entrar sin más que hacer una llamada a los métodos
synchronized del objeto. Una vez que el hilo está dentro del método synchronized,
ningún otro hilo puede efectuar una llamada a otro método synchronized sobre el mismo
objeto.

Intercambio de mensajes

Una vez que el programa se ha dividido en sus partes lógicas, a modo de hilo, es preciso
definir exactamente como se comunicarán entre si dichos hilos. Java utiliza los métodos
wait y notify para el intercambio de información entre hilos.

Thread

En Java los hilos se representa mediante una clase. La clase Thread encapsula todo el
control necesario sobre los hilos. Hay que tomar la precaución de distinguir claramente
un objeto Thread de un hilo en ejecución. Un objeto Thread se define como el panel de
control o proxy de un hilo en ejecución. En el objeto Thread hay métodos que controlan
si el hilo se está ejecutando, está durmiendo, en suspenso o detenido. La clase Thread es
la única manera de controlar el comportamiento de los hilos. En la siguiente instrucción
se muestra como acceder al hilo en ejecución actual:

Thread t = Thread.currentThread(); * el hilo actual se almacena en la variable t *

Runnable

Si queremos tener más de un hilo necesitamos crear otra instancia de Thread. Cuando
construimos una nueva instancia de Thread, necesitamos decirle que código ejecutar en
el nuevo hilo de control. Se puede comenzar un hilo sobre cualquier objeto que
implemente la interfaz Runnable.
Runnable es una interfaz simple que abstrae la noción de que se desea que algún código
se "ejecute" asíncronamente. Para implementar Runnable, a una clase le basta con
implementar un solo método llamado run. Este es un ejemplo que crea un nuevo hilo.

class ThreadDemo implements Runnable {


ThreadDemo() {
Thread ct = Thread.currentThread();
Thread t = new Thread(this, "demo Thread");
System.out.println("hilo actual: " + ct);
System.out.println("Hilo creado: " + t);
t.start();
try {
Thread.sleep(3000);
} catch (interrupteExecption e) {
System.out.println("Interrumpido");
}
System.out.println(!saliendo del hilo main");
}
public void run() {
try {
for >(int y = 5; y > 0; y--) {
System.out.println(" " + i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
System.out.println("hijo interrumpido");
}
System.out.println("saliendo del hilo hijo");
}
public static void main (String args []) {
new ThreadDemo();
}
}

El hilo main crea un nuevo objeto Thread, con new Thread (this, "Demo Thread"),
pasando this como primer argumento para indicar que queremos que el nuevo hilo llame
al método run sobre este (this) objeto. A continuación llamamos a start, lo que inicia el
hilo de la ejecución a partir del método run. Después, el hilo main se duerme durante
3000 milisegundos antes de imprimir un mensaje y después termina. Demo Thread
todavía está contando desde cinco cuando sucede esto. Se continúa ejecutando hasta que
termina con el bucle de run. Esta es la salida después de cinco segundos:

C:\> java ThreadDemo


Hilo actual: Thread[main, 5, main]
Hilo creado: Thread[demo Thread, 5, main]
5
4
3
saliendo del hilo main
2
1
saliendo del hilo hijo

Prioridades de los hilos

El planificador de hilos hace uso de las prioridades de los mismos para decidir cuándo
debe dejar a cada hilo que se ejecute, de manera que los hilos con mayor prioridad
deben ejecutarse más a menudo que lo de menor prioridad. Cuando está ejecutándose un
hilo de baja prioridad, y otro de mayor prioridad se despierta de su sueño, o de la espera
por un operación de E/S, debe dejarse que se ejecute de manera inmediata, desalojando
al hilo de menor prioridad. Cuando los hilos son de igual prioridad deben desalojarse los
unos a los otros, cada cierto tiempo, utilizando el algoritmo circular round-robin para
gestionar el acceso al la CPU.

En JDK 1.0 la planificación de hilos es un problema que no está completamente


resuelto. Por lo que si pretendemos tener un comportamiento predecible sobre las
aplicaciones deberemos utilizar hilos que, voluntariamente, cedan el control de la CPU.
Sincronización

Cuando dos o más hilos necesitan acceder de manera simultánea a un recurso de datos
compartido necesitan asegurarse de que sólo uno de ellos accede al mismo cada vez.
Java proporciona un soporte único, el monitor, es un objeto que se utiliza como cerrojo
exclusivo. Solo uno de los hilos puede ser el propietario de un monitor en un instante
dado. Los restantes hilos que estuviesen intentando acceder al monitor bloqueado
quedan en suspenso hasta que el hilo propietario salga del monitor.

Todos los objetos de Java disponen de un monitor propio implícitamente asociado a


ellos. La manera de acceder a un objeto monitor es llamando a un método marcado con
la palabra clave synchronized. Durante todo el tiempo en que un hilo permanezca en un
método sincronizado, los demás hilos que intenten llamar a un método sincronizado
sobre la misma instancia tendrán que esperar. Para salir del monitor y permitir el control
del objeto al siguiente hilo en espera, el propietario del monitor sólo tiene que volver
del método

La sentencia synchronized

Si se utiliza una clase que no fue diseñada para accesos multihilo y, por ello, dispone de
métodos no sincronizados que manipulan el estado interno, puede envolver la llamada al
método en un bloque sincronizado. El formato general de la sentencia sincronizada es el
siguiente:

synchronized(objeto) sentencia;

En el ejemplo, objeto es cualquier referencia al objeto, y sentencia suele ser un bloque


que incluye una llamada al método de objeto, que solo tendrá lugar una vez que el hilo
haya entrado con éxito en el monitor de objeto. Ahora veremos las formas de
sincronización con un ejemplo:

class Callme {
void call (String msg) { * también podía haber puesto
synchronized antes de void *
System.out.print("[" + msg);
try Thread.sleep(1000); catch (Exception e);
System.out.println("]");
}
}
class caller implements Runnable {
String msg;
Callme target;
public caller(Callme t, String s) {
target = t;
msg = s;
new Thread(this).start();
}
public void run() {
synchronized(target) {
target.call(msg);
}
}
}
class Synch {
public static void main(String args[]) {
Callme target = new Callme();
new caller(target, "Hola");
new caller(target, "Mundo");
new caller(target, "Sincronizado");
}
}

Este programa imprime por pantalla el literal "Hola Mundo Sincronizado", cada palabra
en una línea y entre comillas, se crea una instancia de Callme y tres instancias de caller
que cada una de ellas referencia al mismo Callme con lo que necesitamos de una
sincronización para el acceso a Callme, pues sino se mezclarían las tres llamada al haber
una sentencia sleep que retrasa la ejecución de Callme dando lugar a que antes de que
acabe un proceso deje libre el acceso a dicho objeto.

Comunicación entre hilos

Veamos, por ejemplo, el problema clásico de las colas, donde uno de los hilos produce
datos y otro los consume. Para que el problema sea más interesante supongamos que el
productor tiene que esperar a que el consumidor haya terminado, para empezar a
producir más datos. En un sistema basado en sondeo el consumidor estaría
desperdiciando ciclos de CPU mientras espera a que el productor produzca. Una vez
que el productor ha terminado, se queda sondeando hasta ver que el consumidor ha
finalizado, y así sucesivamente.

Evidentemente, hay una forma mejor de hacerlo. Java proporciona un mecanismo


elegante de comunicación entre procesos, a través de los métodos wait, notify y
notifyAll. Estos métodos se implementan como métodos de final en Object, de manera
que todas las clases disponen de ellos. Cualquiera de los tres métodos sólo puede ser
llamado desde dentro de un método synchronized.

• wait: le indica al hilo en curso que abandone el monitor y se vaya a dormir hasta
que otro hilo entre en el mismo monitor y llame a notify.
• notify: despierta al primer hilo que realizó una llamada a wait sobre el mismo
objeto.
• notifyAll_: despierta todos los hilos que realizaron una llamada a wait sobre el
mismo objeto. El hilo con mayor prioridad de los despertados es el primero en
ejecutarse.

El ejemplo de el productor y el consumidor es en Java como sigue:

class Q {
int n;
boolean valueSet = false;
synchronized int get() {
if (!valueSet)
try wait(); catch(InterruptedException e);
System.out.println("Obtenido: " + n);
valueSet = false;
notify();
return n;
}
synchronized void put(int n) {
if (valueSet)
try wait(); catch(InterruptedException e);
this.n = n;
valueSet = true;
System.out.println("Colocado: " + n);
notify();
}
}
class Producer implements Runnable {
Q q;
Producer (Q q) {
this.q = q;
new Thread(this, "Producer").start();
}
public void run() {
int y = 0;
while(true) {
q.put(i++);
}
}
}
class Consumer implements Runnable {
Q q;
Consumer(Q q) {
this.q = q;
new Thread(this, "Consumer").start();
}
public void run() {
while(true) {
q.get();
}
}
class PC {
public static void main(String args[]) {
Q q = new Q();
new Producer(q);
new Consumer(q);
}
}

Bloqueos

Los bloqueos son condiciones anómalas inusuales, pero muy difíciles de depurar, donde
dos hilos presentan una dependencia circular sobre un par de objetos sincronizados. Por
ejemplo, si un hilo entra en el monitor sobre el objeto X y otro hilo entra en le monitor
sobre el objeto Y, y si X intenta llamar a cualquier método sincronizado sobre Y, tal y
como cabe esperar quedará detenido. Sin embargo, si Y, por su parte, intenta llamar a
cualquier método sincronizado con X, entonces quedará esperando indefinidamente, ya
que para conseguir el cerrojo de X tendría antes que liberar su propio cerrojo en Y, con
el fin de que el primer hilo pudiera completarse.

Resumen de la interfaz de programación (API) de hilos

Se incluye a continuación una referencia rápida a todos los métodos de la clase Thread
que se han comentado en este capítulo.

Métodos de clase
Estos son los métodos estáticos que deben llamarse de manera directa en la clase
Thread.

• currentThread: el método estático devuelve el objeto Thread que representa al


hilo que se ejecuta 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 los hilos
de menor prioridad no sufran inanición.
• Sleep(int n): el método sleep provoca que el intérprete ponga al hilo en curso a
dormir durante n milisegundos. Una vez transcurridos los n milisegundos, dicho
hilo volverá a estar disponible para su ejecución. Los relojes asociados a la
mayor parte de los intérpretes Java nos serán capaces de obtener precisiones
mayores de 10 milisegundos.

Métodos de instancia

• start: indica al intérprete de Java que cree un contexto de hilo del sistema y
comience a ejecutarlo. A continuación, el método run objeto de este hilo será
llamado en el nuevo contexto del hilo. Debe tomarse la precaución de no llamar
al método start más de una vez sobre un objeto hilo dado.
• run: constituye el cuerpo de un hilo en ejecución. Este es el único método de la
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: 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.
• 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: se utiliza para revivir un hilo suspendido.
• setPriority(int p): asigna al hilo la prioridad indicada por el valor entero pasado
como parámetro.
• getPriority: devuelve la prioridad del hilo en curso, que es un valor entre 1 y
10.
• setName(String nombre): identifica al hilo con un nombre mnemónico. De esta
manera se facilita la depuración de programas multihilo.
• getName: devuelve el valor actual, de tipo cadena, asignado como nombre al
hilo mediante setName.

Capítulo 9. Utilidades.
• Envoltura de tipo simple
o Number
 Double y Float
 Integer y Long
o Character
o Boolean
• Enumeraciones
o Interfaz de enumeración
o Vector
 Stack
o Dictionary
 Hashtable
 Properties
• Runtime
o Gestión de memoria
• System
o Propiedades del entorno
• Date
o Get y Set
o Comparación
• Math
o Transcendentes
o Exponenciales
o Redondeo
• Random

La biblioteca de clases de Java contiene un conjunto de clases de utilidades utilizadas en


todos los paquetes principales de Java. Estas clases están almacenadas en los paquetes
java.lang y java.util. Se utilizan para almacenar conjuntos de objetos, realizar la interfaz
con funciones del sistema de bajo nivel, funciones matemática, generación de números
aleatorios y manipulación de fecha/hora.

Envolturas de tipo simple

Como ya sabemos, Java utiliza tipos primitivos como int y char por razones de
rendimiento. A veces, se necesitará crear una representación como objeto de uno de
estos tipos simples, por lo que Java proporciona clases para cada uno de estos tipos
simples.

Number

La clase abstracta Number representa una interfaz a todos los tipos escalares estándar:
long, int, float y double. Number tiene métodos de acceso que devuelven el valor
posiblemente redondeado del objeto de cada uno de los objetos simples:

• doubleValue() devuelve un double.


• floatValue() devuelve un float.
• intValue() devuelve un int.
• longValue() devuelve un long.

Double y Float
Double y Float son subclases de Number. Ambas clases tienen dos constructores para
inicializarlas con valores double y float, o, lo que es muy práctico, se pueden inicializar
con una representación tipo String del valor:

Double d1 = new Double(3.14159);


Double d2 = new Double("314159E-5");

Integer y Long

La clase Integer es una envoltura alrededor de int, short y byte. Long es una envoltura
alrededor del tipo long. Además de los métodos heredados de Number tienen otros muy
útiles:

• parseInt(String) convierte la variable String en el valor int que representa.


• parseInt(String, base) hace lo mismo que el método anterior, pero especifica
una base distinta de la décima.
• toString(int) convierte el int que se pasa al método en su representación como
cadena.
• toString(int, base) igual al anterior, pero puedo cambiar de base.

Character

Character es una envoltura simple alrededor de un char. Tiene varios métodos útiles
static, que se pueden utilizar para probar varias condiciones de un carácter:

• isLowerCase(char ch) devolverá true si el carácter es una letra minúscula.


• isUpperCase(char ch) lo mismo pero para letras mayúsculas.
• isDigit(char ch) e isSpace(char ch) devuelven true para caracteres numéricos y
espacios en blanco respectivamente.
• toLowerCase(char ch) y toUpperCase(char ch) convierten entre mayúscula y
minúscula y viceversa.

Boolean

Boolean es un envoltorio muy fino alrededor de valores boolean, que solo es útil para
situaciones de paso por referencia.

Enumeraciones

Java tiene matrices para almacenar grupos de datos de tipo similar, que son muy útiles
para modelos simples de acceso a datos. Sin embargo, las enumeraciones ofrecen una
manera más completa y orientada a objetos para almacenar conjuntos de datos de tipo
similar. Las enumeraciones tienen su propia asignación de memoria y posibilidad de
una nueva asignación para ampliarlas. Tienen interfaces de método para su iteración y
recorrido. Se pueden indexar mediante algo más complejo y útil que los simples enteros.

Interfaz de enumeración

Enumeration es una interfaz simple que permite enumerar todos los elementos de
cualquier conjunto de objetos. Especifica dos métodos. El primero, un método boolean
llamado hasMoreElements, devuelve true cuando todavía quedan más elementos que
extraer. El segundo, nextElement devuelve una referencia a objeto genérica cuyo tipo
hay que convertir al tipo de clase de la cual el objeto es una instancia.

Vector

Un Vector es una matriz ampliable de referencias a objeto. Internamente, un Vector


implementa una estrategia de crecimiento para minimizar la reasignación y el espacio
desperdiciado. Los objetos se pueden almacenar al final de un Vector utilizando el
método addElement o en un índice dado mediante el método insertElement. Se puede
almacenar una matriz en un Vector utilizando el método copyInto. Una ves se ha
almacenado un conjunto de objetos en un Vector, se puede utilizar para buscar un
elemento en concreto utilizando los métodos contains, indexOf, o lastIndexOf. También
se puede extraer un objeto de una posición específica de un Vector utilizando los
métodos elementAt, firstElement y lastElement.

Stack

Una Stack es una subclase de Vector que implementa una pila simple del tipo FIFO.
Además de los métodos de Vector estándares, Stack implementa push que coloca
objetos en la parte superior de la pila y pop que retira y devuelve el objeto superior de la
pila. También se puede utilizar peek para obtener el objeto superior de la pila, pero no
retirarlo. El método empty devolverá true si no hay nada en la pila. El método search
comprobará si existe un objeto en la pila y devuelve el número de pops que se
necesitarán para que dicho objeto quede en la parte superior de la pila o -1 si el objeto
pasado como parámetro no se encuentra.

Dictionary

Un Dictionary es una clase abstracta que representa un depósito para almacenar


claves/valores. Una clave es un nombre que se utiliza para recuperar un valor más
adelante. Dada una clave y un valor, se puede almacenar el valor en un Dictionary con
el método put(clave, valor). Después se puede utilizar get(clave) para recuperar. Se
pueden devolver las claves y valores como una enumeration utilizando los métodos
keys y elements. El método size devuelve el número de parejas clave/valor almacenadas
en un diccionario e isEmpty devuelve true cuando no queda ninguna pareja. Se puede
utilizar el método remove(clave) para eliminar una pareja clave/valor.

Hashtable

Una Hashtable es una implementación concreta de un Dictionary. Se puede utilizar una


instancia de Hashtable para almacenar objetos arbitrarios que están indexados por
cualquier otro objeto arbitrario. La utilización mas habitual de una Hashtable es utilizar
una String como clave para almacenar objetos como valores. El ejemplo siguiente crea
una Hashtable para almacenar información acerca de este libro:

import java.util.Dictionary;
import java.util.Hashtable;

class HTDemo {
public static void main(String args[]) {
Hashtable ht = new Hashtable();
ht.put("title", "Manual de Java");
ht.put("author", "Algunos");
ht.put("email", "nisesabe@puesalli.es");
show(ht);
}
static void show(Dictionary d) {
System.out.println("Título: " + d.get("title");
System.out.println("Autor: " + d.get("author");
System.out.println("E-mail: " + d.get("email");
}
}

La salida de este programa muestra cómo el método show, que toma un Dictionay
abstracto como parámetro, es capaz de recuperar todos los valores que se han
almacenado en el método main.

Properties

Properties en una subclase de Hashtable que añade algunos métodos adecuados para
obtener valores que puede que no estén definidos. Se puede especificar un valor por
omisión junto con el nombre en el método getProperty; por ejemplo,
getProperty("nombre","valor por omisión"). Si no se encuentra la propiedad "nombre",
se devuelve "valor por omisión". Además, cuando se construye un objeto Properties, se
le puede pasar otra instancia de Properties en el constructor para que se utilice como
propiedades por omisión de la nueva instancia.

Runtime

La clave Runtime encapsula el proceso del intérprete de Java que se ejecuta. No se


puede crear una instancia de Runtime; sin embargo, se puede obtener una referencia al
objeto Runtime que se está ejecutando actualmente llamando la método estático
Runtime.getRuntime. Una vez tenga el descriptor del proceso en ejecución, puede
llamar a varios métodos que controlan el estado y comportamiento de la máquina virtual
de Java.

Gestión de memoria

Aunque Java es un sistema de recogida de "basura" automática, se podría desear el


conocer el tamaño del montículo de objetos y su espacio libre para comprobar la
eficiencia de su código. Utilizar los métodos totalMemory y freeMemory de Runtime
para obtener esa información.

System

La clase System contiene un conjunto curiosos de funciones y variables globales.


Vamos a ver algunas de las más útiles aquí. La entrada y salida estándar del interprete
de Java se almacena en las variables in y out. Hemos estado utilizando
System.out.println() en la mayoría de los ejemplos de este libro.

El método currentTimeMillis devuelve la hora actual en milisegundos desde el 1 de


enero de 1970. Habitualmente es un número muy grande. Se puede utilizar este long
para inicializar un objeto Date, como ya veremos. El método arraycopy se puede utilizar
para copiar rápidamente una matriz de cualquier tipo desde un sitio a otro. Es mucho
más rápido que el bucle equivalente escrito en Java.

Propiedades del entorno

El intérprete de Java muestra las variables de entorno a través de una instancia de


Properties, cuyo descriptor se puede obtener llamando al método System.getProperties.
System.getProperties es una función adecuada que llama a getProperty sobre este objeto
Properties de entorno.

Date

La clase Date se utiliza para presentar una fecha y una hora. Se pueden manipular el día,
mes, año, día de la semana, horas, minutos y segundos. Hay varios constructores para
objetos Date. El más simple, Date(), inicializa el objeto con la fecha y hora actual. Hay
tres constructores más que ofrecen niveles de especificidad mayores para la precisión
que se desea para la hora:

• Date(año, mes, día) establecerá la hora a las 00:00:00 del día especificado.
• Date(año, mes, día, horas, minutos) fijará la fecha y hora, dejando los
segundos a 0.
• Date(año, mes, día, horas, minutos, segundos) establecerá la hora exacta.

Get y Set

Date incluye un conjunto de métodos para obtener (get) y establecer (set) el año, mes,
día, hora, minuto y segundo de una instancia. Sólo se puede obtener el día de la semana,
ya que está relacionado con el día del mes. Cada una de las funciones get -getYear,
getMonth, getDate, getDay, getHours, getMinutes y getSeconds- devuelve un int. Cada
uno de los métodos set -setYear, setMonth, setDate, setHours, setMinutes y setSeconds-
toma un parámetro int.
Se puede obtener la representación long de un objeto Date con el método getTime. Es el
número en milisegundos desde el 1 de enero de 1970.

Comparación

Si se tienen dos objetos Date que se desean comparar, se podrían convertir a su


representación como long para compararlos como números enteros en milisegundos. La
clase Date incluye tres métodos que se pueden utilizar para una comparación directa:
before, after y equals. Por ejemplo, new Date(96, 11, 10).before(new Date(96, 11, 7))
devuelve true, porque el 10 es anterior al 7.

Math

La clase Math contiene todas las funciones en coma flotante que se utilizan en
geometría y trigonometría. Hay dos constantes double que se utilizan en este tipo de
cálculos, E (aproximadamente 2.72) y PI (aproximadamente 3.14).

Transcendentes
Las tres funciones siguientes aceptan un parámetro double para un ángulo en radianes y
devuelven el resultado de su respectiva función trascendente:

• sin (double a) devuelve el seno del ángulo a en radianes.


• cos (double a) devuelve el coseno del ángulo a en radianes.
• tan (double a) devuelve la tangente del ángulo a en radianes.

Estas funciones aceptan un parámetro double que es el resultado de una función


trascendente y devuelven el ángulo en radianes que produciría ese resultado. En cierto
modo como Jeopardy, el conjunto de funciones siguiente es la inversa de sus
correspondientes sin "a":

• asin (double r) devuelve el ángulo cuyo seno es r.


• acos (double r) devuelve el ángulo cuyo coseno es r.
• atan (double r) devuelve el ángulo cuya tangente es r.
• atan2 (double a, double b) devuelve el ángulo cuya tangente es a/b.

Exponenciales

• pow(double y, double x) devuelve y elevado a x.


• esp(double x) devuelve e elevado a x.
• log(double x) devuelve el logaritmo natural de x.
• squrt(double x) devuelve la raíz cuadrada de x.

Redondeo

• ceil (double a) devuelve el número completo más pequeño mayor o igual que a.
• floor (double a) devuelve el número completo más grande menor o igual que a.
• rint (double a) devuelve el valor double truncado de a.
• round (float a) devuelve a redondeado al int más cercano.
• round (double a) devuelve a redondeado al long más cercano.

Hay versiones con múltiples formas de los métodos abs, min y max para int, long. float
y double:

• abs (a) devuelve el valor absoluto de a.


• max (a, b) devuelve el máximo de a y b.
• min (a, b) devuelve el mínimo de a y b.

Random

La clase Random es un generador de números pseudo- aleatorios. Se les llama números


pseudo-aleatorios porque son secuencias distribuidas uniformemente obtenidas a partir
de una semilla inicial. Cuando se inicializa un objeto Random con una semilla, se puede
obtener a continuación lo que parece una secuencia aleatoria. Pero, si se vuelve a
utilizar la misma semilla para inicializar otro objeto Random, se obtendrá la misma
secuencia. Habitualmente, se utiliza la hora actual como semilla de un objeto Random,
lo que disminuye la posibilidad de obtener secuencias de números repetidas. Hay cinco
tipos de números aleatorios que se pueden extraer de un objeto Random. Se puede
extraer un int, distribuido uniformemente a lo largo del rango de int, mediante el
método nextInt. Del mismo modo, se puede obtener un long mediante nextLong. Los
métodos nexFloat y nextDouble devuelven un float y double respectivamente
distribuidos uniformemente entre 0.0 y1.0. Por último, nextGaussian devuelve un
double de distribución gaussiana centrada en 0.0 con una desviación típica de 1.0. A
esta distribución se le llama curva tipo campana.

Capítulo 10. Entrada/Salida.


• File
o Directorios
o FilenameFilter
• InputStream
• OutputStream
• Flujo de archivos
o FileInputStream
o FileOutputStream
o ByteArrayInputStream
o ByteArrayOutputStream
• StringBufferInputStream
• Flujos filtrados
o BufferedOutputStream
o PushbackImputStream
• SequenceInputStream
o PrintStream
• Beneficios de los flujos

La mayoría de los programas no pueden alcanzar sus metas sin acceder a datos externos.
Estos datos se recuperan a partir de un origen de entrada. Los resultados de un programa
se envían a un destino de salida. La noción genérica de una fuente de entrada puede
representar muchos tipos de entrada distintos: desde un archivo de disco, un teclado o
un conector (socket) de red. Estas abstracciones son una manera limpia de tratar la E/S
sin necesitar que todo el código comprenda la diferencia entre un teclado y una red.

Java llama flujo a esta abstracción y la implementa con varias clases del paquete java.io.
El flujo de E/S representa todos los orígenes y destinos de los datos detrás de una
interfaz uniforme. La entrada está encapsulada en la clase InputStream y la salida en la
clase OutputStream. Estas dos clases abstractas son las que todos los objetos deberían
referenciar cuando tratan la E/S en general.

File

Un File es el único objeto del paquete de E/S que referencia a un archivo de disco real.
La clase File no especifica cómo se recupera o almacena la información en los archivos;
sólo describe las propiedades de un objeto archivo. Los archivos son un origen y un
destino primario de los datos dentro de la mayoría de los programas. A pesar de que se
les imponente restricciones severas cuando se utilizan dentro de applets, los archivos
siguen siendo un recurso básico para almacenar información persistente y compartida.
Los objetos archivo se pueden crear utilizando uno de los tres constructores disponibles.
El ejemplo siguiente crea tres archivo: f1, f2 y f3. El primer objeto File se construye
utilizando un trayecto de directorio como único argumento. El segundo se crea
utilizando dos argumentos, el trayecto y el nombre de archivo. El tercero se crea
utilizando el trayecto de archivo asignado a f1 y un nombre de archivo; f3 refiere al
mismo archivo que f2.

File f1 = new File("/");


File f2 = new File("/","autoexec.bat");
File f3 = new File(f1, "autoexec.bat");

Directorios

Un directorio es un File que contiene una lista de otros archivos y directorios. Cuando
se crea un Objeto File y es un directorio, el método isDirectory devolverá true. En este
caso, se puede llamar al método list sobre ese objeto para extraer la lista de los otros
archivos y directorios que contiene. Si se llama al método list sobre un objeto File que
no sea un directorio se provoca una NullPointerException en tiempo de ejecución.

import java.io.File;
class Dirlist {
public static void main(String args[]) {
String dirname = "/java";
File f1 = new File(dirname);
if (f1.isDirectory()) {
System.out.println("Directorio de " + dirname);
String s[] = f1.list();
for (int y = 0; i < s.length; y++) {
System.out.println(s[i] + " es un
directorio");
} else {
System.out.println(s[i] + " es un
archivo");
}
} else {
System.out.println(dirname + " no es un
directorio");
}
}
}

La ejecución de este programa lista el contenido del directorio /java de nuestro PC.

FilenameFilter

A menudo se desea limitar el número de archivos devueltos por el método list para que
se incluyan únicamente aquellos archivos que cumplan un cierto patrón de nombre de
archivo. Con este fin, el paquete java.io incluye una interfaz llamada FilenameFilter. Un
objeto que implemente FilenameFilter tiene un único método, accept, al que se llama
una vez por cada archivo de una lista. El método accept devuelve el valor true si se
debiera incluir el archivo en la lista. El ejemplo siguiente amplía el programa anterior
restringiendo la visibilidad de los nombres de archivo devueltos por el método list. La
restricción se aplica a archivos con nombres que terminan con la extensión de archivo
que se pasa como parámetro cuando se construye le objeto:
import java.io.*;
public class OnlyExt implements FilenameFilter {
String ext;
public OnlyExt(String ext) {
this.ext = "." + ext;
}
public boolean accept (File dir, String name) {
return name.endsWith(ext);
}
}

InputStream

InputStream es una clase abstracta que define el modelo de Java para el flujo de entrada.
Todos los métodos de esta clase lanzarán una IOException si se producen condiciones
de error. Este es un breve resumen de los métodos de InputStream:

• read() devuelve una representación como entero del siguiente byte de entrada
disponible.
• read(byte[]) intenta leer hasta b.length bytes situándolos en b y devuelve el
número real de bytes que se leyeron con éxito.
• read(byte b[], int off, int len) intenta leer hasta len bytes situándolos en b
comenzando en b[off], y devuelve el número de bytes que se leyeron con éxito.
• skip(long n) omite n bytes de la entrada, y devuelve el número de bytes que se
han omitido.
• available() devuelve el número de bytes de entrada disponibles actualmente
para su lectura.
• close() cierra el origen de entrada. Los intentos de lectura posteriores generarán
una IOException.
• mark(int limitelectura) coloca una marca en el punto actual del flujo de
entrada que seguirá siendo válida hasta que se lean limitelectura bytes.
• reset() devuelve el puntero de entrada ala marca establecida previamente.
• markSupported() devuelve true si se admiten mark/reset en este flujo.

OutputStream

Igual que InputStream, OutputStream es una clase abstracta que define el flujo de
salida. Todos los métodos de esta clase devuelven un valor void y lanzan una
IOException en caso de error. Esta es una lista de los métodos de OutputStream:

• write(int b) escribe un único byte en un flujo de salida. Observar que el


parámetro en un int, lo que permite que se llame a write con expresiones sin
tener que convertir su tipo a byte.
• write(byte b[]) escribe una matriz completa de bytes en un flujo de salida.
• write(byte b[], int off, int len) escribe len bytes de la matriz b, comenzando a
partir de b[off].
• flush() inicializa el estado de la salida de manera que se limpian todos los
buffers.
• close() cierra el flujo de salida. Los intentos de escritura posteriores generarán
una IOException.
Flujo de archivo

FileInputStream

La clase FileInputStream utiliza archivos de datos reales como base del flujo de entrada.
El ejemplo siguiente crea dos FileInputStreams que están utilizando el mismo archivo
de disco real. Cualquiera de los dos constructores disponibles en esta clase pueden
lanzar una FileNotFoundException.

InputStream f0 = new FileInputStream("/autoexec.bat");


File f = new file("/autoexec.bat");
InputStream f1 = new FileInputStream(f);

Aunque probablemente el primer constructor es el que más se utiliza habitualmente, el


segundo permite examinar el archivo más de cerca utilizando sus métodos antes de
asignarlo a un flujo de entrada. Cuando se crea un FileInputStream, también se abre
para lectura. FileInputStream sobrescribe seis de los métodos de la clase abstracta
InputStream. Si se intentan utilizar los métodos mark o reset en un FileInputStream se
generará una IOException.

FileOutputStream

FileOutputStream comparte el mismo estilo de constructores que FileInputStream. Sin


embargo, la creación de FileOutputStream no depende de que el archivo ya exista.
FiliOutputStream creará el archivo antes de abrirlo como salida cuando se crea el
objeto. Si se intenta abrir un archivo de sólo lectura como punto final de un
FiliOutputStream, se lanzará una IOException.

ByteArrayInputStream

ByteArrayInputStream (flujo de entrada de matriz de bytes) es una implementación de


un flujo de entrada que utiliza una matriz de bytes como origen. Esta clase tiene dos
constructores, y ambos necesita una matriz de bytes que proporcione el origen de los
datos. Un ByteArrayInputStream implementa un método adicional además de los
admitidos por FileInputStream, reset() reinicializa el puntero del flujo situándolo al
comienzo del mimo, que en este caso es el comienzo de la matriz de bytes que hemos
utilizado en el constructor.

ByteArrayOutputStream

ByteArrayOutputStream tiene dos constructores. En la primera forma, se crea un buffer


de 32 bytes. En la segunda, se crea un buffer con un tamaño igual al argumento en
bytes, que en este ejemplo es 1024 bytes:

OutputStream out0 = new ByteArrayOutputStream();


OutputStream out1 = new ByteArrayOutputStream(1024);

El tener una matriz de bytes como destino de la salida proporciona algunas


oportunidades nuevas para un OutputStream, y la clase ByteArrayOutputStream se
aprovecha de ellas. Igual que la mayoría de los otros métodos de escritura, devuelven un
void y lanzan una IOException cuando hay una condición de error.

StringBufferInputStream

Un StringBufferInputStream es idéntico a ByteArrayInputStream a excepción de que el


buffer interno es una cadena en lugar de una matriz de bytes y que no existe la clase
StringBufferOutputStream correspondiente. Tiene un constructor:

StringBufferInputStream(String s);

Flujos filtrados

Los flujos filtrados amplían los flujos básicos, proporcionando una sincronización. En
un sistema de E/S multihilo, se pueden producir resultados inesperados cuando se
permite que múltiples hilos operen sobre el mismo flujo. Aunque es posible tener hilos
múltiples en Java leyendo o escribiendo en el mismo flujo, hay una buena razón para
permitir que sólo un hilo tenga acceso directo a un flujo de E/S único. Todos los
constructores y métodos proporcionados en esta clase son idénticos a los mencionados
anteriormente para InputStream y OutputStream, a excepción de que están
sincronizados.

BufferedOutputStream

La salida a BufferedOutputStream es idéntica a cualquier OutputStream con la


excepción de que se añade un método de entrada con buffer, la salida con buffer no
proporciona ninguna funcionalidad adicional. En Java, los buffers para la salida están
ahí para incrementar el rendimiento. La forma del primer constructor,
BufferedOutputStream(OutputStream sal), crea un buffer de 32 bytes, mientras que la
del segundo, BufferedOutputSteram(OutputStream sal, int tamaño), permite especificar
el tamaño de buffer a utilizar.

PushbackInputStream

Una de las utilidades originales de los buffers es la implementación de la de la


devolución. La devolución se utiliza en un InputStream para permitir que se lea un
carácter y después se devuelva al flujo de entrada. Proporciona un mecanismo para
"mirar" lo que viene por un InputStream sin perderlo. Aunque la clase
PushbackInputStream (flujo de entrada con devolución) está bastante limitada en cuanto
a ámbito, ya que un intento de devolver más de un solo carácter provoca que se lance
una IOException. Esta clase tiene un único constructor, PushbackInputStream
(InputStream ent).

SequenceInputStream

La clase SequenceInputStream (flujo de entrada secuencial) admite la utilidad original


de concatenar múltiples InputStreams en uno sólo. La construcción de un Sequence es
diferente de cualquier otro InputStream. Un constructor SequenceInputStream utiliza
como argumento un par de InputStreams o una Enumeration de InputStreams:
SequenceInputStream(Enumeration e);
SequenceInputStream(InputStream s0, InputStream s1);

Al funcionar, la clase satisface las peticiones de lectura del primer InputStream hasta
que se termina y después conmuta al segundo. En el caso de una Enumeration, continúa
a lo largo de todos los InputStreams hasta llegar al último.

PrintStream

La clase PrintStream proporciona todas las utilidades de formato que hemos estado
utilizando desde el principio del libro de los descriptores de archivo de System.
Pensabas que se introducía "System.out.println" sin pensar demasiado en las clases que
proporcionaban el formato de la salida que se presentaba. PrintStream tiene dos
constructores, PrintStream(OutputStream sal) y PrintStream(OutputStream sal, boolean
auto), donde auto controla si java vacía el flujo de salida cuando vaya a la salida un
carácter de línea nueva "\n". En el primer constructor no se vacía.

Los objetos PrintStream de Java admiten los métodos print y println para todos los
tipos, incluyendo Object. Si un argumento no se un tipo simple, los métodos
PrintStream llamarán al método toString del objeto y a continuación imprimirán el
resultado.

Beneficios de los flujos

La interfaz de flujos de datos de E/S en Java proporciona una abstracción limpia para
una tarea compleja y a menudo incómoda. La composición de las clases de flujo
filtradas permite que se construya dinámicamente una interfaz de flujo personalizada
que se adapte a los requisitos de trasferencia de datos. Los programas de Java que
utilicen las clases abstractas y de alto nivel InputStream y OutputStream funcionarán
adecuadamente en el futuro incluso cuando se invierten clases de flujo concretas nuevas
y mejoradas. Como veréis en el capítulo siguiente, esté modelo funciona muy bien
cuando pasamos de un conjunto de flujos basados en un sistema de archivos a los flujos
de red y de conector.

Capítulo 11. Trabajo en red.


• IndexAddress
o Métodos de fabrica
• Datagramas
o DatagramPacket
• Conectores "para clientes"
• Conectores "para servidores"
• URL
• URLConnection

Este capítulo cubre el paquete java.net. La integración de Java con la red se ha hecho
legendaria, gracias en gran parte a esta biblioteca de clases. Java admite el protocolo de
TCP/IP de Internet ampliando la interfaz de E/S de flujo ya establecida, presentada en el
capítulo anterior, y añadiendo las características que se necesitan para crear objetos de
E/S a través de la red. Java admite las familias de protocolo TCP y UDP. TCP se utiliza
para E/S fiable de tipo secuencial a través de la red. UDP admite un modelo más rápido,
punto a punto y orientado a datagrama.

InetAddress

Independientemente de si se realiza una llamada telefónica, se envía un correo


electrónico o se establece una conexión a través de Internet, las direcciones son
fundamentales. Java admite los nombres de Internet a través de la clase InetAddress.
Las direcciones de Internet, reducidas a su nivel más bajo, están formadas por un
identificador de nodo de 32 bits y un selector de puerto de ese nodo de 32 bits. En
Internet el direccionamiento lo gestionan servidores de nombres que traducen nombres
habituales y fáciles de recordar a sus correspondientes direcciones de 32 bits.

Métodos de fábrica

La clase InetAddress no tiene constructores visibles. Para crear un objeto InetAddress,


se tiene que utilizar uno de los métodos de fábrica disponibles. Estos métodos son
simplemente métodos estáticos que devuelven una instancia de la clase en la que
residen. En este caso, InetAddress tiene tres métodos, getLocalHost, getByName y
getAllByName, que se pueden utilizar para crear instancias de InetAddress.

La clase InetAddress también tiene unos cuantos métodos no estáticos, que se pueden
utilizar sobre los objetos devueltos por los métodos que se acaban de mencionar:

• getHostName() devuelve una cadena que representa el nombre de nodo


asociado con el objeto InetAddress.
• getAddress() devuelve una matriz de bytes de cuatro elementos que representa
la dirección en Internet del objeto en el "orden de red".
• toString devuelve una cadena que contiene el nombre del nodo y la dirección
IP.

Datagramas

Los datagramas son bloques de información del tipo "lanzar y olvidar" que se
transfieren a través de la red. Para la mayoría de los programas de la red, el utilizar un
flujo TCP/IP en vez de un datagrama UPD es más sencillo y hay menos posibilidades de
tener problemas. Sin embargo, cuando se requiere un rendimiento óptimo, y está
justificado el tiempo adicional que supone el realizar la verificación de los datos, los
datagramas son un mecanismo realmente útil.

Cuando se ha lanzado un datagrama hacia su destino, no hay ninguna seguridad de que


llegue o incluso de que haya alguien allí para cogerlo. Igualmente, cuando se recibe un
datagrama, no es seguro que no se haya dañado por el camino o que el que lo haya
enviado esté todavía ahí para recibir una respuesta.

DatagramPacket
Se pueden crear los DatagramPackets utilizando dos constructores. El primero utiliza
solamente un buffer de bytes y una longitud. Ese utiliza para recibir datos a través de un
DatagramSocket. El segundo añade una especificación de dirección de destino y un
puerto, que es utilizada por un DatagramSocket para determinar dónde se enviarán los
datos del paquete. Considerar que estas dos formas equivalen a crear un "un buzón de
entrada" en el primer caso, y a rellenar y poner una dirección en un sobre en el segundo.
Estos son los prototipos de los dos constructores:

DatagramPacket(byte ibuf[], int ilong);


DatagramPacket(byte ibuf[], int ilong, InetAddress idir, int ipuerto);

Hay varios métodos de acceder al estado interno de un DatagramPacket. Permiten un


acceso completo a la dirección de destino y número de puerto de un paquete, además de
a sus datos y su longitud. A continuación se presenta un resumen de cada uno de ellos:

• getAddress() devuelve la InetAddress de destino. Se utiliza habitualmente para


envíos.
• getPort() devuelve el número entero de puerto de destino. Se utiliza para envíos.
• getData() devuelve la matriz de bytes de los datos contenidos en el datagrama.
Se utiliza para recuperar los datos del datagrama después de haberlo recibido.
• getLength() devuelve la longitud de los datos válidos contenidos en la matriz de
bytes que se devolverían con el método getData. Habitualmente no coincide con
la longitud de la matriz de bytes.

Conectores "para clientes"

Los conectores (sockets), o los conectores TCP/IP en concreto, se utilizan para


implementar conexiones basadas en flujo, punto a punto, bidireccionales y fiables entre
nodos de Internet. Se puede utilizar un conector para conectar el sistema de E/S de Java
a otros programas que pueden residir en la máquina local o en cualquier otra máquina
de Internet. La clase Socket, a diferencia de DatagramSocket, implementa una conexión
continua muy fiable entre el cliente y el servidor.

Al crear un objeto conector también se establece la conexión entre direcciones de


Internet. No hay métodos ni constructores que muestren explícitamente los detalles del
establecimiento de la conexión del cliente. Se puedan utilizar dos constructores para
crear conectores:

• Socket(String nodo, int puerto) crea un conector que conecta el nodo local con
el nodo y puerto nombrados.
• Socket(InetAddress dirección, int puerto) crea un conector utilizando un
objeto InetAddress ya existente y un puerto.

En un conector se puede examinar en cualquier momento la información de dirección y


puerto asociada con él utilizando los métodos siguientes:

• getInetAddress() devuelve la InetAddress asociada con el objeto Socket.


• getPort() devuelve el puerto remoto al que está conectado este objeto Socket.
• getLocalPort() devuelve el puerto local al que está conectado este objeto
Socket.
Cuando se ha creado el objeto Socket, también puede ser examinado para acceder a los
flujos de entrada y salida asociados con él. Todos estos métodos pueden lanzar una
IOException si se han invalidado los conectores debido a una pérdida de conexión en la
red. Estos flujos se utilizan exactamente igual que los flujos de E/S que hemos visto en
el capítulo anterior para enviar y recibir datos:

• getInputStream() devuelve el InputStream asociado con este conector.


• getOutputStream() devuelve el OutputStream asociado con este conector.
• close() cierra el InputStream y el OutputStream.

Conectores "para servidores"

Los ServerSockets se deben utilizar para crear servidores de Internet. Estos servidores
no son necesariamente máquinas, de hecho son programas que están esperando a que
programas cliente locales o remotos se conecten a ellos en puertos públicos. Los
ServerSockets son bastante diferentes de los Sockets normales. Cuando se crea un
ServerSocket, se registrará en el sistema que tiene interés en conexiones de cliente.
Tiene un método adicional, accept, que es una llamada que se bloquea ya que espera
que un cliente inicie la comunicación y después devuelve un Socket normal.

Los dos constructores de ServerSocket reflejan el número del puerto en el que se desean
aceptar las conexiones y, opcionalmente, durante cuánto tiempo se desea esperar a que
se deje de utilizar el puerto. Ambos constructores pueden lanzar una IOExeption bajo
condiciones adversas. Estos son los dos prototipos:

• ServerSocket(int puerto) crea un conector de servidor en el puerto


especificado.
• ServerSocket(int puerto, int número) crea un conector de servidor en el puerto
especificado esperando número milisegundos si el puerto se está utilizando.

Funcionalmente, el método accept de un ServerSocket es una llamada que se bloquea y


que espera que un cliente inicie la comunicación y después devuelve un Socket normal.
Después, este conector se utiliza para la comunicación con el cliente.

URL

Un URL proporciona una forma razonablemente inteligente de identificar o direccionar


de manera única la información en Internet. Los localizadores de recursos
uniformes(URL) son los componentes más importantes de la red mundial. Tienen el don
de la ubicuidad; todo visualizador utiliza URL para identificar la información de la red.
De hecho, la red se reduce a Internet con todos los recursos direccionados como URL,
con HTML, funcionando como lenguaje para la visualización de la información
contenida en los URL y que proporciona una interfaz del tipo "apuntar y pulsar" para ir
a otros URL. Dentro de la biblioteca de clases de red, la clase URL proporciona una
API simple y concisa para acceder a la información a través de Internet utilizando URL.

La clase URL de Java tiene cuatro constructores, y todos ellos pueden lanzar una
MalformedURLException. La forma más utilizada habitualmente especifica el URL con
una String que es idéntica a lo que se muestra en un visualizador.
URL(String espec);

Las dos formas siguientes del constructor permiten que se descomponga el URL en las
partes que lo componen:

URL(String protocolo, String nodo, int puerto, String archivo);


URL(String protocolo, String nodo, String archivo);

El cuarto constructor permite utilizar un URL como contexto de referencia y después


crea un nuevo URL desde ese contexto. Aunque esto parece un poco retorcido,
realmente es bastante sencillo y útil:

URL(URL1 contexto, String espec);

URLConnection

Una URLConnection es el objeto que utilizamos para examinar las propiedades del
recurso remoto referenciado o para obtener su contenido. En la práctica, hay un poco de
solapamiento entre estas funciones. Por ejemplo, podemos establecer condiciones de
estado dentro de la URLConnection de manera que sólo se devuelva el contenido del
URL si se cumplan unas ciertas condiciones. Estas condiciones se corresponden y son
sólo útiles en relación a la especificación de protocolo HTTP.

Capítulo 12. Applets.


• Etiqueta de applet HTML
• Paso de parametros: getparameter(string)
• getdDocumentBase y getCodeBase
• AppletContext y showDocument
• Orden de inicialización de los applets
o init
o start
o paint
o update
o stop
o destroy
• Volver a pintar
o repaint
• Dimensionado de los gráficos
• Métodos de gráficos simples
o drawLine
o drawArc y fillArc
o drawPolygon y fillPolygon
• Color
o Color(int, int, int)
o Color(int)
o Color(float, float, float)
o HSBtoRGB(float, float, float), RGBtoHSB(int, int, int, float[])
o getRed(), getGreen(), getBlue()
o getRGB
o setPaintMode() y seXORMode(Color)
o Drawstring
• Utilización de los tipos de letra
• Posicionamiento y tipos de letra: FontMetrics

Los applets son pequeñas aplicaciones a la que se accede en un servidor de Internet, se


transmiten a través de la red, se instalan automáticamente y se ejecutan como parte de
un documento de la red.

Los applets, comienzan con dos líneas que importan los paquetes de Java.applet y
Java.awt.(AWT es el equipo de herramientas de ventana abstracta)

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

Etiqueta de applet en HTML

La etiqueta [< parama name=nombreatributo value = valoratributo >] [< parama


name=nombreatributo2 value = valoratributo . . . [Código HTML que se visualiza en
ausencia de Java]

Paso de parámetros:

La etiqueta de applet HTML permite el paso de parámetros a los applets. Mediante el


método:

• getparameter (obtener parámetro) que devuelve un String (que se deberá


convertir si se desea utilizar como otro tipo).

Forma de extraer cada parámetro:

String Nomtipo = getparameter("nomtipo") ;


int tamtipo = integer.parseint(getparameter("tamtipo")) ;
float precedente = float.valueof(getparameter("precedente") ;
boolean pagado = Boolean.valueof(getparameter("cuentahabilitada")) ;

getdocumentbase y getcodebase

Java permitirá que el applet cargue datos desde el directorio que contenía el archivo
HTML que inicio el applet(base de documento) y el directorio desde el cual se cargo el
archivo de la clase(base de código). Los métodos:

• getdocumentbase (obtener base de documento) y getcodebase (obtener base de


código) devuelven objetos URL que se pueden concatenar con una cadena que
contenga el nombre de un archivo.

Appletcontext y Showdocument
• Appletcontext (contexto de applet) es una interfaz que permite acceder a
información del entorno de ejecución de el applet.
• Showdocument(URL) (mostrar documento) envía la ventana del visualizador
principal a un nuevo URL.
• Showdocument(URL, destino) toma un URL al que ir y una cadena que
representa el marco destino en el que debe aparecer el nuevo documento, las
cadenas destino validas son:

"_self" (mostrar en marco actual)


"_parent" (en marco padre)
"_top" (en el marco superior)
"_blank" (en una nueva ventana de visualización)

Ejemplo:

Getappletcontext().showdocument(new URL (home, "archivo.html"),"_blank") ;

Orden de inicialización de los applets

• init (inicializar) es primer método al que se llama, en el es donde se deberían


inicializar las variables.
• start (comenzar) se le llama como punto de partida después de que se haya
detenido un applet y cada vez que se visualiza en pantalla un documento HTML.
• Paint (pintar) se le llama cada vez que se daña el applet.
• Update (actualizar) primero rellena un applet con su color en segundo plano y
después llama a paint.
• Stop (parar)se le llama cuando un visualizador de red abandona el documento
HTML que contiene el applet.
• Destroy (destruir)cuando el entorno determina que es necesario eliminar
completamente el applet y liberar recursos.

Volver a pintar

• repaint (repintar) obliga a que se vuelva a pintar el applet.

Variaciones:

• repaint(tiempo) donde tiempo, representa un plazo máximo.


• repaint(x, y, w, h) solo cambia las partes de la pantalla que caen dentro del
rectángulo.
• repaint(tiempo, x, y, w, h) Es una combinación de las dos.

Dimensionado de los gráficos

• size (tamaño), devuelve un objeto Dimension (dimensión) en el cual se pueden


referenciar las variables de instancia width (anchura) y height (altura). Ejemplo:

dimension d=size()
System.out.println(d.width + "," + d.height);
Métodos de gráficos simples

El objeto graphics tiene varias funciones simples de dibujo:

• drawrect (rectángulo sin relleno)


• drawoval (ovalo sin relleno)
• fillrect (rectángulo con relleno)
• filloval (ovalo con relleno)

Se les llama con cuatro parámetros : intx, inty, int anchura e int altura.

Ejemplo:

dimension d=size();
g.drawrect(0, 0, d.width-1, d.height-1);

• drawline(x1, y1, x2, y2) (dibujar línea) dibuja una línea entre las coordenadas
(x1, y1) y (x2, y2)

Ejemplo:

drawline(int x1, int y1, int x2, int y2)

• drawarc(x, y, anchura, altura) (dibujar arco) dibuja un arco limitado por un


rectángulo (x, y, anchura, altura) que comienza en angulocomienzo y recorre
una distancia angular de angulobarrido.

Ejemplo:

drawarc(int x, int y, int anchura, int altura, int angulocomienzo, int


aungulobarrido)

• fillarc (rellenar arco) exactamente igual que el anterior, solo que rellena el arco
creado.
Drawpolygon (dibujar polígono) dibuja un polígono definido por dos matices,
cada una con puntos x e y, y un numero que es el numero de pares de puntos
(este método no cierra el polígono automáticamente).

Ejemplo:

drawpolygon(int[], int[], int)

• Fillpolygon (rellenar polígono). Igual que el anterior, solo que rellena el


polígono creado.

Color

Se pueden utilizar las variables estáticas de color para especificar varios colores
habituales:
black(negro)
red(rojo)
green(verde)
blue(azul)
yellow(amarillo)
cyan
magenta
orange(naranja)
pink(rosa)
gray(gris)
drakgray(gris oscuro)
lightgray(gris claro)

Ejemplo:

color.black

Para crear un nuevo color se utiliza uno de los siguientes constructores de color:

• color(int, int, int) Los enteros representan los niveles de rojo, verde y azul entre
0 y 255.
• color(int) el nivel de rojo, verde y azul se ha empaquetado en el int de la
siguiente manera:
el rojo en los bits 16 al 23 el verde del 8 al 15 y el azul del 0 al 7.
• color(float, float, float) los tres valores correspondientes a los tres colores,
están entre 0.0 y 1.0.

Métodos de color




• hsbtorgb(float, float, float) devuelve un RGB entero preparado para el
constructor
• color(int).
• rgbtohsb(int, int, int) devuelve una matriz float de valores HSB que
corresponde a enteros RGB.
• getred(), getgreen(), getblue(). Obtienen los componentes de color rojo, verde
y azul respectivamente.
• getrgb() Obtiene un entero con los valores de rojo, verde y azul empaquetados.
• Setpaintmode() modo por defecto para pintar, los pixeles se sobrescriben con el
color actual.
• Setxormode(color) alterna entre el color actual y el nuevo color especificado.
• Drawstring(string, x, y) dibuja la cadena con el color y tipo de letra por defecto
desde una coordenada x, y.

Utilización de los tipos de letra

El constructor de font, crea un nuevo tipo de letra con el nombre, estilo y tamaño
especificado en puntos.
Ejemplo:

Font tipogrueso=new font("Helvetica", font.bold | font.italic, 24);

• getfont (obtener tipo de letra de un objeto).


• setfont (establecer tipo de letra de un objeto).
• getfamily obtiene una cadena con el nombre de la familia de tipos de letra.
• getname obtiene el nombre lógico de letra.
• getsize devuelve el tamaño en puntos del font.
• getstyle devuelve un entero que representa el estilo del font.
• isbold, isitalic, isplain. devuelven un boolean que es true si el tipo de letra está
en negrita, cursiva o normal, respectivamente.

Posicionamiento y tipos de letra

La clase fontmetrics (métrica de tipo de letra), permite visualizar texto con precisión.

Utilización de fontmetrics.

• Stringwith devuelve la anchura del string.


• Byteswitdh, charswidth devuelve la anchura de la matriz de bytes especificada
con este tipo de letra del objeto gráfico.
• Getascent, getdescent, getheight. obtienen el ascenso, descenso y la altura del
tipo de letra.
Donde:

Altura: tamaño de arriba a abajo del carácter más alto del tipo de letra. Línea
base: línea sobre la que están alineadas las partes inferiores de los caracteres
Ascenso: distancia desde la línea base hasta la parte superior de un carácter.
Descenso: distancia entre la línea base hasta la parte inferior de un carácter.

• getmaxascent, getmaxdescent obtienen el ascenso y descenso de los bits


dibujados, de todos los caracteres de este tipo de letra. Puede ser distinto del
valor devuelto por getascent y getdescent ya que estos no incluyen los espacios
en blanco.

Capítulo 13. Equipo de herramientas de


ventana abstracta.
• Componentes
o Container
o Panel
o Canvas
o Label
o Button
o Checkbox
o CheckboxGroup
o Choice
o List
o Scrollbar
o TextField
o TextArea
• Organización
o LayoutManager
• Componentes de menú
• Eventos

AWT(abstrac windowing toolkit, equipo de herramientas de ventana abstracta).

Componentes

Un componente (component) es una clase abstracta que encapsula todos los atributos
de un componente visual.
Todos los elementos de la interfaz de usuario que se visualizan en la pantalla y que
interactuan con un usuario son subclases de component.

Subclases de Component

• Container (contenedor). Contendrá métodos adicionales que permiten que otros


componentes se aniden dentro de el.
• Panel. Es una subclase de container.
Se pueden añadir otros componentes a panel mediante el método add (añadir),
modificar estos componentes con move (desplazar), resize (cambiar el tamaño).
• Canvas (lienzo). En el se puede pintar cualquier componente.
• Label (etiqueta). Es una subclase de canvas.
• Button (botón). Componente que se puede utilizar para ejecutar alguna acción
cuando el usuario lo pulsa y lo libera.
• Checkbox (cuadro de comprobación). Se utiliza para seleccionar una condición
booleana.
Cuando se construye un checkbox se le pasa un string y opcionalmente un
boolean que especifica el estado inicial de la marca de comprobación.
Se puede establecer y obtener el estado actual de la marca de comprobación con
los métodos setstate y getestate respectivamente.
• Checkboxgroup. Permite crear un grupo de opciones que solo se pueden
seleccionar de una en una.
Los métodos getcheckboxgroup y secheckboxgroup se utilizan para obtener y
establecer el grupo al que pertenece un chekbox.
• Getcurrent, setcurrent. Obtienen y establecen, respectivamente, el checkbox
seleccionado actualmente.
• Choice (opción). Se utiliza para crear un menú de selección emergente.
El método additem (añadir elemento) añade nuevas cadenas a la clase choice.
• Countitems (contar elementos). Devuelve el numero de elementos de el menú
de opciones.
• List (lista). Proporciona una lista de selección compacta, de elecciones múltiples
y de desplazamiento.
• Scrollbar (barras de desplazamiento). Se utiliza para seleccionar valores
continuos entre un mínimo y un máximo especificados.
• Textfield (campo de texto). Implementa un área de entrada de texto de una sola
línea.
• Seteditable. Congela el contenido del textfield.
• Iseditable. Comprueba la posibilidad de edición.
• Gettext. Obtiene el valor actual del campo.
• Settext. Establece el valor actual.
• Setechocaracter. Establece el carácter único que mostrará el textfield para todos
los caracteres que se introduzcan, muy útil para esconder códigos o información
secreta.
• Textarea (área de texto). Es un editor de texto muy simple.
• Appendtext. Añade su parámetro string al final del buffer.
• Inserttext. Inserta su string en una posición dada, de base 0 que comienza al
principio del buffer.
• Replacetext. Copia caracteres del string a partir del primer índice y hasta el
segundo.

Organización

Cada objeto container tiene un gestor de organización, Layoutmanager.


Java tiene varias clases de layoutmanager predefinidas:

• Flowlayout (organización continuada). Los componentes se colocan desde la


esquina superior izquierda, de izquierda a derecha y de arriba a abajo.
• Borderlayout (organización con limites). Tiene cuatro componentes estrechos y
de anchura fija en los extremos, y un área grande en el centro que se amplia y
contrae en dos dimensiones, cada una de estas áreas tiene un nombre que es un
string: north, south, easty, west, representan los cuatro lados y center el área del
centro.
• Insets (recuadros). Se utiliza para recuadrar un panel.
• Cardlayout (organización con tarjetas).
• Window (ventana). Parecido a panel con la excepción de que crea su propia
ventana de nivel superior, en vez de estar contenida dentro de otro panel.
• Frame (marco). Tiene una barra de titulo, esquinas para cambiar el tamaño y
una barra de menú.

Componentes de menú

Cada ventana de nivel superior, puede tener una barra de menú asociada con ella. Un
objeto menubar (barra de menú) puede contener varios objetos menú en él.
Los menús tienen una lista de objetos menuitem (elemento de menú) dentro de ellos.
Menú, es una subclase de menuitem, lo que permite anidar submenus de forma
jerárquica.

Eventos

Cualquier componente puede gestionar eventos sobrescribiendo ciertos métodos


llamados por la implementación por defecto de método handleevent (gestionar evento)
de component, se llama a dicho método con una instancia de la clase event (evento).
Estos son algunos ejemplos:
A los eventos relacionados con el ratón, se les llama con una copia del evento original y
la ubicación (x, y) del evento.

• MouseEnter se le llama cuando el ratón entra por primera vez en un


component.
• MouseExit se le llama cuando el ratón abandona el componente.
• MouseMove se le llama cuando se mueve por el componente.
• MouseDown se le llama cuando se pulsa primero cualquier botón del ratón.
• MouseDrag se le llama cuando el ratón se mueve con un botón pulsado.
• MouseUp se le llama cuando se libera el ratón.
• Keydown se le llama cuando se pulsa una tecla.
• Keyup se le llama cuando se libera una tecla.
• Shifthdown comprueba si esta pulsada la tecla shift.
• Controldown comprueba si esta pulsada la tecla control.

Para tratar eventos especiales como retornos desde los componentes button, scrollbar y
menu, hay que reescribir el método action (acción). Se llamara a este método con el
evento original y un segundo parámetro que es el componente de la UI que creo el
evento de acción. Se debe inspeccionar este objeto para ver que componente ha
provocado la acción y seleccionar así el gestor correcto para cada componente. Se puede
utilizar el operador instanceof (instancia de) para ver si el objeto es de un tipo en
particular, como button, antes de convertir su tipo.

Capítulo 14. Tratamiento de imágenes.


• Cargador de imágenes simple
• ImageObserver
• ImageProducer
• ImageFilter e ImageFilterSource
o CropImageFilter
o RGBImageFilter

Java trabaja con los formatos de imágenes más populares de la red hoy en día como son
JPEG y GIF.

Cargador de imágenes simple.

El caso más simple es cargar una sola imagen en una pagina. Este es un applet que lo
hace.

/*< title >simpleimageload< /title >


*< applet code="simpleimageload" width=200 height=200 >
*< param name="img"value="nombre.gif" >
*< /applet >
*/
Import java.applet.* ;
Import java.awt.* ;

public class simpleimageload extends applet {


image art ;
public void init() {
art = getimage(getdocumentbase(),
getparameter("img")) ;
}
public void paint (graphics g) {
g.drawimage(art, 0, 0, this) ;
}
}

En el método init se asigna la variable art a la imagen que devuelve getimage (obtener
imagen). El método getimage utiliza la cadena que devuelve getparameter (obtener
parámetro),"nombre.gif", que es el nombre de la imagen, que se obtiene a partir de la
etiqueta del applet

Imageobserver

Es una interfaz abstracta que se utiliza para obtener avisos cuando se está generando una
imagen.

• Imageupdate (actualizar imagen). Se puede mostrar la animación que se desee,


un indicador de progreso o una pantalla que atraiga, ya que se informa del
proceso de la transferencia.
A este método, se le llama con la image (imagen) que se está o ha sido
actualizada, con un entero que comunica el estatus(estado) del informe de la
actualización, y un rectángulo (x, y, with, height).
Devuelve false si se ha terminado la carga, true en caso contrario.

Imageproducer

Imageproducer (generador de imagen) es una interfaz abstracta para objetos que desean
generar datos de imágenes. Un objeto que implemente la interfaz imageproducer,
proporcionara matrices de enteros o bytes que representan los datos de la imagen.
Createimagen (crear imagen) toma un objeto imageproducer y devuelve un objeto
image(imagen).

Imagefilter e Imagefiltersource

• Imagefilter (filtro de imagen) e Imagefiltersource (origen de filtro de imagen)


se utilizan para crear nuevas imágenes filtrando imágenes existentes.
• Cropimagefilter (filtro de imagen para recorte) crea una nueva imagen a partir
de una región de una imagen existente.
• Rgbimagefilter (filtro de imagen rgb) se utiliza para obtener datos de cada pixel
de una imagen origen, que podemos modificar sobre la marcha para convertirla
en una nueva imagen.

Anda mungkin juga menyukai