1. Introducción a los objetos

Anuncio
Sistemas Informáticos I (Ingeniería Informática. UNED)
1. Introducción a los objetos
1.1 Todo objeto tiene una interfaz.
La creación de tipos abstractos de datos (clases) es un concepto fundamental en la POO. Es posible la creación de
variables de un tipo (objeto o instancia) y manipularlas (mensajes). Los miembros de cada clase comparten algunos rasgos
comunes. Siempre que aparezca la palabra clave “tipo” (type) puede sustituirse por la “clase” (class) y viceversa.
Las peticiones que se puedan hacer a un objeto se encuentran definidas en su interfaz, que viene determinada por el
tipo de objeto. La interfaz establece qué peticiones pueden hacerse a un objeto en particular.
1.2 La implementación oculta.
La creación de clases debe ser de forma que únicamente se exponga lo necesario, ocultando el resto. Este enfoque
permite la modificación de la parte oculta sin preocuparse de las implicaciones de la modificación. Otro motivo es evitar
las manipulaciones indebidas de los métodos de las clases.
Java usa tres palabras clave para establecer los límites en una clase, estos modificadores de acceso determinan quién
puede usar las definiciones a las que preceden:
public: Las definiciones están disponibles para todo el mundo.
private: Sólo el creador del tipo puede acceder a esas definiciones. Si se intenta acceder se obtiene un error en
tiempo de compilación.
protected: Actúa como privada con la diferencia de que una clase heredada tiene acceso a miembros protegidos
pero no a los privados.
Cuando no se especifica el modo tiene un acceso por defecto, se suele denominar acceso amistoso (friendly) ya
que las clases pueden acceder a los miembros amigos de otras clases que estén en el mismo paquete (package),
fuera del paquete, estos miembros amigos se convierten en private.
1.3 Reutilizar la implementación
La manera más simple de reutilizar una clase es usar un objeto de esta clase, también es posible ubicar un objeto de esa
clase dentro de otra clase, creación de un objeto miembro. La nueva clase puede construirse a partir de un número
indefinido de otros objetos, de igual o distinto tipo, en cualquier combinación necesaria para lograr la funcionalidad
deseada dentro de la nueva clase. Este concepto es la composición (agregación), que se representa mediante la relación “esparte-de”.
La composición conlleva una gran carga de flexibilidad. Los objetos miembros de la nueva clase suelen ser privados.
Esto permite cambiar los miembros en tiempo de ejecución, para así cambiar de manera dinámica el comportamiento de un
programa.
La herencia no es tan flexible, ya que el compilador debe emplazar restricciones de tiempo de compilación en las
clases creadas por herencia. Es recomendable intentar el uso de la composición (simple y sencilla) que aplicar herencia.
1.4 Herencia: Reutilizar la interfaz.
La herencia expresa la semejanza entre tipos haciendo uso del concepto de tipos base y tipos derivados. Un tipo base
contiene todas las características y comportamientos que comparten los tipos que de él se derivan. El uso de la herencia
permite construir una jerarquía de tipos que expresa el problema en términos de los propios tipos. De esta forma el modelo
principal lo constituye la jerarquía de tipos, de manera que se puede ir directamente de la descripción del sistema en el
mundo real a la descripción del sistema en código.
Al heredar a partir de un tipo existente, se crea un nuevo tipo. Este nuevo tipo contiene no sólo los miembros del tipo
existente (aunque los datos private estén ocultos e inaccesibles) sino que duplica la interfaz de la clase base.
Hay dos formas de diferenciar el comportamiento de la clase derivada de la clase base original. El primero es añadir
nuevas funciones. La segunda (y más importante) es variar el comportamiento de una función ya existente en la clase base.
A esto se le llama redefinición (anulación o superposición) de la función. Para ello simplemente se crea una nueva
definición de la función dentro de la clase derivada.
1.4.1 La relación es-un frente a la relación es-como-un
El principio de sustitución dice que la filosofía a seguir entre clases bases y derivadas es sólo superponer las funciones
de la clase base (un círculo es un polígono), es lo que se conoce como relación es-un, en esta relación es posible sustituir un
objeto de la clase derivada por otro de la clase base.
 J.M. Godoy, F. Gómez y E. Rubio. 2002-2003
página 1
Sistemas Informáticos I (Ingeniería Informática. UNED)
Existen casos en los que es necesario añadir nuevos elementos a la interfaz del tipo derivado, extendiendo así la
interfaz y cuando un nuevo tipo, en este caso la sustitución por el tipo base no es perfecto y esta relación se define escomo-un.
1.5 Objetos intercambiables con polimorfismo
Se puede desear tratar un objeto como su tipo base, con lo que el código no dependerá de tipos específicos. En los
compiladores no-OO una llamada a una función crea una ligadura temprana, es decir, genera una llamada a una función
con nombre específico y el montador resuelve esta llamada a la dirección absoluta del código a ejecutar. En POO, el
programa no puede determinar la dirección del código hasta tiempo de ejecución, utilizándose otro esquema, el de ligadura
tardía, al enviar un mensaje a un objeto, no se determina el código invocado hasta el tiempo de ejecución. El compilador se
asegura de que la función exista y hace la comprobación de tipos de los argumentos y del valor de retorno, pero se
desconoce el código exacto a ejecutar.
El proceso de tratar un tipo derivado como si fuera el tipo base se le llama conversión de tipos (moldeado) hacia arriba
(casting), es decir, moverse hacia arriba por el diagrama de herencias.
1.5.1 Clases base abstractas e interfaces
Puede ser deseable que la clase base sólo presente una interfaz para sus clases derivadas, es decir, que no se creen
objetos de ella, sino sólo de las derivadas. Esto se consigue convirtiendo esa clase en abstracta con la palabra clave
abstrac. Si se intenta construir un objeto de una clase abstracta el compilador lo evita.
También se puede utilizar la palabra clave abstrac para describir un método que aún no ha podido ser implementando,
un método abstracto sólo puede existir dentro de una clase abstracta. Cuando se hereda la clase, debe implementarse el
método o de lo contrario la clase heredada se convierte en abstracta. La creación de métodos abstractos permiten poner un
método en una interfaz sin verse forzado a proporcionar un fragmento de código para ese método.
La palabra clave interface toma el concepto de clase abstracta, evitando totalmente las definiciones de funciones, es
una herramienta muy útil al proporcionar la separación perfecta entre interfaz e implementación.
1.6 Localización de objetos y longevidad
Un factor importante en la POO es la manera de crear y destruir objetos. Java utiliza el enfoque de crear objetos
dinámicamente en el montículo (heap). En este enfoque, no es necesario conocer hasta el tiempo de ejecución el número de
objetos, longevidad o tipo. Si se necesita un nuevo objeto simplemente se construye en el montículo. Como el
almacenamiento se gestiona dinámicamente, en tiempo de ejecución, la cantidad de tiempo necesaria para asignar espacio
de almacenamiento en el montículo es mayor que el necesario en la pila. Sin embargo la flexibilidad es esencial para
resolver en general los problemas de programación. Cada vez que se desea crear un objeto se usa la palabra clave new para
construir una instancia dinámica de ese objeto.
El aspecto de la longevidad se resuelve, en el caso de la pila, por el compilador que determina cuánto dura cada objeto
y puede destruirlo cuando no es necesario. En el enfoque del montículo el compilador no tiene conocimiento de su
duración, lo proporciona un recolector de basura que descubre automáticamente cuándo se ha dejado de utilizar un objeto,
y puede ser destruido. Esto disminuye la cantidad de código y ofrece un nivel superior de seguridad.
1.6.1 Colecciones e iteradores.
En un problema concreto se pueden desconocer el número y duración de objetos para solucionarlo, y en consecuencia
cómo almacenarlos. En la POO se soluciona creando otro tipo de objetos que tiene referencias a otros objetos. Además este
nuevo objeto, llamado contenedor, se expandirá a sí mismo cuando sea necesario para albergar todo lo que se coloque en
su interior.
Todos los contenedores tienen alguna manera de introducir y extraer cosas, pero la extracción puede ser problemática
porque una función de selección única suele ser restrictiva. La solución es un iterador, que es un objeto cuyo trabajo es
seleccionar los elementos de dentro de un contenedor y presentárselos al usuario. Como clase también proporciona un
cierto nivel de abstracción, que puede ser usada para separar los detalles del contenedor del código al que éste esté
accediendo. El contenedor se abstrae a través del iterador hasta convertirse en una secuencia, que se puede recorrer con el
iterador, sin preocuparse de la estructura subyacente (Arraylist, LinkedList, Stack...). Java2 cuenta con un iterador
denominado Iterator (en Java 1.0 y 1.1 Enumeration).
Página 2
 J.M. Godoy, F. Gómez y E. Rubio. 2002-2003
Sistemas Informáticos I (Ingeniería Informática. UNED)
1.6.2 Jerarquía de raíz única.
En Java todas las clases heredan, en última instancia, de una única clase base, Object. De esta forma todos los objetos
tienen una interfaz común, por lo que en última instancia son del mismo tipo.
Puede garantizarse que todos los objetos de una jerarquía de raíz única tienen cierta funcionalidad y junto a la creación
de todos los objetos en el montículo simplifica el paso de argumentos. Además simplifica la implementación del recolector
de basura ya que su soporte se puede instalar en la clase base y podrá enviar los mensajes apropiados a todos los objetos
del sistema.
Dado que está garantizado que en tiempo de ejecución la información de tipos está en todos los objetos, jamás será
imposible encontrar un objeto cuyo tipo no pueda ser determinado, esto es importante en operaciones a nivel de sistema y
por la flexibilidad que proporciona al programar.
1.6.3 Bibliotecas de colecciones
Para utilizar un contenedor, basta con añadirle referencias a objetos y luego preguntar por ellas. Dado que el
contenedor sólo guarda objetos de tipo Object, al añadirle una referencia se hace un moldeado hacia arriba a Object
perdiendo la identidad. Al recuperarlo, se obtiene una referencia a Object y no una referencia al tipo introducido. Para
recuperar la interfaz del objeto introducido en el contenedor se debe hacer un moldeado hacia abajo, a un tipo erróneo, se
mostrará un error en tiempo de ejecución, exception.
Para solucionar el moldeado hacia abajo y las comprobaciones en tiempo de ejecución, se crean los tipos
parametrizados, que son clases que el compilador puede adaptar automáticamente para que trabajen con tipos
determinados. Dado que C++ no tiene jerarquía de raíz única es la solución que adopta (template). Java no los tienen ya
que logra lo mismo explotando la unicidad de raíz de su jerarquía, aunque lo hace de manera complicada.
1.7 Multihilo
A veces, es necesario hacer uso de las interrupciones para el manejo de tareas críticas en el tiempo, pero hay una gran
cantidad de problemas en los que simplemente se intenta dividir un problema en fragmentos de código que pueden ser
ejecutados por separado, de manera que se logra un menor tiempo de respuesta para todo el programa en general. Estos
fragmentos de código que pueden ser ejecutados por separado, se denominan hilos y el concepto general multihilo. Los
hilos son una herramienta para facilitar la planificación de un monoprocesador. Si el sistema operativo soporta múltiples
procesadores, es posible asignar cada hilo a un procesador distinto de manera que los hilos se ejecuten verdaderamente en
paralelo. Uno de los aspectos más destacables es que el programa no tiene de ocuparse de ello. Para los recursos
compartidos por varios hilos se pueden establecer bloqueos a los accesos.
El hilo de Java está incluido en el propio lenguaje y se soporta a nivel de objeto. El bloqueo de memoria de cualquier
objeto se logra mediante la palabra clave synchronized. Otros tipos de recursos se deben bloquear por el programador
creando un objeto que represente el bloqueo que todos los hilos deben comprobar antes de acceder al recurso.
 J.M. Godoy, F. Gómez y E. Rubio. 2002-2003
página 3
Sistemas Informáticos I (Ingeniería Informática. UNED)
2. Todo es un objeto
2.1 Los objetos se manipulan mediante referencias
En Java, todo se trata como un objeto, utilizando una única sintaxis consistente, que se utiliza en todas partes. Aunque
se trata todo como un objeto, el identificador que se manipula es una referencia a un objeto. Es la referencia, la que actúa
sobre el objeto.
La referencia puede existir por sí misma sin necesidad de que exista un objeto. De esta forma, si se desea tener una
palabra o frase, se crea una referencia String :
| String s;
Esta sentencia sólo crea la referencia, no el objeto. Si se decide enviar un mensaje en este momento, se obtendrá un
error en tiempo de ejecución porque s no se encuentra vinculado a nada. Una práctica más segura es iniciar la referencia en
el mismo momento de su creación:
| String s = “abcd“
Cuando se crea una nueva referencia, debe conectarse con un objeto. Java utiliza la palabra reservada new, para crear
un objeto nuevo.
| String s = new String (“abcd“)
2.2 Donde reside el almacenamiento?
Hay seis lugares diferentes donde almacenar información:
1.
2.
3.
4.
5.
6.
Página 4
Registros. Elemento de almacenamiento más rápido. Número limitado de ellos. Los asigna el compilador en
función de sus necesidades. No se tiene control directo sobre ellos.
La pila. Reside en la memoria RAM, tiene soporte directo del procesador a través del puntero de pila, que se
mueve hacia abajo para crear más memoria y hacia arriba para liberarla. Este método es una manera rápida y
eficiente de asignar espacio. El compilador debe conocer, mientras está creando el programa, el tamaño exacto y
la vida de todos los datos almacenados en la pila. El uso de la pila pone limitaciones de flexibilidad a los
programas.
El Montículo. Espacio de memoria de propósito general, en el que residen los objetos Java. En el montículo, a
diferencia de la pila, el compilador no necesita conocer cuanto espacio de almacenamiento se necesita asignar.
Proporciona gran flexibilidad.
Almacenamiento estático: (con una ubicación/posición fija pero en RAM). Contiene datos que están disponibles
durante todo el tiempo que se esté ejecutando un programa. Se utiliza static para especificar que un elemento
particular de un objeto sea estático, pero los objetos nunca se sitúan en el espacio de almacenamiento estático.
Almacenamiento constante. Los valores constantes se suelen ubicar directamente en el código del programa. En
ocasiones las constantes suelen ser acordonadas por si mismas, de forma que puedan ser ubicadas
ocasionalmente en ROM.
Almacenamiento no-RAM. Los dos ejemplos principales son: objeto de flujo de datos (stream), que se
convierte en flujo o corrientes de bits, generalmente para ser enviados a otra máquina y los objetos persistentes,
que son ubicados en disco. Java proporciona soporte para persistencia ligera.
 J.M. Godoy, F. Gómez y E. Rubio. 2002-2003
Sistemas Informáticos I (Ingeniería Informática. UNED)
2.3 Los tipos primitivos
Los tipos primitivos tienen un tratamiento especial, no es eficiente crear un objeto de este tipo con new ya que lo
coloca en el montículo. Para estos tipos se crea una variable automática que no es una referencia.
La variable guarda el valor y se coloca en la pila para que sea más eficiente. Java determina el tamaño de cada tipo
primitivo, que no varía de una plataforma a otra:
Tipo primitivo
boolean
Tamaño
-
Máximo
-
char
16 bits
byte
8 bits
Mínimo
-
Tipo envoltura
Boolean
16
Unicode 0
Unicode 2 -1
Character
-128
127
Byte
15
15
short
16 bits
-2
+2 -1
Short
int
32 bits
- 231
+231 -1
Integer
long
64 bits
63
-2
+2 -1
Long
float
32 bits
IEEE754
IEEE754
Float
double
64 bits
IEEE754
IEEE754
Double
void
-
-
-
Void
63
Todos los tipos numéricos tienen signo. El tamaño de boolean no está explícitamente definido; sólo se especifica que
debe ser capaz de tomar los valores True o False.
Los tipos de datos primitivos tienen también clases “envoltura“, de forma que si se desea crear un objeto no primitivo
en el montículo para representar ese tipo primitivo, se hace uso del envoltorio asociado. Por ejemplo:
char c = 'x';
Character C = new Character(c);
2.4 Números de alta precisión
Java incluye dos clases para llevar a cabo aritmética de alta precisión: BigInteger y BigDecimal. Aunque estos tipos
vienen a encajar en la misma categoría de las clases “envoltorio“, ninguna de ellas tiene un tipo primitivo.
Ambas clases tienen métodos que proporcionan operaciones análogas que se llevan a cabo con tipos primitivos (int y
float), utilizando llamadas a métodos en lugar de operadores. Son más lentas que las primitivas.
2.5 Arrays en Java
Java garantiza que los arrays estarán siempre inicializados y que no se podrá acceder más allá de su rango. La
comprobación de rangos se resuelve con una pequeña sobrecarga de memoria en cada array, además de verificar el índice
en tiempo de ejecución.
Cuando se crea un array de objetos, se está creando realmente un array de referencias a los objetos, y cada una de estas
se inicializa con un valor especial representado por la palabra clave null. Debe asignarse un objeto a cada referencia antes
de usarla y si se intenta hacer uso de una referencia que aún vale null, se informará de que ha ocurrido un problema en
tiempo de ejecución.
 J.M. Godoy, F. Gómez y E. Rubio. 2002-2003
página 5
Sistemas Informáticos I (Ingeniería Informática. UNED)
2.6 Ámbito
Determina tanto la visibilidad como la vida de los nombres definidos dentro de éste ámbito. El ámbito se determina por
la ubicación de las llaves {}, por ejemplo:
{
int x=12;
/* sólo x disponible */
{
int q=96;
/* tanto x como q están disponibles */
}
/* sólo x disponible */
/* q está fuera del ámbito o alcance */
}
Una variable definida dentro de un ámbito solamente está disponible hasta que finalice su ámbito. La visibilidad en
Java está implementada en modo de anidamiento descendente. Si una variable se define en un determinado nivel, y
posteriormente en un nivel inferior, el compilador comunicará que la variable está duplicada.
{
int x;
{
int x; }
Ilegal
}
2.7 Ámbito de los objetos
Los objetos no tienen la misma vida que los tipos primitivos. Cuando se crea un objeto con new, éste perdura hasta el
final del ámbito, de manera que:
{
String s = new String(“un string“);
/* fin del ámbito */
}
La referencia s desaparece al final del ámbito, sin embargo, el objeto String al que apunta s sigue ocupando memoria.
Java tiene un recolector de basura, que recorre todos los objetos que fueron creados con new y averigua cuales no
serán referenciados más, liberando la memoria que estos ocupan. En Java no es necesario hacer ninguna reserva de
memoria, simplemente se crean los objetos y cuando dejan de ser necesarios, desaparecen por sí mismos. Esto elimina el
problema de programación denominado “agujero de memoria“, que se produce en otros lenguajes cuando no se destruyen
explícitamente los objetos para liberar memoria.
2.8 Crear nuevos tipos de datos: clases
Un nuevo tipo de objeto se define mediante la palabra clave class, que siempre va seguida del nombre del nuevo tipo,
por ejemplo:
class UnNombreDeTipo {/* Aquí va el cuerpo de la clase */}
Esto introduce un nuevo tipo, siendo posible crear un objeto de este tipo haciendo uso de la palabra new, por ejemplo:
UnNombreDeTipo u = new UnNombreDeTipo ();
2.9 Campos y métodos
Cuando se define una nueva clase, es posible poner dos tipos de elementos:
datos miembros (denominados campos)
funciones miembros (denominados métodos).
Un dato miembro es un objeto de cualquier tipo con el que te puedes comunicar a través de su referencia. También
puede ser algún tipo primitivo. Si es una referencia a un objeto, hay que reinicializar esa referencia para conectarla a algún
objeto real (utilizando new) en una función especial denominada constructor. Si se trata de un tipo primitivo es posible
inicializarla directamente en el momento de definir la clase. Cada objeto mantiene el espacio de almacenamiento necesario
para todos sus datos miembro; estos no son compartidos con otros objetos:
class SoloDatos
{
Página 6
 J.M. Godoy, F. Gómez y E. Rubio. 2002-2003
Sistemas Informáticos I (Ingeniería Informática. UNED)
int i;
float f;
boolean b;
}
Es posible asignar valores a los datos miembro, pero primero es necesario hacer referencia al miembro del objeto.
s.i = 47;
s.f = 1.1f;
f.b = False;
También es posible que un objeto pueda contener otros datos que se quieran modificar. Para ello hay que seguir
concatenando puntos por ejemplo:
miAvion.TanqueIzquierdo.capacidad = 100;
2.10 Valores por defecto para los miembros primitivos
Cuando un tipo de datos primitivo es un miembro de una clase, se garantiza que tenga un valor por defecto si no se
inicializa:
Tipo
Primitivo
Valor por
defecto
Tipo
Primitivo
Valor por
defecto
boolean
false
int
0
char
u0000 (null)
long
0L
byte
(byte) (0)
float
0.0f
short
short (0)
double
0.0d
Esta garantía no se aplica a las variables “locales“ -- aquellas que no sean campos de clases. Si dentro de una función
se tiene: int x; entonces x tomará algún valor arbitrario. El compilador Java advierte como error de esta circunstancia.
2.11 Métodos, parámetros y valores de retorno
Los métodos en Java determinan los mensajes que puede recibir un objeto. Las partes fundamentales de un método son
su nombre, sus parámetros, el tipo de retorno y el cuerpo. Su forma básica se asemeja a la siguiente:
tipoRetorno nombreMetodo ( /* lista de parámetros */ )
{ /* cuerpo del método */ }
El tipo de retorno es el tipo de valor que surge del método tras ser invocado. La lista de parámetros indica los tipos y
nombres de las informaciones que es necesario pasar en ese método. Cada método se identifica unívocamente mediante el
nombre del método y la lista de parámetros.
En Java, los métodos pueden crearse como parte de una clase. Es posible que un método pueda ser invocado sólo por
un objeto, que llevará a cabo la llamada al método. Un método puede invocarse mediante la expresión completa de su ruta
cualificada, o bien, asignando a una variable el valor de retorno de un objeto, en este caso, la declaración de la variable
debe corresponderse con el valor que retorna el método.
El acto de invocar a un método suele denominarse envío de un mensaje a un objeto. Por ejemplo en:
int x = a.f(x);
el mensaje es f() y el objeto es a.
2.12 Lista de parámetros
La lista de parámetros de un método especifica la información que se le pasa. Esta información tiene forma de objeto y
se debe especificar los tipos de objetos a pasar, y nombre a utilizar en cada uno. Debe tenerse en cuenta que realmente no
se están manipulando directamente objetos, sino que se están pasando referencias. Un método puede devolver el tipo que se
desee, pero si no se desea devolver nada, se debe indicar que el método devuelve void
Cuando el tipo de retorno es void, se utiliza la palabra clave return sólo para salir del método, siendo innecesaria
cuando se llega al final del mismo. Es posible salir de un método en cualquier punto, pero si se devuelve un valor de
retorno distinto de void, el compilador obligará a devolver el tipo apropiado (mediante mensaje de error).
 J.M. Godoy, F. Gómez y E. Rubio. 2002-2003
página 7
Sistemas Informáticos I (Ingeniería Informática. UNED)
2.13 Construcción de un programa en Java
2.13.1 Visibilidad de nombres
Para producir un nombre no ambiguo para una biblioteca, el identificador utilizado se asemeja a los nombres de
dominio de Internet, pero a la inversa, dado que es posible garantizar que estos sean únicos. Una vez que se da la vuelta al
nombre de dominio, los nombres supuestamente representan directorios. Este mecanismo hace posible que todos sus
ficheros residan automáticamente en sus propios espacios de nombres, y cada clase de un fichero debe tener un
identificador único.
2.13.2 Utilización de otros componentes
Cuando se desee utilizar una clase predefinida en un programa, el compilador debe saber donde encontrarla. Para ello,
debe utilizarse la sentencia import. Esta palabra clave le dice al compilador que traiga un paquete, que es una biblioteca de
clases.
La mayoría de las veces se utilizan componentes de bibliotecas de Java estándar que vienen con el propio compilador,
simplemente se hace:
import java.util.ArrayList;
para indicar al compilador que se desea utilizar la clase ArrayList de Java. Sin embargo, util contiene bastantes clases
y se podría desear utilizar varias de ellas sin tener que declararlas todas. Esto se logra sencillamente utilizando el * que
hace las veces de comodín, así:
import java.util.*;
importaría la colección de clases que forman parte de la librería útil.
2.17.3 La palabra clave static
Al crear una clase se está describiendo qué apariencia tienen sus objetos y cómo se comportan. No se tiene nada hasta
crear un objeto de esa clase con new, momento en el que se crea el espacio de almacenamiento y los métodos pasan a estar
disponibles.
Hay dos situaciones en las que este enfoque no es suficiente. Una es cuando se desea tener sólo un fragmento de
espacio de almacenamiento para una parte concreta de datos, independientemente de cuántos objetos se crean, o incluso
aunque no se cree ninguno. La otra es cuando se necesita un método que no esté asociado a ningún objeto particular de una
clase. Puesto que los métodos estáticos no precisan de la creación de ningún objeto, no pueden acceder directamente a
miembros de métodos no estáticos sin referirse al objeto con nombre del que son propiedad.
Para declarar un dato o un miembro a nivel de clase como estático, basta con colocar la palabra clave static antes de la
definición, así:
class PruebaEstatica {
static int i = 47;
}
Ahora, incluso si se construyen dos objetos de tipo PruebaEstatica, sólo habrá un espacio de almacenamiento para
PruebaEstatica.i. Ambos objetos compartirán la misma i:
PruebaEstatica st1 = new PruebaEstatica();
PruebaEstatica st2 = new PruebaEstatica();
En este momento, tanto st1.i como st2.i tienen el valor 47, puesto que se refieren al mismo espacio de memoria.
Hay dos maneras de referirse a una variable estática. Es posible manejarlas a través de un objeto como en st2.i.
También es posible referirse a ellas directamente a través de su nombre de clase, algo que no se puede hacer con miembros
no estáticos. Ésta manera especial énfasis en la naturaleza estática de esa variable.
PruebaEstatica.i ++;
El operador ++ incrementa la variable. En ese momento tanto st1.i como st2.i valdrán 48.
Algo similar se aplica a los métodos estáticos. Es posible referirse a ellos, bien a través de un objeto o bien con la
sintaxis adicional NombreClase.metodo(). Un método estático se define de manera semejante:
class FunEstatico {
static void incr() {PruebaEstatica.i++}
}
Página 8
 J.M. Godoy, F. Gómez y E. Rubio. 2002-2003
Sistemas Informáticos I (Ingeniería Informática. UNED)
Mientras que static al ser aplicado a un miembro de datos, cambia definitivamente la manera de crear los datos (uno
por cada clase en lugar de uno por cada objeto no estático), al aplicarse a un método no es tan drástico. Un uso importante
de static para los métodos es permitir invocar a un método sin tener que crear un objeto. Esto, es esencial en la definición
del método main(), que es el punto de entrada para la ejecución de la aplicación.
 J.M. Godoy, F. Gómez y E. Rubio. 2002-2003
página 9
Sistemas Informáticos I (Ingeniería Informática. UNED)
3 Controlar el flujo del programa.
3.1 Asignación
La asignación de tipos primitivos es sencilla y directa, ya que el dato primitivo alberga el valor actual y no una
referencia a un objeto. En la asignación de objetos, sin embargo, cuando se hace una asignación de un objeto a otro, se
copia la referencia de un sitio a otro, es decir, si se hace C=D siendo ambos objetos, tanto C como D apuntarán al objeto
que originalmente apuntaba D. Este fenómeno se llama uso de alias. El uso de alias también se produce en el paso de un
objeto a un método, de forma que se pasa una referencia.
3.2 Operadores
Relacionales(<,>,<=...), generan un resultado de tipo boolean, su uso para tipos primitivos es tal cual, pero para
comparar los contenidos de dos objetos (referencias) se debe usar equals.
Lógicos (AND &&, OR ||, NOT !), producen un valor lógico, pero sólo es posible aplicarlos a los valores boolean. La
evaluación de operadores lógicos sufre cortocircuito, es decir, se evalúan solo hasta que es posible determinar sin
ambigüedad la certeza o falsedad de toda expresión lógica.
Operadores de bit. Permiten manipular bits individuales, los operadores son AND (&), OR(|), XOR(^), NOT(~); estos
operadores son combinables con el de asignación (&=) a excepción del operador NOT. Los tipos boolean se pueden
manipular con estos operadores, pero no se les puede aplicar el operador NOT.
Operadores de desplazamiento. Estos operadores manipulan bits y sólo se pueden utilizar con tipos primitivos enteros.
Al desplazar un char será convertido a int y el resultado será también un int. Sólo se desplazan los n-1 bits más
significativos para evitar salirse de rango.
Operador ternario if-else, tiene la forma
exp-booleana ? Valor_0 : valor_1
Si la expresión booleana es cierta se evaluará valor_0, en caso contrario valor_1.
Conversión. Normalmente Java convierte los tipos cuando cree apropiado, y la conversión es segura exceptuando las
conversiones reductoras, en las que se corre el riesgo de perder información.
Página 10
 J.M. Godoy, F. Gómez y E. Rubio. 2002-2003
Sistemas Informáticos I (Ingeniería Informática. UNED)
4 Inicialización y limpieza
4.1 Inicialización garantizada con el constructor.
En Java, el diseñador de cada clase puede garantizar que se inicialice cada objeto proporcionando un método especial
llamado constructor. Si una clase tiene constructor, Java lo llama automáticamente al crear un objeto, quedando la
inicialización garantizada. Para evitar conflictos el nombre del constructor es el mismo que el de la clase. Los constructores
no tienen valor de retorno.
El constructor por defecto es aquél que no tiene parámetros, y se utiliza para crear un objeto básico. Si se crea una
clase sin constructores, el compilador siempre creará un constructor por defecto. Sin embargo si se define algún constructor
(con o sin parámetros) el compilador no creará uno automáticamente.
4.2 Sobrecarga de métodos
La sobrecarga de métodos es esencial para permitir que se use el mismo nombre de métodos con distintos tipos de
parámetros y es esencial para los constructores.
4.2.1 Distinción de métodos sobrecargados.
Orden de los parámetros
Los tipos primitivos sino coinciden y se tiene un tipo de datos menor al parámetro del método, ese tipo se
promociona (a excepción de char que se promociona a int). Si el parámetro es mayor se debe convertir
explícitamente sino el compilador mostrará un mensaje de error.
NO se pueden usar los tipos de valores de retorno para distinguir los métodos sobrecargados.
4.2.2 La palabra clave this.
La palabra clave this, que sólo se puede usar dentro de un método, produce la referencia al objeto por el que se ha
invocado al método. Si se invoca a un método de una clase dentro de un método de esa misma clase no es necesario usar
this.
Cuando se escriben varios constructores para una clase, hay veces en las que uno quisiera invocar a un constructor
desde otro para evitar la duplicación de código. Esto se puede lograr utilizando this. Se puede invocar a un constructor
utilizando this, pero no a dos. Además, la llamada al constructor debe ser la primera cosa que se haga o se obtendrá un
mensaje de error del compilador. El compilador NO permite tampoco invocar a un constructor desde dentro de otro método
que no sea un constructor.
En los métodos estáticos no existe la posibilidad de usar this ya que no se puede invocar a métodos no estáticos desde
dentro de métodos estáticos (al revés sí), y se puede invocar a un método estático de la propia clase sin objetos.
4.3 Limpieza.
En Java existe el método finalize, que se puede definir en cada clase, y si el recolector de basura está preparado para
liberar el espacio de almacenamiento utilizado por el objeto, primero invocará a finalize, y sólo recuperará la memoria del
objeto durante la pasada del recolector de basura. Sin embargo finalize no es en sí un destructor, es decir, si se quiere hacer
alguna cosa cuando finalice la vida del objeto se debe programar fuera de finalize, ya que puede que éste no llegue nunca a
ejecutarse. La verdadera utilidad de finalize es la verificación de la condición de muerte de un objeto.
4.4 Inicialización de miembros
Las variables definidas localmente en un método deben estar inicializadas, en caso contrario se obtendrá un error en
tiempo de compilación. En el caso de atributos de tipo primitivo esto no es así y Java tiene una inicialización por defecto.
Al definir una referencia a un objeto dentro de una clase sin inicializarla a un nuevo objeto, la referencia recibe el valor
null.
Si se define una clase y no se inicializa se obtiene una excepción. Es posible invocar a un método para proporcionar un
valor de inicialización, pero si tiene parámetros no pueden ser de una clase que no esté inicializada, el compilador daría
error.
 J.M. Godoy, F. Gómez y E. Rubio. 2002-2003
página 11
Sistemas Informáticos I (Ingeniería Informática. UNED)
4.5 Inicialización de constructores
Dentro de una clase el orden de inicialización lo determina el orden en que se definen las variables dentro de la clase.
Las definiciones pueden estar dispersas dentro de las definiciones de los métodos, pero las variables se inicializan antes de
invocar a ningún método, incluido el constructor.
Los objetos estáticos se inicializan antes que los no estáticos aunque sólo se hace una vez, es decir en la primera
llamada.
Al aplicar la herencia se inicializan las clases bases antes que los constructores de las derivadas, y se llama a la clase
base una vez por cada clase derivada.
Página 12
 J.M. Godoy, F. Gómez y E. Rubio. 2002-2003
Sistemas Informáticos I (Ingeniería Informática. UNED)
5. Ocultar la implementación.
5.1 El paquete: la unidad de biblioteca.
Un paquete es lo que se obtiene al utilizar la palabra clave import. Las importaciones proporcionan un mecanismo
para gestionar los espacios de nombres. Existe la necesidad de controlar los espacios de nombres en Java, para tener la
capacidad de crear un nombre completamente único sin importar las limitaciones de Internet.
Al crear un fichero de código fuente en Java, se crea lo que comúnmente se denomina una unidad de compilación.
Cada una de estas unidades tiene un nombre que acaba en .java, y dentro de la unidad de compilación sólo puede haber una
única clase pública, sino el compilador dará error. El resto de clases de esa unidad de compilación quedan ocultas al
exterior del paquete y son de apoyo para la clase pública principal.
Al compilar un fichero .java, se obtiene un fichero de salida que tiene exactamente el mismo nombre con la extensión
.class.
Una biblioteca también es un conjunto de ficheros de clase. Cada fichero tiene una clase que es pública (aunque no es
obligatorio), de forma que hay un componente por cada fichero. Si se desea que los componentes permanezcan unidos es
necesario el uso de pakage al principio del archivo, en la primer línea que no sea un comentario, indicando que esa unidad
de compilación es parte de un paquete.
A la hora de buscar los archivos de clase el interprete de Java procede de la siguiente forma. En primer lugar encuentra
la variable de entorno CLASSPATH, a partir de esta raíz, toma el nombre del paquete y reemplaza cada punto por una
barra para generar un nombre relativo a la raíz CLASSPATH y busca los ficheros .class correspondientes.
En caso de colisiones de nombres al utilizar import clase.*, el compilador se queja y se deberá especificar cual es la
clase que se quiere usar. Es posible que exista alguna unidad de compilación sin ninguna clase pública, en este caso el
archivo se puede llamar como se desee.
5.2 Modificadores de acceso en Java.
Public: Las declaraciones del miembro estarán disponibles a todo el mundo
Private: Nadie, puede tener acceso a ese miembro excepto a través de los métodos de esa clase.
Protected: Como private pero dando acceso a los descendientes por herencia.
Amistoso: Acceso por defecto, permite el acceso a las clases del mismo paquete. Si no hay un nombre de
paquete explícito se usa el mismo directorio (paquete por defecto).
Ejemplo de acceso a clase con constructor privado
class sopa {
private sopa () {}
// permite la creación a través de un método estático.
public static sopa hacersopa () {
return new sopa();
}
// Patrón devuelve una referencia
private static sopa sp1=new sopa();
public static acceso () {
return ps1;
}
public void f() {..}
}
public class Almuerzo {
void prueba () {
sopa priv_2 =sopa.hacerSopa();// el uso de sopa priv_2 = new sopa(); dará error
sopa.acceso().f();
 J.M. Godoy, F. Gómez y E. Rubio. 2002-2003
página 13
Sistemas Informáticos I (Ingeniería Informática. UNED)
6. Reutilizando clases
6.1 Herencia
En Java siempre se está haciendo herencia cuando se crea una clase, ya que al menos se herede explícitamente de otra
clase, se hereda implícitamente de la clase raíz estándar de Java object. Para heredar se usa la palabra clase extends
seguida del nombre de la clase base. Al hacer esto se tienen automáticamente todos los datos miembro y métodos de la
clase base.
Class derivada extends ClaseBase {
...
Las clases derivadas y las clases base pueden tener un método main cada una, de hecho esto es interesante para poder
realizar pruebas individuales de las clases, no importa que la clase no sea pública ya que el método main si lo es.
Se debe de tener cuidado de hacer los métodos de la clase extendida públicos, sino la herencia sólo es posible en
clases del mismo paquete (acceso amistoso). Es posible sobreescribir los métodos heredados, en este caso sí queremos
referirnos al método original (el de la clase base) se utiliza la palabra clave super (super.metodo). También se pueden
añadir nuevos métodos a la clase derivada.
6.1.1 Inicialización de la clase base.
La herencia no es una simple copia de la interfaz de la clase base, cuando se crea un objeto de la clase derivada, éste
contiene dentro de él un subobjeto de la clase base. Es esencial que este subobjeto de la clase base se inicialice
correctamente, para ello Java inserta automáticamente llamadas al constructor de la clase base en el constructor de la clase
derivada, de forma que se inicializan las clases bases antes que los constructores de la clase derivada puedan acceder a
ellos.
En el caso de que el constructor de la clase base tenga parámetros se debe escribir explícitamente la llamada al
constructor de la clase base mediante super y la lista de parámetros apropiada. Además debe ser lo primero que se haga en
el constructor de la derivada. Sino se hace el compilador envía un error. Dado que no puede aparecer nada antes de la
llamada al constructor de la clase base no se podrán capturar excepciones en el constructor de la derivada, lo que a veces es
un inconveniente.
Si una clase en Java tiene un nombre de método sobrecargado varias veces, la redefinición de ese nombre de método
en la clase derivada no esconde las versiones de la clase base, es decir, la sobrecarga funciona igual con independencia del
nivel de herencia.
6.2 La palabra clave final.
6.2.1 Declaración de datos como final.
Si el dato es de un tipo primitivo, se convierte en constante en tiempo de compilación y se le debe dar un valor en
tiempo de la definición. Un campo que se defina como estático y final sólo tiene un espacio de almacenamiento que no se
puede modificar.
Sin embargo cuando declaramos como final cualquier objeto, en realidad se hace final la referencia a una constante, es
decir, una vez que la referencia se inicializa a un objeto, ésta nunca se puede cambiar para que apunte a otro objeto, pero, sí
se puede modificar el objeto en sí:
public class DatosConstantes {
...
final int i1=9;
public static final int VAL =99;
final valor Val2=new Valor();
...
DatosConstantes dc1 = new datosConstantes();
dc1.i1++; // ERROR i1 es constante
dc1.Val2.i1++; // Referencia constante el objeto no
dc1.Val2=new Valor(); // ERROR referencia constante
...
...
Si declaramos un dato como final y no se inicializa hasta el momento de su uso es lo que se conoce como constante blanca,
en cualquier caso se debe inicializar antes de su uso y ésto lo garantiza el compilador.
Es obligatorio hacer las asignaciones a constantes, bien en el momento de definición del campo o bien en el
constructor, de forma que se garantice que el campo constante se inicializa siempre antes de ser usado.
Java permite hacer parámetros constantes declarándolos como final en la lista de parámetros. Esto significa que dentro
del método no se puede cambiar aquello a lo que apunta la referencia al parámetro.
Página 14
 J.M. Godoy, F. Gómez y E. Rubio. 2002-2003
Sistemas Informáticos I (Ingeniería Informática. UNED)
6.2.2 Declaración de métodos como final
Hay dos razones para justificar los métodos constates, evitar que las clases heredadas varíen su significado y por
motivos de eficiencia. Cualquier método privado de una clase es implícitamente constante, no se puede acceder ni
modificar (si se intenta se crea uno nuevo en realidad).
6.2.3 Clases constantes
Al definir una clase como constante (final) se establece que no se puede heredar de esta clase bien sea por cuestiones
de diseño, seguridad o eficiencia. Al definir una clase como constante todos sus métodos son implícitamente constantes ya
que no se pueden modificar.
6.3 Carga de clases e inicialización.
En general se puede asegurar que el código de las clases se carga en el momento de su primer uso, esto normalmente
ocurre cuando se construye el primer objeto de esa clase, pero también se da una carga cuando se accede a un dato o
método estático. En el momento del primer uso es también donde se da la inicialización estática. Todos los objetos
estáticos y el bloque de código estático se inicializan en el orden textual en el momento de la carga. Los datos estáticos se
inicializan solo una vez.
Si se utiliza herencia la clase base se carga con independencia de si se crea un objeto de la clase derivada.
Posteriormente se lleva a cabo la inicialización estática de la clase raíz y posteriormente la siguiente clase derivada.
Tras cargar las clases necesarias se puede crear el objeto. Primero se pone a sus valores por defecto todos los datos
primitivos y las referencias a objetos se ponen a null. Después se invoca al constructor de la clase base.
 J.M. Godoy, F. Gómez y E. Rubio. 2002-2003
página 15
Sistemas Informáticos I (Ingeniería Informática. UNED)
7. Polimorfismo
La conexión de una llamada a un método se denomina ligadura. Cuando se lleva a cabo la ligadura antes de ejecutar el
programa, es decir, en tiempo de compilación, se denomina temprana. La correspondencia en tiempo de ejecución se llama
ligadura tardía o dinámica. Cuando un lenguaje implementa la ligadura tardía, debe haber algún mecanismo para
determinar el tipo de objeto en tiempo de ejecución e invocar al método adecuado.
Toda ligadura de métodos en Java es tardía excepto en los métodos constantes. Esto permite asegurar el polimorfismo
ya que se puede escribir el código que trata la clase base y saber que todas las clases derivadas funcionarán correctamente
al usar el código.
7.1 Clases y métodos abstractos.
Se crea una clase abstracta cuando se desea manipular un conjunto de clases a través de un interfaz común. Todos los
métodos de clases derivadas que encajan en la declaración de la clase base se invocarán utilizando el mecanismo de
ligadura dinámica (si los parámetros son diferentes se tiene sobrecarga). Toda clase que tenga uno o más métodos
abstractos se considera abstracta. No es posible crear objetos de una clase abstracta, y si se hereda de ella se deben de
proporcionar definiciones de los métodos que en la clase base eran abstractos. Si no se hace así la clase derivada será
también abstracta y el compilador obligará a definirla como tal. Se puede tener una clase cuyos métodos sean no abstractos
y esté definido como abstracto, eso tiene sentido si no se desea que se creen instancias de ellos.
Abstract class claseAbstracta
....
public abstract void metodoAbstracto();
public void metodo_no_abstracto() {...};
....
...
class clasederivadadeAbstracta
....
public void metodoAbstracto ()
{//Definición}
....
....
7.2 Constructores y polimorfismo
En el constructor de la clase derivada siempre se invoca a un constructor de la clase base, encadenando la jerarquía de
herencias de forma que se invoca a un constructor de cada clase base, después se llama a los inicializadores de miembros
en el orden de declaración y finalmente al cuerpo del constructor de la clase derivada.
Al utilizar la herencia y utilizar finalize, se debe superponer éste método en la clase derivada si se quiere hacer alguna
limpieza especial ya que se puede tener control sobre el orden de finalización. El orden más adecuado es finalizar primero
la clase derivada y después la clase base.
Sin embargo, cuando dentro de un constructor se invoca a un método de ligadura dinámica del objeto que se construye,
se utiliza la definición superpuesta de ese método, el efecto puede ser inesperado y dar lugar a errores difíciles de detectar.
Esto es así porque se llama a un método que podría manipular miembros que no han sido aún inicializados.
7.2.1 Conversión hacia abajo
Dado que en la conversión hacia arriba se pierde información específica de los tipos, tiene sentido hacer una
conversión hacia abajo si se quiere recuperar la información de tipos, es decir, moverse hacia abajo por la jerarquía. La
conversión hacia arriba siempre es segura ya que la clase base no puede tener una interfaz mayor que la derivada, sin
embargo la conversión hacia abajo no está tan claro. Para garantizarlo, Java, realiza una comprobación de todas las
conversiones en tiempo de ejecución.
Página 16
 J.M. Godoy, F. Gómez y E. Rubio. 2002-2003
Sistemas Informáticos I (Ingeniería Informática. UNED)
8 Interfaces y clases internas
8.1 Interfaces
Mediante la palabra clave abstract, que permite crear uno o más métodos sin definición dentro de una clase, se
proporciona parte de la interfaz sin proporcionar la implementación, que será creada por sus descendientes. La palabra
clave interface produce una clase completamente abstracta, que no tiene ningún tipo de implementación. Una interfaz
puede contener campos, pero éstos son implícitamente estáticos y constantes, proporcionando sólo la forma, pero no la
implementación.
Definición de interfaz
Implementación
tipo_acceso interfaz nombre {
tipo_devuelto metodo1 (parámetro);
...
tipo_var1 = valor;
}
Acceso public o no se usa.
Las variables son implícitamente final y static
Un método privado no es parte de la interfaz
acceso class nombreclase implements nombre
{
....
}
Acceso public o no se usa
Los métodos que implemente la interfaz deben ser public
8.2 Herencia múltiple
Dado que una interfaz no tiene implementación alguna (no tiene espacio de almacenamiento asociado) se pueden
combinar varias interfaces. Sólo se puede heredar desde una clase no interfaz, el resto de elementos base deben ser
interfaces.
Al implementar múltiples interfaces pueden colisionar nombres de métodos. Al mezclar la sobrecarga, la
implementación y la superposición, dado que las funciones sobrecargadas no pueden diferir sólo en el valor de retorno,
podemos tener errores del compilador. Por ello se debe evitar la utilización de métodos con los mismos nombres en las
interfaces.
Una interfaz puede extender otra interfaz o bien puede combinar varias interfaces sin ningún tipo de problema.
8.2.1 Constantes de agrupamiento
Cualquier campo que se ponga en un interfaz se convierte automáticamente en estático y constante, por ello la
interface es una herramienta conveniente para la creación de grupos de valores constantes (tipo enumerado de C).
8.2.2 Inicialización de atributos
Los atributos definidos en las interfaces son automáticamente estáticos y constantes. Estos no pueden se constantes
blancas, pero pueden inicializarse con expresiones no constantes. Dado que los campos son estáticos, se inicializan cuando
se carga la clase por primera vez, lo que ocurre cuando se accede a cualquiera de los atributos por primera vez. Los
atributos aunque no son pare del interfaz se almacenan, sin embargo, en el área de almacenamiento estático de esa interfaz.
Int num = (int)(Mathrandom()*10);// La espresión no es constante pero sería válida en un interfaz
8.2.3 Interfaces anidadas
Las interfaces anidadas, y las que no lo son, pueden tener visibilidad pública o amistosa y pueden implementarse como
clases públicas, amistosas y privadas. La novedad de las interfaces anidadas es que pueden definirse privadas.
8.3 Clases internas
Es posible colocar una definición de clase dentro de otra definición de clase, lo que se denomina clase interna. Esto
permite agrupar clases que están relacionadas, además de controlar la visibilidad de una con la otra. Las clases internas
tienen su razón de ser al comenzar a hacer una conversión hacia una clase base (en particular a una interfaz). Esto aumenta
la ocultación de la implementación.
8.3.1 Ámbitos y clases internas en métodos
Las clases internas pueden crearse dentro de un método o incluso en un ámbito arbitrario. Las razones son:
Se implementa una interfaz de algún tipo, de forma que se puede crear y devolver una referencia.
Para crear una clase que no esté públicamente disponible.
 J.M. Godoy, F. Gómez y E. Rubio. 2002-2003
página 17
Sistemas Informáticos I (Ingeniería Informática. UNED)
8.3.2 Clases internas anónimas
Tienen la forma
public class clase1 {
public claseInterna nombre() {
return new claseInterna() {
...
} ;
}
}
Cómo se ve, combina la creación del valor de retorno con la definición de la clase que representa ese valor de retorno y
es anónima (no tiene nombre). Este código crea un objeto de una clase anónima heredada de claseInterna, a la referencia
que devuelve new se le hace una conversión hacia arriba automáticamente para convertirla en una referencia a
claseInterna, es decir, se puede considerar una abreviación de:
class miclaseInterna implements claseInterna {
...
}
return new miclaseInterna();
Si se define una clase anónima interna y se desea utilizar un objeto definido fuera de la clase interna anónima, el
compilador exige que el objeto externo sea constante.
Una clase interna tiene acceso automático a los miembros de la clase contenedora ya que mantiene una referencia al
objeto particular de la clase contenedora que era responsable de crearla.
8.3.3 Clases internas estáticas
Si no se necesita una conexión entre el objeto de la clase interna y el objeto de la clase externa, se puede hacer estática
la clase interna. Esto es así ya que una clase interna estática quiere decir:
1. No se necesita un objeto de la clase externa para crear un objeto de la clase interna estática.
2. No se puede acceder a un objeto de una clase externa desde un objeto de la clase interna estática.
3. Las clases interna no estáticas no pueden tener campos, datos o clases internas estáticas.
Las clases internas hacen referencia a la clase externa mediante: claseExterna.this.
8.3.4 ¿Porque las clases internas?
Cada clase interna puede heredar independientemente de una implementación. Por consiguiente, la clase interna no
está limitada por el hecho de que la clase externa puede ya estar heredando de una implementación. Es decir, las clases
internas permiten heredar de forma efectiva de más de un no interfaz.
Además se tienen las siguientes características adicionales:
1. La clase interna tiene múltiples instancias, cada una con sus propia información de estado que es independiente de
la información del objeto de la clase externa.
2. En una clase externa se pueden tener varias clases internas, cada una de las cuales implementan la misma interfaz
o hereda de la misma clase de distinta forma.
3. El momento de la creación del objeto de la clase interna no está atado a la creación del objeto de la clase externa.
4. No hay relaciones es – un dentro de la clase interna.
10 Manejo de errores con excepciones.
10.1 Excepciones básicas.
Una condición excepcional es un problema que evita la continuación de un método o del alcance actual, debido a que
no se dispone de la información necesaria para tratar el problema en el contexto actual. Todo lo que se puede hacer es salir
del contexto actual y relegar el problema a un contexto superior. Esto es lo que ocurre cuando se lanza una excepción:
Se crea el objeto excepción en el montículo con new.
Se detiene el cauce normal de ejecución y se lanza la referencia al objeto excepción desde el contexto actual.
Página 18
 J.M. Godoy, F. Gómez y E. Rubio. 2002-2003
Sistemas Informáticos I (Ingeniería Informática. UNED)
El mecanismo de gestión de excepciones busca el lugar apropiado donde continuar ejecutando el programa.
Este lugar es el gestor de excepciones, cuyo trabajo es recuperar el problema de forma que el programa pueda o
bien intentarlo de nuevo, o bien simplemente continuar.
Como cualquier objeto en Java las excepciones tienen su constructor, en el caso de las excepciones estándar dos, uno
por defecto y otro que toma un parámetro string de forma que pueda ubicar la información pertinente en la excepción.
Tras crear el objeto excepción (new) se le da a throw la referencia resultante. Además se puede lanzar cualquier tipo
de objeto Throwable que se desee. Habitualmente se lanzará una clase de excepción diferente para cada tipo de error. La
información sobre cada error se representa tanto dentro del objeto excepción como implícitamente en el tipo de objeto
excepción elegido.
10.2 Capturar una excepción.
Si un método lanza una excepción, debe asumir que esa excepción será capturada y tratada. El método acaba en el
momento del lanzamiento, para evitarlo, se puede establecer un bloque excepcional dentro del método para que capture la
excepción:
try {
// Código que puede generar excepciones
}
Toda excepción lanzada debe acabar en el manejador de excepciones, hay uno para cada tipo de excepción que se
desee capturar. Los manejadores de excepción siguen inmediatamente al bloque try y se identifican por la palabra clave
catch:
try {
// Código que puede generar excepciones
} catch (tipo1 id1) {
// Manejo excepciones tipo 1
} catch (tipo2 id2) {
// Manejo excepciones tipo 2
} catch (tipo3 id3) {
// Manejo excepciones tipo 3
....
}
En este código sólo se ejecuta una sentencia catch (no pasa como en switch que le hace falta un break). No se está
limitado a las excepciones estándar de Java, se pueden crear las propias excepciones para indicar un error especial, para
ello se debe heredar de un tipo de excepción existente.
10.2.1 La especificación de excepciones
La especificación de excepciones utiliza la palabra clave Throws seguida de todos los tipos de excepción potenciales.
No se puede engañar sobre una especificación de excepciones, si un método provoca excepciones y no las maneja, el
compilador lo detecta e indica que o bien se tratan o se colocan en la especificación de excepciones todas las excepciones
que el método puede lanzar. Este sistema permite a Java asegurar la corrección de la excepción en tiempo de compilación.
10.2.2 Captura de cualquier excepción.
La clase base de todas las excepciones es la clase Exception (si se usa en una lista debe ser la última), para conseguir
información se puede llamar a los métodos de su tipo base Throwable.
String getMessage() : Mensaje
String getLocalizedMessage(): Mensaje ajustado al escenario particular
String toString: Descripción del objeto Throwable
void printStackTrace (): Imprime el objeto y la traza de pila de llamadas
void printStackTrace(PrintStream): Apunta a un flujo de datos
void printStackTrace (PrintWriter)
Throwable fillInStackTrace (): registra la información relativa al estado actual de la pila.
 J.M. Godoy, F. Gómez y E. Rubio. 2002-2003
página 19
Sistemas Informáticos I (Ingeniería Informática. UNED)
10.2.3 Relanzar una excepción
Si se desea se puede volver a lanzar una excepción que se acaba de capturar. Esto hace que la excepción vaya al
contexto inmediatamente superior de manejadores de excepciones. Cualquier cláusula catch subsiguiente del mismo
bloque try seguirá siendo ignorada.
10.3 Excepciones estándar de Java.
Hay un grupo de tipos de excepciones que Java siempre lanza automáticamente, están agrupadas bajo una única clase
denominada RuntimeException. Si no se capturan estas excepciones en tiempo de ejecución, se invoca a
printStackTrace() para esa excepción y el programa finaliza su ejecución.
10.3.1 Restricciones
Al aplicar la herencia a clases que tengan excepciones, el constructor de la clase derivada debe declarar cualquier
excepción del constructor de la clase base en su especificación de excepciones. El constructor de la clase derivada no
puede capturar las excepciones de la clase base. Justo porque existe una especificación de excepciones en una versión de la
clase base de un método, no tiene porque existir en la versión de la clase derivada del mismo, esto es distinto a las normas
generales de la herencia.
Página 20
 J.M. Godoy, F. Gómez y E. Rubio. 2002-2003
Sistemas Informáticos I (Ingeniería Informática. UNED)
11 El sistema de E/S de Java
11.1 La clase file
La clase file puede representar el nombre de un archivo particular, o los nombres de un conjunto de archivos de un
directorio. Si se trata de un conjunto de archivos, se puede preguntar por el conjunto con el método list(), que devuelve un
array de strings.
También se puede usar un objeto File para crear un nuevo directorio o una trayectoria de directorios completa si esta
no existe. También se pueden mirar las características de archivos, ver si es objeto File representa un archivo o un
directorio, y borrar un archivo.
11.2 Entrada y Salida
Las bibliotecas de E/S usan a menudo la abstracción del flujo, que representa cualquier fuente o consumidor de datos
como un objeto capaz de producir o recibir fragmentos de código. El flujo oculta los detalles de lo que ocurre con los datos
en el dispositivo de E/S real.
Todo lo que deriva de las clases InputStream o Reader tiene el método básico Read() para leer y lo que deriva de
OutputStream o Writer tiene el método Write(). Aunque por lo general no se usan ya que a través de otras clases (que sí
los usan) se aplican objetos por capas que proporcionan una funcionalidad final mayor.
11.2.1 Tipos de InputStream
El trabajo de InputStream es representar las clases que producen entradas desde distintas fuentes:
Un array de bytes
Un objeto string
Un archivo.
Una tubería.
Una secuencia de otros flujos que se agrupan todos en uno
Otras fuentes, como una conexión a Internet.
11.2.2 Tipos de OutputStream
Esta categoría incluye las clases que deciden donde irá la salida. Un array de bytes, un fichero o una tubería. No puede
ser un String.
11.2.3 Leer de un InputStream con un FilterInputStream.
Las clases de FilterInputStream son:
DataInputStream: Permite leer tipos de datos primitivos (readByte(), readFloat()...) además de objetos String.
BufferedInputSream: Se usa para evitar una lectura cada vez que se solicitan nuevos datos, es decir, crea un
espacio intermedio.
LineNumberInputStream: Mantiene un seguimiento de los números de línea en el flujo de datos de entrada.
Puede llamar a getLineNumber() y a SetLineNumber(int).
PushbackInputStream:Tiene un espacio de almacenamiento intermedio de un byte para el último carácter a
leer.
11.2.4 Escribir un OutputStream con FilterOutputStream.
Las clases son:
DataOutpuStream: Da formato a los datos primitivos y objetos string, convirtiéndolos en un flujo de forma que
cualquier DataInputStream los pueda leer.
PrintStream: Produce salida formateada, maneja la visualización.
BufferedOutputStream: Se usa para evitar una escritura física cada vez que se envía un paquete de datos.
 J.M. Godoy, F. Gómez y E. Rubio. 2002-2003
página 21
Sistemas Informáticos I (Ingeniería Informática. UNED)
11.3 E/S estándar.
El término E/S estándar hace referencia al flujo de información que utiliza todo programa, de forma que toda entrada y
salida fluye por la E/S estándar, mientras que todos sus mensajes de error se envían a la salida de error estándar. Esto
proporciona la posibilidad de encadenar programas de modo que la salida de uno se convierta en la entrada estándar del
siguiente.
Siguiendo este modelo Java tiene System.in, System.out y System.err. La clase System de Java permite redirigir los
flujos de entrada, salida y error haciendo uso de las llamadas a los métodos estáticos setIn(InputStream),
setOut(PrintStream) y SetErr(PrintStream).
11.4 Compresión.
La biblioteca de E/S de Java contiene clases que dan soporte a la lectura y escritura de flujos en un formato
comprimido. Estas clases están envueltas en torno a las clases E/S existentes para proporcionar funcionalidad de
compresión. Sin embargo, no derivan de Reader y Writer, sino que son parte de las jerarquías InputStream y
OutputStream. Esto se debe a que la biblioteca de compresión funciona con bytes en lugar de caracteres.
Las clases de compresión son:
CheckedInputStream: La función GetCheckSum() produce una suma de comprobación de cualquier
InputStream.
ChekedOutputStream: La función GetCheckSum() produce una suma de comprobación de cualquier
OutputStream.
DeflaterOutputStream:Clase base de las funciones de compresión.
ZipOutputStream: DeflaterOutputStream que comprime datos a formato de archivos ZIP.
GZipOutputStream: DeflaterOutputStream que comprime datos a formato de archivos GZIP.
InflaterItputStream:Clase base de las funciones de descompresión.
ZipInputStream: InflaterInputStream que descomprime datos a formato de archivos ZIP.
GZipInputStream: InflaterInputStream que descomprime datos a formato de archivos GZIP.
11.4.1 Archivos Java (JAR).
El formato ZIP también se usa en el formato JAR; que es una forma de coleccionar un grupo de archivos en un único
archivo comprimido, con la diferencia de que los ficheros JAR son multiplataforma.
Los archivos JAR son particularmente útiles al trabajar con Internet ya que permite cargar de forma conjunta todos los
archivos que conforman un applet. Mediante estos archivos la carga es más rápida y además soporta firmas digitales.
Un archivo JAR consta de un único archivo que contiene una colección de archivos ZIP junto con una declaración que
los describe. La utilidad jar comprime automáticamente los archivos que se seleccionan.
jar [opciones] [manifiesto] destino archivo(s) Entrada
Las opciones son:
c: Crear archivo nuevo o vacío.
t: Lista la tabla de contenidos.
x: Extrae todos los archivos
xfile: Extrae el archivo nombrado
f: Indica que se va a dar nombre al archivo
m: Indica que el primer parámetro será el nombre de un archivo de declaración creado por el usuario
v: Genera una salida que describe que va haciendo JAR
O: Sólo almacena los archivos no los comprime
M: No crear automáticamente un archivo de declaración
Mediante jar no se puede añadir o actualizar archivos de uno ya existentes, tampoco se pueden mover.
11.5 Serialización de objetos
La serialización de objetos de Java permite tomar cualquier objeto que implemente la interfaz Serializable y
convertirlo en una secuencia de bits que puede ser restaurada para generar el objeto original. El mecanismo de serialización
compensa automáticamente las diferencias entre sistemas operativos.
La serialización de un objeto es bastante simple, siempre que el objeto implemente la interfaz serializable (que no
tiene métodos). Para serializar un objeto, se crea algún tipo de objeto OutputStream y se envuelve en un
ObjectOutputStream. En este momento se invoca writeObject() y el objeto se serializa enviándose al OutputStream.
Página 22
 J.M. Godoy, F. Gómez y E. Rubio. 2002-2003
Sistemas Informáticos I (Ingeniería Informática. UNED)
Para el proceso inverso, se envuelve un InputStream en un ObjectInputStream y se invoca a readObject(), como
devuelve un Object, normalmente se deberá hacer una conversión hacia abajo, por ello una vez recuperado un objeto
serializado, hay que asegurarse que la JVM pueda encontrar su clase, bien en el path o cualquier otro lugar especificado.
Se puede controlar la serialización implementando la interfaz Externalizable que extiende Serializable añadiendo dos
métodos: writeExternal() y readExternal().
Cuando se está controlando la serialización, puede ocurrir que haya un subobjeto en particular que no se desee que se
salve y recupere de forma automática. Por ejemplo, si contiene información sensible (contraseñas). Para controlar esto, se
puede desactivar la serialización en una base campo-a-campo mediante la palabra clave trasient. Esto indica que no se
debe guardar o recuperar ese dato en la serialización:
acceso trasient tipo nombre
11.6 Identificar símbolos de una entrada
Las dos clases de Java para dividir las secuencias de caracteres en una secuencia de símbolos son StreamTokenizer y
StringTokenizer.
11.6.1 StreamTokenizer
StreamTokenizer no deriva de InputStream ni de OutputStream, sólo funciona con objetos InputStream, por lo que
pertenece a la parte de E/S de la biblioteca. Esta clase divide el flujo de entrada en Tokens que están delimitados por
conjuntos de caracteres.
11.6.2 StringTokenizer
Esta clase facilita el análisis semántico de un flujo de entrada, y devuelve todos los símbolos contenidos en una cadena
de caracteres de uno en uno, estos símbolos son los caracteres consecutivos delimitados por tabuladores, espacios y saltos
de línea.
 J.M. Godoy, F. Gómez y E. Rubio. 2002-2003
página 23
Sistemas Informáticos I (Ingeniería Informática. UNED)
13 Crear ventanas y applets
13.1 Applet básico
13.1.1 Restricciones de los applets
Un applet no tiene acceso al disco local
El tiempo de ejecución aumenta ya que se debe descargar.
Con los applets de confianza firmados digitalmente se suavizan las restricciones de acceso.
13.1.2 Ventajas de los applets.
No precisan instalación, siendo independiente de la plataforma
No hay peligro de que un código erróneo cause daño a la máquina.
13.1.3 Marcos de trabajo de la aplicación
Las bibliotecas se agrupan dependiendo de su funcionalidad, o bien para facilitar la construcción de otras clases. Cierta
categoría de biblioteca es el marco de trabajo de aplicación, cuya meta es ayudar en la construcción de aplicaciones
proporcionando una clase o un conjunto de clases que producen el comportamiento básico deseado para toda aplicación de
un tipo particular. Para adaptar el comportamiento a las necesidades particulares se debe heredar de la clase aplicación y
superponer los métodos que interesan.
Los applets se construyen utilizando un marco de trabajo de aplicación. Se hereda de JApplet y se superponen los
métodos apropiados. Los métodos que controlan la ejecución y creación de un applet son:
init(): se invoca automáticamente para lograr la primera inicialización del applet, incluyendo la disposición de
componentes. Siempre se superpone.
start(): Se invoca cada vez que se visualiza un applet en el navegador para permitirle empezar sus operaciones
normales. Se invoca tras init().
stop(): Es invocado cada vez que el applet se aparta de la vista de un navegador web. Se invoca antes que
destroy().
destroy(): Se invoca cuando se descarga el applet para liberar recursos.
13.1.4 Ejecución de applets dentro de un navegador.
Los applets se deben de ejecutar en una página web. La sintaxis de una etiqueta APPLET estándar es:
<APPLET
CODEBASE = urlBasecódigo
CODE = archivoClassApplet
ALT= texto alternativo si el navegador entiende la etiqueta APPLET pero no puede
ejecutarla.
NAME=nombreinstanciaApplet
WIDTH= pixels HEIGHT= pixels
ALING= tipoAlineamiento
>
<PARAM NAME= nombreAtributo_1 VALUE = valorAtributo_1>
<PARAM NAME= nombreAtributo_2 VALUE = valorAtributo_2>
....
<PARAM NAME= nombreAtributo_n VALUE = valorAtributo_n>
</APPLET>
Página 24
 J.M. Godoy, F. Gómez y E. Rubio. 2002-2003
Sistemas Informáticos I (Ingeniería Informática. UNED)
14 Hilos múltiples
Puede ser necesario convertir un programa en subtareas separadas que se ejecuten independientemente. Cada una de
estas subtareas recibe el nombre de hilo. Un proceso es un programa en ejecución autocontenido con su propio espacio de
direcciones. Un sistema operativo multitarea es capaz de ejecutar más de un proceso a la vez, proporcionándole ciclos de
UCP periódicamente. Por consiguiente, un único proceso puede tener múltiples hilos ejecutándose concurrentemente
14.1 Creando hilos con Thread.
La forma más simple de crear un hilo es heredar de la clase Thread, que tiene todo lo necesario para crear o ejecutar
hilos. El método run debe ser sobreescrito para que el hilo haga lo deseado, es decir es el código simultáneo de los hijos.
El método start de la clase Thread lleva a cabo alguna inicialización especial para el hijo y después llama a run().
El orden con el que la UCP atiende a un conjunto de hilos es indeterminado, a menos que se cambien las prioridades
haciendo uso del método setPriority() de Thread. Además cuando se crea un Thread no se captura su referencia, es el
propio hilo el que guarda su referencia de forma que el recolector de basura no pueda limpiarlo.
14.2 Crear hilos con Runnable.
Para poder combinar la clase principal del programa con un hilo se le debe añadir al primero funcionalidades
adicionales, esto se consigue mediante la interfaz Runnable. El uso programa/hilo no es tan obvio, cuando se empieza el
programa se crean un objeto que es Runnable, pero no se arranca el hilo. Esto hay que hacerlo explícitamente:
hilo_propio= new Thread (nombreclase.this); // nombreclase implementa Runnable
Cuando algo tiene una interfaz Runnable, simplemente significa que tiene un método run() (pero sin propiedades
como cuando se hereda de Thread). Por lo tanto, para producir un hilo a partir de un objeto Runnable, hay que crear un
objeto Thread separado, pasándole el objeto Runnable al constructor Thread especial. Después se puede llamar al método
start() de ese hilo, que lleva a cabo la inicialización y llama a run()
hilopropio.start();
14.3 Hilos demonio
Un hilo demonio es aquél que supuestamente proporciona un servicio general en segundo plano mientras que se está
ejecutando el programa, no siendo parte esencial del programa. Por consiguiente, cuando todos los hijos no demonio
acaban, se finaliza el programa. Se puede saber si un hilo es demonio llamando a isDeamon(), y se puede activar o
desactivar el funcionamiento como demonio de un hilo con setDeamon. Si un hilo es demonio, todos los hilos que creen
serán demonios.
14.4 Compartir recursos
Con la capacidad multihilo existe la posibilidad de que dos o más hilos traten de usar el mismo recurso limitado a la
vez. Hay que prevenir las colisiones por un recurso. Para ello Java tiene integrado un soporte para prevenir las colisiones
de acceso a un recurso, este es declarar el método que accede al mismo como synchronized. Sólo un hilo puede invocar a
un método synchronized en cada instante para cada objeto.
Acceso synchronized tiporetorno método (parámetros) {
// Zona sincronizada
....
}
Las secciones críticas también se pueden utilizar para especificar el objeto cuyo bloqueo se usa para sincronizar el
código adjunto:
synchronized (objetoSincronizado) {
....
// A este código sólo puede acceder un Thread
}
14.5 JavaBeans
Cuando se crea un Bean, hay que asumir que se ejecutará en un entorno multihilo. Esto significa:
1. Siempre que sea posible sus métodos public deberían ser synchronized.
 J.M. Godoy, F. Gómez y E. Rubio. 2002-2003
página 25
Sistemas Informáticos I (Ingeniería Informática. UNED)
2.
Al disparar un evento multidifusión a un conjunto de oyentes, hay que asumir que se podría añadir o eliminar
oyentes de la lista al recorrerla.
14.6 Bloqueo
Un hilo puede estar en uno de los siguientes estados:
1. Nuevo: El hilo se ha creado pero no se ha arrancado.
2. Ejecutable: El hilo puede ponerse en ejecución cuando le sea asignada la UCP
3. Muerto: Al finalizar su método run(). Si se hace llamando a stop se produce una excepción.
4. Bloqueado: Hay algo que evita que se ejecute el hilo.
14.6.1 Bloqueándose
Un hilo puede estar bloqueado por:
Se ha puesto a dormir con sleep
Se ha suspendido con suspend, no se reanuda hasta recibir resume() (en desuso para Java 2).
Se suspende con wait(). No se reanuda hasta recibir notify() o notifyAll(). En este caso se liberan los objetos
synchronized que podría estar usando el hilo.
El hilo está esperando que se complete una E/S.
El hilo está intentando acceder a un método synchronized usado por otro.
Se puede llamar a yield() para ceder voluntariamente la UCP para que se ejecuten otros hilos.
Página 26
 J.M. Godoy, F. Gómez y E. Rubio. 2002-2003
Sistemas Informáticos I (Ingeniería Informática. UNED)
15 Computación distribuida
15.1 Programación en Red.
15.1.1 Identificar una máquina.
Para identificar de forma unívoca una máquina se utiliza el nombre de dominio DNS o un cuarteto de tres cifras, en
ambos casos la dirección IP se representa internamente como un número de 32 bits, y se puede lograr un objeto Java que lo
representa, usando el método static InetAdress.getByName() se obtiene un objeto de tipo InetAddress:
InetAddress Val = InetAddress.getByName (args[0]);// Args[0] es el nombre de la máquina
// Si args[0]= null, se consigue LocalHost
Una máquina se identifica con la dirección IP y por el puerto, por ello cuando se establece un cliente o un servidor hay
que elegir un puerto en el que tanto el cliente como el servidor acuerden conectarse. Los servicios del sistema se reservan
el uso de los puertos 1 a 1024.
15.1.2 Sockets
El socket es la abstracción software que se usa para representar los terminales de una conexión entre dos máquinas.
Para una conexión dada hay un socket en cada máquina. En Java se crea un socket para hacer una conexión a la otra
máquina, después se crea un InputStream y un OutputStream a partir del socket para poder tratar la conexión como un
objeto flujo E/S. Hay dos clases de sockets basados en flujos: un ServerSocket que usa un servidor para escuchar eventos
entrantes y un Socket que usa un cliente para iniciar una conexión. Cuando un cliente hace una conexión, ServerSocket
devuelve vía accept() un socket, a través del que se realiza la comunicación. En éste momento se usa getInputStream() y
getOutputStream() para producir los objetos InputStream y OutputStream correspondientes a cada Socket.
// servidor
ServerSocket s= new ServerSocket (PUERTO);
try {
// Bloqueado esperando conexión
Socket conect = s.accept();
....
} finally {
// Cierra la conexión
s.close();
}
....
// cliente
.....
Socket conectCliente= new Socket(addr, Puerto);
...
try {
.....
} finally {
// Cierra la conexión
conectCliente.close();
}
....
15.1.3 Objeto URL.
Dado que todos los navegadores utilizan URL, existe en Java una clase del mismo nombre que proporciona una
interfaz API simple y concisa para acceder a información a través de Internet. El constructor más usado es:
URL(URL objeto_URL, String especificación_URL);
que nos permite relacionarlo con la clase applet:
URL url = new URL (getDocumentBase(), “pagina.html”);
getAppletContext().showDocument(url);
AppletContext es una interfaz que nos proporciona información desde el entorno de ejecución del Applet, dentro de
un Applet, y una vez se ha obtenido su contexto, se puede visualizar otro documento llamando a ShowDocument().
Del objeto URL se puede obtener, por un lado el directorio donde cuelga el fichero clase de Applet con getCodeBase()
y el directorio donde está el fichero HTML (y su nombre) desde el que arranca el applet.
15.2 Conectividad a Base de Datos de Java (JDBC).
JDBC permite a Java construir aplicaciones de bases de datos clientes/servidor independientes de la plataforma. Para
apoyar estas funciones es interesante utilizar exclusivamente identificadores de tipos SQL genéricos. Para trabajar con una
base de datos en un principio nos debemos conectar, crear unas sentencia, ejecutar la petición y acceder al conjunto
resultado.
Para abrir una base de datos hay que crear un URL de base de datos que especifica:
1. Que se está utilizando JDBS con “jdbc”.
2. El subprotocolo, nombre del control de la conexión (por ejemplo “odbc”).
 J.M. Godoy, F. Gómez y E. Rubio. 2002-2003
página 27
Sistemas Informáticos I (Ingeniería Informática. UNED)
3. Identificador de la base de datos, varía en función del controlador de base de datos, es un nombre lógico que es
asociado por el administrador de base de datos a un directorio físico donde se hayan los datos.
Así por ejemplo:
String dbURL = “jdbc:odbc:pepe”;
Class.forName (“sun.jdbc.odbc.JdbcOdbcDriver”);// Carga del controlador.
Connection c = DriverManager.getConnection (dbURL, usuario, contrasenya);
/* Este método estático es el que conecta con la base de datos. Devuelve un objeto tipo
connection que se usará para consultar la base de datos */
Statements = c.createStatement();//Se crea un estamento que permite hacer las consultas.
ResultSet r= s.executeQuery (CONSULTA_SQL); // Es un iterador
while (r.next()) {
r.getString(campo)...// Usa getInt(), getString(), getFloat()... dependiendo del tipo esperado
s.close();
15.3 Servlets
Los servlets proporcionan una solución excelente para soporte en el lado servidor. La arquitectura del API servlet es la
de un proveedor de servicios clásico con un método service() a través del cual se enviarán todas las peticiones del cliente
por parte del software contenedor del servlet, y los métodos del ciclo de vida init() y destroy(), que se invocan sólo cuando
se carga y descarga el servlet.
public
public
public
public
public
public
interface Servlet
void init (ServletGetConfig config) Throws ServletException;
ServletConfig getServletConfig();
void Service (ServletRequest req, ServletResponse res) Throws ServletEsception, IOException
String getServletInfo();
void destroy();
El propósito de getServletConfig() es devolver un objeto ServletConfig que contiene los parámetros de inicialización y
arranque del servidor. El método setServletInfo() devuelve una cadena de caracteres que contiene información sobre el
servlet, como el autor, versión etc.
El atributo más conveniente de la API de servlets lo constituyen los objetos auxiliares que vienen con la clase
HttpServlet (que es extensión de GenericServlet) para darle soporte.
El servlet se inicializa llamando al método init(), al cargar el servidor tras arrancar por primera vez el contenedor de
servlets. Cuando un cliente hace una petición a una URL que resulta representar un servlet, el contenedor de servlets
intercepta la petición y hace una llamada al método service(), tras configurar los objetos HttpServletRequest y
HttpServletResponse (que son extensiones de ServletRequest y ServletResponse).
15.3.5 Gestionar sesiones con servlets.
HTTP es un protocolo sin sesión, de forma que no se puede distinguir entre una persona que accede repetidamente al
sitio, y otra persona diferente. Una de las soluciones aportadas son las cookies, que son una pequeña pieza de información
enviada por un servidor web a un navegador. El navegador almacena la cookie en el disco local y cuando se hace otra
llamada al URL asociado a la cookie, ésta se envía junto con la llamada, proporcionando así que la información deseada
vuelva al servidor.
El API servlet proporciona la clase Cookie. Esta clase incorpora todos los detalles de cabecera HTTP y permite el
establecimiento de varios atributos de cookie. Para usarlas se añaden al objeto respuesta.
Cookie oreo = new Cookie (“una”, “2000”); // Una es el nombre y 2000 el valor.
res.addCookie(oreo);
String valor= getValue(“una”);
15.3.4 La clase Session
Una sesión es una o más solicitudes de páginas por parte de un cliente a un sitio web durante un periodo definido de
tiempo. Un objeto SessionServlet vive en la parte servidores del canal de comunicación; su meta es capturar datos útiles
sobre ese cliente a medida que el cliente recorre e interactúa con el sitio web.
La clase session del API servlet usa la palabra Cookie para hacer su trabajo, sin embargo, todo objeto Session necesita
algún tipo de identificador único almacenado en el cliente y que se pasa al servidor.
15.4 Java Server Pages
Las Java Server Pages (JSP) son una extensión del estándar Java definido sobre las extensiones de sevlets. El objetivo
de JSP es la creación simplificada de páginas web dinámicas, al combinar HTML con Java en el mismo documento.
Página 28
 J.M. Godoy, F. Gómez y E. Rubio. 2002-2003
Sistemas Informáticos I (Ingeniería Informática. UNED)
El código Java está rodeado de etiquetas especiales que indican al contenedor de JSP que deberá usar el código para
generar un servlet o parte de uno.
La primera vez que el contenedor de JSP carga un JSP se genera, compila y carga en el contenedor de Servlets el
código servlet necesario para cumplimentar las etiquetas JSP. Las porciones estáticas de la página HTML se producen
enviando objetos String estáticos a write(). Las porciones dinámicas se incluyen directamente en el servlet. Hasta que el
código JSP de la página no se modifique, se comporta como si fuera una página HTML estática con servlets asociados. Si
se codifica, se recompila y recarga automáticamente la siguiente vez que se solicite esa página. Las etiquetas JSP son:
< % código JSP %>
Las directivas son mensajes al contenedor de JSP y se delimitan por @
<%@ directiva { atributo = “valor”} * %>
No envían nada al flujo Out, permiten configurar atributos y dependencias.
15.5 RMI (Remote Mode Invocation).
RMI hace un uso intensivo de las interfaces. Cuando se desea crear un objeto remoto, se enmascara la implementación
subyacente pasando una interfaz. Por consiguiente, cuando el cliente obtiene una referencia a un objeto remoto, lo que
verdaderamente logra es una referencia al un interfaz, que resulta estar conectado a algún fragmento de código local. Al
crear una interfaz remota hay que seguir ciertas normas:
Debe ser public.
Debe extender la interfaz java.rmi.Remote.
Cada uno de sus métodos debe declarar java.rmi.RemoteException en su cláusula Throws.
Todo objeto remoto pasado como parámetro o valor de retorno debe declararse como la interfaz remota, no como
la clase implementation.
 J.M. Godoy, F. Gómez y E. Rubio. 2002-2003
página 29
Descargar