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.
Siguiendo los pasos de esta página, usted podrá crear y ejecutar una sencilla aplicación
en Java.
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:
3) Ejecutar la Aplicación.
import java.applet.Applet;
import java.awt.Graphics;
Cree un archivo en el mismo directorio con el nombre Hello.html, que deberá contener
la HelloWorld.class creada anteriormente.
<HTML>
<HEAD>
<TITLE> A Simple Program </TITLE>
</HEAD>
<BODY>
Hello World!
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).
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.
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!
Identificadores
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
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.
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
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 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;
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:
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:
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.
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.
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.
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
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
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;
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.
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.
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;
00100000 32 00100011 35
>> 2 >> 2
00001000 8 00001000 8
int a = -1;
a = a >>> 24;
11111111111111111111111111111111 -1
>>> 24
00000000000000000000000011111111 255
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:
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.
Todos los operadores lógicos booleanos combinan dos valores boolean para dar como
resultado un valor boolean.
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.
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.
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,
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
if-else
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.
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
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
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
[ inicialización; ]
do { cuerpo; [ iteración; ] } while ( terminación );
for
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.
int a, b;
for (a = 1, b = 4, a < b, a++, b--) {
System.out.println("a = " + a);
System.out.println("b = " + b);
}
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.
Este bucle utiliza continue para provocar que se impriman dos números en cada línea.
Excepciones
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.
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.
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.
Aunque se haya asignado p a null, p2 todavía apunta al objeto creado por el operador
new.
referencia_a_objeto.nombre_de_variable
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.
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:
Constructores
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.
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.
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 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);
}
}
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.
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.
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.
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;
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 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:
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
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.
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.
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:
También existen constructores para caracteres ASCII (caracteres de 8 bits) frente a los
caracteres Unicode de Java (caracteres de 16 bits).
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.
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í:
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:
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.
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.
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
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.
Una excepción es una condición anormal que surge en una secuencia de código durante
la ejecución.
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
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");
}
}
}
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;
finally
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:
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.
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:
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.
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.
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;
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.
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.
• 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.
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.
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.
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
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:
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:
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:
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:
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
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
Hashtable
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
Gestión de memoria
System
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
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:
Exponenciales
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:
Random
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.
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:
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.
FileOutputStream
ByteArrayInputStream
ByteArrayOutputStream
StringBufferInputStream
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
PushbackInputStream
SequenceInputStream
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.
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.
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
Métodos de fábrica
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:
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.
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:
• 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.
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:
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:
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.
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.* ;
Paso de parámetros:
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:
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:
Ejemplo:
Volver a pintar
Variaciones:
dimension d=size()
System.out.println(d.width + "," + d.height);
Métodos de gráficos simples
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:
Ejemplo:
• 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:
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.
El constructor de font, crea un nuevo tipo de letra con el nombre, estilo y tamaño
especificado en puntos.
Ejemplo:
La clase fontmetrics (métrica de tipo de letra), permite visualizar texto con precisión.
Utilización de fontmetrics.
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.
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
Organización
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
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.
Java trabaja con los formatos de imágenes más populares de la red hoy en día como son
JPEG y GIF.
El caso más simple es cargar una sola imagen en una pagina. Este es un applet que lo
hace.
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.
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