0% encontró este documento útil (0 votos)
20 vistas52 páginas

Java

El 'Ligero Manual de Java' de Alberto Garrote Martín proporciona una introducción al lenguaje de programación Java, comenzando con la motivación para aprender y un primer programa simple. El documento cubre elementos básicos de programación, programación orientada a objetos y aspectos avanzados, incluyendo clases, objetos, herencia y manejo de excepciones. Se enfatiza la importancia de la práctica y el uso de herramientas como el JDK y la JVM para compilar y ejecutar programas en Java.

Cargado por

sebastian 810
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
20 vistas52 páginas

Java

El 'Ligero Manual de Java' de Alberto Garrote Martín proporciona una introducción al lenguaje de programación Java, comenzando con la motivación para aprender y un primer programa simple. El documento cubre elementos básicos de programación, programación orientada a objetos y aspectos avanzados, incluyendo clases, objetos, herencia y manejo de excepciones. Se enfatiza la importancia de la práctica y el uso de herramientas como el JDK y la JVM para compilar y ejecutar programas en Java.

Cargado por

sebastian 810
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd

LIGERO MANUAL DE JAVA

Profesor: Alberto Garrote Martín


Índice general

1. Lo primero es antes 3
1.1. Motivación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3

1.2. Nuestro primer programa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3

1.3. Consideraciones sobre el programa anterior . . . . . . . . . . . . . . . . . . . 4

2. Elementos básicos de la programación 6


2.1. Comentarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6

2.2. Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6

2.2.1. Tipos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

2.2.2. Declaración . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

2.2.3. Inicialización . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

2.2.4. Ámbito . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

2.3. Constantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

2.4. Operadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

2.4.1. Aritméticos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

2.4.2. De comparación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

2.4.3. Lógicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

2.4.4. Precedencia de los operadores . . . . . . . . . . . . . . . . . . . . . . 14

2.5. Conversiones de tipo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

2.6. Cadenas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

2.7. Entrada de datos a un programa . . . . . . . . . . . . . . . . . . . . . . . . . 15

2.7.1. Como parámetros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16

2.7.2. Desde el propio programa . . . . . . . . . . . . . . . . . . . . . . . . 16

2.8. Condicionales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16

2.8.1. La instrucción if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

2.8.2. La instrucción switch . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

2.9. Bucles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18

2.9.1. La instrucción while . . . . . . . . . . . . . . . . . . . . . . . . . . . 18

2.9.2. La instrucción for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

2.9.3. La instrucción do-while . . . . . . . . . . . . . . . . . . . . . . . . . . 19

1
2.10. Funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20

2.10.1. Paso de parámetros . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21

3. Programación orientada a objetos 22


3.1. Un poco de historia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22

3.2. Clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22

3.3. Objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25

3.3.1. El valor null . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26

3.3.2. Miembros estáticos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26

3.4. Encapsulación (u ocultación) . . . . . . . . . . . . . . . . . . . . . . . . . . . 27

3.4.1. Paquetes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27

3.4.2. Niveles de visibilidad . . . . . . . . . . . . . . . . . . . . . . . . . . . 28

3.5. Herencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28

3.5.1. Constructores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

3.6. Polimorsmo (o redenición de métodos) . . . . . . . . . . . . . . . . . . . . 29

3.7. Sobrecarga de métodos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31

3.8. Enumeraciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31

3.9. Clases abstractas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33

3.10. Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34

3.11. Gestión de errores: Excepciones . . . . . . . . . . . . . . . . . . . . . . . . . 35

3.12. Conexión a bases de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37

4. Aspectos avanzados 40
4.1. Clases internas (o anidadas) . . . . . . . . . . . . . . . . . . . . . . . . . . . 40

4.1.1. Clases anónimas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42

4.2. Applets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44

4.3. Gestión de eventos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45

4.3.1. Clases de eventos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46

4.3.2. Oyentes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47

4.4. Control de versiones: Subversion . . . . . . . . . . . . . . . . . . . . . . . . . 50

2
Tema 1

Lo primero es antes

1.1. Motivación
 Cris, he estado pensando un rato y creo que aprender un lenguaje de programación
puede llegar a ser una tarea sencilla dijo de pronto el gran lósofo Leonardo Mesa ante
la estupefacción de todos los reunidos en aquella sala.

 ½Yo aún diría más, querido Leo! respondió su colega Cristino Leopardo, levantando
la jarra de cerveza sin alcohol con una mano y sujetando el sombrero con la otra ½Me
atrevería a armar que hasta puede resultar divertido!.

Sin duda, estas sabias palabras son ciertas, pero no puede faltar la siguiente coletilla:
siempre que se le dedique el esfuerzo necesario. Y es que, con un lenguaje de programación,
sucede igual que con cualquier idioma hablado, el único secreto para su aprendizaje consiste
en practicar lo suciente. La principal peculiaridad de Java es que se trata de un lenguaje
orientado a objetos, lo que implica estructurar nuestro pensamiento en base a clases y objetos,
y en cómo estos se relacionan entre sí en este sentido, destaca por encima de todos el
concepto de herencia .

En cierto modo, un programador de Java puede imaginarse a sí mismo como un Hacedor


de criaturas (que serían los objetos), generadas a partir de ciertos moldes o plantillas (que
serían las clases).

 ¾Como un dios? dijo Leo, abriendo los ojos como platos.

Lo más importante consiste en establecer las características de dichas criaturas (estaría-


mos hablando de atributos de los objetos), así como el funcionamiento de las mismas (nos
estaríamos reriendo a los métodos que no dejan de ser funciones).

1.2. Nuestro primer programa


De la misma forma que para aprender un nuevo idioma es fundamental empezar a hablarlo
cuanto antes, para dominar un lenguaje de programación lo ideal es comenzar a programar
enseguida aunque apenas se tenga idea de lo que hace el código, como será nuestro caso al
principio. La metodología que vamos a seguir consiste en ir adquiriendo el conocimiento
necesario de manera progresiva, a medida que lo precisemos.

3
Vamos a empezar con el típico programa con el que se inicia todo principiante y, para
ello, teclearemos en un editor de texto cualquiera, el siguiente código:

Fragmento de código 1.1: [Link]


1 /∗ Ejemplo de programa en Java que muestra
2 un saludo universal por la pantalla de
3 la consola . ∗/
4
5 public c l a s s Hola {
6 //Método que se encarga de iniciar el programa
7 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
8 System . out . p r i n t l n ( " Hola mundo" ) ; //Función que imprime el mensaje
9 }
10 }

1.3. Consideraciones sobre el programa anterior


Este es un buen momento para explicar algunos aspectos relevantes sobre el código que
acabamos de crear:

Lo primero que debemos tener en cuenta es que un programa codicado en Java dis-
tingue mayúsculas de minúsculas; hay que tener cuidado con eso.

Las instrucciones se ejecutan de manera secuencial y deben terminar con el símbolo ` '. ;
Los bloques código encerrado entre llaves denen el código asociado a ciertas
estructuras de Java y, normalmente, contienen más de una instrucción. Los bloques no
terminan con un ` '. ;
Cualquier programa en Java está formado por, al menos, una clase. Al principio debe-
remos escribir cada una de ellas en un chero aparte, cuyo nombre coincida con el de la
clase y su extensión sea .java. En nuestro ejemplo, solo existe una clase denominada
Hola y, por tanto, el chero debería llamarse [Link].
Todo programa para consola en Java debe contener una única clase que codique en
su interior el método main(). Esta función desempeña un papel destacado, ya que
constituye el punto de ejecución inicial del programa.

Para poder compilar y ejecutar un programa en Java, necesitamos el JDK (Java De-
velopment kit ), que se ditribuye gratuitamente. Una vez instalado, dispondremos del
comando javac, que permite compilar un chero fuente y generar un nuevo chero
con extensión .class. Se trata de un chero escrito en un lenguaje intermedio que
más adelante podrá ser interpretado por la JVM (Java Virtual Machine ) mediante el
comando java. De esta forma, se consigue que un programa escrito ½una única vez!
pueda ser ejecutado en cualquier equipo con la JVM instalada, independientemente
del microprocesador utilizado.

Por tanto, para compilar nuestro chero fuente y obtener el chero [Link], hay
que ejecutar el siguiente comando desde la consola:

4
$ javac Hola . java

A continuación, para ejecutar el programa y visualizar por consola el resultado, hay


que lanzar el comando que sigue nótese cómo no hay que explicitar la extensión
.class :

$ java Hola

Si ha salido por consola el mensaje  Hola, mundo , debéis sentiros llenos de orgullo y
satisfacción, vamos... ½como reyes!

5
Tema 2

Elementos básicos de la programación

2.1. Comentarios
Un comentario es una línea de texto (o un fragmento) que el programa ignora a la hora de
ejecutar el script. Se utiliza para explicar el código contenido en el script o para hacer pruebas
inhabilitando temporalmente alguna línea de código. Para crear un comentario simplemente
se escribe una doble barra justo en el punto de la línea donde queremos que comience a
ignorarse el texto hasta el nal de la misma. Pueden verse ejemplos de este tipo de comentario
en las líneas 6 y 8 del [Link].
Existe otro tipo de comentario que abarca un bloque de líneas consecutivas, escribiendo
los caracteres /* al inicio del bloque y */ al nal del mismo. Un ejemplo lo constituyen las
tres primeras líneas del algoritmo anterior (todas ellas se consideran comentarios).

La última variante de comentario que admite Java se conoce como Javadoc, y permite
documentar variables, métodos y clases (ya veremos qué son) de forma que esa información
sea visible con solo situar el ratón sobre dicho elemento en el propio código. Veámoslo con
un ejemplo:

Fragmento de código 2.1: Javadoc


1 /∗ ∗ Descripcion : Metodo que calcula la media de dos enteros .
2 ∗ @param a r g 1 Es el primer entero que interviene en el calculo .
3 ∗ @param a r g 2 Es el segundo entero .
4 ∗ @return Devuelve un numero decimal con la media aritmetica .
5 ∗ @author L e o n a r d o Mesa .
6 ∗/
7
8 double metodo ( int arg1 , int arg2 ) {
9 return ( arg1 + arg2 ) / 2 ;
10 }

2.2. Variables
Una variable es un nombre que se asigna a un conjunto de bytes consecutivos ubicados en
memoria RAM, cuyo contenido se puede modicar. En esencia, una variable es un almacén

6
temporal de datos. La cantidad de bytes que ocupa en RAM viene determinado por su tipo.

Para entender bien el funcionamiento de las variables, imaginaremos la memoria RAM


como una pila de cajas cuyas direcciones de memoria son letras. Mira la gura:

Figura 2.1: Representación cticia de la RAM

2.2.1. Tipos

Como acabamos de mencionar, el tipo de una variable delimita el espacio en RAM que
puede ocupar un dato almacenado en dicha variable. Los tipos de Java se pueden clasicar
en tres categorías, que pasamos a diseccionar en los siguientes apartados.

Simples
Los tipos de datos simples, también llamados básicos o primitivos, se describen en la
tabla que se muestra a continuación:

Tabla 2.1: Tipos simples

Tipo Descripción
boolean Ocupa un byte para almacenar true o false
byte Ocupa un byte. Almacena un entero entre -128 y 127
char Ocupa dos bytes. Almacena un carácter unicode
short Ocupa dos bytes. Entero entre -32768 y 32767
int Usa cuatro bytes para almacenar un entero
long Usa ocho bytes para almacenar un entero
oat Usa cuatro bytes para almacenar un número decimal
double Usa ocho bytes para almacenar un número decimal
void Se trata de un tipo especial que indica ausencia de tipo

En la siguiente gura vamos a ver cómo deberíamos visualizar dos variables simples, de
acuerdo con la representación RAM en forma de cajas que acabamos de ver.

La primera podría ser una variable de tipo byte que almacena un 5 en la posición de
memoria `X ' y que, por tanto, ocuparía un byte es decir, una caja de memoria.

7
La segunda podría estar representando a una variable de tipo short, ocuparía dos bytes y
estaría guardando un 83 en la posición `Y ' en la imagen, en lugar de dos cajas, se muestra
una más grande.

Figura 2.2: Almacenamiento de variables simples en RAM

Arrays
Un array es un tipo de variable especial que agrupa un número de variables del mismo
1
tipo , almacenadas de manera consecutiva en memoria RAM. Se puede acceder de forma
individual a cada una de las variables del grupo, a través del nombre del array seguido de
un índice entre corchetes. El índice correspondiente a la primera variable es el 0, el de la
segunda si existe es el 1, y así sucesivamente.

Objetos
Una variable de tipo objeto es especial en el sentido de que no almacena directamente
el objeto, sino que guarda la dirección donde este se encuentra ubicado. Si te suenan los
punteros de C, puedes imaginar una variable de tipo objeto como un puntero en C; si no
te suenan no pasa nada, imagínala como un enlace duro a un chero de Linux. Entraremos
más en detalle con los objetos en el tema dedicado a ellos.

Figura 2.3: Almacenamiento de objetos en RAM


1 Eltipo de un array puede ser cualquiera: un tipo simple, un objeto e, incluso, otro array (en este caso
se habla de matriz o array multidimensional en lugar de array de arrays ).

8
2.2.2. Declaración

Antes de poder utilizar una variable, hay que declararla. Ello consiste, simplemente, en
indicar al compilador el tipo de la variable y su nombre, para que reserve el espacio necesario
en RAM. Una vez declarada, todavía no la podremos usar hasta que le asignemos un valor
inicial lo estudiaremos en el siguiente apartado. Las variables de tipo simple se declaran
en un paso, mientras que los arrays y los objetos requieren dos. Veámoslo:

Fragmento de código 2.2: Declaración de variables


18 ...
19 //Declaración de una variable llamada num cuyo tipo es int, es decir,
20 //su tamaño es el necesario para almacenar un número entero.
21 int num ;
22 ...
23 //Declaración de dos variables de tipo char, que se usan para almacenar
24 //un carácter en cada una.
25 char car1 , c a r 2 ;
26 ...
27 int [ ] v e c t o r ; //Esta instrucción declara un array de enteros vacío de momento,...
28 v e c t o r = new int [ 1 0 ] ; //... y reserva espacio en RAM para albergar 10 enteros consecutivos...
29 ...
30 ClaseA o b j e t o ; //Declaramos una variable del tipo ClaseA...
31 o b j e t o = new ClaseA ( ) ; //... y reservamos el espacio RAM donde se guardará el objeto.
32 ...

Vamos a abrir un paréntesis para prestar atención a la última declaración que aparece
en la línea 30. Se aprecia cómo un objeto se está utilizando como una variable normal, salvo
por un pequeño detalle: ½el tipo del objeto es una clase! Por tanto, podemos ir imaginando
las clases como un tipo de variable un poco especial.

2.2.3. Inicialización

Al declarar una variable, el compilador solo debe reservar el espacio necesario en RAM y
asociarle un nombre, para futuras referencias. Ese espacio reservado seguramente contendrá
una ristra arbitraria de ceros y unos, por lo que el programador debe asegurarse de no hacer
uso de la variable hasta que se le asigne un valor inicial apropiado. A este proceso se le
conoce con el nombre de inicialización.

De nuevo, recurrimos a los ejemplos para tener una referencia:

Fragmento de código 2.3: Inicialización de variables


5 ...
6 int i ; //Declaramos una variable entera...
7 i = 8 ; //... y la inicializamos a 8.
8 ...
9 char c ; //Declaramos una variable carácter...
10 c = 'Z ' ; //... y la inicializamos con la letra Z.
11 ...
12 boolean b ; //Declaramos una variable booleana...
13 b = true //... y la inicializamos con el valor true.
14 ...
15 int [ ] v e c t o r ; //Esta instrucción declara un array de enteros vacío,...

9
16 v e c t o r = new int [ 1 0 ] ; //... reserva espacio en RAM para albergar 10 enteros consecutivos...
17 v e c t o r [ 1 ] = 3 4 ; // ... y, nalmente, inicializamos el segundo elemento a 34.
18 ...
19 ClaseA o b j e t o ; //Declaramos una variable del tipo ClaseA,...
20 o b j e t o = new ClaseA ( ) ; //... reservamos espacio en RAM y más adelante se inicializaría.
21 ...

Java nos va a ahorrar tiempo y espacio a la hora de escribir código, unicando la decla-
ración y la inicialización en una sola instrucción. Veamos cómo, reescribiendo el fragmento
anterior, esta vez de manera abreviada:

Fragmento de código 2.4: Declaración e inicialización


5 ...
6 int i = 8 ; //Declaramos e inicializamos una variable entera a 8.
7 ...
8 char c = 'Z ' ; //Lo mismo con una variable de tipo carácter.
9 ...
10 boolean b = true //Análogo para una variable booleana.
11 ...
12 int [ ] v e c t o r = new int [ 1 0 ] ; //Declaramos un array y reservamos espacio para 10 enteros...
13 v e c t o r [ 1 ] = 3 4 ; // ... e inicializamos el segundo elemento a 34.
14 ...
15 //En el caso de los arrays, el proceso completo se puede efectuar ½en un solo paso!:
16 char [ ] v = { ' a ' , ' b ' , ' c ' } //Declaramos, reservamos e inicializamos un array de 3 caracteres.
17 ...
18 ClaseA o b j e t o = new ClaseA ( ) ; //La inicialización de objetos es especial. Queda pendiente.
19 ...

2.2.4. Ámbito

Llegados a este punto, deberíamos explicar cuál es el alcance o ámbito de una variable.
Podríamos denirlo como el territorio del programa donde esa variable es conocida y que,
en cierto modo, también determina su tiempo de vida.

Sin embargo, para realizar una clasicación completa de los ámbitos, es primordial refe-
rirnos al concepto de clase, que todavía desconocemos, así que vamos a retrasar su desarrollo
hasta disponer de todo el conocimiento. Momentáneamente nos quedaremos con la idea de
que un ámbito viene denido, además de por el concepto de clase, por el de bloque, elemento
este constituido por el código delimitado entre dos llaves de apertura y cierre emparejadas
como ya explicamos.

Por tanto, podemos concluir de manera parcial, claro, que cada bloque de código
establece un ámbito y, por consiguiente, cualquier variable será accesible únicamente desde
el bloque en el que está denida.

2.3. Constantes
Una constante es una variable que, tras ser inicializada, permanece inmutable durante
toda su existencia. En otras palabras: es una variable no variable. Se declara e inicializa igual
que una variable convencional, salvo por dos detalles sintácticos:

10
Por convenio, se deberían escribir en mayúsculas para distinguirlas de las variables
de un vistazo.

La declaración debe ir precedida de la palabral nal.

Como es lógico, una constante no puede aparecer nunca salvo cuando se inicializaen
el lado izquierdo de una asignación, siempre deberá hacerlo en el lado derecho. Veamos un
ejemplo donde se declara y se utiliza una constante:

Fragmento de código 2.5: Uso de constantes


6 ...
7 // Declaramos e inicializamos una constante
8 f i n a l f l o a t PI = 3 . 1 4 1 5 9 2 f ;
9 int l o n g i t u d ;
10
11 // Calculamos la longitud de una circunferencia de radio 8
12 l o n g i t u d = 2 ∗ PI ∗ 8 ;
13 ...

2.4. Operadores
Ya hemos visto cómo se realiza la primera asignación a una variable, recordemos que lo
habíamos denominado inicialización. Por desgracia, no es todo tan simple como ahí se ha
presentado. A la hora de asignar el resultado de un cálculo a una variable, existen aspectos
relacionados con los operadores implicados, sus prioridades y precedencias, así como alguna
que otra restricción adicional, que nos pueden complicar la vida si no estamos muy atentos.
Por esta razón deberíamos dominar dichas particularidades, si pretendemos programar con
un mínimo de soltura y cierta seguridad.

2.4.1. Aritméticos

Antes de entrar en materia con los operadores aritméticos, es necesario aclarar un par de
cosas sobre una asignación muy habitual que nos encontraremos en casi todos los programas
independientemente del lenguaje empleado y que tiene innumerables variantes:

Fragmento de código 2.6: Incremento


10 ...
11 // Inicializamos una variable entera a 0
12 int i = 0 ;
13
14 // Realizamos una operación de incremento en una unidad sobre la variable i
15 i = i + 1;
16 ...

La primera vez que alguien se topa con esta instrucción, suele quedar extrañado por el
hecho de que la variable aparece en ambos lados de la igualdad.

Una regla que puede ayudarnos a entender más fácilmente el sentido de este tipo de
asignaciones, dice así:

11
Cuando una variable aparece en el lado derecho de una asignación, está haciendo
referencia a su contenido y, cuando aparece en el lado izquierdo, a su ubicación
en RAM.

Por tanto, la forma correcta de interpretar las asignaciones donde una misma variable
aparece en ambos miembros de la igualdad, es la siguiente:

En primer lugar, nos debemos jar en el lado derecho de la asignación y realizar el


cálculo indicado, sustituyendo la variable por su valor actual (en el ejemplo, el valor
actual de la variable i es 0, y la operación 0 + 1 da como resultado 1).

En segundo lugar, debemos jar nuestra atención en el lado izquierdo de la asignación


y recordar que, en esa posición, la variable hace referencia a su ubicación. Por tanto, lo
que sucede es que el resultado del cálculo obtenido en el paso anterior, se almacena
en la ubicación de la variable i (de forma que ahora i pasa a valer 1).

Veamos, ahora sí, cómo funcionan los principales operadores aritméticos:

Fragmento de código 2.7: [Link]


1 public c l a s s O p e r a d o r e s A r i t m e t i c o s {
2
3 //Método que se encarga de iniciar el programa.
4 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
5
6 //Declaración e inicialización eventual de variables.
7 int x = 5 , y = 2 , z ;
8 f l o a t p , q = 3 . 0 f ; //Conviene observar cómo se inicializa una variable oat.
9 double r = 4 . 5 ;
10
11 x = 2 ; //Hemos modicado el valor de x. Ahora vale 2.
12 x = x + 3 ; //Ahora x vale 5.
13 x = x − 1 ; //Ahora vale 4.
14 z = x ∗ y ; //Inicializamos z con el valor 8.
15 z = z / 3 ; //Ahora z vale 2. Ha tenido lugar una división entera.
16 p = x / 3 ; //Ahora p vale 1.0. Se ha realizado una división entera.
17 p = x / q ; //Ahora p vale 1.3333334. Se ha efectuado una división decimal.
18 x = x % 2 ; //x vale 2, pues % (el operador módulo) obtiene el resto de la división entera.
19 y += 4 ; //Es un abreviatura de y = y + 4. Por lo tanto, al nal, y = 6.
20 y −= 1 ; //Abreviatura de y = y - 1. Por consiguiente, y = 5.
21 r ∗= 1 0 ; //Abreviatura de r = r * 10. En consecuencia r vale 45.0.
22 y /= z ; //¾Cuánto vale y?
23 x %= z ; //¾Cuánto vale ahora x?
24
25 //Incrementar y decrementar en una unidad una variable, aún se puede simplicar más:
26 x++; //Equivale a x += 1;
27 ++x ; //Idem.
28 x −−; //Equivale a x -= 1;
29 −−x ; //Idem.
30 }
31 }

12
Cuando el operador ++ afecta a una variable no incluida en una asignación, como sucede
en las líneas 26 y 27 del fragmento anterior, el resultado nal de la operación es idéntico
en ambos casos: la variable se incrementa en una unidad. Sin embargo, no ocurre lo mismo
cuando la operación aparece en el lado derecho de una asignación. En este caso, el incremento
de la variable tiene lugar antes de empezar a evaluar la expresión si los signos aparecen antes
de la variable (a su izquierda), o bien se incrementa después de haber terminado la evaluación
si los signos aparecen después del nombre de la variable (a su derecha).

Exactamente lo mismo sucede con el operador de decremento, pero restando unidades en


lugar de sumarlas, como es obvio.

Vamos a ilustrar lo recién explicado con más ejemplos, para que no haya dudas:

Fragmento de código 2.8: Operadores ++ y --


6 ...
7 int i = 3 , j = 2 , k ;
8
9 k = 5 ∗ i ++; //Equivale a estas dos instrucciónes: k = 5 * i; i = i + 1; Por tanto k = 15.
10 k = 5 ∗ ++i ; //Equivale a las instrucciones: i = i + 1; k = 5 * i; Por tanto, ahora k = 20.
11 k = j −−; //Piensa qué guardará k.
12 k = 5 + −− j ; //Idem.
13 ...

2.4.2. De comparación

Los operadores de comparación devuelven un valor booleano true o false  dependiendo


de si se verica o no la comparación que se está calculando. En este apartado hay que
resaltar un aspecto que provoca no pocos errores en los principiantes. Consiste en confundir
el operador de igualdad (==) con el de asignación (=), así que mucha atención al respecto
cuando estemos escribiendo código.

El resto de operadores son bastante intuitivos y no inducen a error. Veamos cuáles son:

Tabla 2.2: Operadores de comparación

Operador Utilización El resultado es true si...


== op1 == op2 op1 y op2 son iguales
!= op1 != op2 op1 y op2 son distintos
< op1 < op2 op1 es menor que op2
<= op1 <= op2 op1 es menor o igual que op2
> op1 > op2 op1 es mayor que op2
>= op1 >= op2 op1 es mayor o igual que op2

2.4.3. Lógicos

Permiten realizar cálculos con valores booleanos y habitualmente estarán involucradas


operaciones de comparación. Son los siguientes:

13
Tabla 2.3: Operadores lógicos

Operador Utilización El resultado es...


&& op1 && op2 true, si op1 y op2 son true. Si op1 es false, no se evalúa op2
|| op1 || op2 false, si op1 y op2 son false. Si op1 es true, no se evalúa op2
! ! op true si op es false y false si op es true
& op1 & op2 true, si op1 y op2 son true. Siempre se evalúa op2
| op1 | op2 false, si op1 y op2 son false. Siempre se evalúa op2

2.4.4. Precedencia de los operadores

Cuando en un cálculo intervienen varios operadores es vital conocer en qué orden se


aplica cada uno de ellos, para obtener el resultado correcto. En la siguiente lista mostramos
cuál es la precedencia de los operadores estudiados, de mayor a menor:

operadores unarios: ++,  , !

operadores multiplicativos: *, /, %

operadores aditivos: +, 

operadores de comparación: <, <=, >, >=

operadores de (des)igualdad: ==, !=

And simple: &

Or simple: |

And doble: &&

Or doble: ||

En caso de que aparezcan dos operadores consecutivos con igual precedencia, hay que
tener en cuenta el orden en que se asocian los operadores, para saber cuál se aplica primero.
Para ello se sigue una regla muy sencilla: Todos los operadores binarios asocian de izquierda
a derecha.

Vamos a estudiar un par de ejemplos que aclaren la situación:

Fragmento de código 2.9: Precedencia de operadores


6 ...
7 int a = 8 , b = 4 , c = 2 ;
8 //El producto tiene mayor precedencia que la suma.
9 System . out . p r i n t l n ( a + b ∗ c ) ; //Por tanto, el resultado es 16.
10 //Si se quisiera calcular primero la suma y luego el producto, habría que usar paréntesis.
11 System . out . p r i n t l n ( ( a + b ) ∗ c ) ; //¾Cuál sería el resultado?
12 ...
13 //El producto y la división tienen igual precedencia.

14
14 System . out . p r i n t l n ( a / b ∗ c ) ; //Como asocian de izquierda a derecha, se obtendría 4.
15 //Para alterar la asociatividad, también se emplearían paréntesis.
16 System . out . p r i n t l n ( a / ( b ∗ c ) ) ; //¾Cuál sería el resultado?
17 ...

2.5. Conversiones de tipo


En determinadas situaciones, es conveniente que una variable de un tipo determinado,
almacene un valor que pertenece a otro tipo distinto pero, en cierto modo, compatible con
el original. Si se dan las condiciones ideales, Java hará la conversión de forma automática.
En los otros casos, deberá ser el programador quien la realice de manera explícita. A este
proceso de conversión se le conoce con el término inglés casting.

De momento no vamos a profundizar más en este asunto, ya que estudiaremos su funcio-


namiento través de los ejercicios propuestos.

2.6. Cadenas
Las cadenas de texto no son un tipo simple de Java, sino que se tratan de objetos de
la clase String. Además, estos objetos tienen multitud de métodos para el tratamiento de
cadenas. Como todavía no sabemos nada de objetos ni de métodos, vamos a diferir su estudio
detallado hasta que podamos entenderlo.

Nos conformaremos de momento con explicar cómo se declaran e inicializan variables de


tipo cadena y qué operador se usa para concatenarlas. Todo ello a través de ejemplos:

Fragmento de código 2.10: Cadenas de texto


6 ...
7 S t r i n g cadena = " Esta cadena t i e n e " ;
8 S t r i n g numero = "5 " ;
9
10 //Para concatenar cadenas se utiliza también el operador +; por tanto, dependiendo del tipo
11 //de los operandos, este operador funciona como suma o concatenación. Cuando esto sucede,
12 //se dice que el operador está sobrecargado.
13 S t r i n g r e s u l t a d o = cadena + numero + " p a l a b r a s " ;
14 System . out . p r i n t l n ( r e s u l t a d o ) ;
15 ...

2.7. Entrada de datos a un programa


Existen dos formas de pasar datos a un programa. Una es hacerlo en el momento de eje-
cutar el programa, a modo de parámetros (también llamados argumentos ), y la otra consiste
en leerlos desde el teclado una vez el programa se ha iniciado, a través de un objeto creado
para ese n.

15
2.7.1. Como parámetros

De igual manera que pasábamos datos a un chero por lotes en MS-DOS a través de
parámetros, también lo podemos hacer a un programa escrito en Java.

Suponiendo que hemos creado y compilado un chero llamado [Link] podría-


mos pasarle parámetros en el momento de ejecutarlo, de la siguiente manera:

$ java Argumentos uno dos 3

Esos parámetros se recogen en la variable args que es un array de cadenas y, por ejemplo,
se podrían visualizar por pantalla. Vemos cómo se haría:

Fragmento de código 2.11: [Link]


1 /∗
2 Ejemplo de programa en Java que muestra por consola los
3 argumentos introducidos al ejecutarlo .
4 ∗/
5
6 public c l a s s Argumentos {
7
8 //Método que se encarga de iniciar el programa
9 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
10
11 //Mostramos uno a uno los tres parámetros
12 System . out . p r i n t l n ( " Primer argumento : " + a r g s [ 0 ] ) ; //Muestra uno
13 System . out . p r i n t l n ( " Segundo argumento : " + a r g s [ 1 ] ) ; //Muestra dos
14 System . out . p r i n t l n ( " Tercer argumento : " + a r g s [ 2 ] ) ; //Muestra 3
15 }
16 }

2.7.2. Desde el propio programa

Durante la ejecución de un programa también se pueden solicitar datos al usuario, para


que los introduzca desde el teclado. Esta técnica requiere del uso de una clase que se encuentra
disponible dentro de una extensa biblioteca de utilidades predenidas en Java. La clase en
cuestión se llama Scanner y debe ser importada a nuestro programa antes de declarar la
nuestra propia.

La importación de una clase Scanner, así como la denición de un objeto y su utilización,


se ilustra en los ejercicios. Para leer frases completas, previamente hay que establecer el
delimitador de campos con la sentencia: [Link]( \n);

2.8. Condicionales
Todo lenguaje de programación dispone de instrucciones que pueden alterar la naturaleza
secuencial de los programas. Estas instrucciones son capaces de generar bifurcaciones en el
código, de modo que la ejecución del programa tome un camino u otro, dependiendo de
cierta condición.

Vamos a estudiar las dos instrucciones que realizan esta tarea en Java.

16
2.8.1. La instrucción if

La sintaxis de esta instrucción admite dos variantes principales. Vamos a ver la más
simple:

i f ( cond ) { //También funciona: if (cond == true) {...


//Instrucciones
}

En este caso, si la condición es cierta, se ejecutan las instrucciones del bloque. Si es falsa,
no se ejecuta nada y se pasa a la instrucción siguiente.

Veamos la otra modalidad:

i f ( cond ) {
//Instrucciones
}
else {
//Instrucciones
}

En este caso, si la condición es cierta, se ejecutan las instrucciones del primer bloque y,
si es falsa, se ejecutan las del segundo bloque.

Es posible anidar varias instrucciones if y, además, pueden existir varios niveles de anida-
miento. Sin embargo, cuando hay que codicar una bifurcación múltiple, quizá sea más
cómodo usar la instrucción switch que se explica en el punto siguiente.

Pero antes de dar por cerrada la instrucción if, aprovechamos para introducir un operador
de asignación que habíamos obviado intencionadamente en el apartado correspondiente, ya
que en este encaja mucho mejor. Se trata del operador ternario ?: cuya sintaxis es la mostrada
a continuación:

v a r i a b l e = cond ? v a l o r 1 : v a l o r 2 ;

Su funcionamiento es muy simple: si la condición es verdadera, se asigna a la variable el


valor1. Si es falsa, se le asigna el valor2. Por tanto, equivale al siguiente if-else :

i f ( cond ) {
variable = valor1 ;
}
else {
variable = valor2 ;
}

2.8.2. La instrucción switch

Esta instrucción proporciona una forma más sencilla para manejar las bifurcaciones múl-
tiples, siendo una alternativa al uso de sentencias if-else consecutivas. Veamos su sintaxis:

switch ( exp ) {
case v1 :
//Instrucciones
break ;
case v2 :
//Instrucciones

17
break ;
...
case vN :
//Instrucciones
break ;

//Esta sentencia y las instrucciones que siguen son opcionales.


default :
//Instrucciones
}

El funcionamiento de esta instrucción consiste en evaluar la expresión inicial exp, cuyo


tipo debe ser byte, short, char o int. A continuación, el resultado se compara secuencialmente
con los valores no variables que aparecen en cada uno de los casos. Cuando coincide con
alguno, ejecuta ½todas las instrucciones secuencialmente! hasta encontrar un break o la última
instrucción, lo que hace que termine la ejecución del switch y se pase a la siguiente sentencia.
Si no coincidiera con ningún caso, ejecutaría las instrucciones del caso por defecto, default.
Si este no existiera, no ejecutaría nada y pasaría a la siguiente línea de código.

El break no es obligatorio y, si no aparece en alguno de los casos, continuaría la ejecución


de las sentencias de forma secuencial, ½aunque pertenezcan a otros casos!

2.9. Bucles
Los bucles son otra forma de alterar el ujo natural de ejecución de sentencias. Para un
ser humano, la realización de una acción repetitiva o un conjunto de ellas, puede resultar
cansado y hasta aburrido. Sin embargo, os puedo asegurar que una computadora, si tuviera
un corazoncito que le dotara de sentimientos, gozaría hasta la extenuación realizando este
tipo de tareas.

2.9.1. La instrucción while

Existen varias formar de repetir un conjunto de instrucciones. Veamos primero la más


extendida, en su forma más simple:

...
while ( cond ) {
//Instrucciones
}
...

La explicación de su funcionamiento es bastante sencilla: cuando el programa llega a la


sentencia while, se evalúa la condición. Si es cierta, se ejecutan las instrucciones del cuerpo
del bucle. Al terminar dicha ejecución, se vuelve a evaluar la condición. Si sigue siendo cierta,
se vuelven a ejecutar dichas instrucciones. Esto se repite hasta que la condición es falsa o
se encuentra una instrucción break, en cuyo caso termina la instrucción while y se pasa a
ejecutar la siguiente.

Además de break, dentro de un bucle podemos toparnos con una instrucción continue,
que hace que evalúe de nuevo la condición del while sin haber ejecutado los comandos del

18
cuerpo posteriores a continue. En otras palabras, la ejecución pega un salto a la siguiente
vuelta del bucle.

Es muy habitual combinar bucles con instrucciones condicionales en su interior. Un uso


típico de esta clase de bucle lo podemos ver a continuación:

Fragmento de código 2.12: [Link]


1 public c l a s s BucleW {
2 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
3 int i ;
4
5 i = 1;
6 while ( i <=10) {
7 System . out . p r i n t l n ( " Vuelta : " + i ) ;
8 i ++;
9 }
10 }
11 }

2.9.2. La instrucción for

Ese mismo programa se puede escribir de manera más compacta utilizando la instrucción
for. Lo único que cambia es la apariencia, pues ambos son totalmente equivalentes y el
resultado de la ejecución es idéntico. Aprovechamos la coyuntura para presentarlo a través
de este ejemplo:

Fragmento de código 2.13: [Link]


1 public c l a s s BucleF {
2 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
3 int i ;
4
5 //Bucle for equivalente al while anterior
6 for ( i =1; i <=10; i ++) {
7 System . out . p r i n t l n ( " Vuelta : " + i ) ;
8 }
9 }
10 }

Existen variantes de este bucle que le aportan mayor potencia y exibilidad, pero las
veremos más adelante a través de ejercicios.

2.9.3. La instrucción do-while

La última instrucción para generar bucles que vamos a estudiar, es una variación del while
en la que se invierte el orden de funcionamiento: primero se ejecuta el cuerpo del bucle y
luego se evalúa la condición para conocer si se deben dar más vueltas o no. La forma general
de esta instrucción es:

...
do {
//Instrucciones

19
} while ( cond ) ;
...

2.10. Funciones
Hay veces que, en varios puntos de un mismo programa, se debe ejecutar una secuencia
de instrucciones idéntica. En estos casos, para evitar la repetición de código, se utiliza una
técnica que consiste en asignar un nombre a esa secuencia de instrucciones y sustituir las
apariciones de dicha secuencia por el nombre asignado.

La asociación entre la secuencia de instrucciones y el nombre, se denomina denición de


la función. Cuando utilizamos el nombre en lugar de las intrucciones, se están realizando
llamadas a la función. De aquí se deduce que para utilizar una función, deben cumplirse dos
condiciones:

En algún punto del programa debe aparecer la denición de dicha función.

Debe existir una o más llamadas a dicha función a lo largo del programa (la denición
por sí misma, no hace nada, no ejecuta el código que contiene).

Una función sirve para estructurar y organizar el código de un programa de manera


lógica, compacta y más fácil de mantener, pero sobre todo, evita la reiteración de código
dentro del programa. Las funciones que aparecen en la denición de una clase, se denominan
métodos. Sin embargo, de momento, nosotros las vamos a seguir llamando funciones.

Una función opcionalmente puede admitir argumentos de forma similar a como lo
hacía un programa y además está capacitada para devolver un resultado, si se considera
oportuno.

Veamos en primer lugar, cómo sería la sintaxis de una denición:

<tipo_que_devuelve> nombre (< t i p o > arg1 , . . . , <t i p o > argN ) {


//Aquí vendría el código de la función
return <valor_que_devuelve >; //Solo aparece si devuelve algo
}

Veamos ahora la sintaxis de una llamada:

nombre ( v a l o r 1 , . . . , v a l o r N) ;

Es importante destacar que si la función devuelve un valor, entonces deberá contener


entre su código al menos una instrucción return, que hace que la función termine y devuelva
dicho valor. Además, la llamada tendrá que aparecer en el lado derecho de una instrucción
de asignación o de impresión.

Si la función no devuelve nada, entonces el <tipo_que_devuelve> tiene que ser void


y el código no deberá contener ninguna instrucción return. Además, la propia llamada ya
constituye una instrucción y, por tanto, no podrá aparecer como parte de una asignación,
por ejemplo.

20
2.10.1. Paso de parámetros

Ya hemos dicho que a una función se le pueden pasar parámetros de cualquiera de los
tipos vistos de manera opcional.

En caso de que pasemos algún parámetro a una función, debemos saber que existen dos
formas diferentes de hacerlo:

Paso por valor : Con esta técnica, el argumento de la llamada también denominado
parámetro real  se copia en una variable local de la función llamada parámetro
formal  y todo cálculo que se haga dentro de la función, se realiza sobre la copia. La
principal consecuencia de esto es que el argumento original no se ve afectado cuando
la función termina.

Paso por referencia : Mediante esta otra técnica, lo que se copia en la variable local de
la función el parámetro formal no es el argumento sino su dirección en memoria.
Ahora cualquier cálculo que se haga dentro de la función, se realizará directamente
sobre el argumento y, por tanto, sí se verá afectado.

Hay lenguajes de programación que permiten establecer qué técnica utilizar cuando se va
a pasar un parámetro a una función. Java, en cambio, obliga a realizar paso de parámetros
por valor siempre. Sin embargo, cuando pasamos un array o un objeto como parámetro de
una función, a efectos prácticos funciona como si se tratara de un paso por referencia, debido
a la naturaleza de esos dos tipos.

21
Tema 3

Programación orientada a objetos

3.1. Un poco de historia


Los objetos hacen su entrada en el mundo de la programación a nales de los años 60
en Noruega, de la mano de Simula-67, un lenguaje basado en Algol  antepasado tam-
bién de Pascal . Los conceptos exhibidos en Simula interesaron tanto a Alan Kay, de la
empresa Xerox Parc, que decidió aplicarlos a su visionario proyecto Dynabook. En él preten-
día desarrollar una computadora personal pequeña, barata y con buenas prestaciones. Esta
computadora debía ofrecer una fuerte interacción con el usuario, a través del uso de un ratón
y de una interfaz totalmente gráca compuesta de ventanas, menús, paneles, botones, etc.

La idea era que este entorno pudiera ser fácilmente ampliable por el usuario, utilizando
para ello un sencillo lenguaje de programación enfocado a la forma de pensar de las personas.
Así es como nació el lenguaje Smalltalk.

Ninguna de las versiones de Smalltalk aparecidas en la década de los 70, gozó de gran
popularidad. Solo a nales de los 80 surgió cierto interés por la orientación a objetos, pasando
a aplicarse en sistemas y lenguajes ya existentes por ejemplo, Bjarne Stroustrup creó C++
a partir de C . En los años 90 esta tecnología llegó al terreno comercial y se puso de moda,
provocando su incorporación en muchas aplicaciones y sistemas operativos.

En la actualidad, cualquier programador conoce las nociones básicas sobre las que se
apoya la orientación a objetos. Sin embargo, no son tantos los que realmente entienden las
razones que hacen de esta tecnología una herramienta de programación superior al resto si
hubiera que denirlo con tres palabras, estas serían reutilización de código .

½Yo sí las entiendo, yo sí! gritó Cristino mientras hacía aspavientos con los brazos.

Por desgracia, no es este el objetivo del presente manual por otro lado ligero así que
aparcaremos el asunto hasta encontrar un momento más propicio para contarlo.

3.2. Clases
Las clases son la base de la programación orientada a objetos. De hecho, todo el código de
una aplicación debe estar incluido en una clase. A su vez, una clase está formada por atributos
que no son otra cosa que variables y por métodos que no dejan de ser funciones.

22
También es posible que una clase contenga otras clases, bajo ciertas condiciones que veremos
en los ejercicios.

Por consiguiente, podemos visualizar una clase como un contenedor de variables, de


métodos y, en ocasiones, también de otras clases:

Figura 3.1: Imagen que debemos hacernos de una clase.

Como ya dijimos al inicio del manual, podemos considerar las clases como una especie de
plantillas que nos van a permitir crear objetos, que son los verdaderos protagonistas de este
paradigma de programación. Veamos un ejemplo de clase que nos permitirá generar objetos
coche (de momento solo se muestra el esqueleto, pero se va a desarrollar de forma completa
a través de los ejercicios):

Fragmento de código 3.1: [Link]


1 public c l a s s Coche {
2 //Denimos los atributos de un coche
3 S t r i n g marca ;
4 String color ;
5 int n i v e l C o m b u s t i b l e ;
6
7 //Denimos los métodos
8 void a r r a n c a r ( ) {
9 //Código asociado a este método
10 }
11
12 void c i r c u l a r ( ) {
13 //Código asociado a este método
14 }
15 }

23
Figura 3.2: Objetos creados a partir de la clase Coche.

Dentro de una clase cualquiera pueden aparecer unos métodos especiales denominados
constructores, que facilitarán la inicialización de los objetos de dicha clase. Sin embargo, no
los vamos a presentar ahora sino un poco más adelante, cuando veamos la herencia.

En denitiva, y para resumir, podemos ver un programa de consola como un conjunto


de clases interactuando entre sí en el que solo una de ellas debe contener el método main(),
que constituye el punto de ejecución inicial del programa ½cuidado, porque no sucede lo
mismo con las aplicaciones grácas!. Teniendo lo anterior en cuenta, podríamos asociar un
programa en Java con la siguiente imagen:

Figura 3.3: Ilustración de un programa.

24
3.3. Objetos
½Por n...! ½Ha llegado el momento de estudiar la entidad que da nombre al estilo de
programación en el que nos encontramos inmersos!

Sabemos que un objeto se genera a partir de una clase. Simplicando un poco, podemos
imaginar una clase como el tipo de una variable y un objeto como una variable de dicho tipo
que se inicializa mediante el operador new. Es importante que dediques el tiempo necesario
para asimilar esto que acabamos de decir... Tómate tu tiempo.

Ahora, sin más dilación, vamos a estudiar un ejemplo donde se hace uso de una clase y
un objeto derivado de ella:

Fragmento de código 3.2: [Link]


1 //Clase que dene el objeto Coche
2 c l a s s Coche {
3 //Denimos los atributos de un coche
4 S t r i n g marca ;
5 String color ;
6 int n i v e l C o m b u s t i b l e ;
7
8 //Denimos los métodos
9 void a r r a n c a r ( ) {
10 //Código asociado a este método
11 }
12
13 void c i r c u l a r ( ) {
14 //Código asociado a este método
15 }
16 }
17
18 //Clase principal con la función main()
19 public c l a s s P r i n c i p a l {
20 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
21
22 //Declaramos una variable de tipo Coche
23 Coche objCoche ;
24
25 //Reservamos espacio en RAM para ese objeto
26 objCoche = new Coche ( ) ;
27
28 //Incializamos el objeto (en realidad, sus atributos)
29 objCoche . marca = " Seat " ;
30 objCoche . c o l o r = " Blanco " ;
31 objCoche . n i v e l C o m b u s t i b l e = 6 ; //En litros, por ejemplo
32
33 //Ahora vamos a conducir un rato
34 objCoche . a r r a n c a r ( ) ;
35 objCoche . c i r c u l a r ( ) ;
36 }
37 }

Bonito, ¾verdad?

 ½Es lindoooo! dijo Leo con lágrimas en los ojos Pero... ½no me enteré de nada!

25
Si has sido un alumno avispado, te habrás dado cuenta de que hemos creado un chero
.java con dos clases: Coche y Principal. Es posible denir más de una clase en un chero .java,
con la condición de que solo una de ellas sea pública. Sin embargo, al compilar, apareceran
dos cheros .class, uno por cada clase: [Link] y [Link].

3.3.1. El valor null

Se trata de un valor especial toman las variables de tipo objeto cuando no han sido
inicializadas es decir, se ha declarado la variable pero no se le ha asignado ningun objeto
con el operador new , o bien cuando la variable sí tenía asignado un objeto pero se eliminó
la referencia al mismo.

Resumiendo, si una variable contiene el valor null, realmente signica que se encuentra
vacía. Esta circunstancia es fuente de numerosos errores, ya que no se puede operar sobre
una variable asumiendo que contiene un objeto, no siendo así.

3.3.2. Miembros estáticos

Hasta ahora no lo habíamos dicho, pero al conjunto de atributos y métodos de una clase
se les conoce con el nombre genérico de miembros.

 !P... jua, jua, jua! rompió a reir Cristino.

Hay que tener claro que cuando denimos un atributo en una clase y creamos objetos,
cada uno de estos objetos tendrá un valor posiblemente distinto para dicho atributo. Por
ejemplo, si pensamos en el atributo color de la clase Coche anterior, el objeto coche1 puede
tener color rojo y el objeto coche2 ser blanco. Repetid mentalmente lo que acabamos de
decir...

 Cada objeto tiene sus propios valores, cada objeto... repetía Leo Mesa en su cabeza,
como un mantra que le absorbía misteriosamente los pensamientos.

Sin embargo, en ocasiones conviene que alguno de los atributos de una clase tenga un
valor común para todos sus objetos. Esto se consigue declarando el atributo como estático
y, para ello, basta con preceder su declaración por la palabra reservada static. Los atributos
estáticos se pueden utilizar sin necesidad de crear un objeto de la clase.

De la misma manera, los métodos de una clase también se pueden declarar estáticos lo
hemos visto en la función main() . Sin embargo, el signicado es distinto.

La principal característica de un método estático de una clase es que, dicho método, se


puede ejecutar sin haber creado previamente un objeto de la clase. Además, debe cumplir
las siguientes restricciones:

Solo puede llamar a métodos también estáticos.

Solo puede acceder a datos estáticos.

No puede utilizar super ni this ambos conceptos se estudiarán cuando hablemos de


la Herencia .

26
Por último, aunque no es tan habitual, también puede aparecer algún bloque de código
denido como estático. La sintaxis empleada es: static {código } y provocará que el código
entre llaves se ejecute una única vez cuando se cargue la clase.

En este punto, debería haber surgido una pregunta: Si un miembro estático se puede
utilizar sin objetos de por medio, ¾qué sintaxis se emplea?

La forma de llamar a una variable o un método de un objeto era: [Link] u


[Link](). Si no existe objeto alguno, ¾cómo lo hacemos? Muy simple. En lugar de
poner el nombre del objeto usamos el de la clase, de la siguiente manera: [Link]
o [Link]().

3.4. Encapsulación (u ocultación)


Se trata de una característica de los lenguajes orientados a objetos, cuyo objetivo es
facilitar el uso de las clases a los programadores que las utilizan y, de paso, reducir la
complejidad del programa.

Normalmente, cualquier clase tiene denidos varios atributos y métodos. Sin embargo,
solo algunos de ellos van a ser de utilidad a la hora de hacer uso de dicha clase; el resto los ha
podido emplear el programador que ha creado la clase para proporcionar cierta funcionalidad
a los primeros o para organizar mejor el código pero, para el uso convencional de la clase, no
se van a necesitar en absoluto. La encapusulación permite hacer invisibles a estos últimos,
dejando a la vista únicamente los atributos y métodos necesarios para la manipulación de la
clase.

Una analogía que nos puede ayudar a entender mejor en qué consiste la encapsulación
gira en torno al conocimiento que debe abarcar un conductor de coche para circular con
su automóvil. Está claro que el proceso de arranque del vehículo involucra ciertos procesos
químicos, eléctricos y mecánicos, que el conductor no tiene por qué conocer en profundidad
para poder utilizar su coche. Ni tan siquiera debe conocer todas las piezas que conforman el
motor, ni qué papel juega cada una de ellas.

 Leo, la junta de la trócola de mi seiscientos no tiene secretos para mí... ½que lo sepas!
dijo susurrando Cristino a su amigo.

Simplicando un poco, basta con que un piloto se desenvuelva bien con el manejo de los
pedales, la palanca de cambios y los giros del volante. Digamos que esa es la interfaz que
presenta un coche para interactuar con su conductor.

3.4.1. Paquetes

 Ji, ji, ji  se rio Leo, encogiéndose de hombros mientras se tapaba la boca con ambas
manos.

 Jo, jo, jo  le secundó Cristino sonoramente, abriendo mucho la boca.

Un paquete no es más que una manera de organizar las clases en realidad, los cheros
.class  dentro de directorios de manera que, por ejemplo, todas las clases del paquete
denominado nombre deberán estar ubicadas en el directorio nombre. De la misma manera,
si el nombre del paquete consta de dos componentes, por ejemplo, nombre1.nombre2, todas

27
sus clases se guardarán en el directorio nombre1/nombre2. Como consecuencia de lo anterior,
podrán existir clases con igual nombre en distintos paquetes, evitándose así posibles conictos
de nomenclatura.

Android, sin ir más lejos, usa por defecto el paquete [Link], es decir, un nombre
de dominio en orden inverso.

Para que una clase pertenezca a un paquete, hay que escribir como primera línea del
chero y en minúsculas: package nombre

Cuando se utiliza en un programa la instrucción import [Link] en realidad esta-


mos importando la clase ejClase del paquete nombre, lo que signica que podremos utilizar
sus métodos y atributos dentro del programa. También podemos importar todas las clases
de dicho paquete con la sintaxis: import nombre.*

3.4.2. Niveles de visibilidad

Para restringir el acceso o la visibilidad a los atributos y métodos de una clase, se


pueden utilizar los siguientes modicadores. Aparecen ordenados de menor a mayor restric-
ción:

public: Cuando precedemos la denición de un método o un atributo de este modi-


cador, estamos indicando que será visible desde cualquier lugar del programa. También
aplicable a clases.

protected: La aparición de esta etiqueta hace que sea visible desde cualquier clase del
mismo paquete y desde sus clases derivadas. No es aplicable a clases.

Por defecto: Cuando no aparece ninguna etiqueta, las clases, métodos o atributos
serán visibles desde cualquier clase del propio paquete.

private: Si lo precedemos por esta etiqueta, solo será visible desde la propia clase. No
es aplicable a clases.

En este punto, conviene destacar que es una práctica habitual impedir el acceso directo
a los atributos de un objeto, declarándolos privados. Para poder llevar a cabo un acceso
indirecto a los mismos, se crean un par de métodos públicos por atributo: uno para obtener
el valor del atributo denominado getter  y otro para modicarlo llamado setter .
Piensa, como ejercicio, qué parámetro debe recibir y qué valor devolver, cada uno de ellos.

El sentido de lo anterior es que se pueda alterar el código de la clase en cuestión, sin


que otro eventual programador que la esté utilizando, advierta las modicaciones. Es decir,
habremos conseguido separar la interfaz de la implementación. Lo veremos con más claridad
en un ejercicio.

3.5. Herencia
Es la principal característica de un lenguaje orientado a objetos. Consiste en denir una
nueva clase a partir de otra existente, mediante la sintaxis:

28
c l a s s Nueva extends V i e j a {
//Código adicional
}

A partir de este momento, la nueva clase denominada clase hija o subclase  hereda
de forma automática todos los atributos y métodos de la clase existente llamada clase
padre o superclase . Además, se pueden crear nuevos atributos y métodos que extiendan la
funcionalidad de la clase original.

Conviene tener presente que cualquier clase en Java, hereda implícitamente de la clase
Object, es decir, esa es la clase raíz de cualquier árbol de herencia. Como dicha clase im-
plementa ciertos métodos (como, por ejemplo, toString() ), cualquier otra clase dispondrá
también de ellos.

La herencia múltiple consiste en que una clase herede de varios padres, pero en Java no
funciona. Por tanto, toda clase derivada en Java solo tendrá un padre. A esto se le conoce
como herencia simple.

3.5.1. Constructores

Un constructor es un método especial que sirve para inicializar un objeto. Es especial en


el sentido de que no devuelve ningún tipo aunque sí admite parámetros y debe tener el
mismo nombre de la clase. Además, es el método al que se está llamando cuando se ejecuta
la instrucción new para crear el objeto.

Dentro del constructor de una subclase se puede llamar al constructor de la superclase


utilizando el método especial super(), con los parámetros que este necesite. Debe ser la
primera instrucción dentro del constructor de la subclase.

Cuando dentro de un método de la clase se quiere hacer referencia al objeto que lo está
ejecutando, se utiliza la palabra this. Veremos algún ejemplo de uso en los ejercicios.

3.6. Polimorsmo (o redenición de métodos)


Se trata de la última de las tres características que distingue un lenguaje orientado a
objetos de otro que no lo es. Para alterar el comportamiento de una clase padre, aparte de
añadir atributos y métodos adicionales, existe una técnica más que consiste en la posibilidad
de modicar el código de los métodos heredados ½manteniendo intacta su estructura!, es decir,
el nombre, los parámetros y el valor devuelto son idénticos, pero el código que contienen
dichos métodos es distinto. De este modo, se conseguirá que los objetos de una subclase se
comporten de forma distinta a como lo hacen los objetos de su clase padre.

Existe una consecuencia de todo esto que es extremadamente importante en la POO. Es


una regla de oro, que se debe tener siempre presente, y que dice así:

 Allí donde se puede utilizar un objeto de una clase, también se puede usar un objeto de
cualquier subclase suya, pero no al revés .

Cuando vayamos a redenir un método en la clase hija, conviene precederlo de la directiva


@Override para indicar nuestra intención al compilador y que nos avise cuando, por error,

29
1
cambiemos la rma del método (si no lo hacemos así, se crearía un nuevo método en lugar
de redenir el de la clase padre y no nos enteraríamos).

Veamos un ejemplo:

Fragmento de código 3.3: [Link]


1 public c l a s s Padre {
2 S t r i n g a p e l l i d o = " Ruiz " ;
3
4 void d e c i r A p e l l i d o ( ) {
5 System . out . p r i n t l n ( "Mi primer a p e l l i d o e s : " + a p e l l i d o ) ;
6 }
7
8 void l l o r a r ( ) {
9 System . out . p r i n t l n ( "Yo l l o r o a s i : S n i f , s n i f " ) ;
10 }
11 }

Fragmento de código 3.4: [Link]


1 public c l a s s H i j o extends Padre {
2 @Override
3 void l l o r a r ( ) {
4 System . out . p r i n t l n ( "Yo l l o r o a s i : Buaaaaaa , buaaaaaa " ) ;
5 }
6
7 void s o s ( ) {
8 System . out . p r i n t l n ( " Papi , me hago p i s " ) ;
9 }
10 }

Fragmento de código 3.5: [Link]


1 public c l a s s Polimorfismo {
2 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
3 Padre p = new Padre ( ) ;
4 H i j o h = new H i j o ( ) ;
5
6 p. decirApellido ();
7 h. decirApellido ();
8
9 p. llorar ();
10 h. llorar ();
11 h . sos ( ) ;
12
13 p = h ; //Correcto, según la regla de oro
14 p . l l o r a r ( ) ; //Piensa qué mostraría esta instrucción
15 p . s o s ( ) ; //Piensa si esta instrucción tiene sentido
16
17 h = p; //Incorrecto, según la misma regla
18
19 //El problema anterior se resuelve con un casting
20 h = ( Hijo ) p ;
1 La
rma de una función en general, viene dada por su nombre, el tipo que devuelve y los tipos de los
argumentos que recibe.

30
21
22 h . l l o r a r ( ) ; //Piensa qué mostraría esta instrucción
23 h . s o s ( ) ; //Piensa si tiene sentido
24 }
25 }

3.7. Sobrecarga de métodos


Es importante no confundirlo con polimorsmo. Ya hemos visto en qué consiste la so-
brecarga en operadores, en particular, el operador + podía funcionar como suma o como
concatenación, dependiendo del tipo de sus argumentos.

La sobrecarga de un método consiste en denir dos o más métodos con igual nombre
pero que varían o bien en cuanto al número de argumentos, o bien en cuanto a su tipo. El
compilador de Java sabrá cuál tiene que ejecutar analizando dichos argumentos.

Es bastante habitual que el constructor de una clase se encuentre sobrecargado, es decir,


que contenga varias versiones de constructor.

Veamos un ejemplo donde se crean tres funciones cuadrado distintas:

Fragmento de código 3.6: [Link]


1 public c l a s s Sobrecarga {
2 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
3 int i = 5 ;
4 float j = 6 f ;
5
6 System . out . p r i n t l n ( cuadrado ( i ) ) ;
7 System . out . p r i n t l n ( cuadrado ( j ) ) ;
8 cuadrado ( i , " El cuadrado de " ) ;
9 }
10
11 s t a t i c f l o a t cuadrado ( int num) {
12 return num ∗ num ;
13 }
14
15 s t a t i c f l o a t cuadrado ( f l o a t num) {
16 return num ∗ num ;
17 }
18
19 s t a t i c void cuadrado ( int num, S t r i n g t e x t o ) {
20 System . out . p r i n t l n ( t e x t o + num + " e s : " + (num ∗ num ) ) ;
21 }
22 }

3.8. Enumeraciones
Una enumeración es un nuevo tipo de datos que establece cuáles son los posibles valores
que puede tomar una variable de dicho tipo. Realmente, podemos considerar un tipo enume-
rado como una clase especial, cuyos objetos escritos en mayúsculas por convenio vienen

31
denidos en una lista y pueden contener atributos (en este caso, el constructor de la clase
debe ser privado para evitar la creación de nuevos objetos).

Hay que resaltar que toda enumeración hereda de la clase [Link] por lo que
dispondrán de una serie de métodos heredados de dicha clase. Destacan los siguientes:

name(): Devuelve el nombre del objeto como un String.


ordinal(): Devuelve un entero con la posición del objeto en la lista. Empieza por 0.
values(): Devuelve un array que contiene todos los objetos de la lista ordenados.

Un ejemplo simple es el de una variable que debe almacenar los días de la semana (sin
atributos) y no puede tomar otro valor distinto. Veamos cómo se deniría un tipo enumera-
ción para dicha variable:

Fragmento de código 3.7: [Link]


1 public enum DiaSemana {
2
3 //Denimos los posibles valores para el tipo en cuestión
4 LUNES, MARTES, MIERCOLES, JUEVES, VIERNES, SABADO, DOMINGO
5 }

Para utilizar dicho tipo, se procedería como en el siguiente ejemplo:

Fragmento de código 3.8: [Link]


1 public c l a s s EjemploEnum {
2
3 //Declaramos una variable del tipo enum que hemos denido en el fragmento anterior
4 DiaSemana d i a ;
5
6 //Para asignar un objeto a una variable de este tipo, se usa la siguiente sintaxis:
7 d i a = DiaSemana .LUNES; //Asignaría el objeto LUNES a la variable dia
8
9 //Ahora mostramos por pantalla el día guardado
10 System . out . p r i n t l n ( " El d i a almacenado e s : " + d i a . name ( ) ) ;
11 }

Veamos ahora un ejemplo en el que los objetos albergan atributos y, por tanto, también se
creará el correspondiente constructor. La idea es asociar a cada objeto el nombre abreviado
y el número de letras del nombre completo. Se haría como indicamos a continuación:

Fragmento de código 3.9: [Link]


1 public enum DiaSemana2 {
2
3 //Denimos los posibles valores para el tipo en cuestión
4 LUNES ( "Lun" , 5 ) ,
5 MARTES ( "Mar" , 6 ) ,
6 MIERCOLES ( "Mie" , 9 ) ,
7 JUEVES ( " Jue " , 6 ) ,
8 VIERNES ( " Vie " , 7 ) ,
9 SABADO ( "Sab" , 6 ) ,
10 DOMINGO ( "Dom" , 7 ) ; // Atención al punto y coma

32
11
12 //Ahora vamos a denir los atributos que tienen cada uno de esos objetos
13 private f i n a l S t r i n g nomAbreviado ;
14 private f i n a l int numLetras ;
15
16 //Le toca el turno al constructor
17 private DiaSemana2 ( S t r i n g nomAbreviado , int numLetras ) {
18 this . nomAbreviado = nomAbreviado ;
19 this . numLetras = numLetras ;
20 }
21
22 //Por último, creamos los getters
23 public S t r i n g getNomAbreviado ( ) {
24 return nomAbreviado ;
25 }
26
27 public int getNumLetras ( ) {
28 return numLetras ;
29 }
30 }

Y se utilizaría de la siguiente manera:

Fragmento de código 3.10: [Link]


1 public c l a s s EjemploEnum2 {
2
3 //Declaramos una variable del tipo enum que hemos denido en el fragmento anterior
4 DiaSemana2 d i a ;
5
6 d i a = DiaSemana2 .LUNES;
7
8 //Ahora mostramos por pantalla los atributos del día guardado
9 System . out . p r i n t l n ( d i a . nomAbreviado + " t i e n e " + d i a . numLetras ) ;
10 }

3.9. Clases abstractas


Cuando se desarrolla una jerarquía de clases, puede existir un comportamiento común a
todas ellas, pero se lleva a cabo de forma diferente para cada una. Entonces conviene usar
una clase especial, denominada abstracta, cuya peculiaridad reside en que no se pueden crear
objetos de esa clase. Su utilidad consiste en servir de base para derivar nuevas clases de
las que ya sí se podrán crear objetos. Además de atributos y métodos convecionales, una
clase abstracta también deberá contener al menos un método abstracto, que es aquel que solo
presenta la rma y carece, por tanto, de implementacion (es decir, no tiene código asociado).
Estos métodos deberán ser implementados (aparecer ya con código) en las clases derivadas.

Por ejemplo, un concepto abstracto típico es el de animal. Un animal no es algo concreto


sino que designa una clase muy amplia de seres ya sí concretos (perros, gorriones, delnes,
etc.). La clase animal podría contener métodos que se deben codicar de manera distinta de-
pendiendo del animal como, por ejemplo, gritar (el perro ladra y el gorrión pía) o desplazarse

33
(el perro camina y el gorrión vuela). Estos métodos deberían declararse como abstractos y
codicarse luego en cada una de las clases derivadas de manera apropiada.

Otro ejemplo típico de concepto que debería considerarse como abstracto es el de vehículo.
A partir de él, se pueden derivar otros concretos como coches, motos, bicicletas, aviones, etc.

Aprovechemos el siguiente ejemplo para conocer también su sintaxis:

Fragmento de código 3.11: [Link]


1 public abstract c l a s s Mamifero {
2
3 //Declaramos los atributos
4 S t r i n g nombre ;
5 String fecha ;
6
7 //Implementamos un método convencional
8 void mamar ( ) {
9 System . out . p r i n t l n ( "Ahora e s t o y chupando de l a t e t a " ) ;
10 }
11
12 //Y al menos hay que declarar (sin implementar) un método abstracto
13 abstract void d e s p l a z a r ( ) ;
Al ser abstracta, no se pueden crear objetos directamente de esta clase. Solo podemos
utilizarla para derivar otras clases que ya sí implementen el código de los métodos abstractos
denidos en ella:

Fragmento de código 3.12: [Link]


1 public c l a s s Gato extends Mamifero {
2
3 //Denimos el método abstracto
4 public void d e s p l a z a r ( ) {
5 System . out . p r i n t l n ( "Ahora e s t o y caminando . . . " ) ;
6 }
7 }

Fragmento de código 3.13: [Link]


1 public c l a s s D e l f i n extends Mamifero {
2
3 //Denimos el método abstracto
4 public void d e s p l a z a r ( ) {
5 System . out . p r i n t l n ( "Ahora e s t o y nadando . . . " ) ;
6 }
7 }

3.10. Interfaces
Podemos imaginarnos una interface como un caso extremo de clase abstracta, en el
sentido de que todos sus métodos son abstractos. Además, cuando se implementen en una
clase particular, habrá que denirlos obligatoriamente como públicos.

Si la interfaz contiene atributos, estos serán estáticos y constantes de manera implícita,


aunque no aparezcan las palabras static ni nal.

34
Eso sí, la sintaxis es muy diferente con respecto a una clase abstracta:

Fragmento de código 3.14: [Link]


1 public interface E j I n t e r f a z {
2
3 //Declaramos una variable
4 int mayoriaEdad = 1 8 ;
5
6 //Ahora declaramos un par de métodos
7 public void Metodo1 ( int a , S t r i n g b ) ;
8
9 public int Metodo2 ( int a ) ;
10 }

Para que una clase pueda utilizar la interfaz, debe indicarse con la palabra reservada
implements, como se indica en el siguiente ejemplo:

Fragmento de código 3.15: [Link]


1 public c l a s s E j C l a s e C o n I n t e r f a z implements E j I n t e r f a z {
2
3 //Aquí podría aparecer código adicional para la clase, incluyendo atributos y métodos
4
5 //Y, obligatoriamente, habría que implementar todos los métodos denidos en la interfaz
6 public void Metodo1 ( int a , S t r i n g t e x t o ) {
7 System . out . p r i n t l n ( t e x t o + " : " + a ) ;
8 }
9
10 public int Metodo2 ( int a ) {
11 System . out . p r i n t l n ( " Eres a d u l t o hace : " + a − mayoriaEdad + " a n i o s " ) ;
12 }
13 }

Es habitual que una clase herede de otra e implemente una interfaz (o más) de forma
simultánea. La sintaxis utilizada es:

Fragmento de código 3.16: [Link]


1 public c l a s s EjHI extends Clase implements I n t e r f a z 1 , I n t e r f a z 2 {
2
3 //Aquí vendría el código de la clase
4
5 }

Como se observa en el ejemplo anterior, una clase puede implementar más de una interfaz
a la vez debiendo aparecer separadas por comas.

Además, también se puede utilizar la herencia entre interfaces o, dicho con otras palabras,
una interfaz puede heredar de otra de la misma forma que lo hacen las clases.

3.11. Gestión de errores: Excepciones


En todo programa existen fallos que solo se detectan en tiempo de ejecución. A estos
errores se les denomina excepciones. En realidad, una excepción es un objeto que se crea
cuando tiene lugar un error de este tipo y que almacena información sobre lo que ha sucedido.

35
Las excepciones se clasican jerárquicamente, de manera que todas descienden de la clase
Exception. Para manejar excepciones, se emplean una serie de instrucciones organizadas en
tres bloques, precedidos por las palabras reservadas try, catch y nally respectivamente, que
tratan de reaccionar para que el programa salga airoso ante la situación de error.

try: Este bloque debe contener las instrucciones que son susceptibles de producir un
error.

catch(TipoDeExcepción e): Puede aparecer más de un bloque de este tipo. Contiene


las instrucciones que se deben ejecutar en caso de que se haya producido una excepción
del tipo indicado, al ejecutar el bloque try.

nally: Este bloque es opcional y sus instrucciones se ejecutarán siempre, haya habido
error o no.

Veamos su funcionamiento en un ejemplo:

Fragmento de código 3.17: [Link]


1 import j a v a . u t i l . Scanner ;
2
3 public c l a s s Excepciones {
4
5
6 //Metodo que se encarga de iniciar el programa
7 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
8 Scanner dato = new Scanner ( System . i n ) ;
9 int num, den ;
10
11 //Incluimos la petición de enteros, ya que esto puede generar errores
12 try {
13 System . out . p r i n t l n ( " I n t r o d u c e e l numerador : " ) ;
14 num = dato . n e x t I n t ( ) ;
15 System . out . p r i n t l n ( " I n t r o d u c e e l denominador : " ) ;
16 den = dato . n e x t I n t ( ) ;
17 System . out . p r i n t l n ( " Resultado : " + (num / den ) ) ;
18 }
19
20 catch ( A r i t h m e t i c E x c e p t i o n e ) {
21 System . out . p r i n t l n ( " Error : D i v i s i o n por c e r o " ) ;
22 }
23
24 //Esta excepción la lanza el objeto Scanner
25 catch ( j a v a . u t i l . InputMismatchException e ) {
26 System . out . p r i n t l n ( " Error : Debes i n t r o d u c i r numeros e n t e r o s " ) ;
27 }
28
29 finally {
30 System . out . p r i n t l n ( " Fin d e l programa " ) ;
31 }
32 }
33 }

36
Una excepción muy común es NullPointerException. Aparece cuando se intenta realizar
alguna operación sobre un objeto no inicializado recuerda, aquel que contiene el valor
null .

También es posible denir excepciones y lanzarlas de manera personalizada, pero su


explicación queda para otra ocasión.

3.12. Conexión a bases de datos


El código necesario para conectar un programa en Java con una base de datos es bastante
sencillo, pero antes de ponernos manos a la obra, debemos asegurarnos de que contamos con
los siguientes elementos bien congurados:

Un servidor en nuestro caso MySQL que contenga una BD con alguna tabla a la
que acceder y, además, un usuario con permisos para manipular dicha BD. Todo eso
se puede congurar con facilidad mediante la herramienta web PhpMyAdmin.

Un driver basado en JDBC para conectar con la BD anterior. Como nosotros vamos
a utilizar MySQL, habrá que bajar el driver JDBC Driver for MySQL (Connector/J)
desde la página de MySQL. Una vez descargado y descomprimido el driver, hay que in-
cluirlo en el proyecto de Eclipse como un archivo JAR externo (Build path 7→ Congure
Build Path 7→ Libraries 7→ Add External JARs ).

A continuación, vamos a mostrar el código necesario para conectar con una BD llamada
Prueba a través del usuario Ana cuya contraseña es Pass. Una vez realizada la conexión,
primero insertaremos un registro en la tabla Alumnos que debe existir y que contiene
los campos Nombre, Apellido y Edad. Después, utilizaremos un bucle para mostrar todos los
registros de la tabla, incluido el recién añadido:

Fragmento de código 3.18: [Link]


1 import j a v a . s q l . Connection ;
2 import j a v a . s q l . DriverManager ;
3 import j a v a . u t i l . Scanner ;
4 import java . s q l . ResultSet ;
5 import com . mysql . jdbc . Statement ;
6
7 public c l a s s ConexionBD {
8
9 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
10 Connection cn = null ;
11 String str ;
12
13 try {
14
15 //Cargamos el driver jdbc para una BD MySQL
16 C l a s s . forName ( "com . mysql . jdbc . D r i v e r " ) ;
17
18 //Creamos una conexión a la BD con el driver que acabamos de cargar
19 s t r = " jdbc : mysql : / / l o c a l h o s t : 3 3 0 6 / Prueba " ;

37
20 cn = ( Connection ) DriverManager . getConnection ( s t r , "Ana" , " Pass " ) ;
21
22 //Si cn no es nulo, la conexión ha tenido éxito
23 i f ( cn != null ) {
24
25 //stmt sirve para ejecutar sentencias de modicación
26 Statement stmt = ( Statement ) cn . c r e a t e S t a t e m e n t ( ) ;
27
28 //Aquí se puede ejecutar un DELETE o un UPDATE en vez del INSERT
29 s t r = "INSERT INTO Alumnos VALUES( ' Luis ' , ' Ruiz ' , 1 8 ) " ;
30 stmt . e x e c u t e ( s t r ) ;
31
32 //El objeto rs (cursor) almacena el resultado de una SELECT
33 s t r = "SELECT ∗ FROM Alumnos" ;
34 R e s u l t S e t r s = ( R e s u l t S e t ) stmt . executeQuery ( s t r ) ;
35
36 S t r i n g datos = "" ;
37 while ( r s . next ( ) ) {
38 datos = datos + r s . g e t I n t ( "Edad" ) + " "
39 + r s . g e t S t r i n g ( "Nombre" ) + " "
40 + r s . g e t S t r i n g ( " A p e l l i d o " ) + "\n" ;
41 }
42
43 System . out . p r i n t l n ( datos ) ;
44 rs . close ( ) ;
45 }
46
47 //Si se produce un error al intentar la conexión, mostramos el mensaje
48 } catch ( Exception e ) {
49 System . out . p r i n t l n ( e . getMessage ( ) ) ;
50 }
51
52 }
53
54 }

Si has prestado atención, te habrás dado cuenta de que el programa anterior se está
conectando a un servidor local (...mysql://localhost...). Si quisiéramos conectar con un ser-
vidor remoto, este debería estar congurado de forma que admitiera conexiones externas.
Para conseguirlo, habría que modicar el chero /etc/mysql/[Link].d/[Link] para
que la línea que contiene el texto:

bind - address = [Link]

quedara de la siguiente forma:

bind - address = [Link]

Además, el usuario que se conecta Juan en el ejemplo anterior debería tener una
cuenta en el servidor remoto con todos los permisos necesarios activados.

Para que MySQL reconozca las tildes y la letra ñ, hay que modicar el chero [Link],
añadiendo las siguientes líneas en las secciones indicadas:

[mysqld]
character-set-filesystem=UTF8

38
character-set-server=UTF8
[client]
default-character-set=UTF8
[mysqldump]
default-character-set=UTF8

39
Tema 4

Aspectos avanzados

4.1. Clases internas (o anidadas)


Una clase interna es aquella que está denida dentro de otra. A la clase que la contiene, se
le suele llamar clase externa para no salirnos de la nomenclatura. La clase interna, a su vez,
puede ser estática o no estática. Las clases internas estáticas se usan muy poco, por lo que
no vamos a detenernos a estudiar sus características. Así pues, queda claro que nos vamos a
centrar en las clases internas no estáticas o, simplemente, clases internas o anidadas  a
secas.

Figura 4.1: Representación de una clase interna

Una clase puede contener una o más clases internas. La relación que existe entre una
clase interna y su correspondiente clase externa la desgranamos en los siguientes apartados:

La clase interna solo es accesible desde la externa; no es visible desde ningún otro lugar
aunque esté declarada como pública.

Desde la clase externa no se puede acceder directamente a ningún miembro de la clase


interna. Para conseguirlo, hay que hacerlo de forma indirecta, a través de un objeto de
la clase interna.

40
Desde la clase interna sí se puede acceder directamente a los miembros de la clase
externa.

Vamos a ver esto que acabamos de contar, en un ejemplo:

Fragmento de código 4.1: [Link]


1 c l a s s Externa {
2 int x = 1 ;
3
4 //Los miembros de la clase Interna no son accesibles directamente desde la Externa, SOLO si
5 //se hace a través de un objeto de la primera.
6 void metodoE1 ( ) {
7
8 System . out . p r i n t l n ( "y = " + y ) ; //Erróneo
9 metodoI1 ( ) ; //Erróneo
10
11 I n t e r n a i = new I n t e r n a ( ) ;
12 System . out . p r i n t l n ( " Externa : y = " + i . y ) ;
13 i . metodoI1 ( ) ;
14 i . metodoI2 ( ) ;
15 }
16
17 void metodoE2 ( ) {
18 System . out . p r i n t l n ( " I n t e r n a : soy un metodo de e x t e r i o r " ) ;
19 }
20
21 class Interna {
22 int y = 2 ;
23
24 void metodoI1 ( ) {
25 System . out . p r i n t l n ( " Externa : soy un metodo de i n t e r i o r " ) ;
26 }
27
28 //En cambio, los miembros de la clase Externa sí son directamente accesibles desde la
29 //clase Interna.
30 void metodoI2 ( ) {
31 System . out . p r i n t l n ( " I n t e r n a : x= " + x ) ;
32 metodoE2 ( ) ;
33 }
34 }
35 }

Para terminar, veamos cómo quedaría la clase principal:

Fragmento de código 4.2: [Link]


1 class ExtPrincipal {
2
3 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
4 I n t e r n a i = new I n t e r n a ( ) ; //Erróneo
5 Externa e = new Externa ( ) ;
6
7 e . metodoE1 ( ) ;
8 }
9 }

41
Además de los cheros [Link] y [Link], al compilar el ejemplo anterior
también aparece un tercer chero que representa a la clase interna, con el curioso nombre
Externa$[Link].

Una clase interna también se puede denir en el interior de cualquier bloque de código, en
particular, dentro de un bucle o un método. En este caso, se le da el nombre de clase interna
local. Su uso es especialmente útil para la gestión de eventos en un applet. Lo veremos en
breve.

Figura 4.2: Esquema de cómo se dene una clase local

4.1.1. Clases anónimas

Una clase interna anónima es una clase interna que no dispone de nombre. Es un paso
más hacia la simplicación de la escritura de código empleado para el manejo de eventos,
pero su sintaxis resulta muy confusa hasta que nos habituamos a utilizarla.

Ya sabemos que una interfaz no se puede instanciar directamente, sin embargo, gracias
a las clases anónimas esto estará permitido.

Figura 4.3: Código para la instanciación de una interfaz

Primero vamos a recordar cómo se utilizaba una interfaz interna:

Fragmento de código 4.3: [Link]


1 class ClaseIfazInterna {
2
3 //Denimos una interfaz interna
4 private interface Saludo {
5 public void mensaje ( ) ;
6 }
7
8 //Implementamos esa interfaz en una clase también interna
9 c l a s s I n t e r n a implements Saludo {
10 public void mensaje ( ) {
11 System . out . p r i n t l n ( " Hola " ) ;

42
12 }
13 }
14
15 public void metodoE ( ) {
16 I n t e r n a i = new I n t e r n a ( ) ;
17 i . mensaje ( ) ;
18 }
19 }
20
21 public c l a s s E j C l a s e I f a z I n t e r n a {
22 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
23 C l a s e I f a z I n t e r n a i i = new C l a s e I f a z I n t e r n a ( ) ;
24 i i . metodoE ( ) ;
25 }
26 }

Ahora vamos a crear un programa que realiza exactamente lo mismo, pero nos ahorramos
nombrar una clase. Presta mucha atención al código porque ahora sí podemos instanciar una
interfaz directamente, gracias a las clases anónimas:

Fragmento de código 4.4: [Link]


1 c l a s s ClaseAnonInterna {
2
3 //Denimos una interfaz interna
4 private interface Saludo {
5 public void mensaje ( ) ;
6 }
7
8 public void metodoE ( ) {
9
10 //Aquí es donde instanciamos la interfaz con una clase anónima
11 Saludo s = new Saludo ( ) {
12 public void mensaje ( ) {
13 System . out . p r i n t l n ( " Hola " ) ;
14 }
15 };
16
17 s . mensaje ( ) ;
18 }
19 }
20
21 public c l a s s EjClaseAnonima {
22 public s t a t i c void main ( S t r i n g [ ] a r g s ) {
23 ClaseAnonInterna a i = new ClaseAnonInterna ( ) ;
24 a i . metodoE ( ) ;
25 }
26 }

Se observa que, al compilar, además de [Link], [Link]


y ClaseAnonInterna$[Link], se ha generado el chero ClaseAnonInterna$[Link] que
representa a la clase anónima. Si hubiera más de una clase anónima, el índice nal del nombre
se iría incrementando secuencialmente.

43
4.2. Applets
Un applet o pequeña aplicación es un tipo especial de programa Java, diseñado para
ejecutarse dentro de una página web. Los applets llegaron con la intención de proporcionar
dinamismo a la web pero, poco a poco, fueron desplazados por otras soluciones más e-
cientes. Sin embargo, aquí los vamos a estudiar porque la programación de applets es una
aproximación simplicada a la programación en Android. Los usaremos, por tanto, como un
trampolín hacia nuestro objetivo nal, la creación de aplicaciones en Android.

Vamos a crear un applet llamado [Link] con el siguiente código y, tras compilarlo,
usaremos el chero [Link] como se muestra más abajo.

Fragmento de código 4.5: [Link]


1 import j a v a . a p p l e t . Applet ;
2
3 public c l a s s EjApplet extends Applet {
4 public void i n i t ( ) {
5 add ( new j a v a . awt . Label ( " Hola mundo" ) ) ;
6 }
7 }

Un applet se puede ejecutar de dos formas:

Desde un navegador, utilizando el chero [Link] mostrado a continuación:

<!DOCTYPE html>
<html>
<head>
<title>Ejemplo de applet</title>
</head>
<body>
<applet code=[Link] width=300 height=200></applet>
</body>
</html>
Desde el visor de applets que proporciona el JDK, utilizando el siguiente comando y
el chero anterior:

$ appletviewer EjemploApplet . html

¾Qué hace tan especial a un applet? Pues que, a diferencia de un programa Java de
consola, aquel carece de método main() ya que se ejecuta en el contexto de un navegador.
Por tanto, es el propio navegador quien va controlar el ciclo de vida del applet, utilizando
ciertos métodos con los que envía noticaciones al mismo. Veamos cuáles son:

init(): Se ejecuta cuando el applet acaba de ser cargado por el contenedor. Es invocado
una única vez y se utiliza para realizar inicializaciones básicas.

44
start(): Inicia la ejecución del applet. Es invocado tras init(). Este método también
se ejecuta, por ejemplo, cuando el usuario restaura una ventana del navegador previa-
mente minimizada.

stop(): Detiene la ejecución del applet al menos temporalmente. Se lanza cuando
el usuario minimiza la ventana del navegador, por ejemplo.

destroy(): Hace lo contrario que init(). Se ejecuta una única vez al nal de la vida del
applet para liberar los recursos antes de ser eliminado por el contenedor. Normalmente
realiza acciones inversas a las efectuadas en init().

Para crear una clase que implemente un applet, esta debe derivar de la clase Applet que
primero habrá que importar del paquete [Link] . La clase Applet tiene los métodos
init(), start(), stop() y destroy() vacíos y nuestro applet deberá sobreescribir los que interese,
añadiendo el código necesario.

Como ejemplo, vamos a construir un applet que escriba por la consola un mensaje indi-
cando que se han ejecutado esos métodos. Es necesario ejecutar este applet desde el visor,
ya que si no, los mensajes no serán visibles:

Fragmento de código 4.6: [Link]


1 import j a v a . a p p l e t . Applet ;
2
3 public c l a s s CicloVidaApplet extends Applet {
4 public void i n i t ( ) {
5 System . out . p r i n t l n ( " i n i t " ) ;
6 }
7
8 public void s t a r t ( ) {
9 System . out . p r i n t l n ( " s t a r t " ) ;
10 }
11
12 public void s t o p ( ) {
13 System . out . p r i n t l n ( " s t o p " ) ;
14 }
15
16 public void d e s t r o y ( ) {
17 System . out . p r i n t l n ( " d e s t r o y " ) ;
18 }
19 }

4.3. Gestión de eventos


Un evento normalmente está relacionado con los componentes grácos de una aplicación.
Se puede denir como un cambio en el estado de un determinado elemento visual del pro-
grama. Muchos eventos son generados por la acción del usuario por ejemplo, un clic sobre
un botón o la pulsación de una tecla en un campo de texto y otros son producidos por
el sistema. Una de las principales tareas del programador consiste en asociar código a los
distintos eventos que tienen lugar en su aplicación.

45
En general, hay una forma sencilla de gestionar los eventos y otra bastante más compleja.
Java utiliza la segunda debido a que resulta mucho más eciente que la primera. Para en-
tender bien en qué consiste esta técnica, es fundamental conocer qué elementos intervienen
y el papel juega cada uno. Vamos a intentar describirlo:

La fuente: Es el objeto sobre el que se ha realizado una acción y que, a su vez, genera
un objeto evento. El ejemplo típico es el de un botón que ha sido pulsado, sin embargo,
cualquier componente gráco de una aplicación puede convertirse en fuente.

El evento: Es el objeto que genera la fuente cuando detecta que se ha llevado a cabo
una acción sobre él. Contiene información de lo que ha sucedido.

El oyente: Es un objeto a quien la fuente notica que ha sucedido el evento. No


solo le avisa, también le pasa el objeto con la información. Un oyente, además, debe
implementar un método que contenga las instrucciones a ejecutar cada vez que ese
evento suceda qué hay que hacer cuando se ha pulsado un botón, por ejemplo.
Todo objeto que quiera convertirse en oyente de un determinado evento sobre una
fuente en particular, debe registrarse como tal en la fuente. Es como apuntarse a una
lista gestionada por el objeto fuente, de forma que cuando suceda el evento, solo los
apuntados en dicha lista serán informados del evento.

Para que no haya dudas, vamos a describir el proceso completo: Un usuario hace clic en
un botón. Este genera un objeto evento con la correspondiente información y se lo envía a los
oyentes que tenga registrados. Estos, a su vez, deben implementar una interfaz conteniendo
un método que ejecute las instrucciones oportunas (lo que tenga que suceder al hacer clic en
el botón).

4.3.1. Clases de eventos

Todos los eventos derivan de la clase EventObject del paquete [Link]. Existen muchas
clases que representan eventos, pero nosotros solo vamos a ver algunas de las más utilizadas:

ActionEvent: Se genera un evento de esta clase cuando se pulsa un botón, se hace


doble clic en un elemento de una lista o se selecciona algún elemento de un menú.
Esta clase contiene la constante entera ACTION_PERFORMED, que se utiliza para
identicar qué evento de los anteriores se ha producido.

MouseEvent: Se genera este tipo de evento cuando se pulsa el ratón, se libera o se


hace clic. Contiene las constates enteras MOUSE_PRESSED, MOUSE_RELEASED
y MOUSE_CLICKED, que sirven para diferenciar las acciones anteriores.

KeyEvent: Se genera un evento de esta clase cuando se pulsa una tecla. Contiene
las constates enteras KEY_PRESSED, KEY_TYPED y KEY_RELEASED, que di-
ferencian las acciones anteriores.

TextEvent: Se genera este tipo de evento cuando se escribe algo en un campo de


texto.

46
4.3.2. Oyentes

Para convertir un objeto en oyente, hay que hacer dos cosas:

El objeto debe implementar alguna de las interfaces para gestionar los eventos. Es-
tas interfaces contienen los métodos que se ejecutarán cuando se produzca el evento
correspondiente.

Registrar el objeto como destinatario de las noticaciones de un evento particular, en


el objeto fuente. Para ello, la fuente dispone de un método para cada tipo de evento. De
manera genérica, cuando se produce el evento Tipo Event, la fuente debería registrar
al oyente con el siguiente método:

public void addTipo Listener(Tipo Listener o)

Para concretar, si tiene lugar el evento ActionEvent, entonces un objeto oyente se re-
gistraría en el objeto fuente que ha generado dicho evento, con la siguiente instrucción:

[Link](oyente);

Es habitual que, en la instrucción anterior, el objeto oyente se dena utilizando una clase
anónima. Lo veremos con más profundidad en los ejercicios.

Por otro lado, hay que tener en cuenta que una misma fuente puede generar varios tipos
de eventos y que un oyente puede registrarse en varias fuentes y, por tanto, recibir diferentes
tipos de eventos.

Veamos algunas de las interfaces que más se utilizan:

ActionListener: En esta interfaz contiene el siguiente método, que se ejecuta cuando


se produce un evento del tipo ActionEvent :

void actionPerformed(ActionEvent ae);

MouseListener: Contiene, entre otros, estos tres métodos que se ejecutan cuando se
produce un evento MouseEvent, es decir, cuando se pulsa el ratón, se libera o se hace
clic, respectivamente:

void mousePressed(MouseEvent me);


void mouseReleased(MouseEvent me);
void mouseClicked(MouseEvent me);

KeyListener: Contiene tres métodos que se ejecutan cuando se produce un evento


KeyEvent, es decir, cuando se pulsa una tecla, se introduce un carácter o se libera una
tecla, respectivamente:

47
void keyPressed(KeyEvent ke);
void keyTyped(KeyEvent ke);
void keyReleased(KeyEvent ke);

TextListener: Contiene un método que se lanza en respuesta al evento TextEvent :

void textChanged(TextEvent te);

Veamos un par de ejemplos donde se crea un applet con un botón y se muestra un mensaje
al pulsarlo. Lo haremos sin usar clases anónimas (que no es lo recomendado):

Fragmento de código 4.7: [Link]


1 import j a v a . a p p l e t . Applet ;
2
3 public c l a s s EjAppletBoton1 extends Applet {
4
5 S t r i n g mensaje = "Boton pulsado " ;
6
7 //Creamos la clase del oyente para que implemente la interfaz que contiene el método que
8 //se ejecutará cuando se pulse el botón
9 c l a s s MiOyente implements j a v a . awt . event . A c t i o n L i s t e n e r {
10 public void a ct io n Pe rf o rm ed ( j a v a . awt . event . ActionEvent e ) {
11 System . out . p r i n t l n ( mensaje ) ;
12 }
13 }
14
15 public void i n i t ( ) {
16
17 //miBoton es el objeto fuente
18 j a v a . awt . Button miBoton = new j a v a . awt . Button ( " Pulsame " ) ;
19
20 //Creamos un oyente y lo registramos para recibir noticaciones del evento. Observa que
21 //no hace falta asignarlo a una variable. Se usa directamente como parámetro
22 miBoton . a d d A c t i o n L i s t e n e r ( new MiOyente ( ) ) ;
23
24 //Incluimos el botón en el applet
25 add ( miBoton ) ;
26 }
27 }

Ahora le toca el turno a una variante donde la clase del oyente se dene dentro del método
init() ; todavía no usamos clases anónimas:

Fragmento de código 4.8: [Link]


1 import j a v a . a p p l e t . Applet ;
2
3 public c l a s s EjAppletBoton2 extends Applet {
4
5 S t r i n g mensaje = "Boton pulsado " ;
6
7 public void i n i t ( ) {

48
8
9 //Ahora creamos la clase del oyente dentro del método init()
10 c l a s s MiOyente implements j a v a . awt . event . A c t i o n L i s t e n e r {
11 public void a ct io n Pe rf o rm ed ( j a v a . awt . event . ActionEvent e ) {
12 System . out . p r i n t l n ( mensaje ) ;
13 }
14 }
15
16 //miBoton es el objeto fuente
17 j a v a . awt . Button miBoton = new j a v a . awt . Button ( " Pulsame " ) ;
18
19 //Creamos un oyente y lo registramos para recibir noticaciones del evento
20 miBoton . a d d A c t i o n L i s t e n e r ( new MiOyente ( ) ) ;
21
22 //Incluimos el botón en el applet
23 add ( miBoton ) ;
24 }
25 }

Recordemos que al denir una clase dentro de un método, se creaba una clase interna
local. Si compilamos el ejemplo anterior, observaremos que se crea una clase con el nombre
EjAppletBoton2$[Link]. El número que aparece tras el dólar serviría para diferen-
ciarla de otra clase con el mismo nombre, que estuviera denida en el interior de un método
distinto.

Para terminar, haremos uso de la técnica recomendada, que no es otra que usar clases
anónimas para denir el oyente. La siguiente imagen muestra de manera simplicada cómo
se usa; es muy similar a la instanciación de interfaces, salvo porque ahora la clase anónima
se dene como el argumento del método que registra al oyente:

Figura 4.4: Código para el registro de un oyente

El objetivo es que la gestión del evento esté lo más concentrada posible. A partir de
ahora, siempre emplearemos esta técnica. Presta mucha atención:

Fragmento de código 4.9: [Link]


1 import j a v a . a p p l e t . Applet ;
2
3 public c l a s s EjAppletBotonClaseAnonima extends Applet {
4
5 S t r i n g mensaje = "Boton pulsado " ;
6
7 public void i n i t ( ) {
8
9 //miBoton es el objeto fuente
10 j a v a . awt . Button miBoton = new j a v a . awt . Button ( " Pulsame " ) ;
11

49
12 //Creamos la clase anónima, el objeto oyente y lo registramos, todo en una instrucción
13 miBoton . a d d A c t i o n L i s t e n e r ( new j a v a . awt . event . A c t i o n L i s t e n e r ( ) {
14 public void a ct io n Pe rf o rm ed ( j a v a . awt . event . ActionEvent e ) {
15 System . out . p r i n t l n ( mensaje ) ;
16 }
17 });
18
19 //Incluimos el botón en el applet
20 add ( miBoton ) ;
21 }
22 }

4.4. Control de versiones: Subversion


En realidad, este apartado no está relacionado con Java en particular. Se trata de una
herramienta que se aplica a cualquier proyecto de software, sea cual sea el lenguaje utilizado.

Cuando se desarrolla un proyecto de mediana o gran envergadura en el que participan


varios programadores (posiblemente dispersos geográcamente), es casi imprescindible contar
con alguna herramienta capaz de llevar a cabo algún tipo de control de versiones del código
fuente, ya que a menudo se realizan cambios en el software que, con frecuencia, son fuente
potencial de errores. Los dos principales servicios que proporciona cualquier aplicación de
control de versiones son:

Registrar los cambios realizados en los archivos de un proyecto quién los ha hecho y
cuándo, de forma que, por ejemplo, se puedan deshacer dichos cambios y alcanzar
una versión anterior.

Sincronizar el trabajo de un grupo de desarrolladores, permitiendo el acceso al código


de forma concurrente y remota.

Hay varias herramientas de este tipo, como RCS, CVS o GitHub pero nosotros vamos a
estudiar Subversion.

Su funcionamiento se basa en la creación de un repositorio de código (pudiendo contener


varios proyectos simultáneamente), de forma que uno de los programadores se encarga de
administrar dicho repositorio, y el resto se limita a utilizarlo.

Pasos para poder utilizar Subversion:

Instalación:

apt-get install subversion


Creación de un repositorio: El directorio /ruta/repositorio no debe existir.

svnadmin create /ruta/repositorio


Importar los directorios y archivos (ya existentes) de un proyecto:

svn import /ruta/proyecto protocolo:///ruta/repositorio -m Msj Log

50
Obtener una copia, parcial o completa, del repositorio (una especie de entorno de
trabajo privado): El directorio /ruta/copia no debe existir.

svn checkout protocolo:///ruta/repositorio /ruta/copia


El protocolo debe ser file si lo importamos desde la propia máquina, o svn si lo
importamos desde otro equipo (en este caso, se deberá editar el archivo del repositorio
conf/[Link] y establecer la variable anon-access = write. A continuación, se
deberá ejecutar el servidor como un demonio, con el comando svnserve -d).
Actualizar la copia de trabajo local:

svn update
Realizar cambios en la copia local (que se actualizarán al ejecutar svn commit):
svn add f.c: Añade un chero al repositorio local.

svn delete fod: Elimina un chero o directorio del repositorio local.

svn copy f1.c f2.c: Copia un chero dentro del repositorio local.

svn move f1.c f2.c: Mueve un chero dentro del repositorio local.

svn revert f.c : Deshace los cambios realizados en un archivo.

Examinar los cambios :

svn status: Muestra los cambios realizados en la copia local.

svn diff: Muestra diferencias entre el archivo copia y el original.

Fusionar los cambios de otros en nuestra copia local:

svn merge: Mezcla los cambios de otros usuarios en nuestra copia, siempre que no
haya conictos.

svn resolved: Si al intentar mezclar se producen conictos, hay que resolverlos a


mano o deshacer nuestros cambios (con svn revert). Una vez hecho esto, ya se puede
ejecutar el comando para indicar que ya no existen dichos conictos.

Enviar los cambios locales al repositorio:

svn commit [fichero descripción]: Se utiliza para enviar la copia local al reposi-
torio y para avisar a otros usuarios de nuestros cambios en un chero (conviene escribir
una descripción de los cambios).

Obtener ayuda sobre comandos:

svn help [comando]

Los IDE modernos como Eclipse o NetBeans permiten gestionar las operaciones
desde el menú, sin necesidad de acceder a la consola. Para ello, se debe instalar un plugin
adecuado en cada caso.

51

También podría gustarte