Vous êtes sur la page 1sur 83

por David Muñoz Díaz

Este docume nt o está escrito para ayudar a quien lo necesite. Puedes hacer con él
lo que quieras, pero siempre de forma gratuita y desinteresa da. Además, es demasiado
malo como para venderlo. Se reirían de ti :)

Si quieres aportar algo a este documen to, no dudes en ponerte en contacto con
el Grupo de Usuarios de Linux de la Universidad Carlos III de Madrid (gul@gul.uc3m.es ).

Gracias a:

· Jose Maria Chumo por sus sugerencias para mejorar este manual

· Fernando Cerezal por publicitarlo en su charla sobre Program ación Orientada a


Objetos y Java

· y a los señores del GUL por mantenerlo disponible para todo el mundo
Contenido
Aquí tienes la lista de contenidos del texto. Los aparta d o s marca dos con * son sólo
orienta tivos y no están amplia me nte explicados aquí, ya que no forma n parte del
objetivo de este texto. Puedes encontr a r una explicación exhaustiva de estos aparta d o s
en cualquier libro de Java, ademá s de las explicaciones que te dará el profesor de la
asignatu r a. Bueno, en alguna s ocasiones, que debería dar.

Prefacio * (que no, que es coña)

Introducción. Cultureta.
pelín de historia

Platón y la OOP
el proble ma...
... y la solución
concepto de objeto
propie da d e s
méto d o s
concepto de clase

Java
¿por qué Java?
caracteríaticas generales de Java *
facilidad (je je jeeee)
lenguaje interp re ta d o
tamaño de los program a s
orientado a Internet
JVM
Cómo usar el compilad or y la JVM (javac, java)

El movimiento se demuestra andando


definción de una mesa
propie da d e s
tipos básicos de datos *
méto d o s
valor de retor no
pará me tr o s
creación y uso de objetos
creación
primer o: las referencias
segun d o: new
uso
el opera dor punto
más sobre creación: constr ucto r e s
¿qué son?
peculiarida de s
definición y uso
breve iniciación al polimorfis mo *
méto d o s get/ set
¿Por qué?
uso

Variables de clase. Modificador Static


el proble ma...
... y la solución
uso de static
en propie da d e s
en méto d o s
consta n te s
El método main, arrays y ¡a lanzar programas!
por qué el méto d o main
definición del méto d o main
argu me n t o s
un inciso: arrays
sacabó el inciso
nuestr o primer progra m a

Programando a lo bestia: estructuras de control


Sentencias condicionales
sentencia if
expresión lógica
bloque de sente ncias
compa r a d o r e s de magnitu d
AND, OR, NOT
compa r a d o r e s de cadenas
ifs anida dos
if sin elses: CUIDADO!
solución rara
solución más rara
sentencia switch
uso
break
comer te el coco 4
Sentencias repetitivas
definición
sentencia while
regla general para el uso de bucles
variación do - while
sentencia for
¿While o for?

Recursividad
definición
metodología: la cola del cine
partes de un método recursivo
los factoriales

Herencia
para qué sirve
cómo se usa: exten d s
redefinición de méto d o s
uso de super()
una nota bastante importante

Casting
prelu dio al casting: las referencias
la señora referencia
cómo funciona
compa r ació n de referencias
Arreglan d o bicicletas: pará me tr os que son referencias
casting
¿Por qué?
cómo se usa
resu me n
comer te el coco 6
upcasting, downcasting, Y siguiendo con el inglés, explicit and implicit cast
upcasting
downcasting
casting implícito
casting explícito
La Clase Object
definición
contenid o *
¿para qué ese contenido?
la gran utilidad de la clase Object: referencias de tipo Object

Interfaces
definición
uso básico de interfaces
uso avanzado de interfaces: referencias de tipo de interfaces
¿herencia?¿Interfaces?¿comorl?

Epílogo

El contenido de este texto es el estrictamente necesario para poder


abordar cualquier problema de Programación. Este texto es, por tanto, la
base necesaria para poder enfrentarnos a las asignatur as de
Programación y Laboratorio de Programación. En este texto no vamos a
ver deter mina dos temas, como Applets, paquetes, pruebas de programas,
etc. Para cualquier consulta sobre dichos temas, te recomiendo los libros
de Java de la editorial Sun o alguno de la infinidad de manuales gratuitos
que hay por la red.

Por tanto, es necesario que te conciencies de que leer este texto no


implica aprobar: implica ser capaz de aprobar. Si te sirve de algo, cuando
yo llegué a primero, sabía bastante menos de lo que hay aquí escrito.
Prefacio

No te engañes: tenemos mucha labor por delante. Pero no hay que


pensar que es ardua y temible. En absoluto. O al menos, espero que no lo
sea para ti.

Programar implica siempre dos cosas:

Primero, hay que comerse el coco bastante, pero no para alcanzar


complejos conceptos (bueno, para esto también), sino para todo lo
contrario: para conseguir poner nuestro pensamiento a la altura de un
cacho de silicio, que no parece muy listo....

Segundo, la curiosidad no mata al gato, sino que

curiosity Skilled the cat


(por cierto, n.p.i. de de quién es esta frase. Se la he copiado a un
miembro del GUL. Así que si esta frase es suya, le pido diculpas por no
pagar derechos de autor)

O sea, que siempre, repito, siempre es bueno parar de estudiar y


encender el ordenador para hacer alguna prueba de lo que estemos
estudian do. Si vemos casting será muy positivo que mandes a tomar por
saco los apuntes y escribas un par de clases para probar “el acceso a los
miembros de una calse vía una referencia de tipo la superclase”. Parece
mentira, pero esta frase tiene sentido. En serio, nunca vas a aprender a
programar con un libro, siempre lo aprenderás por tu cuenta. Así que
vete haciendo a la idea de que vas a pasar muncho rato delante de la
pantalla, aunque espero que no sea demasiado tiempo. O que al menos
no sea tedioso.

He intentado explicar todo este rollo usando ejemplos de la vida


real. Tal vez quede “un poco” asquerosa m e nte pedante explicar las clases
metiendo a Platón por medio, pero creo que haciéndolo vamos a entender
los conceptos más fácilmente, y sobre todo, no se van a olvidar. Por
supuesto, no creas que todo es Platón, también hablaremos de tu vecina,
de la mesa Margarita, de “páginas gué”, la bicicleta Anacleta, y de mil
cosas más. Bueno, mil... lo que se dice mil....

Por supuesto, todo ello respaldado por el pez Otilio, el pato


Onorato y el pájaro que no sé cómo demonios se llama, que no son más
que excusas para apartar un poco la atención y hacer la lectura un pelín
más agradable.

Espero haber logrado, aunque sólo sea en pequeña parte, estos


objetivos. Ahora, como dice la historia de la sopa de piedra, debes poner
tú de tu parte. ¡Ánimo!
Introducción. Cultureta.

Normalmente, para empezar se opta por definir qué es programar,


cómo se lleva a cabo, conceptos como “software”, “hardware”,
“memoria”, etc etc etc. Vamos a ignorar todo
este rollo porque realmente no importa
en absoluto. Nosotros, por ahora, no
nos vamos a preocupar de la Yo soy un
programa do r
capacidad de la memoria o de la leche.
de la velocidad del proce -
sador. A nosotros nos inte-
resa programar.

Cuando aprende m os
a programar siempre se nos
plantea si debemos
compaginar la progra -
mación con el lenguaje de programación. Parece
una tontería, pero podemos aprender OOP sin saber ningún
lenguaje. Es cierto: podemos saber sobre clases, objetos, herencia,
casting, etc sin saber una maldita palabra de ningún lenguaje. Sin
embargo, siempre es positivo compaginar nuestro aprendizaje de
programación con el de un lenguaje propicio para ello.

Además de la OOP, y a modo de curiosidad (cultureta), existen


otros tipos de programación, cada uno con un lenguaje de programación
típico. Esta parte, si quieres, te la puedes saltar.

La programación “lineal” tiene como fundador el antiguo BASIC. A


lo largo de los años, el BASIC ha ido cambiando hasta que hoy en día
existen compiladores de BASIC orientado a objetos (Visual Basic, por
ejemplo, mantiene una filosofía de OOP). La programación lineal se daba
con los primeros intérpretes de BASIC. Aquí hay un ejemplo de este
lenguaje:
10 INPUT “¿Cómo te llamas?”, NOMBRE$
20 SALUDO$ = “¡Hola “+NOMBRE$+” ¡”
30 PRINT SALUDO$
40 END

¿Ves? El programa empieza en un punto, y acaba en otro. Esto,


claro, tiene sus inconvenientes: no podemos hacer repeticiones, ni nada
de eso. Nacieron las “sentencias condicionales”:

10 INPUT “¿Cómo estás?”,COMO$


20 IF COMO$=”bien” GOTO 50
30 IF COMO$=”mal” GOTO 70
40 GOTO 10
50 PRINT “Vaya, me alegro.”
60 GOTO 80
70 PRINT “Alégrate!”
80 END

El siguiente paso es la programación procedimental: consiste en dividir el


código en fragmentos independientes. Lenguaje típico, el Pascal.
var
cad:String;

procedure debuti;
begin
Writeln('me alegro');
end;

procedure notandebuti;
begin
Writeln('alegrate!');
end;

begin
Writeln('¿Cómo estás?');
readln(cad);
if (cad='bien') then debuti else notandebuti;
end.

Hay muchos más tipos de programación, y también muchos otros


lenguajes muy raros, Smalltalk, ADA, Prolog.... En realidad, hoy en día
muchos de estos lenguajes se usan con fines didácticos o experimentales.

A nosotros nos interesa la “Programación Orientada a Objetos”


(OOP, de Object - Oriented Program ming). Se basa en “encapsular”
fragmentos de código en “burbujas” independientes entre sí. Los códigos
mostrados antes no tienen sentido en OOP. Vamos a ver por qué.

Platón y la OOP

La pregunta es las siguiente: Tenemos una mesa en la cocina, una


en el salón, una mesilla de noche y una en un taller. Estas mesas son
diferentes entre sí, pero, sin embargo, hay algo en ellas que hace que
todas SEAN mesas. Una es cuadrada, otra redonda, unas con tres patas y
otras con cuatro. ¿Por qué, si son tan diferentes, son todas mesas?

Este hombre de aquí dijo que había un mundo


aparte del nuestro en el que existían unos “seres”
perfectos, inmutables, universales, etc etc etc, a quienes
llamó Ideas. Los seres de nuestro mundo físico serían
“copias” imperfectas de estas Ideas. Imagina por tanto
que todos los objetos que tenemos alrededor son copias
de las Ideas. De esta forma, al ser copias imperfectas, no
serían iguales entre sí, pero seguirían teniendo esa
“esencia común”.

Esta teoría sirve para explicar la realidad y dar un


poco de cabida a la ciencia. Si la ciencia trata sobre las cosas globales, es
decir, los hechos universales, no los específicos, entonces tiene que haber
algo universal.

Bueno, pues nuestra intención es semejante: vamos a tratar de


explicar, no la realidad, sino los problemas que nos encontram o s, que de
una forma u otra, son parte de la realidad. ¿o no?

En OOP definimos un concepto más que fundame ntal: el concepto


de objeto .
Un objeto es un conjunto de:

a) Datos
b) Métodos

Supongamos dos objetos semejantes, por ejemplo, dos mesas. Datos


que pueden concernir a las mesas, por ejemplo, son su color, número de
patas, su forma, etc. Así, si tenemos dos “objetos mesa”, cada uno con
datos diferentes, tenemos dos mesas diferentes, que es lo que
planteába mo s al principio: dos objetos de la misma naturaleza (“mesa”),
pero diferentes entre sí. ¡A Platón esto le habría encantado!

Debido a que los datos de un objeto definen en parte este objeto (lo
diferencian de los demás), a veces llamamos a estos datos “Propiedades ”.

¿Se te ocurre algún método propio de una mesa? A mí no, las mesas
son objetos puramente pasivos, ellas mismas no hacen nada. Sin
embargo, vamos a suponer que el cambio de color es una acción propia
de la mesa (y no del pintor).

De acuerdo, ya tenemos el diseño de nuestra mesa. Ya conocemos el


concepto de objeto . Vamos a seguir con el señor Platón.

¿Cómo un carpintero puede crear una mesa? Según este griego, su


alma, antes de nacer, vislumbró las Ideas, y durante la vida de la persona,
el alma recuerda algunas de estas Ideas (aunque no todas....) Así, un
carpintero recuerda haber visto la “Idea de Mesa”, y por ello sabe hacer
mesas. Es decir, el carpintero se FIJA en la “Idea de Mesa” para crear una
mesa.

Si ya conocemos lo que es un Objeto, ¿Qué es una Idea? Pues,


sencillamente, la definición de ese objeto, que, por cierto, nosotros
llamamos clases . Platón dice que los objetos físicos son copias de las
Ideas. Nosotros decimos que los objetos con instancias de las clases.
Para Platón, los objetos son copias de unos
seres universales llamados Ideas. Las Ideas
son, por tanto, la definición de los
objetos físicos, aunque éstos pueden ser
diferentes entre sí. Para la OOP, los
Objetos son instancias de unas
definiciones generales que llamamos
Clases. Dos objetos con propiedades
diferentes
(dos mesas con diferente color)
siguen siendo instancias de la misma
clase (la “clase mesa”).

Éstos son los


conceptos funda ment ales de la
OOP. Es estrictamente necesario compren derlos a fondo antes de seguir
adelante. Párate un momento a pensar si entiendes a fondo...

OBJETO
CLASE
PROPIEDADES
MÉTODOS
INSTANCIA

¡Cuidado!
Si decimos que dos objetos de la misma clase tienen las mismas
propiedades, tal vez lo que realmente queramos decir es que dos
objetos de la misma clase tienen EL MISMO VALOR PARA TODAS Y
CADA UNA.DE SUS PROPIEDADES. Es evidente que dos objetos de la
misma clase van a tener las mismas propiedades: dos mesas
tendrán color, forma, número de patas, etc. Pero tal vez digamos
que dos mesas tienen las mismas propiedades cuando queremos
decir que ambas mesas tienen la misma forma, el mismo color y el
mismo número de patas. Hay que tener cuidado con esto, aunque
tampoco es tan importante.

Para comerse el coco.... (1)


Tenemos dos objetos de la misma clase, con exactamente las
mismas propieda des ¿Son el mismo objeto?

Java

Java es un lenguaje de programación. La verdad es que no va a


recibir ningún premio por este simple hecho. Sin embargo, sí va a recibir
cierta atención por nuestra parte, porque, sencillamente, es la
herramienta que nosotros vamos a utilizar. Que quede muy claro, Java es
un MEDIO para programar en OOP. Podemos programar en OOP con otros
muchos lenguajes, Delphi, Visual Basic, o C++.
Java nació con una intención muy clara: hacer la vida más fácil al
programa do r. C+ + es el lenguaje más potente que existe ahora mismo.
Pero tiene un grave problema: es lo más complicado que hay. Un ejemplo:

#include <owl.h>

// Define la clase derivada de TApplication

class THelloApp : public TApplication


{
public:
THelloApp(LPSTR AName,
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)

: TApplication(AName,
hInstance,
hPrevInstance,
lpCmdLine,
nCmdShow) {};
virtual void InitMainWindow();
};

// el MainWindow de la clase

void THelloApp::InitMainWindow()
{
MainWindow = new TWindow(NULL, "Hola Mundo");
}

int PASCAL WinMain(HINSTANCE hInstance,


HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
THelloApp HelloApp ("HelloApp",
hInstance,
hPrevInstance,
lpCmdLine,
nCmdShow);
HelloApp.Run();
return HelloApp.Status;
}

Bueno, pues este fragmento de código abre una ventana con el


título “Hola Mundo!”.

Visto lo visto, Java trata de hacer la vida un poco más fácil.


Además, un programa en Java ocupa bastante poco espacio en disco,
mientras que uno en C++ ocupa todo lo que quieras y un poco más. Así
que, gracias a eso, Java es también propicio para aplicaciones que corran
por Internet y esas cosas. Pero todo eso ya lo veremos.
Java NO lo entiende directamente el procesador. Java es un
lenguaje INTERPRETADO, es decir, que se “traduce” para que el
procesador pueda entenderlo. C++, una vez compilado, es entendido
perfectamente por el procesad or, mientras que Java no. Java se compila y
se generan los “bytecodes”, que es otro lenguaje mucho más sencillo,
pero el procesador sigue sin entenderlo. Para ello, los bytecodes tienen
que INTERPRETARSE por la “Java Virtual Machine” (JVM), que, en realidad,
es otro programa (escrito, por cierto, en C++) que sabe interpretar los
bytecodes. Bueno, esto es un poco extraño, pero en realidad es muy fácil.
Al escribir un programa en C+ +, por ejemplo, se compila y funciona
solito. Pero al escribirlo en Java, se compila y necesita otro programa que
lo haga funcionar. Este otro programa se llama JVM. Ya está. No es tan
mortal, ¿no?

¿Ventajas de Java sobre los lenguajes no interpreta dos? muchas,


por ejemplo, seguridad: Si un programa en Java intenta hacernos la
puñeta, la JVM lo detendr á, mientras que el procesador no podrá detener
nunca un virus escrito en C++. Otra ventaja es el ya mencionado
reducido tamaño de los programas en Java. Otra, mucho más importante,
un programa en Java funciona en Windows, en Linux, en Mac, en Solaris,
etc, porque lo que cambia es la JVM.

Aquí no vamos a extender nos en el funcionamiento de los


compiladores de Java y las JVM. Sin embargo, vamos a recordar un poco
el proceso.

1.- Creamos el archivo “xxxx.java”


2.- lo compilamos con “javac xxxx.java”
3.- lo ejecutam os con “java xxxx”

El paso 3 es el que invoca a la JVM. Más explicaciones, ayuda en


línea.

Antes de seguir adelante, vamos a pararnos un poco para


recapacitar sobre si has entendido los conceptos de:

LENGUAJE Empiezo a
plantear me
LENGUAJE INTERPRETADO esto de
programar....
Y COMPILADO
COMPILADOR
JVM
BYTECODES

Para comerse el coco......(2)


Teniendo en cuenta que la JVM es
un programa escrito en un lenguaje
compilado, ¿puede ser entendido por la propia JVM?

El movimiento se demuestra andando.

Vamos a escribir un pequeño programa en Java. ¿Su fin? definir las


mesas que tanto tiempo nos han acompa ñad o. Recordemos la CLASE
MESA:

Propiedades: color, número de patas, forma


Métodos: cambiar el color

Bueno, vamos a pensar un poco. Como sabrás, los nombres o


IDENTIFICADORES en Java son una cadena de caracteres que almacenan
un valor determinado, o el nombre de un método, o el nombre de una
clase, o yo qué sé. Tenemos que tratar de inventarnos identificadores con
nombres sencillos, que no sean rebuscados, sin caracteres raros. A mí se
me ocurren los siguientes:

para el color, pues “color”


para el número de patas, “numPatas”
para la forma, pues “forma”
para el método que cambia el color, pues “cambiarColor”

Definición de propiedade s

Vamos a centrarnos en las propiedades, y luego veremos los


métodos.

Seguimos pensando. El color será una cadena de caracteres


(“marrón”, “violeta”, joer, una mesa violeta...), que en Java se denomina
“String”, al igual que la forma (“Redonda”, ”cuadrada”). El número de
patas será un número entero, que se llama “int”.

La definición de la clase Mesa.java empezaría así:

class Mesa {
String color;
int numPatas;
String forma;
.
.
.

Esto ya tenemos que ser capaces de entenderlo bien. Una mesa


tiene un color, un número de patas y una forma. El número de patas es
un número entero, mientras que la forma y el color son cadenas de
caracteres. Bueno, pues así se han definido. Ahora no ha lugar a pensar
cómo se define el color de una mesa determina da, o su forma, o cómo
saber qué color tiene otra mesa cualquiera..... todo eso ya lo veremos.
Ahora estamos DEFINIENDO la clase Mesa, no hacemos nada más.

En cualquier libro encontrarás los tipos básicos de variables y sus


rangos de valores. Tendrás que aprendértelos, aunque sólo para el
examen. Luego, puedes olvidarlos. Básicamente son

int, short, long, double, float, char,


String y boolean.
Este es un
buen
momento
para
aprenderte los
tipos
de datos
básicos.

Definición de métodos

¿ya te los has aprendido?


¡Bien! Vamos a por
el método
cambiarColor.
Un método
consta, aparte de su nombre
(o IDENTIFICADOR), claro está, de

a) valor de retorno.
b) parámetros

Un método puede devolver un valor, o puede no hacerlo. Por


ejemplo, un método que calcule la suma de dos números debería
devolver otro número, o un método que abra un fichero del disco debe
devolver si ha podido abrirlo o no. Ahora bien, hay métodos que no
tienen por qué devolver nada. Por ejemplo, un método que muestre un
saludo en pantalla no tiene por qué devolver nada.

A la hora de definir el tipo de dato que devuelve un método, se


antepo ne al nombre de ese tipo de dato (int, float, boolean, String....) al
nombre del método. Por ejemplo:

int calculaSuma() / / devuelve un entero


float raizCuadrada() / / devuelve un real
boolean abrirArchivo() / / devuelve un valor lógico
Los métodos que no devuelven nada se declaran como void:

void saludo() / / no devuelve nada

Bien. Vayamos ahora a por los parámetr os.

Un método que calcule 5*4 está claro que no va a necesitar parámetr os,
porque el método no puede hacer nada más que calcular 5*4. Sin
embargo, un método que calcule la multiplicación de dos enteros
cualquiera necesitará conocer esos enteros, es decir, se los daremos como
parámetro. Es como si queremos saber dónde está determinada calle, y le
pregunta m os a alguien: tenemos que decirle qué calle buscamos.
Imagínate, “Disculpe, ¿podría decirme dónde está la calle?” No tiene
sentido, habría que preguntar “Disculpe (educación ante todo), ¿podría
decirme dónde está la calle Butarque?”. Bueno, pues como éste se te
pueden ocurrir mil ejemplos

Es
MUY IMPORTANTE
que diferencies cuándo
un método necesita
parámetros y cuándo no.

Una cosa sí es cierta: no hay una regla general para saber si un


método necesita parámetros, para saberlo necesitamos la mplementación
del código, y el sentido común.

Vamos a ver unos ejemplos:

int suma(int a, int b)


recibe un entero a y un entero b y devuelve otro entero

String concatenar(String a, String b)


recibe dos cadenas de caracteres y devuelve otra cadena de
caracteres

float raizCuadrada(int a)
recibe un entero y devuelve un real

Debe quedar MUY MUY MUY claro este rollo de los parámetr os y
del tipo de dato devuelto por un método. Vamos a ver los métodos
anteriores implementa dos y comenta dos para que se vea todo mucho
mejor, y ya de paso aprendem o s cómo hace un método para devolver un
valor usando “return”:

int suma(int a, int b){


return a+b;
}

Este método recibe dos enteros, a y b, y devuelve su suma, que será


otro entero.
Si hacemos en cualquier parte del programa

int numero = suma(3,5);

estamos diciendo que el entero “numero” va a valer lo que devuelva


el método “suma” pasándole como parámetr os 3 y 5. “suma” devolverá
3+5, y entonces “numero” pasará a valer 3+5.

String concatenar(String a, String b){


return a+“ ”+b; / / las comillas encierran un espacio en blanco
}

Recibe dos cadenas y las concatena poniendo un espacio en blanco


entre medias. Fíjate en la diferencia entre usar “+” con números y usarlo
con cadenas. Si en cualquier punto del programa escribimos

String cad = concatenar(“Hola”,”Mundo”);

estamos diciendo que la cadena (String) “cad” va a valer el


resultado que “concatenar” devuelva pasándole como argumentos las
cadenas “Hola” y “Mundo”; Como “concatenar” coge las dos cadenas que
hemos pasado como parámetros (“Hola” y “Mundo”) y las devuelve juntas
con un espacio entre medias (devuelve “Hola Mundo”), la cadena “cad”
pasará a valer “Hola Mundo”

void saludo(){
System.out.println(“Holas!”);
}

este método no tiene parámetros, y no devuelve nada de nada. Así,


se le invocará en cualquier parte del programa:

saludo();

y ya está. Como en el método no hay definidos parámetros, pues


no le pasamos parámetr os, y como el método no devuelve nada, pues es
ilícito escribir algo como:

int resultado = saludo(); / / mal!!!!!


porque “saludo()” no devuelve nada (recuerda: se ha definido como
“void” y no se ha escrito ningún “return”), y como no devuelve nada, Java
no sabe qué valor meter en “resultado”.

Bueno, pues este es el funcionamiento de un método. A modo de


resu men, cuando un método es invocado, puede necesitar o no
parámetros. Que los necesite o no depende de cómo hemos definido
dicho método, es decir, si hemos definido variables entre los paréntesis
del método:

... metodo(parametro1, parametro2, ....) por ejemplo:


int suma(int a, int b)

o si no las hemos definido:

... metodo() por ejemplo:


void saludo()

Un método puede devolver un valor, o bien puede no devolver nada.


Depende de cómo hemos definido el método: si lo definimos como
“void”:

void saludo()

entonces no devuelve nada, pero si lo definimos de otra forma:

int suma(....)

entonces devolverá un dato de un tipo determina do (int, float...), y será


OBLIGATORIO escribir un “return” en el cuerpo del método.

int suma(int a, int b){


int resultado = a+b;
}

Este método es casi igual al que hemos visto poco más arriba,
peeeeeeeeeero tiene un grave problema: hemos dicho que devuelve un
entero ( INT suma(int a, int b) ), pero no hay ningún “return” en el cuerpo
del método. Habrá un error de compilación!

Una cosa más, que un método devuelva algún valor NO IMPLICA


(curiosamente) que haya alguna variable que reeciba dicho valor. Es decir,
las siguientes líneas son perfectamente legales:

int resultado = suma(5,3);


.
.
.
suma(5,3);
La única pega es que la segunda línea llama al método “suma”, le
pasa sus parámetr os pertinentes, pero nadie recibe el resultado. Esto,
parece una chorrada, pero es útil. Supongam os que tenemos un método:

boolean abrirFichero(String nombreFichero)

cuya tarea es abrir un fichero determinado. Este método devuelve


un boolean que indica si se ha abierto el fichero con éxito. Bien, puede
que en algún momento hagamos:

boolean hemosPodido = abrirFichero(“miFichero.txt”);

para saber si hemos podido abrir el fichero. Esto sería lo más normal,
pero en algunas ocasiones no necesitamos saber si se ha abierto
exitosamente, porque sabemos que va a ser así, por ejemplo, al hacer
algún programilla tonto en nuestra casa. Si no necesitamos asegurar nos
de si el fichero se ha abierto o no, entonces podemos hacer

abrirFichero(“miFichero.txt”);

e ignoramos el valor que abrirFichero devuelve.

Bueno, ¡pues esto es todo sobre los métodos! Antes de seguir


adelante, como siempre, asegúrate de haber entendido bien los conceptos
de:

PROPIEDAD
MÉTODO
TIPO DE DATOS (int, float....)
PARÁMETROS
VALOR DE RETORNO DE UN MÉTODO
SENTENCIA RETURN
VOID
Recuerda siempre que un valor de retorno puede ser ignorado

Ejercicio

Una vez hayas entendido perfectamente todo este rollo, puedes


escribir la definición completa de la clase Mesa. Plantéate qué parámetr os
le pasaríamos al método cambiarColor() y qué valor devolvería. ¡Vamos!

Solución

A mí se me ocurre que cambiarColor() tendría como parámetr os


una única String, y no devolvería nada. El resto de la clase ya lo hemos
definido.
class Mesa {
String color;
int numPatas;
String forma;

void cambiarColor(String nuevoColor){


color = nuevoColor; Empieza lo
bueno...
}
}

Creación y uso de objetos

Ya tenemos definida la clase Mesa. Ahora quiero crear una mesa.


¿Cómo se hace eso?...

primero: las referencias

Cuando creemos un objeto, la JVM genera una “burbujita” que es


nuestro objeto. Claro, que para que ese objeto haga cosas necesitamos
“llamarlo” de alguna forma. Si quieres que, en medio de una fiesta
atestada de gente tu amigo Miguel te diga, por ejemplo, la hora, como
digas “¡Dime la hora!”, no te van a hacer ni caso, porque cada uno
pensará (si te ha oído) que no le estás llamando a él. Tendrás que decir
“¡Miguel, dime la hora!”, para que el señor Miguel te haga caso. En
definitiva, que si vas dando gritos por la calle ni Dios te va a hacer caso,
mientras que si a cada frase le antepones el nombre de la persona a la
que imperas, pues ya te puede atender.

En Java esto es exactamente igual, a cada objeto se le debe dar un


nombre para poder “llamarle” de alguna forma cuando le necesites. Bien,
pues a estos nombres se les denomina Referencias . Si yo creo dos mesas
y quiero cambiar el color de una sóla, ¿Cómo le digo a la JVM a qué mesa
quiero cambiar el color? Pues fácil, si creo dos mesas, a una la llamo
“Margarita” y a la otra “Alberto”, y luego digo que cambie el color de
Margarita.

segundo: el operador “new”

“new“significa “nuevo”. Y este es un ejemplo de otra cosa que por


este simple hecho no va a recibir ningún premio. “new” sirve para

a) crear un nuevo objeto


b) definir la REFERENCIA a ese nuevo objeto.

¿Cómo se usa? Pues muy fácil:


CLASE REFERENCIA = new CLASE();

por ejemplo:

Mesa Margarita = new Mesa();


Mesa Alberto = new Mesa();
por ahora, olvida
los
paréntesis de “new
CLASE()”

¿Por qué esos paréntesis


en “new Mesa()”? Por ahora,
haz caso al pez.

Pues ya sabemos definir objetos e incluso crearlos! Ahora, vamos a


aprender a utilizarlos.

Supongamos que quiero crear dos mesas, Margarita y Alberto, y


quiero cambiar el color de la mesa Margarita. Tendré que llamar al
método cambiarColor() de la mesa Margarita. ¿Cómo se hace esto? Pues
usando algo tan tonto y tan simple como un punto:

Mesa Margarita = new Mesa();


Mesa Alberto = new Mesa();
Margarita.cambiarColor(“Rojo”);

¿Quiero cambiar el color de la mesa Alberto?

Alberto.cambiarColor(“Azul”);

Ya está. Hala. ¿Parecía complicado todo esto? Pues ya ves qué


complicación más grande. Ya sabemos definir objetos, crearlos y usar sus
métodos. Para que te convenzas, aquí tienes un cachito de mi práctica de
Junio:
log.write(" tipoReserva = "+r.getTipoReserva());
log.write(" horaInicio = "+r.getHoraInicio());
log.write(" horaFin = "+r.getHoraFin());
log.write(" fecha = "+r.getStringFecha();

Sabiendo que “log” y “r” son objetos, ¿Hay algo complicado? ¿Hay
algo que no entiendas? ¡Claro que no!

Constructores
A primera vista, parece que “new” es un CONSTRUCTOR, porque su
misión es construir nuevos objetos. Bueno, pues nooooop. “new” INVOCA
a un constructor, NO ES un constructor. Bueno, no es tan difícil. “new”
invoca a un constructor. Vale. ¿Y qué es un constructor?

Un constructor es un método especial, propio de cada clase. Su


función es INICIALIZAR el contenido, las propiedades, el estado, etc etc
del objeto. Podemos hacer que cuando creemos una mesa cualquiera,
comience siendo cuadrada de cuatro patas y azul. Pero eso lo veremos
luego.

¿Por qué el constr uctor es especial? Por varias razones:

Primero : se invoca UNA VEZ para cada objeto. Los métodos de un


objeto se pueden llamar todas las veces que quieras:

Margarita.cambiarColor(“rojo”); / / y acto seguido....


Margarita.cambiarColor(“azul”); / / y otra vez...
Margarita.cambiarColor(“verde”);

Sin embargo, el constructor va a ser invocado una sóla vez, cuando


lo diga “new”.

Segundo : nunca devolverá ningún valor

Tercero : sin embargo, no se define como “void”. Recuerda que


cuando un método no devuelve ningún valor, se define como
“void”. Bueno, pues el constructor no.

Cuarto : su nombre es el mismo nombre de la clase a la que


pertenece. No puedes escoger el nombre del constructor.

¿Recuerdas los paréntesis a los que se refería el pez?

Mesa Margarita = new Mesa();

¿Por qué estos paréntesis? Pues está ya claro: “Mesa()” es el nombre


de un método, el CONSTRUCTOR, y por eso lleva paréntesis.

Bueno, pues una vez dicho esto, vamos a cambiar un poco lo que
decíamos sobre el uso de “new”, y vamos a cambiarlo por:

CLASE REFERENCIA = new CONSTRUCTOR();

Antes de seguir, asegúrate de entender bien los conceptos de

REFERENCIA
OPERADOR PUNTO “.”
NEW
CONSTRUCTOR

Definición de constructores

Vamos a definir un constructor para la clase Mesa. Nuestra


intención, hacer que cada mesa nueva sea cuadrada, azul y con cuatro
patas. Bueno, pues es muy fácil, añadimos a la clase el método en negrita:

class Mesa {
String color;
int numPatas;
String forma;
RECUERDA:
el constructor
tiene el
void cambiarColor(String nuevoColor){ mismo nombre que su
clase,
color = nuevoColor; no devuelve ningún
valor,
} y se define sin
“void”

Mesa() {
color = “azul”;
numPatas = 4;
forma = “cuadrada”;
}
}

Ahora, cuando hagamos

Mesa Margarita = new Mesa();

Margarita será una mesa cuadrada azul de cuatro patas, ¡Una mesa
en condiciones, vaya!

Pero hay un problema: yo no quiero mil mesas iguales. Yo quiero


crear una mesa como a mí me dé la gana, yo no quiero que todas las
mesas sean azules y cuadradas con cuatro patas. ¿Se te ocurre algo para
solucionar esto?

¡Podemos definir un constr uctor con parámetr os! Así, cuando


creemos una mesa le daremos su color, su número de patas y su forma
iniciales. A ver, sería algo así:

Mesa(String colorInicial, int numPatasInicial, String


formaInicial){
color = colorInicial;
numPatas = numPatasInicial;
forma = formaInicial;
}

Esto es genial. Ahora podemos hacer:

Mesa Margarita = new Mesa(“rojo”, 4, “redonda”);


Mesa Catalina = new Mesa(“verde”, 3, “triangular”);

Hala. ¿Qué constructor te gusta más? Podemos prescindir de uno


de ellos. Aunque, gracias a algo que llamaremos “Polimorfism o ”,
podemos quedar nos con los dos constructores A LA VEZ. (¡Vaya! dos
constructores diferentes!)

El Polimorfismo
permite tener varios
métodos diferentes entre sí
y con el mismo nombre.
Un buen momento para echarle
un vistazo!

Si te digo la verdad, yo pocas veces uso los constructores con


parámetros (que no significa que sean malos, ¿eh? no te creas......). En su
lugar, utilizo los.....

Métodos get / s et

Cuando no definimos un constr uctor en una clase, al invocar “new”


la JVM usará el CONSTRUCTOR POR DEFECTO, que inicializa las
propiedades del objeto con ceros, referencias nulas, etc (¡que lo de
“referencias nulas” no te asuste! ya lo veremos). Para inicializar las
propiedades usamos un constructor “personalizado”, “propio”,
“característico”, o como lo quieras llamar. Estupendo.

supón que en medio del programa quiero cambiar UNA SOLA


propiedad de un objeto. En nuestro caso, puedo cambiar el color. Pero
imagina que tengo una mesa roja:
Mesa Margarita = new Mesa(“rojo”, 4, “redonda”);

Y quiero hacer que sea amarilla. Bueno, pues tengo dos opciones:

primera : crear una nueva mesa, casi igual:

Mesa Margarita = new Mesa(“amarillo”, 4, “redonda”);

(Esto parece guai, pero te aseguro que no siempre va a resultar


fácil)

segunda : definir una serie de métodos que cambien UNA SOLA


propiedad del objeto. A este tipo de métodos se les denomina “métodos
set”, porque suelen comenzar con la palabra “set” (“poner”):

Vamos a definir tres nuevos métodos para nuestra clase Mesa:

void setColor(String nuevoColor){


color = nuevoColor;
}

void setForma(String nuevaForma){


forma = nuevaForma;
}

void setNumPatas(int nuevoNu mPatas){


numPatas = nuevoNu mPatas;
}

Fíjate tú qué cosas, oche. Ahora resulta que “setColor” y


“cambiarColor” hacen exactamente lo mismo. Prescindiremos de uno de
ellos. Por ejemplo, de “cambiarColor”.

Está bien esto, ¿eh?

Bueno, tómate un respiro, y vamos a por los métodos “get”. Su


función es obtener (get) el valor de una propiedad determinada. No es
difícil adivinar que todo método “get” se definirá con algún tipo de dato
de retorno (int, String...) y que además tendrá un “return”. Normalmente
los métodos “get” no tendrán parámetr os. ¿Recuerdas todo eso? Si no, ya
sabes....

Vamos a definir los métodos “get” de nuestra clase Mesa.


Empecemos con el color. Llamaremos al método “getColor”. Ya que el
color es una String, getColor devolverá una String:

String getColor(){
return color;
}

De forma semejante haremos con getForma:

String getFoma(){
return forma;
}

getNumPatas devolverá un valor entero:

int getNumPatas(){
return numPatas;
}

Bueno, pues resumimos nuestra clase Mesa; tres propiedades, tres


métodos “set” para definir esas propiedades, tres métodos “get” para
obtener el valor de las propiedades, un constr uctor con parámeros y otro
sin parámetr os, y pasamos del método “cambiarColor”. La clase Mesa
está escrita en la siguiente página. Aquí no cabe....
class Mesa {
String color;
int numPatas;
String forma;

void setColor(String nuevoColor){


color = nuevoColor;
}

void setNumPatas(int nuevoNu mPatas){


numPatas = nuevoNu mPatas;
}

void setForma(String nuevaForma){


forma = nuevaForma;
}

String getColor(){
return color;
}

String getForma(){
return forma;
}

int getNumPatas(){
return numPatas;
}

Mesa(String colorInicial, int numPatasInicial, String


formaInicial){
color = colorInicial;
numPatas = numPatasInicial;
forma = formaInicial;
}

Mesa() {
color = “azul”;
numPatas = 4;
forma = “cuadrada”;
}
}

Variables de clase. Modificador static.

Supón que tenemos una empresa que publica páginas web en


Internet para otras compañías. Supón que, como empresa que somos, en
cada página queremos anunciar nuestros servicios, por ejemplo,
poniendo nuestro número de teléfono. Así, cada vez que alguien visitase
la página de uno de nuestros clientes sabría cuál es nuestro teléfono para
así poder contratar nuestros servicios. Hasta aquí bien, ¿verdad?

Vale. Supón que tenemos una clase paginaGué con las propiedades
dirección , contenido y numeroTeléfon o . Cada vez que creemos una
página, le daremos un contenido, una dirección y nuestro número de
teléfono. Por supuesto
definimos en la clase todos los métodos set y get necesarios. Creo
que eres
Ahora supón que cambiamos nuestra sede y, por tanto,
totalmente capaz de
nuestro número de teléfono.
escribir la clase
pagi
naGué
¡Dios! ¡Hemos creado mil páginas web
y ahora tenemos que cambiar el número de
teléfono de todas! ¡Mil llamadas al método
setNumeroTeléfon o !

Pues no, si hacemos que todos


los objetos de la clase paginaGué compartan
la propiedad numeroTeléfono. Así, si cambiamos
el número de teléfono de una sola página, cambiará el de todas las demás
páginas. Guai.

Creo que este ejemplo es muy ilustrativo, ¿verdad? Bueno, pues


pasemos a la acción.

En Java, una propiedad que se comparte por todos los objetos de


una clase se llama “variable (propiedad) de clase” o “estática”. ¿Por qué?
La primera denominación hace referencia a que podemos entender que
una variable compartida NO pertenece a los propios objetos, sino sólo a
su clase. Es como si las Ideas de Platón definiesen no sólo las
propiedades y el comporta miento de los objetos físicos, sino que además
definiesen el contenido de alguna propiedad. Si todas las mesas fuesen
de madera, la propiedad “material” de una mesa estaría definida en la
Idea de mesa, no en cada mesa.

Bueno, pues es sólo una idea. Respecto al otro nombre, “variable


estática”, éste viene dado porque en Java, para definir una variable
compartida se le antepone el modificados “static ”:

static int numeroTeléfon o;

Hala. Pues ya está. Añadiendo un “static” antes de una propiedad


hacemos que esa propiedad sea compartida por todos los objetos.
Supón ahora que queremos saber cuántas mesas hemos fabricado.
Bueno, pues vamos a añadir una propiedad estática
numMesasFabricadas a la clase Mesa. Inicialmente, numMesasFabricadas
valdrá cero, pero cada vez que un constructor sea invocado, aumentar á
en una unidad. Después definiremos un método
getNumMesasFabricadas() para saber cuántas mesas llevaremos creadas.
Bueno, pues la clase Mesa quedaría definida asín (de nuevo, en una hoja
aparte):
class Mesa {
String color;
int numPatas;
String forma;
static int numMesasFabricadas = 0;
void setColor(String nuevoColor){
color = nuevoColor;
}

void setNumPatas(int nuevoNumPatas){


numPatas = nuevoNumPatas;
}

void setForma(String nuevaForma){


forma = nuevaForma;
}

String getColor(){
return color;
}

String getForma(){
return forma;
}

int getNumPatas(){
return numPatas;
}

Mesa(String colorInicial, int numPatasInicial, String


formaInicial){
color = colorInicial;
numPatas = numPatasInicial;
forma = formaInicial;
numMesasFabricadas = numMesasFabricadas + 1;
}

Mesa() {
color = “azul”;
numPatas = 4;
forma = “cuadrada”;
numMesasFabricadas = numMesasFabricadas + 1;
}

int getNumMesasFabricadas() {
return numMesasFabricadas;
}
}
Y ya está. Ahora, si hacemos:

Mesa Margarita = new Mesa(“Azul”, 3, “redonda”);


int num1 = Margarita.getNumMesasFabricadas();

Mesa Catalina = new Mesa(“Verde”, 4, “triangular”);


int num2 = Catalina.getNumMesasFabricadas();
int num3 = Margarita.getNumMesasFabricadas();

¿Cuánto valdrán num1 , num2 y num3 ? Pues, respectivamente, 1, 2


y 2.

Tal vez te hagas la preguna siguiente: si podemos definir


propiedades compartidas, ¿Podemos hacer lo mismo con los métodos?
¿Tiene sentido definir un método compartido? ¿Cómo se hace? ¿Una
existencia divergente implica un universo divergente?

Bueno, pues sí se puede definir un método estático, y sí que tiene


sentido. Además, se hace igual que con las propiedades, anteponiendo un
“static” a la definición del método. Lo de la existencia lo dejamos para
otro momento.

Por ejemplo, podemos saber cuál es el número de mesas fabricadas


sin necesidad de “preguntár selo” a una mesa determinada. ¡Podemos
preguntar a la propia clase Mesa! Cambiamos el método:

static int getNumMesasFabricadas() {


return numMesasFabricadas;
}

y ahora podemos hacer:

Mesa Margarita = new Mesa(“Azul”, 3, “redonda”);


int num1 = Mesa .getNumMesasFabricadas();

Mesa Catalina = new Mesa(“Verde”, 4, “triangular”);


int num2 = Mesa .getNumMesasFabricadas();
int num3 = Mesa .getNumMesasFabricadas();

Fíjate que la llamada a getNumMesasFabricadas() la hacemos


sobre Mesa y no sobre un objeto determinado. Bueno, éste hecho no debe
traerte de cabeza nunca de los jamáses, ¡de verdad! los métodos y las
variables estáticas no es que se usen demasiado, ¿eh? Además, los
puristas de la OOP (que son personas) no admiten nada estático, ni
propiedades ni métodos, salvo en una excepción: las constantes.

Constantes
¡Sí! También podemos definir constantes en Java. Una constante,
como comprenderás, no sólo no puede cambiar de valor: además, debe
ser compartida por todos los objetos. Vaya una constante tan estúpida
aquélla que no es la misma en todos los objetos de la clase donde esa
constan te se define. En Pascal, C, C+ +, BASIC, etc etc etc las constantes
se definen como const (de constant). Bueno, pues en Java, como es un
lenguaje tan especialito el jodío, se definen con final .

Es decir, que si yo quiero definir una constante, por ejemplo, pi ,


hago:
static final int pi = 3,1415 9 2 6 5 3 5 8 9 7 9 3 2 3 8 4 6 2 6 4 3 3 8 3 2 7 9 5;

Hala. Ahora pi es una variable compartida por todos los objetos de


la clase en la cual hemos definido la propia pi , y además no puede
cambiar de valor. Hala.

Antes de seguir adelante, ya sabes...

VARIABLE COMPARTIDA
MODIFICADOR STATIC
MÉTODO COMPARTIDO
LLAMADAS A MÉTODOS MEDIANTE EL NOMBRE DE LA CLASE
( Mesa.getNumMesasFabricadas() )
MODIFICADOR FINAL

Para comerse el coco..... (3)

La clase Math contiene métodos que hacen cálculos


numéricos, como coseno, seno, etc..... Si miras la definición de
estos métodos, verás que ninguno se salva, todos son métodos
static . ¿Por qué crees que es así?

El método main, arrays y ¡a lanzar programas!

Seguro que te has dado cuenta de que hemos definido mil clases,
métodos y propiedades, pero que realmente todavía no hemos hecho
ningún programa. Bueno. Vamos a solucionar eso.

Un program a siempre tiene un punto de arranque, es decir,


empieza en un momento determinado. Bueno, pues en Java, este punto
de arranque se llama método main .

Un programa suele constar de varios archivos, uno por cada clase


que definimos. Bueno, pues en una de estas clases debemos definir el
método main . Como comprenderás, será una buena práctica de
programación definir una clase exclusivamente para albergar el main . No
es necesario, pero es muy buena práctica, es decir, que al margen de toda
nuestra fabulosa colección de clases definimos otra más cuyo contenido
sea un sólo método main . Bueno, pero vamos con el propio método.
El main se define siempre igual, por norma en Java. Es un método:

estático: ya sabes qué es esto.


público: si lo sabes, te felicito. Básicamente significa que puede
accederse a este
método desde cualquier lugar, a diferencia de otros métodos
que pueden ser privados. Bueno, ya lo veremos, ¿vale?
no devuel ve datos: se define como void
se llama siempre “main”
recibe como argumentos un array de Strings: también lo
veremos.

main queda definido entonces como:

public static void main(String[] IDENT)

Como comprenderás, IDENT es un identificador al que puedes


llamar como te dé la gana. Normalmente recibe el nombre arg o args .
¿Para qué sirve?

Cuando ejecutam os un programa Java, lo que hacemos es escribir

java clase

y esto invoca a la JVM. Pero nosotros somos muy listos, y podemos


arrancar el programa pasándole argumentos:

java clase arg1 arg2 arg3 ... argn

Bueno, pues la JVM coge estos argumentos los mete en una lista. A
este tipo de listas se les denomina array , y funciona de la siguiente
manera:

Un inciso: arrays

Un array es una lista ordenada de elementos. Cada elemento tiene


asociado un índice. El primer índice es el cero, y el último depende del
número de elementos que haya guardados en el array.

Un array, como una variable cualquiera, tiene un TIPO (int, float,


String...) y un IDENTIFICADOR. Veamos cómo se declaran:

tipo[] IDENT = new tipo[tamaño];

por ejemplo, hagamos un array de Strings de 5 posiciones:

String[] miArray = new String[5];

y usamos el array como si fuese una variable normal, pero teniendo


en cuenta los índices en los que guarda mos valores:
miArray[0] = “Posición primera...”;
miArray[1] = “... segunda...”;
miArray[2] = “...tercera...”;
miArray[3] = “...cuarta...”;
miArray[4] = “y al siguiente día dejé el colegio”;

Para saber el número de posiciones de un array usamos el operador


.length
¡Fíjate en el punto inicial de .length !:

int tamaño = miArray.length;

Ahora tamaño vale 5.

sacabó el inciso.

Sigamos con el main.

public static void main(String[] args)

Cuando invocamos a la JVM, ésta determina el número de


argumentos que hemos introd ucido en

java clase arg1 arg2 arg3 ...

y crea el array que hemos llamado args . Bueno, Podemos, por


ejemplo, escribir un programa al que le pasemos como argumentos un
color y la forma, y él nos cree una mesa con cuatro patas y con ese color
y esa forma. Usando System.out.println podemos mostrar en pantalla las
propiedades de la mesa.

Meteremos el main en una nueva clase MesaMain (archivo


MesaMain.java):

class MesaMain{
public static void main(String[] args) {
Mesa Margarita = new Mesa( args[0], 4, args[1] );
System.out.println(“Hemos creado una mesa”);
System.out.println(“con ”+Margarita.getNumPatas()+”
patas,”);
System.out.println(“de color “+Margarita.getColor());
System.out.println(“y de forma “+Margarita.getForma());
}
}

Ahora, si compilamos Mesa y MesaMain con:

javac Mesa.java
javac MesaMain.java
y hacemos:

java MesaMain rojo, redonda

el program a nos dice:

Hemos creado una mesa


con 4 patas,
de color rojo
y de forma redonda

Hala. Nuestro primer programa. Es una pena, normalmente el


primer programa que se escribe es mostrar en pantalla el saludo “Hola
mund o!”, pero alguna vez hay que saltarse las reglas, ¿no? De todas
formas eres perfectamente capaz de programar tal saludo, ¿no? Haz una
clase Saludo que contenga un main que muestre en pantalla el saludo
“¡Hola mundo!”. En total, cinco líneas de código.

¡Cuidado! El programa MesaMain exige que metamos al menos dos


argumentos. Si ponemos sólo uno, habrá un error, y si ponemos siete, los
últimos cinco se ignorarán.

Programando a lo bestia. Estructuras de control.

Hay una serie de estructur as que la mayoría de los lenguajes de


programación poseen. Son las estructuras del control .

Estas estructur as son funda ment almente las condicionales y las


repetitivas . Empecemos por las primeras.

Sentencias condicionales

sentencia if

if es la estructur a condicional por antono ma sia en todos los


lenguajes. Su sintaxis es muy fácil:

if (condición) {
bloque1
} else {
bloque2
}

Si cualquier bloque consta sólo de una sentencia , entonces las


llaves correspon dientes a ese bloque pueden eliminarse. Es cuestión de
comodidad.

Expliquemos. la condición es una expresión lógica, es decir,


booleana. Si esta expresión da como resultado TRUE, se ejecuta el
bloque1 , mientras que si la expresión es FALSE se ejecuta bloque2 . Así
de simple. Claro, que queda un poco al aire eso de la expresión booleana.
Veámosla:

Una expresión booleana es cualquier cosa que pueda dar como


resultado TRUE o FALSE. Por ejemplo, la comparación de dos
variables.Este es un buen momento para ver los comparadores de
magnitud :

== igual a
!= diferente de
> mayor que
< menor que
>= mayor o igual que
<= menor o igual que

Una expresión lógica puede ser combinación de expresiones lógicas


más pequeñas combinadas con los Y, O, NO que ya conocemos, ¿verdad?
En Java se escriben así:

AND &&
OR ||
NOT !

Bueno, pues ya no deben asustarte cosas como

((n1 > 5) && (n2 < 3)) || !(n3 >= n4)

Otro tipo de expresión booleana puede ser un valor boolean


devuelto por un método. Veamos un ejemplo:

Para saber si dos números son iguales utilizamos el símbolo ==

if (num1 = = num2){
System.out.prinln(“Son iguales”);
} else {
System.out.println(“Son diferentes”);
}

Sin embargo, para saber si dos String son iguales no podem o s


utilizar el simbolo de igualdad. Es una peculiaridad de Java que no
viene ahora a cuento:

Aparentemente, si tenemos

String string1 = “Hola”;


String string2 = “Hola”;
String string3 = “Adiós”;
la expresión booleana ( string1 = = string2 ) debería ser TRUE,
pero no es así.

Para comparar Strings utilizamos el método .equals() Éste es un


método que funciona de la siguiente manera: partiendo de las strings
anteriores,

string1.equals(string2) da un resultado de TRUE, mientras que


string1.equals(string3) da como resultado FALSE.

En definitiva, .equals() es el método que utilizamos para comparar


una String con otra. Como ves, un método puede devolver un valor
booleano, por lo que este valor
puede ser utilizado en una sentencia if :

if (string1.equals( string2 )) { RECUERDA:


System.out.println(“Son iguales”); los números se
comparan
} else { con == != > <
>= <=
System.out.println(“Son diferentes”); pero las Strings se
comparan
} con
.equals()

por supuesto, dentro de un


bloque de un if podemos anidar
más if s:

if (tal y cual) {
hacemos esto

if (nosequé){
pues esto otro
} else {
y si no, esto
}

} else {

if (vaya rollo){
bah, yo me piro
} else {
paso de todo
}
}

Una sentencia if puede definirse sin su else correspon diente:


if (string1.equals(string2)){
System.out.println(“Son iguales”);
}
.
.
.

Pero hay que tener mucho cuidado con esto, sobre todo si
anidamos if s. Supongamos que queremos comparar dos enteros n1 y n2 ,
y saber si son iguales o si n1 > n2 . No nos importa saber si n2 > n1 .
Recuerda que cuando un bloque consta sólo de una sentencia (que puede
ser perfectamente una sentencia if ), las llaves pueden eliminarse.

int n1 = 1;
int n2 = 10;

if (n1 != n2)
if (n1 > n2)
System.out.println(“n1 > n2”);

/ / no comprobam os si n2 > n1

else
System.out.println(“Son iguales”);

Bueno, pues este fragmento es erróneo. Si n1 y n2 son iguales, el


programa no avisa. De hecho, tomando los valores 1 y 10, el programa
dice que son iguales. La razón es que el else aparenteme nte pertenece al
primer if , pero en realidad pertenece al segundo if ! El fragmento correcto
sería el siguiente:

int n1 = 1;
int n2 = 10;

if (n1 != n2)
if (n1 > n2)
System.out.println(“n1 > n2”);
else
/ / cualquier sentencia que no haga nada, por
ejemplo
n1 = n1;
else
System.out.println(“Son iguales”);

¡Bueno, hay que pasar siempre por estas cosas! Mucho cuidado
cuando anides if s, pon siempre sus else s, aunque no hagan nada. Otra
opción, tal vez te guste más, es la siguiente:
int n1 = 1;
int n2 = 10;

if (n1 != n2)
if (n1 > n2)
System.out.println(“n1 > n2”);
else { } / / bloque de código vacío
else
System.out.println(“Son iguales”);

Aquí hacemos uso de un bloque vacío , lo delimitamos con { }, y sin


embargo no metemos nada entre esas llaves. Bueno, todo son opciones.

Personalmente te recomiendo que cuando empieces a programar


siempre escribas las llaves, aunque encierren una sola sentencia, o
aunque estén vacías:

int n1 = 1;
int n2 = 10;

if (n1 != n2) {

if (n1 > n2) {


System.out.println(“n1 > n2”);
} else { }

} else {
System.out.println(“Son iguales”);
}

Así siempre verás claramente los bloques y la dependencia de if s y


else s.

Como verás en las pruebas de programas , las sentencias


condicionales son siempre una fuente de errores, debido, por ejemplo, a
cosas como estas. Estos errores se eliminan mediante las pruebas de caja
blanca , es decir, conociendo el código del programa. Si tienes que probar
un programa y te dan el código, mira siempre la dependencia de if s y
else s, ¿vale?

sentencia switch

Esta sentencia es muy peculiar. Permite ejecutar determinada


parte de un fragmento de código en función de un número enteo. Me
explico.

switch (expresión) {

case caso1:
bloque1
case caso2:
bloque2
.
.
.
}

Bueno, pues esto es fácil. Vamos a ver. expresión es un número


entero. Si hay algún caso que coincida con ese número entero, entonces
se ejecutan todos los bloques que haya a partir de ese caso, no sólo el
bloque que correspond e a ese caso. Un poco raro, ¿no? Bueno, veamos
un ejemplo:

switch (n){

case 1:
System.out.println(“uno”);
case 2:
System.out.println(“dos”);
case 3:
System.out.println(“tres”);
}

Date cuenta de que no es necesario poner llaves, sean los bloques


como sean, una sentencia o más.

Si n = 1 , por lo que se ejecuta el caso 1 y todos los casos


posteriores , es decir, el 2 y el 3. Por eso, si n = 1 se imprimiría en
pantalla

uno
dos
tres

mientras que si n = 2 se imprimiría

dos
tres

y si n = 3 , pues se mostraría

tres

Por la razón que hemos visto. Repito. Se ejecuta el bloque que


corresponde al caso y los bloques siguientes. Por eso, si n = 1 , se
ejecuta el caso 1 y los siguientes, es decir, los casos 2 y 3.
¿Hay alguna forma de hacer que se ejecute sólo el caso que
correspo n da, y que no se ejecuten los casos siguientes? ¡Pues claro!
usando break :

switch (n){

case 1:
System.out.println(“uno”);
break;
case 2:
System.out.println(“dos”);
break;
case 3:
System.out.println(“tres”);
break;
}

ahora, si n = 1 , se muestra en pantalla

uno

a diferencia de antes.

Otra cosa sobre los switch es: existe un caso especial, el llamado
default . Se ejecuta este caso si no hay otro caso que correspon da a la
expresión:

switch (n){
case 1:
System.out.println(“uno”);
case 2:
System.out.println(“dos”);
case 3:
System.out.println(“tres”);
case default:
System.out.println(“más de tres”);
}

Aquí, si n = 4 , se mostraría en pantalla

más de tres

mientras que si n = 1 , se vería

uno
dos
tres
más de tres
Bueno, no es tan mortal.

Para comerse el coco...... (4)


¿se te ocurre algún error en el siguiente código de programa?

switch (n){
case 1:
System.out.println(“uno”);
break;
case 2:
System.out.println(“dos”);
break;
case 3:
System.out.println(“tres”);
break;
case default:
System.out.println(“más de tres”);
}
piensa, piensa............

Sentencias repetitivas (o iterativas)

Adivina por qué se llaman así. Nos permiten repetir determinado


fragmento de código un número definido de veces.

En realidad lo que se hace es determinar una condición, una


expresión lógica . Así, mientras esta expresión sea TRUE el fragmento se
repite, hasta que sea FALSE. Es decir, que no definimo s un número de
repeticione s , sino una condición para que se repita . Naturalmente
podemos adecuar una condición para que un fragmento se repita un
número determinado de veces. Bueeeno, poco a poco. Vale.

Ya hemos tomado contacto con las expresiones lógicas, ¿verdad?


Fantástico. Apréndetelas bien, porque las vas a usar muncho muncho
muncho.

while

permite repetir un bloque mientras una condición sea TRUE.


Primero se comprueba la condición, y si es TRUE entonces se ejecuta el
bloque:

while (condición){
bloque
}

Un ejemplo:

int n = 0;
System.out.println(“a contar”);

while (n < = 1 0) {
System.out.println(“Voy por el ”+n);
n = n + 1;
}

System.out.println(“¡Ay!, que me canso”);

Bueno, pues está claro, este fragmento inicializa n a cero, y


mientras n < = 10 hace lo que está entre llaves, es decir, imprimir el
mensaje y aumentar n en una unidad. Es decir, cuenta de cero a diez.

Por supuesto puedes meter while s, if s y todo lo que quieras dentro


de un while .

Esto ya lo sabrás, pero es mi obligación avisarte: Las sentencias


repetitivas son una fuente inagotable de errores. Tienes que tener mucho
cuidado a la hora de usarlas, que va a ser siempre, por cierto. Por favor,
ten mucho cuidado en cómo usas las cosas, cómo planteas las
condiciones, el orden de las líneas dentro de un bloque.... cualquier cosa
puede hacer que tu programa estalle. ¿Qué ocurriría si invertimos el
orden de las dos líneas del bloque del while anterior? ¿y si ponemos
como condición n < 10 ?

Por estas razones suele haber una regla, que no siempre se cumple,
ni tienes por qué cumplirla, pero es conveniente:

primero, inicializamo s las variables en el primer caso del bucle .


Si quiero contar de 0 a 10 empiezo en 0. Parece obvio, pero no lo es.

segundo, la condición tiene que aceptar el primer valor . Es


estúpido que no pueda entrar en el bucle en el primer caso.

tercero, las variables se actualizan al final del bloque . Es decir,


es aconsejable que las variables cambien de valor justo en el final del
bloque, no en la mitad ni al principio. Fíjate, el “n = n + 1 ” está al final.

Repito que no es una regla general, que no tiene por qué ser así,
pero que conviene. De hecho, seguir esta regla a veces complica la
existencia una barbaridad.

una variante de while: do – while

while , como hemos dicho, primero compr ueba la condición y si es


TRUE ejecuta el bloque. do – while (o “duguail” para los amigos) primero
ejecuta el bloque y luego compr ueba la condición. De esta forma, while
puede no ejecutar nunca un bloque, ya que primero compr ueba la
condición, pero do – while, al comprobar la condición al final, siempre
ejecuta el bloque al menos una vez . Típica pregunta de examen.

Bueno, la estructur a es semejante a la del while :

do {
bloque
} while (condición)

Y esas “reglas” de antes, yo que tú intentaba aplicarlas aquí


también.

for

Esta es la más complicada de todas. Se basa en lo siguiente:

Fíjate que while y do – while tenían como tres partes:

inicialización de las variables


condición
actualización de las variables

¿te suena?

int n = 0; / / INICIALIZACIÓN

System.out.println(“a contar”);

while (n < = 1 0) { / / CONDICIÓN


System.out.println(“Voy por el ”+n);
n = n + 1; / / ACTUALIZACIÓN
}

System.out.println(“¡Ay!, que me canso”);

Bueno, pues for resume estas tres partes en una sola sentencia:

for (inicialización, condición, actualización){


bloque
}

Por ejemplo:

System.out.println(“a contar”);

for (int n = 0; n < = 10; n = n + 1){


System.out.println(“voy por el ”+n);
}

System.out.println(“¡Ay! que me canso”);


¿Te percatas de cómo for resume las tres partes en una sola
sentencia?

Bueno, ¿Crees que hay más sentencias repetitivas? Pues nooooop.


En realidad puedes hacer una diferenciación muy simple: for se usa
cuando sabes cuántas veces se va a repetir un bucle, y while en caso
contrario. Por ejemplo, si vas a recorrer un array usarás un for porque
sabes la longitud del array, sabes su tamaño. Si buscas una letra en una
cadena de caracteres usas un for porque sabes cuántos caracteres tiene
esa cadena (usando el método .length() de la clase String, no lo
confun da s con el .length sin paréntesis de un array), pero si esperas a
que el usuario introdu zca una clave, por ejemplo, usarás un while
porque no sabes cuántas veces va a teclear una clave incorrecta. Y
recuerda, do – while no es más que un caso de while .

¿Sabes que con lo dicho hasta ahora puedes programar cualquier


cosa? Bueno, pero no saltes de alegría hasta haberte convencido de que
pilotas los conceptos de

SENTENCIAS CONDICIONALES
IF / ELSE
SWITCH / CASE / CASE ELSE
SENTENCIAS REPETITIVAS
WHILE / DO – WHILE / FOR

Para comerse el coco.....(5)


Ya sabes que for engloba una inicialización, una condición y una
actualización. ¿En qué momento del bucle se produce cada una de estas
partes?

Debo decirte que todo en programación se aprende primero


mediante teoría, y luego mediante horas de práctica. Eso ya lo sabes. Pero
en el uso de bucles y condicionales es especialmente necesario que
practiques con ellos. Trata de hacer programas simples (que sean un un
solo main ) que, por ejemplo, cuenten de un número a otro, que cuenten
pares, impares, que calculen sumatorios, factoriales..... ¿Se te ocurre
alguna forma de calcular los 100 primeros primos? Puedes implementar
la criba de Erastótenes con dos bucles for anidados y un array de
enteros..... En fin, que cualquier cosa que se te ocurra, a por ella sin
temor. Hazlo por mí....

Cosas que nunca se deberían hacer: break y continue

Existen dos sentencias, break y continue, que pueden ayudar a


resolver ciertos problemas a la hora de entrar y salir de los bucles. Son
realmente útiles pero, personalmente, no te recomiendo que las uses,
porque pueden dar lugar a que tu código sea ilegible y poco abordable.
Sólo es una opinión.
Break

En realidad, break ya lo hemos visto con la sentencia switch. Break


lo que hace es justo lo que parece: salir de un bucle:

for (un bucle enorme) {


if (se cumple cierta condición)
break;
}

En cuanto se cumple la condición, saldremos del bucle. Tan simple


como eso. Personalmente, creo que hay maneras más elegantes de
solucionar esto:

for (inicio; condición1; actualización) {


if (condición2) {
...
} else {
break;
}
}

Este fragmento lo que hace es iterar las sentencias “...” hasta que la
condición 2 deje de cumplirse. Por ello, se podría traducir por:

for (inicio; condición1 && condición 2; actualización) {


...
}

que es bastante más elegante, pero para gustos están los colores,
¿no?

Continue

La sentencia continue se utiliza para, dentro de un bucle, acabar la


iteración actual y comenzar la siguiente. Por ejemplo:

for (inicio; condición1; actualización) {


if (! condición 2) {
continue;
}
...
}

Este fragmento funciona de la siguiente forma: al iterar, si no se


cumple la condición 2, se sigue iterando, de forma que las sentencias “...”
se ejecutan sólo si la condición 2 se cumple. Por ejemplo, suponga mo s
que tenemos una lista de nombres y queremos saber cuántos hay
mayores de edad:
for (i=0; i<nu m eroPersonas; i+ +) {
if (persona[i].edad < 17)
continue;

mayoresDeEdad + +;
}

Personalmente, creo que es bastante más legible hacer

for (i=0; i<nu m eroPersonas; i+ +) {


if (persona[i].edad > = 17)
mayoresDeEdad + +;
}

pero la elección, de nuevo, es tuya.

Adicionalmente a todos estos mecanismos de control del flujo del


programa, existe algo llamado etiquetas (labels). Una etiqueta sirve para
dar un nombre a una sentencia, por ejemplo:

etiqueta:
for (inicio; condición; actualización) {
...
}

Las etiquetas son útiles para hacer burradas como la siguiente:

etiqueta:
for (inicio; condición; actualización) {
for(inicio2; condición 2; acualización 2) {
if (condición3)
break etiqueta;
...
}
}

En caso de que se cumpla la condición 3, si hubiéramos puesto


“break” solamente, el segundo bucle se acabaría, pero el primero seguiría
funcionando. Haciendo “break etiqueta” rompem os el bucle marcado por
la etiqueta, es decir, nos cepillamos ambos bucles de un pluma zo.

En fin, no te lo recomiendo, pero si te gusta, ya sabes :)


Existe otra forma de abordar los problemas que se resuelven
mediante sentencias repetitivas. Es una visión muy diferente y que,
desgraciadame n te, cuesta mucho entender. Así que trataré de
esforzar m e. Es el temido tema de...

Recursividad

Como acabo de decir, la recursividad es una forma de solucionar


problemas. Siempre se dice que todo algoritmo iterativo puede traducirse
a una forma recursiva, y viceversa. Bueno, pues es cierto: si tienes un
algoritmo iterativo puedes cambiarlo y convertirlo en recursivo. De hecho
es una pregunta muy propia de los exámenes de Laboratorio de
Programación. Bueno, vamos a ver entonces qué es esto de la
recursividad.

La recursividad es un concepto muy abstracto. Así que atención.


Consiste en que un fragmento de código se usa a sí mismo . Ese es el
corazó n de la definición. Por ejemplo: hay un cuento que narra cómo un
príncipe se metió en un gran barrizal, el cual le impedía caminar, ya que
el lodo era demasiado denso. Así que lo que hizo para salir fue tirar él
mismo de sus botas hacia ariba, primero una, luego la otra, y así
conseguía la fuerza suficiente para avanzar por el barrizal, y logró
salvarse.

Bueno, es un ejemplo muy simple de cómo algo puede usarse a sí


mismo. Desgraciadamente abordare mo s este tema de una forma más
difícil.

Hay un ejemplo típico del uso de la recursividad. Es el cálculo de


factoriales. Ya sabes:

n! = n · (n-1) · (n-2) · ... · 3 · 2 · 1

pero, fíjate:

n! = n · (n-1) · (n-2) · ... · 3 · 2 · 1


(n+1)! = (n+1) · n · (n-1) · (n-2) · ... · 3 · 2 · 1

entonces,
(n+1)! = (n+1) · n!

y, por tanto,
n! = n · (n-1)!

Vale. Cursillo intensivo de Calculo I. Bueno. Observa que la


definición de factorial engloba un factorial. Por eso es recursivo.

Para ver fácilmente cómo funciona la recursividad, vamos a


considerar el siguiente ejemplo. A ver si te gusta.
Supón la cola de la taquilla del cine. Si, antes de entrar a ver la
película hay unas cuantas personas compran do la entrada. Aquí las

tenemos:

En un momento dado, el último de ellos, el de la camiseta amarilla,


desea saber a qué hora es la siguiente sesión. Así que lo que hace es
preguntár selo al de la camiseta roja:

Pero, claro, resulta que el de la camiseta roja tampoco lo sabe. Un


fastidio. Así que lo que hace es preguntárselo al de las rastas, mientras
mantiene al de la camiseta amarilla a la espera.

Pero el de las rastas tampoco lo sabe. Así que hace la misma


pregunta al de los zapatos amarillos, mientras mantiene al de la camiseta
roja a la espera. Por supuesto, el de la camiseta amarilla sigue a la espera

de que le responda el de rojo.


En fin, puedes adivinar que el de los zapatos amarillos no tiene ni
idea, así que lo que hace es preguntárselo a la taquillera de los pelos
rojos. El de las rastas se queda esperando a que el de los zapatos
amarillos le responda. A todo esto, el de rojo está esperando al de las
rastas, y el de la camiseta amrilla está hartándose de esperar.

Pero ¡vaya! resulta que la taquillera de los pelos rojos, a pesar de


ser su primer día de trabajo, está muy puesta en el tema y sabe la
respuesta. Claro, que ella responde a quien le ha pregunta do, al de los
zapatos amarillos:

Ahora que el de los zapatos amarillos sabe la respuesta, se la dice


al de las rastas, que le estaba esperando. El de rojo sigue esperando,
claro, y el de la camiseta amarilla, ni te cuento.

Ahora el de las rastas ya sabe la respuesta. Pues va y contesta a


quien le ha pregunta do. El de la camiseta amarilla, todavía sigue
esperan do.
Una vez que el de rojo sabe la respuesta, por fin, se la dice al de la
camiseta amarilla, que es el primero que preguntó, justito antes de que le
diera un ataque de histeria.

En fin, acabas de ver intuitivamente (espero) cómo funciona un


algoritmo recursivo. El primero llama al segundo y espera a que le
respon d a. El segundo llama al tercero y espera a que le responda,
mientras el primero sigue esperando. Y así sucesivamente... y así
recursivamente....

Todo método recursivo tiene parámetr os y valor de retorno. Es


parte del juego. ¿Por qué?

Bueno, vayamos poco a poco. Un algoritmo recursivo trata de


resolver un problema, por ejemplo, el método factorial() halla un
factorial., por ejemplo, 5!. Si queremos calcular 5! debemos pasar ese 5 al
método:

float resultado = factorial(5);

Lo cual ya es una buena razón para pensar que el método necesita


parámetros. Pero, lo mejor de todo, y lo más importante es que el propio
método “factorial()” llamará a “factorial()” pero con otro parámetro .
factorial(5) llama a factorial(4) , y éste a factorial(3) , y así sucesivamente.
Por tanto, es más que obvio que un método recursivo necesita
parámetros. Ufff...

Pensar que un método recursivo no ha de tener parámetros es


como pensar que en la cola del cine el de la camiseta amarilla pregunta al
de rojo “disculpe, ¿podría decirme?”
Decíamos que un método recursivo siempre devolverá un valor.
Claro, ¿Por qué? Pues muy sencillo: cuando factorial(5) llama a
factorial(4) lo hace para averiguar el factorial de cuatro. Así que
factorial(4) debe devolver un valor, exactamente 24.

Pensar que un método recursivo no devuelve ningún valor es como


pensar que en la cola del cine el de rojo responde al de amarillo “¡claro! la
película empieza a las”. Joer, imagina la conversación.

En definitiva, esto es una especie de demostración intuitiva para


que te percates de que un método recursivo necesita, exige, precisa y
requiere parámetros, y siempre devuelv e algún valor. Si algún método
recursivo no tiene parámetro s y/o no devuelve algún valor, es porque
tiene un diseño extraño, poco común.

Mi compañero de la práctica de Programación, primer cuatrimestre,


resolvió varios problemas de la práctica mediante un método recursivo
que ni tenía parámetr os, ni devolvía valores. Y funcionaba a las mil
maravillas. Pero, repito, es un caso muy raro. Yo jamás habría hecho tal
método recursivo, sino iterativo. O sea, que la máxima de antes no es tal
máxima, pero yo te aconsejo que la sigas siempre que puedas.

Todo método recursivo consta de tres partes. Primero veamos esas


partes y luego te pondré un ejemplo, vale?

a) caso base : el caso en el que no se necesita una llamada


recursiva. Por ejemplo, factorial de 1.
b) caso no base : el caso en el que se hace la llamada recursiva. Por
ejemplo, el factorial de 36.
c) conquista : consiste en, después de hacer la llamada recursiva ,
obtener el resultado que piden al método.

El caso base podríamos relacionarlo con la taquillera de los pelos


rojos. Ella puede contestar a la pregunta sin necesidad de preguntar a
nadie más, es decir, sin hacer ninguna llamada recursiva. El caso no base
es cualquiera de las personas de la cola: para resolver la duda han de
planteársela a otra persona, no pueden resolverla por sí mismos. La
conquista sería un poco más rara: el paso de la respuesta que a uno le ha
llegado hacia la persona que le ha preguntado. Es decir, el de rojo lleva a
cabo la conquista cuando el de las rastas le responde, y el de rojo toma
esa respuesta y se la da al de amarillo.

No confunda s caso no base y conquista: el caso no base es la


condición, y la conquista es la acción. Huy, qué bonito.

Bueno, lo prometido es deuda. Vamos a implementar el método


factorial . Lo implementare m o s poco a poco, así que no te hagas un lío
con las llaves, ¿vale?
Bueno: primero necesitamos parámetr os, un entero, y
devolveremos un entero largo:

long factorial( int numero ) {

Vamos a definir un entero largo, que será la respuesta que el


método devolverá:

long respuesta = 0;

Ahora debemos diferenciar entre caso base y caso no base. El caso


base será el factorial de 1, y el caso no base, cualquier otro.

if ( numero = = 1 ) { / / caso base


respuesta = 1;
} else {

Ahora viene el caso no base. La respuesta se hallará mediante la


llamada recursiva. Ya que n! = n * (n- 1)!, haremos:

respuesta = numero * factorial(numero – 1);


}

Y por último, la conquista. Debemos devolver la respuesta


que hemos hallado a quien nos lo pregunta:

return respuesta;
}

Aquí está el método al completo:

long factorial( int numero ) {

long respuesta = 0; // lo que devolveremos

if ( numero == 1 ) { // caso base


respuesta = 1; // devolveremos 1
} else { // caso no base: llamada recursiva
respuesta = numero * factorial(numero – 1);
}

return respuesta; // ¡conquista!


}
Pues nada, esto compila perfectamente, y funciona. Por cierto, este
código no tiene tratamiento de errores, así que no le hagas perrerías, tipo
factorial(- 3), porque se te va a quedar más colgao que el teleférico. Claro,
que... ¿Por qué no le haces tú las medidas de seguridad? No es difícil...
¡Ánimo!

Bueno, ahora voy a contarte un pequeño truco que nadie cuenta


para entender bien el funcionamiento de un método recursivo.

Verás, cuando un método se llama a sí mismo, imagina que lo que


ocurre es que en la memoria del ordenador se hace una copia del método.
Así que lo que tú deberías hacer para entender un método recursivo es
pensar que existen varias copias del método en memoria. Si tienes este
código:

long factorial( int numero ) {


long respuesta = 0;

if ( numero == 1 ) {
respuesta = 1;
} else {
respuesta = numero * factorial(numero – 1);
}

return respuesta;
}

Trata de pensar que, cuando el programa arranque, por ejemplo, al


calcular el factorial de 3, lo que tendrás es esto:
Uff... no se ve muy bien... he tenido que reducir mucho la letra para
que entren las tres copias. Sorry. Bueno, pues con esta imagen tú puedes
imaginar el proceso que se lleva a cabo al calcular factorial(3) . El
trascurso del programa está marcado con la línea roja.

El dibujo está en la página siguiente, porque es un poco grande. En


él puedes ver cómo la primera llamada viene desde las alturas, en la parte
de arriba a la izquierda. Esa llamada “mete” el “3” como parámetr o en la
primera copia de factorial . Éste, al ver que no es un caso básico, hace la
segund a llamada pasando como parámetro “2” a la segunda copia del
método. La segunda copia actúa de forma semejante, pasando “1” a la
tercera copia. Ésta identifica que es el caso básico y “conquista” la
respuesta, en este caso 1. La respuesta vuelve a la segunda copia, quien
lleva a cabo la segunda conquista, devolviendo a la primera copia “2”. La
primera copia, a su vez, lleva a cabo su conquista devolviendo al ente en
las alturas “6”.
Espero que haya sido una explicación muy gráfica. Creo que no soy
capaz de explicarlo de otra manera mejor, así que, si no lo has pillado
bien, releelo todo despacito. Trata de ir muy lentamente, viendo cómo se
comporta cada copia de factorial , identificando cada paso con la
situación de la cola del cine, ¿vale? Espero que hayas perdido el miedo a
la recursividad, y estoy convencido de que cada vez que preguntes algo a
alguien, y este alguien se lo pregunte a otro te acordarás de todo esto.

Hala. Se acabó. ¿Crees que te voy a dejar sin la lista de conceptos?


Pues noooooo...

MÉTODO ITERATIVO / RECURSIVO


METODOLOGÍA DE LA RECURSIÓN: la cola del cine
MÉTODO RECURSIVO
PARTES DE UN MÉTODO RECURSIVO
caso base, caso no base, conquista

Como curiosidad....
¿Sabes por qué se le llama “conquista”? Pues es muy sencillo. Los
métodos recursivos se suelen usar para resolver problemas partiéndolos
en cachitos más pequeños. Por ejemplo, buscar a una persona en una cola
de gente: primero buscas en la mitad izquierda, y luego en la derecha.
Esto lo verás muy profunda m e n te en Laboratorio de Programación.
Bueno, pues como ya sabrás, hay un refrán árabe que dice: divide y
vencerás . En inglés, “divide and conquer”. De ahí lo de “conquista”.
Bueno, vamos a abordar una de las partes más bonitas de la OOP,
aunque trae sus dolores de cabeza, no te creas. Algunos lenguajes, como
C++ basan gran parte de su ingente potencial en esto que llamamos...

Herencia

Bueno, está claro qué es la herencia. Es lo que unos padres dejan a


sus hijos. O mejor dicho, lo que unos padres transmiten a sus hijos por
vía genética. Las personas somos creadas mediante herencia de genes, a
pachas entre papi y mami. En definitiva, que papá y mamá no nos definen
desde el principio, sino que toman sus propios genes para crearnos a
nosotros. Bueno, afortuna da m e n t e esto no es tan fácil en genética como
en programación.

Pero el concepto que te tiene que quedar muy claro es que la


herencia es un mecanis m o de definición, no un método para suspender
a los de primero de Teleco, ni un galimatías conceptual, sino que es una
forma de definir nuevos objetos a partir de los existentes. La herencia
sirve para definir.

En Java una clase sólo puede heredar de otra clase . No se admite


que una clase herede de varias clases, como sí se puede hacer, por
ejemplo, en C++. En Java, por tanto, no hablamos nunca de herencia
múltiple . Además, la herencia en Java se denomina extensión : decimos
que una clase extiende a otra.

Por tanto tenemos que tener en cuenta que, en Java, los objetos
sólo tienen un padre, y además, heredan de él el 100%, no como en las
personas que heredan el 50%

Bueno, pues sin más preámbulos nos podemos plantear alguna


estructura jerárquica , es decir, algún diagrama de clases en el que
dichas clases se relacionen mediante mecanismos de herencia. Por
ejemplo, se me ocurre pensar que una moto es como una bicicleta con
motor. De ahí lo de “motocicleta”. Bieeeeen
Bueno, pues pasemos a escribir. Por cierto, veremos
muchas
el fin de este código que ahora vamos a escribir es clases
definidas de forma
puramente didáctico, no funcional. muy tonta. A
veces incluso
sin definir. Eso
es porque
Es decir, que vamos a definir una realmente su
contenido
bicicleta y una moto, pero de una no importa
nada.
forma muy estúpida. Verás:
vamos a definir las clases Rueda
y Motor de una forma un tanto
peculiar, aunque simpática
donde quepa:

class Rueda {
void saludar() {
System.out.println(“Soy una rueda”);
}
}

class Motor {
void saludar() {
System.out.println(“Soy un motor”);
}
}

Ahora hacemos que una bicicleta sea, simplemente, dos ruedas:

class Bicicleta {
Rueda delantera;
Rueda trasera;

Bicicleta(){ / / el constructor
delantera = new Rueda();
trasera = new Rueda();
}

void saludar() {
System.out.println(“Soy una bicicleta”);
}
}
Bueno, pues vamos a definir la moto. Para hacer que una clase
“hijo” extienda a otra “padre” , la primera clase se define así:

class hijo extends padre


Bueno, pues a ello.

class Moto extend s Bicicleta {


Motor motor;

Moto() { / / el constructor
motor = new Motor();
}

void saludar(){
System.out.println(“Soy una amoto”);
}
}

Estas cuatro clases son lo más fácil del mundo. Si no las entiendes,
para y vuelve atrás, porque son de lo más básico. Pero seguro que las
entiendes sin problemas, ¿verdad?

Fíjate en el uso de la herencia: la clase Moto sólo define un método


saludar y una propieda d Motor , pero hereda de la clase Bicicleta , por lo
que también posee todos los contenidos de Bicicleta . Por tanto, Moto
hereda de la clase Bicicleta las dos ruedas. ¿Te percatas de cómo se usa
la herencia para definir clases? Definimos el contenido de Moto a partir
del contenido de Bicicleta .

Respecto al método saludar , fíjate que Motor redefine el método


saludar que hereda de Bicicleta . La clase padre tenía un método
saludar , pero la clase hija también lo tiene. Se dice que lo redefine
(overrides) . Sin embargo, debe quedar muy claro que redefinir un
método no implica que el método anterior deje de existir , es decir, que
el método saludar de Bicicleta sigue existiendo a pesar de haberlo
redefinido en la clase moto .

No tendría sentido que una moto saludase como una bicicleta, ¿no?
Aunque no te creas, que gracias al casting , que en breve veremos,
podemos hacer que una moto pueda saludar como moto y como bicicleta.

Bueno, pues eso es todo sobre cómo se usa la herencia. Vemos


cómo hemos definido una moto a partir de la definición de una bicicleta,
¿verdad?. Ahora, en cualquier parte del código, por ejemplo, en un main
podemos hacer sin problemas:

Bicicleta Antonieta = new Bicicleta();


Moto Anacleta = new Moto();
Bueno, pues al crear a Antonieta se crearán las ruedas, y al crear a
Anacleta se creará el motor. Podemos hacer sin problemas:

Antonieta.saludar();
Anacleta.saludar();

y cada una nos saludaría diciendo lo que son. Hala. Voy a compilar
este código a ver si da problemas...... no, parece que no los da.

Vamos a plantear una cuestión: una Bici, al crearse, crea las dos
ruedas. Una moto, al crearse, crea un motor. Pero, ¿Se crean las ruedas al
crearse la moto? Fíjate en el constructor de la clase Moto:

Moto() { / / el constructor
motor = new Motor();
}

Pregunta: ¿Crees que al invocar al constructor de Moto se invoca


automáticame nte el constructor de su superclase Bicicleta?

Desgraciada mente no. Tal y como estamos, al crear una moto se


crea su motor, pero no sus ruedas. Ayayayayyyyyy.... ¿cómo arreglamos
esto?

Opción primera : crear específicamente las ruedas en el


constructor de la moto:

Moto() { / / el constructor
delantera = new Rueda();
trasera = new Rueda();
motor = new Motor();
}

Esto es perfectamente válido. Pero hay otra solución más mejor:

Opción guai: Hacer uso de la palabra super() . Esta palabreja se


utiliza en el constructor de una clase para llamar al constructor de su
superclase. Es decir, que si hacemos:

Moto() { / / el constructor
super();
motor = new Motor();
}

entonces, cada vez que creemos una moto se creará “la bicicleta
que reside en su interior” (oh! una lágrima resbala por mi mejilla....) y
luego el motor. Es como si
al crear a una persona primero
En definitiva, creas lo
que tiene de su
cuando una clase extiende padre y
luego creas el
a otra, el constructor de la
resto de cosas.
clase hija debe llamar al Una visión
muy peculiar,
constructor de la clase madre,
¿no crees?
y esto se hace mediante Por
cierto, conviene
super() . que super() sea
lo primero que
aparezca en el constructor, antes de
cualquier otra sentencia. ¿Te has preguntado
por qué super() tiene paréntesis? Deberías adivinarlo,
pero por si acaso, te lo digo. super() es el constructor de
la superclase, y recuerda que el constructor no es más que
un método un tanto especialito, pero método al fin y al cabo.
Y si no recuerdas mal, los métodos llevan paréntesis siempre....

nota
Cuando una clase hereda de otra no es necesario que llame al constructor de su
superclase, pero siempre que crees una clase hija, lo más normal es que sí lo haga.
Plantéate la lógica de este hecho: supón un mecánico que sabe que una moto es una
bicicleta con motor. Si le piden fabricar una moto, entonces primero creará una bicicleta
y luego le pondrá un motor. Es decir, primero crea la clase padre y luego la hija.
Supón ahora que defines una motopija , que es una moto con aire acondicionado .
Pues al crearla, primero crearás una moto y luego le pondrás el aire. Esto implica crear
primero una bicicleta, luego ponerle motor para hacer la moto, y luego ponerle el aire
para hacer la motopija. Vaya, dos super() ....

Weno, pues, como siempre, antes de seguir asegúrate de pilotar:

HERENCIA
CLASE PADRE / CLASE HIJO
USO DE extends
REDEFINICIÓN (OVERRIDING) DE MÉTODOS
(que a continuación veremos con más detalle)
USO DE super()
CONCEPTO DE MOTO CON AIRE ACONDICIONADO

¡Animo! ¡ya queda poco de OOP!

Preludio al Casting: las referencias


Bueno, no tiene nada que ver con actores. Qué pena, ¿verdad?

Casting significa algo así como “convertir”. El objetivo del casting


es hacer que un objeto que se ha definido mediante herencia se comporte
como un objeto de una de sus superclases. Es decir, que una moto salude
como una bicicleta, o que una moto deje de tener motor, o cosas por el
estilo. En realidad el casting es algo un poco más profundo, pero
empezare m os por lo fácil. No te creas, acabaremos con lo difícil....

Bueno, pues recordemos las definiciones de Moto y de Bicicleta:

class Bicicleta {
Rueda delantera;
Rueda trasera;

Bicicleta(){ / / el constructor
delantera = new Rueda();
trasera = new Rueda();
}

void saludar() {
System.out.println(“Soy una bicicleta”);
}
}

class Moto extend s Bicicleta {


Motor motor;

Moto() { / / el constructor
super(); / / llamamos al constructor de Bicicleta
motor = new Motor();
}

void saludar(){
System.out.println(“Soy una amoto”);
}
}

Vamos a crear dos objetos:

Bicicleta Antonieta = new Bicicleta();


Moto Anacleta = new Moto();

Si hacemos:

Anacleta.saludar();

veremos en pantalla :
Soy una amoto

Como sabemos, una moto es una bicicleta con motor. Eso significa
que una moto tiene una bicicleta dentro de sí. o lo que es lo mismo, que
una moto puede comportarse como una bicicleta. ¿Cómo? Ahora lo
veremos. ¿Para qué sirve? Eso lo veremos luego.

Supón que tenéis una vecina que conoce muy bien a tu madre,
mucho mejor que a ti. Un buen día te ve y te saluda, y te dice “Vaya,
tienes los ojos de tu madre”. ¿Por qué te dice esto? Aparte de porque
realmente puede que tengas los ojos de tu madre, la vecina te lo dice
porque conoce a tu madre . Si no la conociese no podría decirlo, ¿no? Es
decir, que la vecina puede decir lo que has sacado de tu madre si conoce
a tu madre.

Bueno, pues una referencia , que se supone que ya sabes a la


perfección qué es, es como la vecina. Es capaz de ver en los objetos los
rasgos que conoce. Si una referencia es del tipo bicicleta , entonces verá
los contenidos propios de la bicicleta en el objeto al que apunte. Por
ejemplo:

Bicicleta refABicicleta = new Bicicleta();

Estamos cerando una referencia llamada refABicicleta (“referencia


a bicicleta”) que es del tipo Bicicleta y que apunta a una nueva Bicicleta (
new Bicicleta() ). Esto es claramente legal, es como si la vecina ve a tu
madre, no hay nada que no conozca de ella. Pero si hacemos un truco...

Bicicleta refAOtraBicicleta = new Moto();

Hacemos que la referencia refAOtraBicicleta apunte a una nueva


moto. ¿Cómo es esto posible? Pues es como la vecina que se encuentra un
día contigo. La vecina ve en ti lo que has heredado de tu madre, porque
conoce a tu madre . La referencia refAOtraBicicleta ve en la nueva moto
lo que ha heredado de bicicleta porque conoce las bicicletas (es del
tipo bicicleta) .

Entonces, usando la referencia refAOtraBicicleta podemos hacer


que una moto salude como una bicicleta, porque refAOtraBicicleta
conoce el método saludar de la bicicleta : Si hacemos

refAOtraBicicleta.saludar();

veremos

Soy una bicicleta

¡aunque refAOtraBicicleta apunta a una moto!. ¿Por qué? Pues por


lo dicho, porque refAOtraBicicleta no conoce el método que saluda
como una moto, sólo conoce el método que saluda como una
bicicleta . Pretender que refAOtraBicicleta haga saludar a la moto como
si fuese una moto sería como pretender que la vecina te diga que tienes
las manos de tu abuelo, al que nunca conoció. Aquí tienes un pequeño
gráfico que tal vez te ayude:

Vamos a olvidar por ahora los constructores, ¿vale? Bueno, pues ¡te
presento a la señora referencia de tipo Bicicleta!:

¡¡¡TACHAAAAAAN!!!

no esperarás que tres flechas


piensen como
lo hacen el pez y los otros, ¿no?
Vaya unas ideas que tienes...... :)

Bueno, pues ¿Por qué la referencia de tipo Bicicleta tiene esa


forma? Pues fjate en cómo funciona esta referencia con un objeto de tipo
Bicicleta :

¿Te percatas de cómo de “ajusta” la referencia al contenido del


objeto? Una referencia del tipo Bicicleta verá todo el contenido de una
bicicleta, así como tu vecina conoce a tu madre por completo. Ahora
fíjate en esto:
Un objeto de la clase Moto es igual que una bicicleta con dos cosas
añadidas: un motor y un método que saluda de forma diferente. Estos
dos miembros de la clase están arriba marcados en rojo. Fíjate cómo una
referencia de tipo Bicicleta puede adaptarse a un objeto de tipo Moto,
pero no ve más que el contenido de la bicicleta. En otras palabras, que la
referencia no ve ningún miembro pintado en rojo. Por cierto, perdón por
el desenfoque.

Por supuesto, huelga decir que una referencia de tipo Bicicleta no


puede apuntar a objetos que ni sean bicicletas ni hereden de bicicletas. Es
decir, que una referencia de tipo bicicleta no puede apuntar a la mesa
Margarita. Lástima.

Espero que ahora haya quedado bien explicado, o mejor dicho, bien
mostrado:

1- Que una referencia de bicicleta se acopla a un objeto moto. Una


referencia de un tipo puede acoplarse a un objeto de otro tipo

2- Que una referencia de bicicleta al acolparse a una moto no ve


ni motor ni el segundo método saludar . Una referencia de un
tipo acoplada a un objeto de otro tipo sólo ve parte del
contenido objeto

Bueno, acabas de ver cómo funciona una referencia. Como


resu men, una referencia es un conjunto de flechitas que apuntan hacia
determinados contenidos de un objeto. Los contenidos a los que apuntan
vienen determinados por el tipo de la referencia.

Comparación de referencias

Para comprobar si pilotas el sentido de la palabra “referencia”,


hazte la siguiente pregunta:
Tenemos dos objetos diferentes, con referencias diferentes, pero
cuyas propiedades coinciden. Por ejemplo, dos mesas cuadradas azules
de tres patas:

Mesa Margarita = new Mesa(“Azul”, 3, “cuadrada”);


Mesa Catalina = new Mesa(“Azul”, 3, “cuadrada”);

La pregunta es la siguiente: ¿Qué valor lógico (TRUE – FALSE) dará


la siguiente expresión?

Margarita = = Catalina

Pues veamos: estamos comproban do si son iguales dos referencias.


Cada una de ellas apunta a una mesa cuadrada, azul y de tres patas. Así
que, como son iguales, esto debería dar TRUE.

Pues no.

Fíjate: las dos mesas son azules, de tres patas y cuadradas. PERO
SON DOS MESAS DIFERENTES, no son la misma mesa. Es como decir que
dos gemelos, por ser idénticos, son la misma persona. Bueno, ten esto
muy presente siempre, ¿vale? Puedes comparar dos númeos con = =, pero
no dos objetos.

Antes de seguir asegúrate que has alcanzado la Idea platónica de


los dos puntos anteriores. Si no es así, en realidad lo único que tienes que
hacer es pensar muy profunda m e n te en ello, vuelve a escribir las clases,
prueba a hacer pequeños programas... recuerda lo dicho en el prefacio. Y,
por supuesto, si tienes más probelmas, aquí estoy.

Arreglando bicicletas: parámetros que son referencias

Supón ahora que se nos pincha una rueda de la bicicleta. Pues la


llevaremos a un taller:

class Taller {
void arreglar(Bicicleta bici){
System.out.println(“A arreglar!”);
}
}

Bueno, fíjate: definimos un taller con un método arreglar al que le


vamos a pasar como parámetr o una bicicleta. Realmente este método no
hace absolutamente nada con la bicicleta que le damos a arreglar, pero
repito que el objetivo de este rollo es purame nte didáctico.

Pues eso, que ahora podemos hacer sin problemas:

Bicicleta Antonieta = new Bicicleta();


Taller elTaller = new Taller();
/ / después de largas horas de uso...

elTaller.arreglar(Antonieta);

es decir, creamos una bici, un taller, y hacemos que el taller arregle


la bici después de mucho montar. Bueno, pues esto no parece
complicado, ¿verdad?

Bueno, plantéate lo siguiente: Si en el taller arrerglan ruedas de


bicicleta, y una moto es una bicicleta con motor, ¿va a poder el taller
arreglar las ruedas de una moto? ¡Pues claro que sí! Pero no podemos
hacerlo tan a la ligera. Veamos por qué.

El método arreglar requiere como parámetr o una Bicicleta . Sin


embargo, queremos areglar una moto. Bueno, pues es aquí donde
haremos el uso de referencias de antes:

Crearemos primero una moto, pero tendrá una referencia de tipo


bicicleta:

Bicicleta laMotoRompida = new Moto(); // moto con ref.


Bicicleta

Y..... ¡Si! ya podemos arreglar la moto:

Taller elTaller = new Taller();


elTaller.arreglar(laMotoRompida); / / arreglamos una moto en
realidad

¿Te da cuen? Si arreglar necesita una bicicleta y queremos arreglar


una moto, hacemos que una referencia de tipo bicicleta apunte a una
moto.

Bueno, pues fíjate para qué sirve esto de las referencias. ¿Te gusta?
Pues verás ahora.

Un taller que se precie no sólo debe ser capaz de arreglar


pinchazos. También debería poder cambiar el aceite a los motores, ¿no?
Bueno, pues vamos a definir otro método de la clase Taller.

class Taller {
void arreglar(Bicicleta bici){
System.out.println(“A arreglar!”);
}

void cambiarAceite(Moto laMoto){


System.out.println(“El aceite estaba echo una
miiiiierda”);
}
}

Bueno, pues ahora tenemos un peazo taller de la leche. Si tenemos


una moto cualquiera podemos arreglarle los pinchazos y cambiarle el
aceite. Pero fíjate, el método cambiarAceite requiere que le pasemos una
moto (porque cambiar el aceite a una bici se las traería, ¿no?), pero
arreglar requiere una bicicleta. Madre mía. ¡Necesitamos una referencia
de tipo Bicicleta para arreglar la moto y otra de tipo Moto para cambiarle
el aceite!

Taller elTaller = new Taller();


Bicicleta refMoto = new Moto();

Si queremos cambiar el aceite a la moto, es fácil:

elTaller.cambiarAceite(refMoto);

Pero, para arreglar las ruedas necesitábamos una referencia de tipo


bicicleta , y la referencia que tenemos es de tipo Moto . Tendremos que
crear una nueva referencia:

Bicicleta refBicicleta = refMoto;

Esta referencia nueva apunta al mismo objeto al que apuntaba


refMoto . Ahora sí es lícito hacer

elTaller.arreglar(refBicicleta);

Bueno, pues esto es perfectamente legal, funciona. Pero coincidirás


conmigo que es un engorro. Tener dos referencias de tipo distinto para
un mismo objeto no es ser muy ahorrativo. Ni muy práctico, todo sea
dicho.

Casting

Como dijimos hace una eternidad, “casting” es algo así como


“convertir”. El objetivo es el siguiente:

Hemos visto casos en los que un objeto necesita varias referencias


de distinto tipo, como con el taller: arreglar las ruedas necesita una
bicicleta, pero cambiar el aceite necesita una moto. Para cambiar el aceite
a una moto no hay mucho problema, pero para arreglarle las ruedas
había que hacer una nueva referencia de un tipo diferente.

Bueno, pues existe una manera de hacer esto mismo con una sola
referencia . ¿Cómo? Pues muy fácil: convirtiéndola .
Convertir una referencia de un tipo inicial a un tipo final se hace
así:

(tipo final)referencia

Por ejemplo, si quiero convertir un número entero en uno de coma


flotante (¡CUIDADO! no te creas que en este caso hay herencia de algún
tipo, ¿eh?)

(float)miEntero

O si quiero convertir una moto en bicicleta...

(Bicicleta)refMoto

Es decir, que usando esta conversión, puedo hacer sin problemas:

Taller elTaller = new Taller();


Moto Maroto = new Moto();

y, por fin....

elTaller.cambiarAceite(Maroto);
elTaller.arreglar( (Bicicleta)Maroto ); / / esto es casting!!!!!!!!!

Tenemos una sola referencia, llamada Maroto que es de tipo Moto ,


pero podemos usar esta referencia de tipo Moto en métodos que requieran
una referencia de tipo Bicicleta (como el método arreglar ) porque
podemos convertirla . ¡BIEN!

Relee el párrafo anterior.


Vuelve a leerlo.
¿Está claro?
¿Seguro?
Fabuloso.
¿Realmente te parece el casting lo que te parecía antes?

Hala. A modo de resumen:

1. Una moto puede saludar como una bici. Podemos hacer que un
objeto de un tipo se comporte como un objeto de otro tipo.

2. El método arreglar ve a una moto como si fuese una bici,


mientras que cambiarAceite ve la misma moto como lo que
realmente es, una moto. Podemos hacer que un método vea a un
objeto como si fuese de una clase y que otro método vea el
mismo objeto como si fuese de otra clase.
3. Esto lo podemos hacer mediante varias referencias de diferentes
tipos que apunten al mismo objeto....

4. O mediante una sola referencia y el “truco” del casting

El Casting se acaba aquí. Con estos conocimientos puedes


enfrentarte a un fragmento de código que contenga conversiones de
referencias, es decir, casting. Pensado puedes saber qué sentencias serán
correctas y cuales no lo serán. Sólo tienes que pensar qué contenidos del
objeto es capaz de ver una referencia.

Antes de seguir (y por supuesto después de realizar tus ejercicios


espirituales) plantéate los conceptos de:

REFERENCIA
TIPO DE UNA REFERENCIA
CÓMO las referencias apuntan a un objeto
CÓMO dos referencias de diferentes tipos ven contenidos
diferentes de un
mismo objeto
COMPARACIÓN DE REFERENCIAS: = = no funciona como
quisiéramos....
CÓMO un método requiere referencias de determinado tipo (como
parámetros)
SENTIDO DE usar distintas referencias para un objeto
FINALIDAD DEL CASTING
SINTAXIS DEL CASTING

¡Recuerda!

Vimos cómo se usaba el casting para convertir enteros a coma


flotante. A esto se le llama también CASTING, pero no tiene nada
que ver con herencia. El Casting se usa para convertir

a) tipos básicos (de int a float, por ejemplo)


b) referencias de objetos (llevamos tol rato haciéndolo)

Para comerte el coco.... (6)

Dadas las siguientes definiciones:

class A {
int getn() {
return 1;
}
}

class B extends A {
int getn() {
return 2;
}
}

class ABMain {
public static void main(String[] args){
A a = new A();
B b = new A();
System.out.println(“numero:”+a.getn());
System.out.println(“numero:”+b.getn());
}
}

Si lanzamos el programa, ¿Qué vemos en pantalla? Madre mía,


típica cuestión de examen. Bueno, un puntito para ti.

Upcasting y downcasting. Y siguiendo con el inglés, explicit and


implicit casting.

Este apartado es muy corto y sólo sirve para que aprenda m os un


par de términos. Sólo hay un pequeño problema: es un pelín enrevesado.
Trataré de explicarlo fácilmente, pero eso no va a ser suficiente. Así que
abre bien los ojos y lee cada párrafo siete veces. Para hacerlo más corto
aún, vamos a seguir con la bicicleta y la moto como ejemplos, ¿vale?

Bicicleta Anacleta = new Bicicleta();


Moto Maroto = new Moto();

Upcasting: convertir una moto en una bicicleta. Es decir, convertir


una referencia de un tipo hijo a un tipo padre:

(Bicicleta)Maroto

Downcasting: lo contrario:

(Moto)Anacleta

Lo recordarás fácilmente si te imaginas la clase padre encima de la


clase hijo. “UPcasting” es “ir hacia arriba” y “DOWNcasting” sería “ir hacia
abajo”.
Voy a adjuntar te este pequeño fragmento de código para entender
genial los dos siguientes conceptos, casting implícito y explícito:

class A {
int getn() {
return 10;
}
}

class B extend s A {
int getm() {
return 20;
}
}

class C {
void pillaUnA ( A j ){
System.out.println("n = "+j.getn());
}

void pillaUnB ( B j ) {
System.out.println("m = "+j.getm());
}
}

Ahora supongam os un main cualquiera con las siguientes


declaraciones:

public static void main(String[] args) {


A a = new A();
B b = new B();
C c = new C();
.
.
.

Weno. Es perfectamente lícito hacer:

c.pillaUnA(a);
c.pillaUnA(b);

En el primer caso, a es una referencia de tipo A y por eso puede


pasarse al método pillaUnA como parámetro.

En el segundo caso, AL LORO, b es una referencia de tipo B. Si


queremos pasarla al método pillaUnA deberíamos hacer:

c.pillaUnA( (A)b );

es decir, hacer UPCASTING. Sin embargo, este casting no es


necesario hacerlo, porque el compilador lo hace directamente. A esto se
le llama casting implícito .
Hagamos ahora:

c.pillaUnB(b);
c.pillaUnB(a);

En el primer caso, no hay ningún problema. En el segundo caso,


estamos pasando una referencia de tipo A a un método que requiere una
referencia de tipo B. Es decir, al contrario de antes. Bueno, pues como
pillaUnB() necesita un parámetr o de tipo B, la referencia a tendremos que
convertirla a una referencia de tipo B. Es decir, hay que hacer
DOWNCASTING. Bueno, pues este casting no es implícito, no lo hace el
compilador solito, como antes, sino que necesita que nosotros lo
hagamos. A este casting se le denomina casting explícito .

Por regla general, el upcasting es implícito y el downcasting es


explícito .

AVISO: dado el código:

A a = new A();
C c = new C();

podemos hacer downcasting así:

c.pillaUnB( (B)a );

per fíjate: la referencia a apunta a un objeto de tipo A que no tiene


el método getM(). Es decir, que sintácticamente el casting es lícito, pero
pillaUnB() jamás podrá acceder al método getM() del parámetro ,
sencillamente porque en este caso no el parámetr o no tiene dicho
método.

Es un poco engorroso, pero básicamente lo que te planteo es que


este downcasting puede hacerse, puede compilar, pero jamás va a poder
ejecutarse.

¡Ya hemos acabado! plantéate de nuevo:

UPCASTING
DOWNCASTING
CASTING IMPLÍCITO
CASTING EXPLÍCITO

Ejercicio:

Dado el siguiente main, ¿Qué sentencias son lícitas? ¿cuáles


compilan y cuáles no? ¿cuáles tienen upcasting y downcasting? ¿Cuáles
tienen casting implícito y cuáles explícito?

public static void main(String args[]){


A a = new A();
B b = new B();
C c = new C();

c.pillaUnA(a);
c.pillaUnA(b);

c.pillaUnA ( (A)b );
c.pillaUnA ( (B)a );

c.pillaUnB (a);
c.pillaUnB (b);

c.pillaUnB ( (A)b );
c.pillaUnB ( (B)a );
}

Solución:

Es normal fallar en algunas, así que no te desesperes si no aciertas,


¿eh? Por cierto, estoy seguro de que esta solución es wena al 99%, pero no
al 100%. Así que si no concuerda con lo que tú dices, y estás seguro de
que no te has equivocado, tal vez lo haya hecho yo... De todas formas, he
compilado el código y parece que se ciñe a estos resultados. Por
supuesto, lo mejor cuando tengas dudas es encender el ordenador y
probar.....

c.pillaUnA(a); / / normal
c.pillaUnA(b); / / upcasting implícito

c.pillaUnA ( (A)b ); / / upcasting explícito


c.pillaUnA ( (B)a ); / / compila pero no ejecuta

c.pillaUnB (a); / / requiere downcasting explícito


c.pillaUnB (b); / / normal

c.pillaUnB ( (A)b ); / / requiere downcasting explícito


c.pillaUnB ( (B)a ); / / compila pero no ejecuta

Bueno, visto esto, vamos a por la maravillosa...

La Clase Object

La Clase Object es una clase que Java tiene definida en sí mismo


con su propio mecanismo. Es decir, que no vas a tener que definirla, así
que tranki.

La clase Object se define como superclase de toda clase Java. Es


decir, que si defines una clase cualquiera, será una clase que herede de la
clase Object , aunque no lo definas como tal. La clase Mesa o Bicicleta
son, en realidad, extensiones de Object . Fíjate qué cosas.

Bien. ¿Qué contenidos tiene la clase Object ? Pues esta clase tiene
unos métodos muy extraños:
clone() , que sirve para hacer clonar objetos,
equals() , para comparar objetos,
getClass() , para obtener la clase de un objeto,

y algunos más. En definitiva, que la clase Object tiene un contenido


bastante rarito. ¿Cuál es el sentido de este contenido? Pues muy fácil:
como cualquier objeto hereda de Object , Object contiene los métodos
que todo tipo de objetos debería tener. Métodos para que un objeto se
clone, se compare con otro objeto y cosas por el estilo. De esta forma, si
tu objeto necesita tales métodos, pues ya los tiene definidos.

Sin embargo, estos métodos sirven para todo tipo de objetos. Por lo
que no es difícil adivinar que serán todos métodos muy generales. Así
que lo más probable es que, si quieres utilizar uno de estos métodos,
tendrás que redefinirlo (override).

Pero el auténtico potencial de la clase Objwect no reside en su


contenido, sino en el hecho de que es la superclase de toda clase Java. Y
dado que ya pilotas la herencia y el casting, te darás cuenta de que
cualquier objeto podrá verse referenciado por una referencia de tipo
Object ..Para ver un ejemplo,vamos a definir las siguientes clases:

class Florero {
void saludar(){
System.out.println(“Soy un florero”);
}
}

class Foto {
void saludar(){
System.out.println(“Soy una fotografía”);
}
}

class Lampara {
void saludar(){
System.out.println(“Soy una lámpara”);
}
}
Bueno, pues el objetivo de nuestro programa es escribir un método
que meteremos en la clase Mesa (¿te acuerdas de ella?) que sirva para
colocar cosas encima de una mesa. Recuerda la clase Mesa :

class Mesa {
String color;
int numPatas;
String forma;
static int numMesasFabricadas = 0;

void setColor(String nuevoColor){


color = nuevoColor;
}
void setNumPatas(int nuevoNumPatas){
numPatas = nuevoNumPatas;
}

void setForma(String nuevaForma){


forma = nuevaForma;
}

String getColor(){
return color;
}

String getForma(){
return forma;
}

int getNumPatas(){
return numPatas;
}

Mesa(String colorInicial,
int numPatasInicial,
String formaInicial){

color = colorInicial;
numPatas = numPatasInicial;
forma = formaInicial;
numMesasFabricadas = numMesasFabricadas + 1;
}

Mesa() {
color = “azul”;
numPatas = 4;
forma = “cuadrada”;
numMesasFabricadas = numMesasFabricadas + 1;
}

int getNumMesasFabricadas() {
return numMesasFabricadas;
}
}

Bueno, pues ahora queremos hacer un método ponerEncima() para


colocar encima de una mesa una lámpara, una foto o un florero, es decir,
objetos de las clases que antes hemos definido. Por supuesto, el
contenido del método no importa en absoluto.

A primera vista se nos ocurre hacer un método para cada tipo de


objeto a colocar, es decir, escribir un ponerEncimaUnFlorero,
PonerEncimaUnaLampara, ponerEncimaUnaFoto , más o menos así:

void ponerEncimaUnFlorero (Florero nuevoFlorero) {


.
.
.
}

void ponerEncimaUnaFoto (Foto nuevaFoto) {


.
.
.
}

void ponerEncimaUnaLampara(Lampara nuevaLampara) {


.
.
.
}

Bueno, normal, ¿no? Un método específico para colocar cada


objeto.

Bueno, pues ahora vamos a hacer uso de la clase Object. Como es la


superclase de toda clase Java, podemos hacer un método que coloque
cualquiera de los objetos anteriores:

void ponerEncima ( Object nuevoObjeto ) {


.
.
.
}

Hala. Ya podemos hacer:

Mesa Margraita = new Mesa();

Florero f = new Florero();


Margarita.ponerEncima(f);

Lampara l = new Lampara();


Margarita.ponerEncima(l);

Hala. Revive este pequeño capítulo sobre la clase Object y no sigas


adelante si no has entendido a la perfección:

CLASE OBJECT
SU CONTENIDO
SU USO COMO REFERENCIA GLOBAL (para toda clase Java)

Ejercicio:

Plantéate los siguientes fragmentos de código

Lampara l = new Lampara();


Margarita.ponerEncima( (Lampara) l );

Lampara l = new Lampara();


Margarita.ponerEncima( (Object) l);
Object l = new Lampara();
Margarita.ponerEncima(l);

Object l = new Lampara();


Margarita.ponerEncima( (Object) l);

Échales un ojo de nuevo.......... te lo


aseguro:
¡usarás la clase
Object
¿sabes qué es lo mejor? más de lo que tú
te crees!

Que todos funcionan.

¿Has visto lo genial que


es la clase Object ?

Verás, ahora debemos parar un


minuto para reconsiderar una cosa. En el prefacio he dicho que iba a
explicar todo el rollo de Java basándo me en la vida real, en ejemplos
sencillos e ilustrativos, y desgraciada me nte a partir del casting ese hecho
ha empeza do a decaer, y hemos alcanza do un punto en el que
consideramo s métodos ( ponerEncima() ) que ni siquiera hemos definido,
sólo hemos escrito su cabecera. Hay que entender que del casting en
adelante es todo muy abstracto, y por ello poner ejemplos de la vida real,
pues ejem.... la verdad, es complicado.

Vamos, lo que pretendo hacer es disculpar me si todo esto te ha


parecido demasiado tedioso, que en realidad lo es. Sólo que sepas que he
hecho lo que he creído mejor, hemos dejado métodos sim implementar
porque hacerlo sería complicar las cosas demasiado y no vendría a
cuento; las explicaciones son más escuetas que al principio porque la
intención es mostrar la idea limpiamente: partir de un objetivo, plantear
un problema, aportar soluciones y escoger la mejor.

En definitiva, si esta parte te ha parecido lo peor, siento que haya


sido así, y espero y deseo de corazón que encuentres fácilmente una
explicación mejor. Por cierto, el pez, el pato y el pájaro que no tengo ni
idea de cómo se llama, también te piden disculpas.

Bueno, pues estos tres bichos también te animan a seguir adelante,


ya que queda muy poco. De verdad. Lo prometo. Bueno, abordemos ahora
un tema bastante curioso. Son las...

Interfaces

Vamos a ver.
El objetivo de una interface es definir qué contenidos debe tener
una clase obligatoriamente. Es algo así como un contrato: una clase
puede implem entar una interface si se compro mete a definir
determinados contenidos. Veamos.

Si yo soy un trabajador, tengo obligatoriamente que pagar


impuestos. Eso significa que todo trabajador se compro mete a pagar
impuestos. Bueno, debería.

Las interfaces, repito, definen qué contenidos debe tener una clase.
Por ejemplo, qué métodos debe tener obligatoriamente, pero no define
esos métodos. Es decir, que una interface sólo es una colección de
cabeceras de métodos. La clase que implemente esa interface debe
implementar esos métodos. Lo vemos con un ejemplo: Si definimos:

interface Trabajador {
void pagarImpuest os();
}

Entonces, toda persona que sea trabajador, trabaje en lo que


trabaje, debe implementar esta interface:

class Funcionario implement s Trabajador {


String nombre;
String dirección;
int edad;
int sueldo;
.
.
.

void pagarImpuest os(){


System.out.println(“Como funcionario, pago mis
impuesto s”);
sueldo = sueldo – (0.16*sueldo);
}
}

Hala, ahora cada funcionario pagará el 16% de su sueldo. Pero


fíjate, un presentador de televisión paga más impuestos que un
funcionario. Eso significa que su método pagarImpuesto s() será
ligeramente diferente. Su sueldo se ve reducido en un 30%, y además, el
mensaje que nos avisa de tal pago es un poco más pijo.

class Presentador implement s Trabajador {


String nombre;
int cuánInsoportableSoy;
.
.
.

void pagarImpuest os(){


System.out.println(“O sea, yo pago un plusss s”);
sueldo = sueldo – (0.30*sueldo);
}
}

¿Te das cuenta? Tenemos dos personas diferentes, que nada tienen
que ver la una con la otra, excepto una cosa: que ambas son trabajador as.
Así, deben pagar impuestos. Pero fíjate que cada una lleva a cabo ese
pago de forma diferente. La Interface Trabajador no determina cómo
va a llevarse a cabo tal pago, solamente exige que se lleve a cabo .

Bueno, creo que hasta ahora todo va más o menos bien, ¿no? Ahora
vamos a por lo mejor de las interfaces.

¿Lo has pillado? En realidad, el objetivo de una interface es que una


clase A pueda comunicarse con otra clase B independiente me n te de cómo
B esté escrita. Por ejemplo: supón que ahora definimos la clase Hacienda .
Esta clase exigirá a todos los trabajadores que paguen sus impuestos.
Pero hay miles de trabajadores distintos, unos son funcionarios, otros
presentadores, pero otros son otras cosas. Sin embargo, Hacienda juega
con un as en la manga: como hemos exigido que cada trabajador, sea
como sea, implemente a la interface Trabajador , podemos referenciar a
cualquier clase que implement e a Trabajador mediante una referencia
de tipo Trabajador:

class Hacienda {
.
.
.
void quePagueElCurrante(Trabajador currante) {
currante.pagarImpuesto s();
}
}

Joer, qué cosa. Resulta que al método quePagueElCurrante le


pasamo s como parámetr o cualquier objeto que implemente a la interface
Trabajador , y éste método se encarga de hacer que pague. ¿Te fijas?
Primero: no nos importa quñe tipo de trabajador sea, sólo nos importa
que implemente a Trabajador. Segundo: La referencia a este objeto es de
tipo Trabajador, no es ni Presentador ni Funcionario ni nada.

El mecanismo de la referencia de tipo Trabajador es semejante a la


señora Referencia que vimos en la Herencia: es una flechita que apunta
sólo a los contenidos de Trabajador , el resto de los contenidos ni los ve.
Es como si lo único importante de un trabajador fuese que pague
impuestos.
Ahora, en un programa podemos hacer:

Funcionario Fulgencio = new Funcionario();


Presentador JesusVazque z = new Funcionario();
Hacienda malditaHacienda = new Hacienda();

malditaHacienda.quePagueElCurrante( Fulgencio );
malditaHacienda.quePagueElCurrante( JesusVazque z );

Bueno, ahora, si lo has entendido todo, te preguntar ás la diferencia


entre Interfaces y Herencia. Bueno, esta es tal vez la mejor pregunta que
puedas hacer.

Cuando una clase hereda de otra, hereda todos sus contenidos. En


las Interfaces no se definen contenidos, por lo que no se puede hereda
ningún contenido. Una interface no es una clase , como lo son las
superclases en la herencia, por lo que una interface no se puede
instanciar :

Trabajador yop = new Trabajador(); / / lo peorcito.

Una clase puede implementar varias interfaces a la vez, pero no


puede heredar de varias clases a la vez. NO EXISTE LA (maravillosa)
HERENCIA MÚLTIPLE EN JAVA, diga lo que diga tu profesor (vale, pero
si te lo pregunta en el examen responde lo que él diga!). Las Interfaces
pueden parecer un mecanismo de herencia múltiple, pero no es así en
realidad.

Supongo que tu profesor de dará más razones para diferenciar


interfaces de herencia. Yo sólo te digo las más representativas (para mí).

Hala. Repasa los conceptos de

HERENCIA (ea, fastidiate)


INTERFACE
USO DE IMPLEMENTS
USO DE REFERENCIAS DEL TIPO DE UNA INTERFACE
DIFERENCIAS ENTRE HERENCIA E INTERFACES

¿Te doy una sorpresa?

Ya hemos acabado.

Epílogo

Es una pena, me gusta pensar en un epílogo como en el final de un


libro de aventuras y no de un cursillo rápido de OOP y Java.

Hemos visto cómo es la estructura de la OOP: clases y objetos.


Hemos visto cómo se llevan a cabo en Java. Hemos visto programación
estructur a da, es decir, sentencias de control: bucles y sentencias
condicionales, la fatal historia de la recursividad, herencia, casting,
interfaces.... y poco más.

Desgraciada mente hay otros puntos que no he tocado, porque


realmente son un tanto innecesarios porque ocuparían un hueco que no
es necesario hacer: clases abstractas, tipos básicos de datos, clases para
acceso a disco (los asquerosos flujos o streams), la estúpida
BufferedString.... en fin, unas cuantas cosas que no son realmente
necesarias: tu profesor te las enseñará durante tres horas cada una de
ellas, cuando no son necesarios más de diez minutos.

¿Por qué estas lagunas? Muy sencillo: porque mi intención no es


enseñarte Java a fondo, (eso lo hará el “mastering Java” se la editorial
SUN) sino enseñarte a programar en OOP. ¿Y eso? Fácil: si sabes
programar, entenderás cualquier cosa fácilmente. Cuando hayas leído
todo este mamotreto de letras y dibujos, y cuando lo entiendas a la
perfección, no te costrará aprender a usar flujos de entrada y salida en
disco, manejo de excepciones, acceso restringido a miembros de clase, o
yo qué sé.

Es decir, que aprendas primero a caminar... para luego echar a


correr.

Tal vez algún día escribas tú la segunda parte de “Java para


aprobar” explicando todo lo que me he dejado atrás. Sería muy bonito. Si
quieres, te paso los JPG del pez, el pato y el pajaro ese que no sé cómo se
llama.

Bueno, aquí acabamos. Espero que no se te haya hecho muy tedioso


(jeeee, que iluso soy). Pásalo bien, estudia mucho, pregunta lo que quieras
y sácale partido a todo esto, ¿vale?

David Muñoz Díaz


Grupo de Usuarios de Linux de la
Universidad Carlos III de Madrid
gul@gul.uc3m.es

The Stone Soup Story

Once upon a time, somewhere in Eastern Europe, there was a great


famine.
People jealously hoarded whatever food they could find, hiding it even
from
their friends and neighbors. One day a peddler drove his wagon into a
village,
sold a few of his wares, and began asking questions as if he planned to
stay
for the night.

"There's not a bite to eat in the whole province," he was told. "Better keep
moving on."

"Oh, I have everything I need," he said. "In fact, I was thinking of making
some stone soup to share with all of you." He pulled an iron cauldron
from his
wagon, filled it with water, and built a fire under it. Then, with great
ceremo ny, he drew an ordinary - looking stone from a velvet bag and
droppe d it
into the water.

By now, hearing the rumor of food, most of the villagers had come to the
square or watched from their windows. As the peddler sniffed the "broth"
and
licked his lips in anticipation, hunger began to overcome their skepticism.

"Ahh," the peddler said to himself rather loudly, "I do like a tasty stone
soup. Of course, stone soup with CABBAGE - - that's hard to beat."

Soon a villager approached hesitantly, holding a cabbage he'd retrieved


from
its hiding place, and added it to the pot. "Capital!" cried the peddler. "You
know, I once had stone soup with cabbage and a bit of salt beef as well,
and
it was fit for a king."

The village butcher managed to find some salt beef...and so it went,


through
potatoes, onions, carrots, mushroo m s, and so on, until there was indeed
a
delicious meal for all. The villagers offered the peddler a great deal of
money for the magic stone, but he refused to sell and traveled on the
next
day. And from that time on, long after the famine had ended, they
reminisced
about the finest soup they'd ever had.

(tomado de un document o de Monte Davis)

Vous aimerez peut-être aussi