Apuntes de Java
Apuntes de Java
1. Introducción
1.1. Historia de Java
1.2. Objetivos de diseño de Java
1.3. Características de Java
1.4. Qué incluye el J2SE (Java 2 Standard Edition)
1.5. Qué es el JRE (Java Runtime Environment)
1.6. Qué se necesita para empezar
3. Métodos
3.1. Declaración de métodos.
3.2. El término void.
3.3. Uso de métodos.
4. Clases - Introducción
4.1. Clases
4.2. Objetos, miembros y referencias
4.3. Conceptos básicos. Resumen
5. Clases - Constructores
5.1. Noción de constructor
5.2. Constructor no-args.
5.3. Sobrecarga de constructores
6. Clases - Miembros estáticos
6.1 Datos estáticos
6.2. Métodos estáticos
6.3. El método main
8. Control de la ejecución
8.1. Resumen de operadores
8.2. Ejecución condicional
8.3. Iteraciones con while
8.4. Iteraciones con for
8.5. Evaluación múltiple
8.6. Devolución de control
8.7. Expresiones
9. Arrays
9.1. Declaración y acceso
9.2. Arrays multidimensionales
10. Strings
10.1. La clase String
10.2. Creación de Strings
10.3. Concatenación de Strings
10.4. Otros métodos de la clase String
10.5. La clase StringBuffer
10. Strings
10.1. La clase String
10.2. Creación de Strings
10.3. Concatenación de Strings
10.4. Otros métodos de la clase String
10.5. La clase StringBuffer
11. Packages
11.1. Cláusula package
11.2. Cláusula import
11.3. Nombres de los packages
11.4. Ubicación de packages en el sistema de archivos
1.4. Herencia
14.1 Composición
1.4.2. Herencia
1. INTRODUCCIÓN
Java 2 (antes llamado Java 1.2 o JDK 1.2) es la tercera versión importante del lenguaje de
programación Java.
No hay cambios conceptuales importantes respecto a Java 1.1 (en Java 1.1 sí los hubo
respecto a Java 1.0), sino extensiones y ampliaciones, lo cual hace que a muchos efectos –
por ejemplo, para esta introducción- sea casi lo mismo trabajar con Java 1.1 o con Java
1.2. Los programas desarrollados en Java presentan diversas ventajas frente a los
desarrollados en otros lenguajes como C/C++. La ejecución de programas en Java tiene
muchas posibilidades: ejecución como aplicación independiente (Stand-alone
Application), ejecución como applet, ejecución como servlet, etc. Un applet es una
aplicación especial que se ejecuta dentro de un navegador o browser (por ejemplo
Netscape Navigator o Internet Explorer) al cargar una página HTML desde un servidor
Web. El applet se descarga desde el servidor y no requiere instalación en el ordenador
donde se encuentra el browser. Un servlet es una aplicación sin interface gráfica que se
ejecuta en un servidor de Internet. La ejecución como aplicación independiente es análoga
a los programas desarrollados con otros lenguajes.
JRE es el entorno mínimo para ejecutar programas Java 2. Incluye la JVM y la API. Está
incluida en el J2SE aunque puede descargarse e instalarse separadamente. En aquellos
sistemas donde se vayan a ejecutar programas Java, pero no compilarlos, el JRE es
suficiente.
El JRE incluye el Java Plug-in, que es el 'añadido' que necesitan lo navegadores (Explorer
o Netscape) para poder ejecutar programas Java 2. Es decir que instalando el JRE se tiene
soporte completo Java 2, tanto para aplicaciones normales (denominadas 'standalone')
como para Applets (programas Java que se ejecutan en una página Web, cuando esta es
accedida desde un navegador).
Java es un lenguaje con control fuerte de Tipos (Strongly Typed). Esto significa que cada
variable y cada expresión tiene un Tipo que es conocido en el momento de la compilación.
El Tipo limita los valores que una variable puede contener, limita las operaciones
soportadas sobre esos valores y determina el significado de la operaciones. El control
fuerte de tipos ayuda a detectar errores en tiempo de compilación.
• Tipos Primitivos
• Referencias
Las referencias se usan para manipular objetos. Se verán en una lección posterior.
El tamaño de los tipos de datos no depende de la implementación de Java. Son siempre los
mismos.
2.3. Variables
Una variable es un área en memoria que tiene un nombre y un Tipo asociado. El Tipo es o
bien un Tipo primitivo o una Referencia.
Es obligatorio declarar las variables antes de usarlas. Para declararlas se indica su nombre
y su Tipo, de la siguiente forma:
tipo_variable nombre ;
Ejemplos:
int i = 5;
char letra = 'c';
boolean flag = false;
2.4. Literales
• Entero largo: l ó L.
• Float: f ó F
• Double: d ó D.
Por ejemplo:
long l = 5L;
float numero = 5f;
En los literales numéricos para float y double puede usarse el exponente (10 elevado a...)
de la forma: 1.5e3D (equivale a 1500)
Literales hexadecimales: Al valor en hexadecimal se antepone el símbolo 0x. Por ejemplo:
0xf (valor 15)
Literales caracter: Un caracter entre apóstrofes (') o bien una sequencia de escape (también
entre '). Las secuencias de escape están formadas por el símbolo \ y una letra o un número.
Algunas secuencias de escape útiles:
La siguiente tabla muestra un resumen de los operadores existentes para las distrintas
clases de tipos primitivos. El grupo 'Enteros' incluye byte, short, int, long y char. El grupo
'Coma flotante' incluye float and double.
3. Métodos
3.1 Declaración de métodos
Un método es:
Por ejemplo:
void haceAlgo() {
. . .
}
Los métodos se invocan con su nombre, y pasando la lista de argumentos entre paréntesis.
El conjunto se usa como si fuera una variable del Tipo devuelto por el método.
Por ejemplo:
int x;
x = sumaEnteros(2,3);
Nota: Esta sintaxis no está completa, pero sirve para nuestros propósitos en este momento.
La sintaxis completa se verá cuando se hable de objetos.
haceAlgo();
Observese que como la función tampoco devuelve ningún valor no se asigna a ninguna
variable. (No hay nada que asignar).
4. Clases - Introducción
4.1. Clases
Las clases son el mecanismo por el que se pueden crear nuevos Tipos en Java. Las clases
son el punto central sobre el que giran la mayoría de los conceptos de la Orientación a
Objetos.
Una clase es una agrupación de datos y de código que actua sobre esos datos, a la que se le
da un nombre.
• Datos (se denominan Datos Miembro). Estos pueden ser de tipos primitivos o
referencias.
• Métodos (se denominan Métodos Miembro).
Por ejemplo:
class Punto {
int x;
int y;
}
Los modificadores son palabras clave que afectan al comportamiento de la clase. Los
iremos viendo progresivamente en los sucesivos capítulos.
Los objetos se manipulan con referencias. Una referencia es una variable que apunta a un
objeto. Las referencias se declaran igual que las variables de Tipos primitivos (tipo
nombre). Los objetos se crean (se instancian) con el operador de instanciación new.
Ejemplo:
Punto p;
p = new Punto();
La primera línea del ejemplo declara una referencia (p) que es de Tipo Punto. La referencia
no apunta a ningún sitio. En la segunda línea se crea un objeto de Tipo Punto y se hace que
la referencia p apunte a él. Se puede hacer ambas operaciones en la misma expresión:
nombre_referencia.miembro
p.x = 1;
p.y = 3;
class Circulo {
Punto centro; // dato miembro. Referencia a un
objeto punto
int radio; // dato miembro. Valor primitivo
float superficie() { // método
miembro.
return 3.14 * radio * radio;
} // fin del
método superficie
} // fin de la
clase Circulo
El acceso a métodos miembros es igual que el que ya se ha visto para datos miembro. En el
ejemplo:
• Los datos miembro pueden ser tanto primitivos como referencias. La clase Circulo
contiene un dato miembro de tipo Punto (que es el centro del círculo).
• El acceso a los datos miembros del Punto centro se hace encadenando el operador .
en la expresión c.centro.x que se podría leer como 'el miembro x del objeto
(Punto) centro del objeto (Circulo) c'.
• Aunque el método superficie no recibe ningún argumento los paréntesis son
obligatorios (Distinguen los datos de los métodos).
• Existe un Objeto Punto para cada instancia de la clase Circulo (que se crea cuando
se crea el objeto Circulo).
5. Clases - Constructores
5.1. Noción de constructor
Cuando se crea un objeto (se instancia una clase) es posible definir un proceso de
inicialización que prepare el objeto para ser usado. Esta inicialización se lleva a cabo
invocando un método especial denominado constructor. Esta invocación es implícita y se
realiza automáticamente cuando se utiliza el operador new. Los constructores tienen
algunas características especiales:
Continuando con los ejemplos del capítulo anterior se podría escribir un constructor para la
clase Punto, de la siguiente forma:
class Punto {
int x , y ;
Punto ( int a , int b ) {
x = a ; y = b ;
}
}
Si una clase no declara ningún constructor, Java incorpora un constructor por defecto
(denominado constructor no-args) que no recibe ningún argumento y no hace nada.
class Punto {
int x , y ;
Punto ( ) { }
}
5.3. Sobrecarga de constructores.
Una clase puede definir varios constructores (un objeto puede inicializarse de varias
formas). Para cada instanciación se usa el que coincide en número y tipo de argumentos. Si
no hay ninguno coincidente se produce un error en tiempo de compilación.
Por ejemplo:
class Punto {
int x , y ;
Punto ( int a , int b ) {
x = a ; y = b ;
}
Punto () {
x = 0 ; y = 0;
}
}
class Punto {
int x , y ;
Punto ( int a , int b ) {
x = a ; y = b ;
}
Punto () {
this (0,0);
}
}
Cuando se declaran varios constructores para una misma clase estos deben distinguirse en
la lista de argumentos, bien en el número, bien en el tipo.
Un dato estático es una variable miembro que no se asocia a un objeto (instancia) de una
clase, sino que se asocia a la clase misma; no hay una copia del dato para cada objeto sino
una sola copia que es compartida por todos los objetos de la clase.
Por ejemplo:
class Punto {
int x , y ;
static int numPuntos = 0;
En el ejemplo numPuntos es un contador que se incrementa cada vez que se crea una
instancia de la clase Punto.
El acceso a las variables estáticas desde fuera de la clase donde se definen se raliza a través
del nombre de la clase y no del nombre del objeto como sucede con las variables miembro
normales (no estáticas). En el ejemplo anterior puede escribirse:
int x = Punto.numPuntos;
No obstante también es posible acceder a las variables estáticas a través de una referencia a
un objeto de la clase. Por ejemplo:
Las variables estáticas de una clase existen, se inicializan y pueden usarse antes de que se
cree ningún objeto de la clase.
Métodos estáticos
Para los métodos, la idea es la misma que para los datos: los métodos estáticos se asocian a
una clase, no a una instancia.
Por ejemplo:
class Punto {
int x , y ;
static int numPuntos = 0;
La sintaxis general para la definición de los métodos es, por tanto, la siguiente:
El aceso a los métodos estáticos se hace igual que a los datos estáticos, es decir, usando el
nombre de la clase, en lugar de usar una referencia:
Dado que los métodos estáticos tienen sentido a nivel de clase y no a nivel de objeto
(instancia) los métodos estáticos no pueden acceder a datos miembros que no sean
estáticos.
El método main
Inicializadores estáticos
En ocasiones es necesario escribir código para inicializar los datos estáticos, quizá creando
algún otro objeto de alguna clase o realizando algún tipo de control. El fragmento de
código que realiza esta tarea se llama inicializador estático. Es un bloque de sentencias que
no tiene nombre, ni recibe argumentos, ni devuelve valor. Simplemente se declara con el
modificador static .
static { bloque_codigo }
Por ejemplo:
class Punto {
int x , y ;
static int numPuntos;
static {
numPuntos = 0;
}
Nota: El ejemplo, una vez más, muestra sólo la sintaxis y forma de codificación. Es
innecesario inicializar la variable tal como se verá más adelante. Además podría
inicializarse directamente con: static int numPuntos = 0;
Desde el punto de vista del lugar donde se declaran existen dos tipos de variables:
• Las numéricas a 0.
• Las booleanas a false.
• Las char al caracter nulo (hexadecimal 0).
• Las referencias a null.
Las variables miembro pueden inicializarse con valores distintos de los anteriores en su
declaración.
int p;
int q = p; // error
El compilador también produce un error si se intenta usar una variable local que podría no
haberse inicializado, dependiendo del flujo de ejecución del programa. Por ejemplo:
int p;
if (. . . ) {
p = 5 ;
}
int q = p; // error
El compilador produce un error del tipo 'La variable podría no haber sido inicializada',
independientemente de la condición del if.
El ámbito de una variable es el área del programa donde la variable existe y puede ser
utilizada. Fuera de ese ámbito la variable, o bien no existe o no puede ser usada (que viene
a ser lo mismo).
El ámbito de las variables locales es el bloque de código donde se declaran. Fuera de ese
bloque la variable es desconocida.
Ejemplo:
{
int x; // empieza el ámbito de x. (x es conocida y
utilizable)
{
int q; // empieza el ámbito de q. x sigue siendo
conocida.
. . .
} // finaliza el ámbito de q (termina el
bloque de código)
. . . // q ya no es utilizable
} // finaliza el ámbito de x
int metodoSobrecargado() { . . .}
int metodoSobrecargado(int x) { . . .}
Sin embargo no se puede sobrecargar cambiando sólo el tipo del valor devuelto. Por
ejemplo:
int metodoSobrecargado() { . . .}
void metodoSobrecargado() { . . .} // error en compilación
Se puede sobrecargar cualquier método miembro de una clase, así como el constructor.
En ocasiones es conveniente disponer de una referencia que apunte al propio objeto que se
está manipulando. Esto se consigue con la palabra reservada this. this es una
referencia implicita que tienen todos los objetos y que apunta a si mismo. Por ejemplo:
class Circulo {
Punto centro;
int radio;
. . .
Circulo elMayor(Circulo c) {
if (radio > c.radio) return this;
else return c;
}
}
El método elMayor devuelve una referencia al círculo que tiene mayor radio, comparando
los radios del Circulo c que se recibe como argumento y el propio. En caso de que el
propio resulte mayor el método debe devolver una referencia a si mismo. Esto se consigue
con la expresión return this.
Para asignar a una referencia el valor nulo se utiliza la constante null. El ejemplo del caso
anterior se podría completar con:
class Circulo {
Punto centro;
int radio;
. . .
Circulo elMayor(Circulo c) {
if (radio > c.radio) return this;
else if (c.radio > radio) return c;
else return null;
}
}
Puede ocurrir que una variable local y una variable miembro reciban el mismo nombre (en
muchos casos por error). Cuando se produce esto la variable miembro queda oculta por la
variable local, durante el bloque de código en que la variable local existe y es accesible.
Cuando se sale fuera del ámbito de la variable local, entonces la variable miembro queda
accesible. Observese esto en el ejemplo siguiente:
. . .
String x = "Variable miembro";
. . .
void variableOculta() {
System.out.println(x);
{
String x = "Variable local";
System.out.println(x);
}
System.out.println(x);
}
Nota: El uso de Strings se verá en un capítulo posterior, aunque su uso aquí resulta
bastante intuitivo. La llamada System.out.println envia a la consola (la salida
estándar habitual) las variables que se pasan como argumentos.
Variable miembro
Variable local
Variable miembro
System.out.println(this.x);
8. Control de la ejecución
if (expresion_booleana)
sentencia
[else
sentencia]
sentencia (a todo lo largo de este capítulo) puede ser una sola sentencia o un bloque
de sentencias separadas por ; y enmarcadas por llaves { y }. Es decir
if (expresion_booleana) {
sentencia;
sentencia;
. . .
}
else {
sentencia;
sentencia;
. . .
}
Sintaxis formato 1:
while (expresion_booleana)
sentencia
Sintaxis formato 2:
do
sentencia
while (expresion_booleana)
La sentencia o bloque se sentencias (se aplica la misma idea que para el if-else) se
ejecuta mientras que la expresion_booleana se evalue como true
La diferencia entre ambos formatos es que en el primero la expresión se evalua al principio
del bloque de sentencias y en el segundo se evalua al final.
El formato es:
step es una sentencia que se ejecuta cada vez que se llega al final de la sentencia o
bloque de sentencias. Es opcional.
Una utilización clásica de un bucle de tipo for se muestra a continuación para evaluar un
contador un número fijo de veces:
for ( ; ; )
sentencia
Obsérvese que se pueden omitir las clausulas pero no los separadores (;).
El formato es:
switch ( expresion_entera ) {
case valor_entero:
sentencia;
break;
case valor_entero:
sentencia;
break;
. . .
default:
sentencia;
}
Se ejecuta el bloque case cuyo valor coincida con el resultado de la expresión entera de
la clausula switch . Se ejecuta hasta que se encuentra una sentencia break o se llega
al final del switch .
El formato es:
return valor
Se utiliza en los métodos para terminar la ejecución y devolver un valor a quien lo llamó.
valor debe ser del tipo declarado en el método.
valor es opcional. No debe existir cuando el método se declara de tipo void. En este
caso, la claúsula return al final del método es opcional, pero puede usarse para devolver
el control al llamador en cualquier momento.
8.7. Expresiones
Cuando se evalua una expresión en un programa el resultado puede denotar una de tres
cosas:
Si la expresión denota una variable o un valor, entonces la expresión tiene siempre un tipo
conocido en el momento de la compilación. Las reglas para determinar el tipo de la
expresión varían dependiendo de la forma de las expresiones pero resultan bastante
naturales. Por ejemplo, en una expresión aritmética con operandos de diversas precisiones
el resultado es de un tipo tal que no se produzca pérdida de información, realizandose
internamente las conversiones necesarias. El análisis pormenorizado de las conversiones de
tipos, evaluaciones de expresiones, etc, queda fuera del ámbito de estos apuntes. En
general puede decirse que es bastante similar a otros lenguajes, en particular C, teniendo en
cuenta la característica primordial de Java de tratarse de un lenguaje con control fuerte de
tipos.
9. Arrays
9.1. Declaración y acceso
Un array es una colección ordenada de elementos del mismo tipo, que son accesibles a
través de un índice.
Un array puede contener datos primitivos o referencias a objetos.
Los arrays se declaran:
Por ejemplo:
int [ ] a;
Punto [ ] p;
La declaración dice que a es un array de enteros y p un array de objetos de tipo Punto. Más
exactamente a es una referencia a una colección de enteros, aunque todavía no se sabe
cuantos elementos tiene el array. p es una referencia a una colección de referencias que
apuntarán objetos Punto.
Un array se crea como si se tratara de un objeto (de hecho las variables de tipo array son
referencias):
También puede crearse de forma explícita asignando valores a todos los elementos del
array en el momento de la declaración, de la siguiente forma:
int [ ] a = { 5 , 3 , 2 };
El acceso a los elementos del array se realiza indicando entre corchetes el elemento del
array que se desea, teniendo en cuenta que siempre el primer elemento del array es el
índice 0. Por ejemplo a[1]. En este ejemplo los índices del array de tres elementos son
0, 1 y 2. Si se intenta usar un índice que está fuera del rango válido para ese array se
produce un error (en realidad una excepción. Las excepciones se tratan en un capítulo
posterior) de 'Indice fuera de rango'. En el ejemplo anterior se produce esta excepción si el
índice es menor que 0 o mayor que 2.
Un array, como cualquier otra referencia puede formar parte de la lista de parámetros o
constituir el valor de retorno de un método. En ambos casos se indica que se trata de un
array con los corchetes que siguen al tipo. Por ejemplo:
Es posible declarar arrays de más de una dimensión. Los conceptos son los mismos que
para los arrays monodimensionales.
Por ejemplo:
int [ ][ ] a = { { 1 , 2 } , { 3 , 4 } , { 5 , 6 } };
int x = a[1][0]; // contiene 3
int y = a[2][1]; // contiene 6
10. Strings
10.1. La clase String
En Java no existe un tipo de datos primitivo que sirva para la manipulación de cadenas de
caracteres. En su lugar se utiliza una clase definida en la API que es la clase String. Esto
significa que en Java las cadenas de caracteres son, a todos los efectos, objetos que se
manipulan como tales, aunque existen ciertas operaciones, como la creación de Strings,
para los que el lenguaje tiene soporte directo, con lo que se simplifican algunas
operaciones.
Un String puede crearse como se crea cualquier otro objeto de cualquier clase; mediante el
operador new:
Observese que los literales de cadena de caracteres se indican entre comillas dobles ("), a
diferencia de los caracteres, que utilizan comillas simples (').
Sin embargo también es posible crear un String directamente, sin usar el operador new,
haciendo una asignación simple (como si se tratara de un dato primitivo):
Además la clase String proporciona constructores para crear Strings a partir de arrays de
caracteres y arrays de bytes. Consultar la documentación del API del JDK para más
detalles.
Java define el operador + (suma) con un significado especial cuando las operandos son de
tipo String. En este caso el operador suma significa concatenación. El resultado de la
concatenación es un nuevo String compuesto por las dos cadenas, una tras otra. Por
ejemplo:
int i = 5;
String x = "El valor de i es " + i;
La clase String dispone de una amplia gama de métodos para la manipulación de las
cadenas de caracteres. Para una referencia completa consultar la documentación del API
del JDK. El siguiente cuadro muestra un resumen con algunos de los métodos más
significativos:
Método Descripción
Devuelve el carácter en la posición indicada por
char charAt(int index)
index. El rango de index va de 0 a length() - 1.
Compara el String con el objeto especificado. El
resultado es true si y solo si el argumento es no nulo
boolean equals(Object obj)
y es un objeto String que contiene la misma
secuencia de caracteres.
Compara el String con otro, ignorando
consideraciones de mayúsculas y minúsculas. Los
boolean dos Strings se consideran iguales si tienen la misma
equalsIgnoreCase(String s) longitud y, los caracteres correspondientes en ambos
Strings son iguales sin tener en cuenta mayúsculas y
minúsculas.
Devuelve el indice donde se produce la primera
int indexOf(char c)
aparición de c. Devuelve -1 si c no está en el string.
Igual que el anterior pero buscando la subcadena
int indexOf(String s)
representada por s.
Devuelve la longitud del String (número de
int length()
caracteres)
String substring(int Devuelve un substring desde el índice begin hasta el
begin, int end) end
Devuelve un string que es la representación del
static String valueOf(int entero i. Observese que este método es estático. Hay
i) métodos equivalentes donde el argumento es un
float, double, etc.
char[] toCharArray()
Transforman el string en un array de caracteres, o a
String toLowerCase()
mayúsculas o a minúsculas.
String toUpperCase()
Dado que la clase String sólo manipula cadenas de caracteres constantes resulta poco
conveniente cuando se precisa manipular intensivamente cadenas (reemplazadno
caracteres, añadiendo o suprimiendo, etc.). Cuando esto es necesario puede usarse la clase
StringBuffer definida también en el package java.lang. del API. Esta clase implanta un
buffer dinámico y tiene métodos que permiten su manipulación comodamente. Ver la
documentación del API.
11. Packages
11.1 Claúsula package
Los packages delimitan el espacio de nombres (space name). El nombre de una clase debe
ser único dentro del package donde se define. Dos clases con el mismo nombre en dos
packages distintos pueden coexistir e incluso pueden ser usadas en el mismo programa.
Una clase se declara perteneciente a un package con la clausula package, cuya sintaxis
es:
package nombre_package;
La clausula package debe ser la primera sentencia del archivo fuente. Cualquier clase
declarada en ese archivo pertenece al package indicado.
package miPackage;
. . .
class miClase {
. . .
Cuando se referencia cualquier clase dentro de otra se asume, si no se indica otra cosa, que
ésta otra está declarada en el mismo package. Por ejemplo:
package Geometria;
. . .
class Circulo {
Punto centro;
. . .
}
Si esto no es así, es necesario hacer accesible el espacio de nombres donde está definida la
clase Punto a nuestra nueva clase. Esto se hace con la clausula import. Supongamos que la
clase Punto estuviera definida de esta forma:
package GeometriaBase;
class Punto {
int x , y;
}
Entonces, para usar la clase Punto en nuestra clase Circulo deberiamos poner:
package GeometriaAmpliada;
import GeometriaBase.*;
class Circulo {
Punto centro;
. . .
}
También es posible hacer accesibles los nombres de un package sin usar la clausula
import calificando completamente los nombres de aquellas clases pertenecientes a otros
packages. Por ejemplo:
package GeometriaAmpliada;
class Circulo {
GeometriaBase.Punto centro;
. . .
}
Sin embargo si no se usa import es necesario especificar el nombre del package cada
vez que se usa el nombre Punto.
De esta forma se pueden tener los packages ordenados según una jerarquía equivalente a
un sistema de archivos jerárquico.
El API de java está estructurado de esta forma, con un primer calificador (java o javax) que
indica la base, un segundo calificador (awt, util, swing, etc.) que indica el grupo funcional
de clases y opcionalmente subpackages en un tercer nivel, dependiendo de la amplitud del
grupo. Cuando se crean packages de usuario no es recomendable usar nombres de
packages que empiecen por java o javax.
Además del significado lógico descrito hasta ahora, los packages también tienen un
significado físico que sirve para almacenar los módulos ejecutables (ficheros con extensión
.class) en el sistema de archivos del ordenador.
Supongamos que definimos una clase de nombre miClase que pertenece a un package de
nombre misPackages.Geometria.Base. Cuando la JVM vaya a cargar en
memoria miClase buscará el módulo ejecutable (de nombre miClase.class) en un
directorio en la ruta de acceso misPackages/Geometria/Base. Está ruta deberá existir y
estar accesible a la JVM para que encuentre las clases. En el capítulo siguiente se dan
detalles sobre compilación y ejecución de programas usando el compilador y la máquina
virtural distribuida por SUN Microsystems (JDK).
Si una clase no pertenece a ningún package (no existe clausula package ) se asume que
pertenece a un package por defecto sin nombre, y la JVM buscará el archivo .class en el
directorio actual.
Para que una clase pueda ser usada fuera del package donde se definió debe ser declarada
con el modificador de acceso public, de la siguiente forma:
package GeometriaBase;
Si una clase no se declara public sólo puede ser usada por clases que pertenezcan al
mismo package.
PASO 1: Con un editor de texto simple (incluso notepad sirve, aunque resulta poco
aconsejabe) creamos un archivo con el contenido siguiente:
package Programas.Ejemplo1;
class HolaMundo {
public static void main ( String [] args) {
System.out.println("Hola a todos");
}
}
En el ejemplo se utiliza la clase del API de Java System. Sin embargo el programa no
tiene ningún import. No obstante el compilador no detecta ningún error y genera el
código ejecutable directamente. Esto se debe a que la clase System está definida en el
package java.lang, que es el único del que no es necesario hacer el import, que es
hecho implicitamente por el compilador. Para cualquier clase del API, definida fuera de
este package es necesario hacer el impor correspondiente.
C:\ApuntesJava>java Programas.Ejemplo1.HolaMundo
Se cargará la JVM, cargará la clase HolaMundo y llamará a su método main que producirá
en la ventana DOS la salida:
Hola a todos
Los archivos .class son invocables directamente desde la línea de comandos (con la
sintaxis java nombreDeClase) si tienen un método main definido tal como se vio en un
capítulo anterior.
Se puede indicar a la JVM que busque las clases en rutas alternativas al directorio actual.
Esto se hace con el parámetro -classpath (abreviadamente -cp) en la línea de
comandos. Por ejemplo si el directorio actual es otro, podemos invocar el programa de
ejemplo de la forma:
Con el parámetro -cp se puede especificar diversas rutas alternativas para la búsqueda de
clases separadas por ;
El esquema habitual es tener un archivo fuente por clase y asignar al archivo fuente el
mismo nombre que la clase con la extensión .java (el nombre .java para la extensión es
obligatorio). Esto generará al compilar un archivo .class con el mismo nombre que el
fuente ( y que la clase). Fuentes y módulos residirán en el mismo directorio.
Es posible definir más de una clase en un archivo fuente, pero sólo una de ellas podrá ser
declarada public (es decir podrá ser utilizada fuera del package donde se define). Todas
las demás clases declaradas en el fuente serán internas al package. Si hay una clase
public entonces, obligatoriamente, el nombre del fuente tiene que coincider con el de la
clase declarada como public . Los modificadores de acceso (public, es uno de ellos) se
verán en el capítulo siguiente.
Los modificadores son elementos del lenguaje que se colocan delante de la definición de
variables locales, datos miembro, métodos o clases y que alteran o condicionan el
significado del elemento. En capítulos anteriores se ha descrito alguno, como es el
modificador static que se usa para definir datos miembros o métodos como
pertenecientes a una clase, en lugar de pertenecer a una instancia. En capítulos posteriores
se tratarán otros modificadores como final, abstract o synchronized. En este
capítulo se presentan los modificadores de acceso, que son aquellos que permiten limitar o
generalizar el acceso a los componentes de una clase o a la clase en si misma.
Los modificadores de acceso permiten al diseñador de una clase determinar quien accede a
los datos y métodos miembros de una clase.
En el ejemplo los datos miembros de la clase Punto se declaran como private, y se incluyen
métodos que devuelven las coordenadas del punto. De esta forma el diseñador de la clase
controla el contenido de los datos que representan la clase e independiza la
implementación de la interface.
class Punto {
private int x , y ;
static private int numPuntos = 0;
int getX() {
return x;
}
int getY() {
return y;
}
}
Si alguien, desde una clase externa a Punto, intenta:
. . .
Punto p = new Punto(0,0);
p.x = 5;
. . .
• public - Todo el mundo puede usar la clase. Se pueden crear instancias de esa
clase, siempre y cuando alguno de sus constructores sea accesible.
• sin modificador - La clase puede ser usada e instanciada por clases dentro del
package donde se define.
Cuando se diseñan clases, es importante pararse a pensar en términos de quien debe tener
acceso a que. Qué cosas son parte de la implantación y deberían ocultarse (y en que grado)
y que cosas forman parte de la interface y deberían ser públicas.
Herencia
Composición
En anteriores ejemplos se ha visto que una clase tiene datos miembro que son instancias de
otras clases. Por ejemplo:
class Circulo {
Punto centro;
int radio;
float superficie() {
return 3.14 * radio * radio;
}
}
Esta técnica en la que una clase se compone o contiene instancias de otras clases se
denomina composición. Es una técnica muy habitual cuando se diseñan clases. En el
ejemplo diríamos que un Circulo tiene un Punto (centro) y un radio.
Herencia
Pero además de esta técnica de composición es posible pensar en casos en los que una
clase es una extensión de otra. Es decir una clase es como otra y además tiene algún tipo de
característica propia que la distingue. Por ejemplo podríamos pensar en la clase Empleado
y definirla como:
class Empleado {
String nombre;
int numEmpleado , sueldo;
Con esta representación podemos pensar en otra clase que reuna todas las características de
Empleado y añada alguna propia. Por ejemplo, la clase Ejecutivo. A los objetos de esta
clase se les podría aplicar todos los datos y métodos de la clase Empleado y añadir
algunos, como por ejemplo el hecho de que un Ejecutivo tiene un presupuesto.
Así diriamos que la clase Ejecutivo extiende o hereda la clase Empleado. Esto en Java se
hace con la clausula extends que se incorpora en la definición de la clase, de la siguiente
forma:
Con esta definición un Ejecutivo es un Empleado que además tiene algún rasgo distintivo
propio. El cuerpo de la clase Ejecutivo incorpora sólo los miembros que son específicos de
esta clase, pero implícitamente tiene todo lo que tiene la clase Empleado.
Los objetos de las clases derivadas se crean igual que los de la clase base y pueden acceder
tanto sus datos y métodos como a los de la clase base. Por ejemplo:
void asignarPresupuesto(int p) {
presupuesto = p;
}
Cuando se crea un objeto de una clase derivada se crea implicitamente un objeto de la clase
base que se inicializa con su constructor correspondiente. Si en la creación del objeto se
usa el constructor no-args, entonces se produce una llamada implicita al constructor no-
args para la clase base. Pero si se usan otros constructores es necesario invocarlos
explicitamente.
En nuestro ejemplo dado que la clase método define un constructor, necesitaremos también
un constructor para la clase Ejecutivo, que podemos completar así:
class Ejecutivo extends Empleado {
int presupuesto;
void asignarPresupuesto(int p) {
presupuesto = p;
}
Herencia - II
El modificador de acceso protected
entonces desde la clase Ejecutivo se puede acceder al dato miembro sueldo, mientras que si
se declara private no.
Up-casting y Down-casting
Aquí se crea un objeto de la clase Ejecutivo que se asigna a una referencia de tipo
Empleado. Esto es posible y no da error ni al compilar ni al ejecutar porque Ejecutivo es
una clase derivada de Empleado. A esta operación en que un objeto de una clase derivada
se asigna a una referencia cuyo tipo es alguna de las superclases se denomina 'upcasting'.
Cuando se realiza este tipo de operaciones, hay que tener cuidado porque para la referencia
emp no existen los miembros de la clase Ejecutivo, aunque la referencia apunte a un objeto
de este tipo. Así, las expresiones:
Por último, la situación para el método toString es algo más compleja. Si se invoca el
método:
Operador cast
Si se desea acceder a los métodos de la clase derivada teniendo una referencia de una clase
base, como en el ejemplo del apartado anterior hay que convertir explicitamente la
referencia de un tipo a otro. Esto se hace con el operador de cast de la siguiente forma:
La clase Object
En Java existe una clase base que es la raíz de la jerarquía y de la cual heredan todas
aunque no se diga explicitamente mediante la clausula extends. Esta clase base se llama
Object y contiene algunos métodos básicos. La mayor parte de ellos no hacen nada pero
pueden ser redefinidos por las clases derivadas para implementar comportamientos
específicos. Los métodos declarados por la clase Object son los siguientes:
InterruptedException {. . .}
public final void wait(long millis, int nanos) throws
IllegalMonitorStateException,
InterruptedException { . . . }
public final void notify() throws
IllegalMonitorStateException { . . . }
public final void notifyAll() throws
IllegalMonitorStateException { . . . }
protected void finalize() throws Throwable { . . . }
}
Las cláusulas final y throws se verán más adelante. Como puede verse toString
es un método de Object, que puede ser redefinido en las clases derivadas. Los métodos
wait, notify y notifyAll tienen que ver con la gestión de threads de la JVM. El
método finalize ya se ha comentado al hablar del recolector de basura.
La cláusula final
Para una clase, final significa que la clase no puede extenderse. Es, por tanto el punto
final de la cadena de clases derivadas. Por ejemplo si se quisiera impedir la extensión de la
clase Ejecutivo, se pondría:
Para un método, final significa que no puede redefinirse en una clase derivada. Por
ejemplo si declaramos:
class Empleado {
. . .
public final void aumentarSueldo(int porcentaje) {
. . .
}
. . .
}
class Circulo {
. . .
public final static float PI = 3.141592;
. . .
}
En el ejemplo se define el valor de PI como de tipo float, estático (es igual para todas
las instancias), constante (modificador final) y de acceso público.
Herencia simple
Java incorpora un mecanismo de herencia simple. Es decir, una clase sólo puede tener una
superclase directa de la cual hereda todos los datos y métodos. Puede existir una cadena de
clases derivadas en que la clase A herede de B y B herede de C, pero no es posible escribir
algo como:
Java implanta otro mecanismo que resulta parecido al de herencia múltiple que es el de las
interfaces que se verá más adelante.
Gestión de Excepciones
Excepciones. Categorías.
Las excepciones son el mecanismo por el cual pueden controlarse en un programa Java las
condiciones de error que se producen. Estas condiciones de error pueden ser errores en la
lógica del programa como un índice de un array fuera de su rango, una división por cero o
errores disparados por los propios objetos que denuncian algún tipo de estado no previsto,
o condición que no pueden manejar.
La idea general es que cuando un objeto encuentra una condición que no sabe manejar crea
y dispara una excepción que deberá ser capturada por el que le llamó o por alguien más
arriba en la pila de llamadas. Las excepciones son objetos que contienen información del
error que se ha producido y que heredan de la clase Throwable o de la clase Exception. Si
nadie captura la excepción interviene un manejador por defecto que normalmente imprime
información que ayuda a encontrar quién produjo la excepción.
• Excepciones verificadas: El compilador obliga a verificarlas. Son todas las que son
lanzadas explicitamente por objetos de usuario.
• Excepciones no verificadas: El compilador no obliga a su verificación. Son
excepciones como divisiones por cero, excepciones de puntero nulo, o índices fuera
de rango.
Generación de excepciones
Supongamos que tenemos una clase Empresa que tiene un array de objetos Empleado
(clase vista en capítulos anteriores). En esta clase podríamos tener métodos para contratar
un Empleado (añadir un nuevo objeto al array), despedirlo (quilarlo del array) u obtener el
nombre a partir del número de empleado. La clase podría ser algo así como lo siguiente:
Observese en el método nuevoEmpleado que se comprueba que hay sitio en el array para
almacenar la referencia al nuevo empleado. Si lo hay se crea el objeto. Pero si no lo hay el
método no hace nada más. No da ninguna indicación de si la operación ha tenido éxito o
no. Se podría hacer una modificación para que, por ejemplo el método devolviera un valor
booleano true si la operación se ha completado con éxito y false si ha habido algún
problema.
Las excepciones son clases, que heredan de la clase genérica Exception. Es necesario por
tanto asignar un nombre a nuestra excepción. Se suelen asignar nombres que den alguna
idea del tipo de error que controlan. En nuestro ejemplo le vamos a llamar
CapacidadEmpresaExcedida.
En nuestro ejemplo:
La clase de la excepción puede declarar otros métodos o guardar datos de depuración que
se consideren oportunos. El único requisito es que extienda la clase Exception. Consultar la
documentación del API para ver una descripción completa de la clase Exception.
Captura de excepciones
Con la primera versión del método nuevoEmpleado (sin excepción) se invocaría este
método de la siguiente forma:
try {
. . .
} catch (Clase_Excepcion nombre) { . . .}
catch (Clase_Excepcion nombre) { . . .}
. . .
Observese que se puede capturar más de un tipo de excepción declarando más de una
sentencia catch. También se puede capturar una excepción genérica (clase Exception) que
engloba a todas las demás.
En ocasiones el código que llama a un método que dispara una excepción tampoco puede
(o sabe) manejar esa excepción. Si no sabe que hacer con ella puede de nuevo lanzarla
hacia arriba en la pila de llamada para que la gestione quien le llamo (que a su vez puede
capturarla o reenviarla). Cuando un método no tiene intención de capturar la excepción
debe declararla metdiante la cláusula throws, tal como hemos visto en el método que
genera la excepción.
Supongamos que, en nuestro ejemplo es el método main de una clase el que invoca el
método nuevoEmpleado. Si no quiere capturar la excepción debe hacer lo siguiente:
Cláusula finally
La cláusula finally forma parte del bloque try / catch y sirve para especificar un
bloque de código que se ejecutará tanto si se lanza la excepción como si no. Puede servir
para limpieza del estado interno de los objetos afectados o para liberar recursos externos
(descriptores de fichero, por ejemplo). La sintaxis global del bloque try / catch /
finally es:
try {
. . .
} catch (Clase_Excepcion nombre) { . . .}
catch (Clase_Excepcion nombre) { . . .}
. . .
finally { . . .}
En ocasiones es muy conveniente poder tratar los datos primitivos (int, boolean, etc.) como
objetos. Por ejemplo, los contenedores definidos por el API en el package java.util (Arrays
dinámicos, listas enlazadas, colecciones, conjuntos, etc.) utilizan como unidad de
almacenamiento la clase Object. Dado que Object es la raíz de toda la jerarquía de objetos
en Java, estos contenedores pueden almacenar cualquier tipo de objetos. Pero los datos
primitivos no son objetos, con lo que quedan en principio excluidos de estas posibilidades.
Para resolver esta situación el API de Java incorpora las clases envoltorio (wrapper class),
que no son más que dotar a los datos primitivos con un envoltorio que permita tratarlos
como objetos. Por ejemplo podríamos definir una clase envoltorio para los enteros, de
forma bastante sencilla, con:
Entero(int valor) {
this.valor = valor;
}
int intValue() {
return valor;
}
}
La API de Java hace innecesario esta tarea al proporcionar un conjunto completo de clases
envoltorio para todos los tipos primitivos. Adicionalmente a la funcionalidad básica que se
muestra en el ejemplo las clases envoltorio proporcionan métodos de utilidad para la
manipulación de datos primitivos (conversiones de / hacia datos primitivos, conversiones a
String, etc.)
Observese que las clases envoltorio tienen siempre la primera letra en mayúsculas.
Hay que tener en cuenta que las operaciones aritméticas habituales (suma, resta,
multiplicación ...) están definidas solo para los datos primitivos por lo que las clases
envoltura no sirven para este fin.
Método Descripción
Integer(int valor)
Constructores a partir de int y String
Integer(String valor)
int intValue() / byte
Devuelve el valor en distintos formatos, int,
byteValue() / float
long, float, etc.
floatValue() . . .
Devuelve true si el objeto con el que se
boolean equals(Object obj)
compara es un Integer y su valor es el mismo.
static Integer Devuelve un Integer a partir de una cadena de
getInteger(String s) caracteres. Estático
static int parseInt(String s) Devuelve un int a partir de un String. Estático.
static String
toBinaryString(int i)
static String Convierte un entero a su representación en
toOctalString(int i) String en binario, octal, hexadecimal, etc.
static String toHexString(int Estáticos
i)
static String toString(int i)
String toString()
static Integer valueOf(String Devuelve un Integer a partir de un String.
s) Estático.
El API de Java contiene una descripción completa de todas las clases envoltorio en el
package java.lang.
Clases abstractas
Concepto
Hay ocasiones, cuando se desarrolla una jerarquía de clases en que algún comportamiento
está presente en todas ellas pero se materializa de forma distinta para cada una. Por
ejemplo, pensemos en una estructura de clases para manipular figuras geométricas.
Podríamos pensar en tener una clase genérica, que podría llamarse FiguraGeometrica y una
serie de clases que extienden a la anterior que podrían ser Circulo, Poligono, etc. Podría
haber un método dibujar dado que sobre todas las figuras puede llevarse a cabo esta acción,
pero las operaciones concretas para llevarla a cabo dependen del tipo de figura en concreto
(de su clase). Por otra parte la acción dibujar no tiene sentido para la clase genérica
FiguraGeometrica, porque esta clase representa una abstracción del conjunto de figuras
posibles.
Para resolver esta problemática Java proporciona las clases y métodos abstractos. Un
método abstracto es un método declarado en una clase para el cual esa clase no
proporciona la implementación (el código). Una clase abstracta es una clase que tiene al
menos un método abstracto. Una clase que extiende a una clase abstracta debe implementar
los métodos abstractos (escribir el código) o bien volverlos a declarar como abstractos, con
lo que ella misma se convierte también en clase abstracta.
Se pueden crear referencias a clases abstractas como cualquier otra. No hay ningún
problema en poner:
FiguraGeometrica figura;
Sin embargo una clase abstracta no se puede instanciar, es decir, no se pueden crear
objetos de una clase abstracta. El compilador producirá un error si se intenta:
Esto es coherente dado que una clase abstracta no tiene completa su implementación y
encaja bien con la idea de que algo abstracto no puede materializarse.
Interfaces
Concepto de Interface
El concepto de Interface lleva un paso más adelante la idea de las clases abstractas. En
Java una interface es una clase abstracta pura, es dcir una clase donde todos los métodos
son abstractos (no se implementa ninguno). Permite al diseñador de clases establecer la
forma de una clase (nombres de métodos, listas de argumentos y tipos de retorno, pero no
bloques de código). Una interface puede también contener datos miembro, pero estos son
siempre static y final. Una interface sirve para establecer un 'protocolo' entre clases.
Para crear una interface, se utiliza la palabra clave interface en lugar de class. La interface
puede definirse public o sin modificador de acceso, y tiene el mismo significado que para
las clases. Todos los métodos que declara una interface son siempre public.
Para indicar que una clase implementa los métodos de una interface se utiliza la palabra
clave implements. El compilador se encargará de verificar que la clase efectivamente
declare e implemente todos los métodos de la interface. Una clase puede implementar más
de una interface.
Declaración y uso
interface nombre_interface {
tipo_retorno nombre_metodo ( lista_argumentos ) ;
. . .
}
Por ejemplo:
interface InstrumentoMusical {
void tocar();
void afinar();
String tipoInstrumento();
}
Referencias a Interfaces
Es posible crear referencias a interfaces, pero las interfaces no pueden ser instanciadas.
Una referencia a una interface puede ser asignada a cualquier objeto que implemente la
interface. Por ejemplo:
Extensión de interfaces
Las interfaces pueden extender otras interfaces y, a diferencia de las clases, una interface
puede extender más de una interface. La sintaxis es:
Agrupaciones de constantes
Dado que, por definición, todos los datos miembros que se definen en una interface son
static y final, y dado que las interfaces no pueden instanciarse resultan una buena
herramienta para implantar grupos de constantes. Por ejemplo:
public interface Meses {
int ENERO = 1 , FEBRERO = 2 . . . ;
String [] NOMBRES_MESES = { " " , "Enero" , "Febrero" , .
. . };
}
System.out.println(Meses.NOMBRES_MESES[ENERO]);
Supongamos que tenemos una clase que representa un botón de acción en un entorno
gráfico de usuario (el típico botón de confirmación de una acción o de cancelación). Esta
clase pertenecerá a una amplia jerarquía de clases y tendrá mecanismos complejos de
definición y uso que no son objeto del ejemplo. Sin embargo podríamos pensar que la
clase Boton tiene miembros como los siguientes.
interface Oyente {
void botonPulsado(Boton b);
}
La interface define un único método botonPulsado. La idea es que este método sea
invocado por la clase Boton cuando el usuario pulse el botón. Para que esto sea posible en
algún momento hay que notificar al Boton quien es el Oyente que debe ser notificado. La
clase Boton quedaría:
El método registrarOyente sirve para que alguien pueda 'apuntarse' como receptor
de las acciones del usuario. Obsérvese que existe una referencia de tipo Oyente. A Boton
no le importa que clase va a recibir su notificación. Simplemente le importa que implante
la interface Oyente para poder invocar el método botonPulsado. En el método click se
invoca este método. En el ejemplo se le pasa como parámetro una referencia al propio
objeto Boton. En la realidad lo que se pasa es un objeto 'Evento' con información detallada
de lo que ha ocurrido.
Con todo esto la clase que utiliza este mecanismo podría tener el siguiente aspecto:
. . .
void botonPulsado(Boton x) {
// procesar click
. . .
}
}
Concepto
Una clase embebida es una clase que se define dentro de otra. Es una característica de Java
que permite agrupar clases lógicamente relacionadas y controlar la 'visibilidad' de una
clase. El uso de las clases embebidas no es obvio y contienen detallas algo más complejos
que escapan del ámbito de esta introducción.
class Externa {
. . .
class Interna {
. . .
}
}
La clase Externa puede instanciar y usar la clase Interna como cualquier otra, sin
limitación ni cambio en la sintaxis de acceso:
class Externa {
. . .
class Interna {
. . .
}
void metodo() {
Interna i = new Interna(. . .);
. . .
}
}
Una diferencia importante es que un objeto de la clase embebida está relacionado siempre
con un objeto de la clase que la envuelve, de tal forma que las instancias de la clase
embebida deben ser creadas por una instancia de la clase que la envuelve. Desde el exterior
estas referencias pueden manejarse, pero calificandolas completamente, es decir
nombrando la clase externa y luego la interna. Además una instancia de la clase embebida
tiene acceso a todos los datos miembros de la clase que la envuelve sin usar ningún
calificador de acceso especial (como si le pertenecieran). Todo esto se ve en el ejemplo
siguiente.
Ejemplo
class Almacen {
private Object [] listaObjetos;
private numElementos = 0;
Almacen (int maxElementos) {
listaObjetos = new Object[maxElementos];
}
public Object get(int i) {
return listaObject[i];
}
public void add(Object obj) {
listaObjetos[numElementos++] = obj;
}
public Iterador getIterador() {
new Iterador();
}
class Iterador {
int indice = 0;
Object siguiente() {
if (indice < numElementos) return
listaObjetos[indice++];
else return null;
}
}
}
Comentarios, documentación y
convenciones de nombres
Comentarios
En Java existen comentarios de línea con // y bloques de comentario que comienzan con /*
y terminan con */. Por ejemplo:
• Los comentarios de documentación deben empezar con /** y terminar con */.
• Se pueden incorporar comentarios de documentación a nivel de clase, a nivel de
variable (dato miembro) y a nivel de método.
• Se genera la documentación para miembros public y protected.
• Se pueden usar tags para documentar ciertos aspectos concretos como listas de
parámetros o valores devueltos. Los tags se indican a continuación.
Toda la documentación del API de Java está creada usando esta técnica y la herramienta
javadoc.
import java.util.*;
Convenciones de nombres
• Utilizar nombres descriptivos para las clases, evitando los nombres muy largos.
• Para los nombres de clases poner la primera letra en mayúsculas y las demás en
minúsculas. Por ejemplo: Empleado
• Si el nombre tiene varias palabras ponerlas todas juntas (sin separar con - o _) y
poner la primera letra de cada palabra en mayúsculas. Por ejemplo:
InstrumentoMusical.
• Para los nombres de miembros (datos y métodos) seguir la misma norma, pero con
la primera letra de la primera palabra en minúsculas. Por ejemplo: registrarOyente.
• Para las constantes (datos con el modificador final) usar nombres en mayúsculas,
separando las palabras con _
El API de Java está formado por una amplísima jerarquía de clases que cubren una gran
cantidad de aspectos relacionados con el desarrollo de software en general. Esta
organizado en packages ordenados por temas. El J2SE (Java 2 Standard Edition) permite la
utilización de todos estos packages en el desarrollo de programas Java y el JRE (Java
Runtime Environment) permite la ejecución de programas que usan cualquiera de las
clases del API.La documentación que acompaña al J2SE contiene un manual de referencia
completo ordenado por packages y clases de todo el contenido del API. Su consulta resulta
imprescindible para cualquier desarrollo.
El siguiente apartado da un somero repaso a los distintos grupos de packages del API,
agrupándolos por temas.
La siguiente tabla muestra una descripción básica del contenido de los distintos packages
que constituyen el API de Java.
Package o grupo de
Descripción
packages
Proporciona las clases necesarias para crear applets, así como las
java.applet
clases que usa ul applet para comunicarse con su contexto.
12 packages.El AWT (Abstract Windows Toolkit) proporciona el
entorno base para todas las clases de manipulación del interfaz
gráfico del usuario. El AWT apareció en la versión 1.0 del JDK y
java.awt.* fue parcialmente sustuituido y sustancialmente mejorado en la
versión 1.1 (con el conjunto de componentes conocido como
swing). Actualmente se mantiene porque es la base del swing
aunque muchos de sus elementos ya no se usan.
2 packages. Contiene las clases relacionadas con el desarrollo de
java.beans.* Java Beans. Los Java Beans representan la estrategia de desarrollo
por componentes de Java.
Es un package fundamental. Proporciona los mecanismos para las
operaciones de entrada/salida de flujos de datos (streams), así
java.io como la serialización (capacidad de los objetos para ser
transformados en flujos de datos), y soporte para los sistemas de
archivos.
3 packages. Proporciona clases básicas para cualquier programa.
(Threads, clases envoltorio, entrada/salida/error estándar, etc.).
java.lang.* Asi como clases que proporcionan la caracteristica de 'reflexión',
es decir la capacidad de las clases de averigurar como están
construidas ellas mismas u otras clases.
Proporciona clases para realiza cálculos aritméticos de cualquier
java.math preción. Así como funciones matemáticas generales
(Trigonometría, aleatorización, etc.)
Proporciona clases para implantar aplicaciones basadas en red.
java.net
Sockets, URL's, direcciones IP, etc.
5 packages. RMI significa Remote Method Invocation.
Proporciona clases que permite que los objetos puedan
java.rmi.* comunicarse con otros objetos en otros programas Java corriendo
en otra máquina virtual conectada por red. Permite el desarrollo de
aplicaciones distribuidas.
java.security.* 5 packages. Clases que implantan el esquema de seguridad de
Java.
Proporciona el API para el aceso y proceso de datos organizados
en bases de datos relacionales y accediendo con mecanismos de
java.sql
lenguaje SQL. Esta API se denomina también JDBC (Java Data
Base Conectivity)
Proporciona clases e interfaces para la manipulación de texto,
java.text fechas, números y mensajes de una forma independiente del
idioma.
3 packages. Contiene la 'collections framework', o conjunto de
clases para manipulación de conjuntos de objetos (colas, pilas,
listas, diccionarios, árboles, tablas hash, etc.). Además tiene varios
conjunts de utilidades para manipulación de fecha y hora,
java.util.*
generación de números aleatorios y manipulación de ficheros
comprimidos en formato ZIP y JAR. El formato JAR (Java
ARchive) es una extensión del formato ZIP que permite
empaquetar clases java compiladas para su ejecución.
Contiene un conjunto de clases para las tecnologías de asistencia
javax.accesibility
relacionadas con los interfaces gráficos.
5 packages. Contiene clases e interfaces para proporcionar
javax.naming.*
servicios de nombres y directorios.
javax.rmi.* 2 packages. Contiene clases relacionadas con el RMI-IIOP.
javax.sound.* 4 packages. Contiene clases para manipulación de sonidos.
16 packages. Conjunto extenso de clases para el configuación del
interface gráfico de usuario. Reemplaza parcialmente al AWT. Su
javax.swing.*
característica más importante es que es independiente de la
plataforma.
12 packages. Contiene clases relacionadas con la especificación
CORBA. CORBA permite la comunicación entre programas de
objetos escritos en diferentes lenguajes (Por ejemplo, Java y C++).
org.omg.*
Es equivalente a RMI, pero mientras que en este todos los
participantes deben ser objetos Java con CORBA se posibilita la
comunicación entre objetos multi-lenguaje.
23. Threads I
23.1. Qué es un Thread
23.2. La clase Thread
23.3. La interface Runnable.
23.4. El ciclo de vida de un Thread.
Java da soporte al concepto de Thread desde el mismo lenguaje, con algunas clases e
interfaces definidas en el package java.lang y con métodos específicos para la
manipulación de Threads en la clase Object.
Desde el punto de vista de las aplicaciones los threads son útiles porque permiten que el
flujo del programa sea divido en dos o más partes, cada una ocupándose de alguna tarea.
Por ejemplo un Thread puede encargarse de la comunicación con el usuario, mientras otros
actuan en segundo plano, realizando la transmisión de un fichero, accediendo a recursos
del sistema (cargar sonidos, leer ficheros ...), etc. De hecho todos los programas con
interface gráfico (AWT o Swing) son multithread porque los eventos y las rutinas de
dibujado de las ventanas corren en un thread distinto al principal.
23.2. La Clase Thread
La forma más directa para hacer un programa multi-thread es extender la clase Thread, y
redefinir el método run(). Este método es invocado cuando se inicia el thread (mediante
una llamada al método start() de la clase thread). El thread se inicia con la llamada al
método run y termina cuando termina éste. El ejemplo ilustra estas ideas:
Cuando se instancia la clase Thread (o una subclase) se crea un nuevo Thread que está en
en su estado inicial ('New Thread' en el gráfico). En este estado es simplemente un objeto
más. No existe todavía el thread en ejecución. El único método que puede invocarse sobre
él es el método start.
Cuando se invoca el método start sobre el thread el sistem crea los recursos necesarios, lo
planifica (le asigna prioridad) y llama al método run. En este momento el thread está
corriendo.
Si el método run invoca internamente el método sleep o wait o el thread tiene que esperar
por una operación de entrada/salida, entonces el thread pasa al estado 'no runnable' (no
ejecutable) hasta que la condición de espera finalice. Durante este tiempo el sistema puede
ceder control a otros threads activos.
Por último cuando el método run finaliza el thread termina y pasa a la situación 'Dead'
(Muerto).