viernes, 29 de agosto de 2014

Programación y Personalización SIG. Introducción a la programación OO


Introducción
Con el fin de proporcionar los conocimientos mínimos necesarios para realizar implementaciones en una programación SIG vamos a realizar una introducción orientada a objetos usando el lenguaje Java.




Estructura de un programa
Un programa (sea informático o no) está compuesto por cuatro partes bien diferenciadas:
Código. Es el conjunto de instrucciones en sí. Normalmente, el código está escrito de forma que sea fácil de entender y manipular por una persona.
Memoria. Ofrece un espacio al programa para almacenar datos y recuperarlos más tarde.
Entrada. Es el conjunto de datos que el programa recibe mientras se ejecuta y que condicionan las acciones que éste realiza y, en consecuencia, los resultados que genera. Normalmente, los datos proceden del usuario (pulsaciones del teclado, movimientos y pulsaciones del ratón, etc.), pero también pueden venir de otros programas. En este último caso tenemos, por ejemplo, los datos enviados por un servidor web a nuestro navegador.
Salida. Es el conjunto de datos generados en forma de resultado durante la ejecución del programa. Estos datos pueden percibirse como acciones desde el punto de vista del usuario. Los resultados pueden ser, pues, vario puntos: un número, una hoja de papel impresa, una imagen en la pantalla, etc.


1. Fundamentos de programación
1.1. Declaración y uso de variables
La memoria de un ordenador permite almacenar y posteriormente recuperar gran cantidad de datos. Cuando se programa con un lenguaje de alto nivel como Java, se utilizan nombres para hacer referencia a los datos que hay en la memoria. En el argot del programador, a estos datos con nombre se les llama variables. Por definición, no puede haber dos variables con el mismo nombre.

Para garantizar una cierta coherencia en el uso de las variables, éstas, además de tener nombre, deben ser de un tipo. El tipo determina qué datos puede almacenar una variable (texto, números, etc.) y qué operaciones podemos realizar con ella. Empezaremos trabajando con los tipos siguientes:

int: valor entero entre –2147483648 y 2147483647.
No admite valores decimales.
Por ejemplo, 2001 ó 175000000.
double: valor real sin límite de rango y de precisión arbitraria.
Admite valores decimales. Por ejemplo, –0,075 ó 3,141592654.
String: texto de longitud arbitraria. Por ejemplo, “La casa de María” u “¡Hola, mundo!”.
boolean: cierto o falso.

Para poder trabajar con una variable, primero tenemos que declararla. Declarar una variable consiste en mencionar su nombre y su tipo. En Java se escribe primero su tipo, después su nombre y se termina la línea con un punto y coma. El punto y coma es importante, porque indica el final de la declaración. Por ejemplo:

int contador;
declara una variable llamada “contador” de tipo int. Como ya hemos visto anteriormente, en esta variable podremos almacenar un valor entre –2147483648 y 2147483647.

Las líneas siguientes:
boolean esClientePreferente;

double capital, interés;



declaran tres variables: “esClientePreferente”, de tipo boolean, y “capital” e “interés”, ambas de tipo double. En la primera podremos almacenar cierto o falso, lo que nos servirá, por ejemplo, para almacenar si se trata de un cliente preferente o no. En las dos últimas, almacenaremos el capital y el interés que estudiamos darle a ese mismo cliente, que puede ser diferente en virtud de que sea cliente preferente o no. Observad que podemos declarar dos o más variables en una misma línea separando sus nombres por medio de la coma.
La línea siguiente:

int valores[];

declara un vector de variables de tipo int llamado “valores”. Un vector es un conjunto de variables del mismo tipo. Utilizaremos “valores[0]” para referir- nos a la primera variable, “valores[1]” para referirnos a la segunda, y así sucesivamente.
En Java, se puede dar cualquier nombre a una variable. Sin embargo, se deben respetar unas pocas reglas. Los nombres de las variables:

No pueden comenzar por un carácter numérico. Por ejemplo, “12” o “1Valor” no son nombres válidos.
No pueden llamarse igual que los elementos del lenguaje. Por ejemplo, no podemos tener una variable que se llame “int” o “double”.
No pueden contener espacios.
Sin embargo, a diferencia de otros lenguajes, Java sí que permite que el nombre de una variable contenga caracteres acentuados.

Otro punto que se debe tener en cuenta es que Java distingue entre mayúsculas y minúsculas. Esto significa que las variables:

double capital, Capital;
son diferentes en todos los sentidos.

1.2. El operador de asignación
Una vez declarada una variable, ésta tiene un valor indefinido. Para empezar a trabajar con ella, se necesita asignarle un valor. La asignación de valores a variables se lleva a cabo mediante el llamado operador de asignación: “=” (el signo de igual). Así pues, las líneas siguientes:

int contador; contador = 0;
declaran la variable “contador” y le asignan el valor 0.

Como podemos observar, a la izquierda del operador de asignación se emplaza el nombre de la variable, y a la derecha, el valor que se desea asignarle. La asignación (como la declaración de variables) termina en punto y coma.
Veamos más ejemplos de asignaciones:

int suma; suma = 3 + 2;
asigna a la variable “suma” el resultado de sumar 3 y 2 (cinco).
int contador; contador = 0;
contador = contador + 1;

asigna, en primer lugar, el valor 0 a la variable “contador”, y luego la incre- menta en una unidad (1). Es importante destacar que cuando asignamos un valor a una variable sobrescribimos su valor anterior. Así pues, al finalizar el código, la variable “contador” valdrá 1.

double interés, capital,
tasaInterés, tiempo;
capital = 60000;
tasaInterés = 0.045;
 tiempo = 6;
interés = capital * tasaInterés * tiempo;

asigna a la variable “interés” los intereses generados por el préstamo de un capital de 60.000 euros a una tasa del 4,5% (expresado en tanto por uno) a seis años. Según la fórmula del interés simple, los intereses serán igual al producto de los tres factores. Como puede apreciarse, el producto se representa median- te el carácter asterisco (*). Al final de la ejecución, “interés” valdrá 16.200.

String nombre, frase; nombre = "María";
frase = "La casa de " + nombre;

asigna a la variable “frase” el resultado de concatenar el texto “La casa de ” y la variable “nombre”. Al finalizar la ejecución, “frase” será igual a “La casa de María”. Debemos notar que el texto siempre se escribe encerrado entre comi- llas dobles (“”).

boolean esPositivo;
int número;
número = –5;
es_positivo = número > 0;

almacena en la variable “esPositivo” el resultado de evaluar si la variable “nú- mero” es mayor que 0. Al finalizar el código, “esPositivo” valdrá false (falso).
Si bien la flexibilidad que ofrece el operador de asignación es notable, para que una asignación sea válida debe haber una cierta compatibilidad entre el tipo de la variable y el tipo del valor que se le quiere asignar. Por ejemplo, la asignación siguiente no es válida:

int peso; peso = 58.5;
pues intenta asignar un valor decimal a una variable entera.
Tampoco es válida la asignación:
String resultado; resultado = 25;
pues intenta asignar un valor entero a una variable de tipo texto.



1.3. Expresiones
Como hemos visto en los ejemplos anteriores, a una variable no sólo se le puede asignar un valor literal o el valor de otra variable. Además, se le puede asignar el valor resultante de una operación. Cuando nos encontramos con una combinación de valores literales, variables y operadores, estamos ante lo que se llama una expresión.
Las expresiones, que siempre generan un valor, están sometidas a unas reglas de evaluación que debemos conocer. Si tenemos, por ejemplo:

int cálculo;
cálculo = 2 + 3 * 5;

La expresión “2 + 3 * 5” se puede interpretar de dos formas: sumar 2 y 3, y el resultado (5) multiplicarlo por 5 (25), o bien multiplicar 3 por 5, y al resultado (15) sumarle 2 (17). Como podemos ver, de la forma de interpretar la expresión depende el resultado.

El lenguaje Java, como muchos otros lenguajes, define unas prioridades o precedencias entre operadores que dictan el orden en el que se realizarán las operaciones. La tabla “Precedencia de los operadores” muestra los operadores más comunes y su precedencia. Cuanta mayor precedencia, antes se evaluará la operación.

Si aplicamos las reglas de precedencia de la tabla al ejemplo anterior, tenemos que la multiplicación precederá a la suma. En consecuencia, el lenguaje resol- verá la expresión multiplicando primero 3 por 5, y sumándole 2 al resultado. Así pues, se asignará el valor 17 a la variable “cálculo”.
Como hemos visto, la precedencia entre operadores permite al lenguaje determinar, de forma unívoca, el valor de una expresión. Sin embargo, este mecanismo no es suficiente en el caso de tener en una expresión dos o más operadores con la misma precedencia. Cuando esto sucede, el lenguaje resuelve la expresión evaluando dichos operadores de derecha a izquierda. Es decir, se operarán en el mismo sentido en el que se leen. Por ejemplo, el código:

int cálculo;
cálculo = 25 % 7 * 3;

calculará primero el residuo resultante de dividir 25 entre 7, y después multiplicará dicho residuo por 3. El resultado será 12.
Supongamos ahora que deseamos sumar 2 y 3, y multiplicar el resultado por 5. Ya hemos visto que el código:

int cálculo;
cálculo = 2 + 3 * 5;

no responde a nuestras necesidades, pues, por precedencia, se evaluará antes la multiplicación que la suma. Para alterar el orden de evaluación de la expre- sión, encerraremos la suma entre paréntesis:

int cálculo;
cálculo = (2 + 3) * 5;

Cuando encerramos parte de una expresión entre paréntesis, esta parte se con- sidera una unidad indivisible y se evalúa independientemente antes de ser operada. Así, volviendo al ejemplo, la expresión se resolverá multiplicando el resultado de evaluar la expresión “2 + 3” por 5. El resultado será 25.
Veamos más ejemplos de expresiones. Las líneas siguientes:

double precioConDescuento, precio, descuento; precio = 15.69; // en euros
descuento = 7; // tanto por ciento
precioConDescuento = precio – precio * descuento / 100;
Calculan el precio rebajado de un producto. En primer lugar, por tener más precedencia, se hace la multiplicación (precio * descuento), después se divide el resultado por cien, y finalmente, se resta el resultado obtenido del valor de la variable “precio”. El resultado será 14,5917.
Las líneas siguientes:

int número;
boolean estáEntreCeroYDiez; número = –5;
estáEntreCeroYDiez = número > 0 && número < 10;


Determinan si un número dado está entre 0 y 10. Para que esto se cumpla, el número debe ser, a la vez, mayor que 0 y menor que 10. En primer lugar, se comprobará si el número es mayor que 0 (número > 0), y después, si es menor que 10 (número < 10), para, finalmente, mediante el operador && (y), ver si ambas condiciones se cumplen. El resultado será igual a false (falso).



1.4. Estructura básica de un programa
El lenguaje Java, como la mayoría de lenguajes de programación, requiere una estructura mínima que acoja el programa y sus componentes. En el caso concreto de Java, a esta estructura mínima se le llama clase. Aunque las clases se estudiarán en detalle en el capítulo 2, es necesario conocer esta estructura para empezar a escribir programas funcionales. A continuación, se muestra la estructura mínima de una clase ejecutable:

public class Programa {
public static void main(String [] args) {
// aquí va el código
}
}

Sin querer entrar en demasiados detalles, el código define una clase (class) lla- mada “Programa” con una función “main” que será el que se ejecutará al ini- ciar el programa. El nombre de la clase ha sido escogido arbitrariamente (es decir, puede cambiarse); no así el nombre de la función, que siempre deberá llamarse “main”. Aunque las funciones se estudiarán en detalle en el apartado 1.7, es necesario adelantar que son simples agrupaciones (funcionales) de código. Este programa deberá guardarse en un fichero que se llame como la clase más el sufijo .java; en este caso, “Programa.java”. El código del programa deberá ir entre las llaves de la función “main”.
El programa siguiente:

public class Programa {
public static void main(String [] args) { double distancia, velocidad, tiempo; velocidad = 120; // en km/h
tiempo = 2.5; // en horas
distancia = velocidad * tiempo; // 300 kmSystem.out.println(distancia); // escribe 300
}
}

Calcula la distancia recorrida por un bólido a partir de su velocidad y el tiempo que ha estado circulando. El texto precedido por doble barra (//) son comen- tarios. Los comentarios son simples anotaciones que el programador hace en el código del programa para facilitar su comprensión. Los comentarios no se ejecutan. La última línea usa la función “System.out.println” para escribir el resultado en la pantalla.

Como puede apreciarse, todas las variables se han declarado al inicio del códi- go. A diferencia de otros lenguajes, en Java las variables pueden declararse en cualquier parte del código, siempre y cuando la declaración preceda a su uso. Sin embargo, es una práctica habitual declararlas al principio del código para mejorar su legibilidad.

1.5. Estructuras de control de flujo
Supongamos que se desea escribir un programa que calcule y muestre por pan- talla el resultado de la suma de los números pares entre 0 y 10. El programa se podría escribir así:

public class SumaPares {
public static void main(String [] args) { int suma;
suma = 2 + 4 + 6 + 8; System.out.println(suma); // escribe 20
}
}
El programa, efectivamente, escribirá “20”. Sin embargo, si se deseara conocer la suma de los números pares entre 0 y 10.000, escribirlos todos sería inviable o, cuando menos, demasiado costoso.

La solución a este problema nos la dan las estructuras de control de flujo del programa, que nos permiten repetir instrucciones o ejecutar unas u otras de- pendiendo de una condición.
Para sumar los números pares entre 0 y 10.000, en primer lugar, necesitamos generar de alguna manera todos esos números pares. Una forma fácil de hacerlo es partiendo del primer par, en este caso el 2, e irle sumando 2 una y otra vez:

númeroPar = 2;
númeroPar = númeroPar + 2; // númeroPar = 4númeroPar = númeroPar + 2; // númeroPar = 6númeroPar = númeroPar + 2; // númeroPar = 8
númeroPar = númeroPar + 2; // númeroPar = 9998

En total, 4.998 líneas de código, mientras “númeroPar” sea más pequeño que 10.000.
Afortunadamente, esto se puede reducir a tres líneas usando una estructura while (mientras):

númeroPar = 2;
while (númeroPar < 10000) { // mientras < 10000númeroPar = númeroPar + 2; // siguiente número par
}

La estructura while repite una serie de instrucciones mientras se cumpla una condición dada. Dicha condición se cierra entre paréntesis a la derecha del while, mientras que las instrucciones que se han de repetir se cierran entre lla- ves ({}). En el ejemplo, se repetirá la suma mientras “númeroPar” sea más pe- queño que 10.000.

Volviendo a nuestro empeño por conocer el valor de la suma de todos estos nú- meros, una vez localizados tan sólo nos queda irlos sumando. Para hacer esto, de- clararemos la variable “suma”, que irá acumulando el resultado. Sólo debemos tener la precaución de ponerla a cero antes de empezar. El programa queda así:

public class SumaPares2 {
public static void main(String[] args) { int suma, númeroPar;
suma = 0;
númeroPar = 2; // el primer número par
while (númeroPar < 10000) { // mientras < 10000 suma += númeroPar;
númeroPar += 2; // siguiente número par
}
System.out.println(suma);
}
}

Al final del programa, se mostrará el resultado por pantalla (24.995.000). He- mos de destacar el uso del operador “+=”, que asigna a una variable el valor de ella misma más el valor especificado. Es decir:

númeroPar += 2;
equivale a:
númeroPar = númeroPar + 2;
Supongamos ahora que queremos crear un programa que sume los múltiplos de un número dado en un intervalo dado. Por ejemplo, los múltiplos de 122 entre 20.000 y 40.000. El programa que suma los múltiplos de dos tiene una limitación: necesita conocer el primer múltiplo del intervalo para poder generar los demás.
Reconocer el primer número par de un intervalo es muy fácil, pero las cosas se complican bastante cuando es cuestión de encontrar el primer múltiplo de 122.

El programa “SumaPares2” construía cada número par sumando dos al inmediatamente anterior. Una vez generado, lo acumulaba, y así hasta haber agotado todos los números pares del intervalo. Sin embargo, ésta no era la única solución: se podría haber optado por recorrer todos los números del intervalo, determinando cuáles son pares y cuáles no. Si el número es par se acumula; si no, no. Esta dicotomía (si se cumple condición, hacer A; si no, hacer B) se pue- de modelar mediante la estructura if/else (si/si no).

Una forma de determinar si un número dado es par o no, es dividiéndolo por dos y analizando el residuo. Si el residuo es cero, el número es par. Teniendo en cuenta que en Java el residuo de una división se obtiene mediante el operador “%” (por cien), esta condición se podría escribir así:

if (número % 2 == 0) {
// código que hay que ejecutar si es par
}
else {
// código que hay que ejecutar si no es par
}

De modo similar a la estructura while, la estructura if ejecuta una serie de instrucciones si se cumple una determinada condición. Dicha condición se cierra entre paréntesis a la derecha del if, mientras que las instrucciones que se han de ejecutar se cierran entre llaves ({}). Opcionalmente, la estructura if acepta una extensiónelse, que especifica una serie de instrucciones que se han de ejecutar en caso de que la condición no se cumpla.
Volviendo al problema, si el número es par, debemos acumularlo, mientras que si no lo es, simplemente lo ignoramos.Utilizando la estructura if, el código quedaría así:

public class SumaPares3 {
public static void main(String [] args) { int número, suma;
número = 1; // primer número del intervalo suma = 0;
while (número <= 9999) { // último número del intervalo if (número % 2 == 0) { // ¿es par?
suma += número; // sí, entonces lo suma
}
número += 1; // siguiente número del intervalo
}
System.out.println(suma);
}

Al igual que “SumaPares2”, “SumaPares3” mostrará 24.995.000 por pantalla. Sin embargo, modificar esta última versión para que opere en otro rango o para que sume los múltiplos de un número diferente de 2, es inmediato. Siguiendo con el ejemplo, si se desea sumar los múltiplos de 122 entre 20.000 y 40.000, basta con cambiar sólo tres líneas:

public class SumaMultiples {
public static void main(String [] args) { int número, suma;
número = 20001; // primer número del intervalo suma = 0;
while (número <= 39999) { // último número
if (número % 122 == 0) { // ¿es múltiplo de 122? suma += número; // sí, entonces lo suma
}
número += 1; // siguiente número del intervalo
}
System.out.println(suma);
}
 

1.6. Esquemas de recorrido y de búsqueda
Hemos visto que cuando se trabaja con estructuras del tipo while, se repiten una serie de instrucciones mientras se cumpla una determinada condición. Generalmente, se recurre a las repeticiones con uno de estos dos objetivos:

Recorrer todos los elementos de un conjunto.
Buscar un elemento de un conjunto.

Este hecho nos lleva a hablar de esquemas de recorrido y de búsqueda. En el ejemplo “SumaPares3”, se analizan los números entre 1 y 9.999. Se comprueba cada número para ver si es par o no y, si lo es, se acumula. No se sale del bucle hasta que no se han analizado todos y cada uno de los números del rango. Es- tamos pues ante un esquema de recorrido.
Los esquemas de recorrido tienen siempre la misma estructura:

while (haya_elementos)
{ hacer_acción(elemento_actual);
siguiente_elemento();
}

Por otro lado, podemos tener repeticiones que tengan como objetivo buscar un determinado elemento dentro de un conjunto. Sin apartarnos de los ejemplos de carácter numérico, supongamos que se desea encontrar el primer múltiplo de 13 entre 789 y 800. Está claro que se deben comprobar los números del rango hasta que se encuentre el primer múltiplo de 13. El código siguiente podría resolver el problema:

int número;
número = 789;
while (número % 13 != 0) { número++;
}

Básicamente, empezando por 789, se va incrementando la variable “número” (“número++” equivale a “número = número + 1”) mientras “número” no sea múltiplo de 13. Sin embargo, esta aproximación comete un error: supone que encontrará algún múltiplo dentro del rango. Si este múltiplo no existiera, el buclewhile continuaría analizando números más allá del rango del problema. Por esto, además de comprobar que no se ha encontrado el número que se buscaba, también se debe comprobar que no se esté saliendo del rango de análisis. El código siguiente tiene en cuenta ambas cosas:

int número;
número = 789; 
while (número <= 800 && número % 13 != 0) { número++;
}

Como se puede observar, en primer lugar, se comprueba que “número” no sea mayor que 800 (es decir, que sea más pequeño o igual a 800) y, en segundo lugar, que no sea múltiplo de 13. Al haber dos condiciones que gobiernan el bucle (nú- mero <= 800 y número % 13 != 0), se puede salir de él por dos motivos:

1.- Por haber agotado todos los números del rango (“número” ha alcanzado el valor 801) o
2.- Por haber encontrado un múltiplo de 13.

Por esta razón, debemos comprobar, al finalizar el bucle, si se ha salido de él por un motivo u otro, pues de ello va a depender el comportamiento del resto del programa:

int número;
número = 789;
while (número <= 800 && número % 13 != 0) { número++;
}
if (número % 13 != 0) { // se ha encontrado un múltiplo
System.out.println(número);
}
else { // no se ha encontrado un múltiplo
System.out.println("No se ha encontrado.");
}

Los esquemas de búsqueda presentan la estructura siguiente:

while (haya_elementos && !
elemento_encontrado) { siguiente_elemento();
}

Es importante observar que la condición “elemento_encontrado” siempre irá negada. Esto siempre será así porque las instrucciones del bucle deben repetir- se mientras no se haya encontrado el elemento que se busca.



 static boolean esPrimo(int número) { int divisor;
divisor = 2;
while (número % divisor != 0 && divisor < número) { divisor ++;
}
 return divisor == // resultado: true o false
 }
primo = esPrimo(19);


1.7. Definición y uso de funciones
En nuestro afán por encontrar números de todo tipo, supongamos ahora que deseamos encontrar el primer número primo de un intervalo dado. Hay que re- cordar que un número primo es aquel que sólo se divide de forma entera por él mismo y por 1. El 13 o el 19, por ejemplo, son números primos.
El siguiente esquema de búsqueda podría resolver el problema:

encontrado = es_primo el primer_número_del_intervalo; while(!encontrado && hay_mas_numeros_en_el_intervalo) {encontrado = es_primo el siguiente_número;
}
if encontrado {
// número encontrado
}
else {
// número no encontrado
}

Probar que un número es primo no es una tarea fácil. De hecho, dado un nú- mero, llamémosle N, la única forma de comprobar que es primo es dividién- dolo entre todos los números primos entre 2 y N – 1, ambos incluidos*. Para que sea primo, los residuos de las divisiones deben ser diferentes de 0. Si hay alguna división cuyo residuo sea 0, el número no es primo.
Para simplificar el problema, probaremos la primalidad de un número divi- diéndolo entre todos los números menores que él, sean primos o no.
Vamos a generar, en primer lugar, un código que compruebe si un número es pri- mo. Concretamente, intentaremos demostrar que no lo es buscando un divisor entero del mismo. Si fallamos en nuestro intento, el número será primo. Como ya hemos visto, se trata de ir dividiendo el número en cuestión entre todos los nú- meros menores que él hasta obtener una división cuyo residuo sea 0. Es decir, de- bemos implementar un esquema de búsqueda del menor divisor entero:

int número, divisor;boolean esPrimo;
número = 19; // número que queremos comprobar
divisor = 2;
while (número % divisor != 0 && divisor < número) {
divisor ++;
}
esPrimo = divisor == número; // resultado: true o false

Como en todos los recorridos de búsqueda, hay dos condiciones que gobier- nan elwhile: la que comprueba que “divisor” no sea divisor entero de “núme- ro” (número % divisor != 0) y la que comprueba que no se haya salido de rango (divisor < número). Consecuentemente, al terminar la ejecución del bucle, “número” será primo si la condición que ha “fallado” ha sido la segunda. En este caso, el número será primo si “divisor” es igual a “número”.
Ahora que ya disponemos de un código capaz de determinar si un número dado es primo, vamos a construir el programa que busca el primer número primo de un intervalo. Basándonos en el pseudocódigo propuesto anterior- mente, el código siguiente busca el primer número primo entre 10.000 y 10.013:

public class BuscarPrimo {
public static void main(String[] args) {int inicio, fin; // inicio y fin del rango int número, divisor;
boolean encontrado;
// inicio y fin del intervalo de búsqueda inicio = 10000;
fin = 10013;
System.out.println("Buscando el primer número " +
" primo entre " + inicio + " y " + fin + "...");
//comprueba si el primer número del intervalo es
//primo
número = inicio; // número que hay que comprobar divisor = 2;
while (número % divisor != 0 && divisor < número) { divisor ++;
}
encontrado = (divisor == número); // resultado
//mientras no se haya encontrado un número primo y
//haya más números en el intervalo
while (! encontrado && número < fin) {
// comprueba el siguiente número
número ++; divisor = 2;
while (número % divisor != 0 && divisor < número) { divisor ++;
}
encontrado = (divisor == número); // resultado
}
if (encontrado) {
//si ha encontrado un número primo lo muestra por
//pantalla
System.out.println(número);
}
else {
System.out.println("No hay ningún número primo " + "en el intervalo");
}
}
}

Básicamente, el código es un esquema de búsqueda del primer número primo, combinado con el código que comprueba si un número es primo visto ante- riormente. Como se puede apreciar, éste último, que hemos enmarcado para distinguirlo con facilidad, se repite dos veces.
Repetir bloques de código dos o más veces dentro de un programa tiene serios inconvenientes:

Reduce la legibilidad. Como se puede apreciar en el programa, cuesta discernir entre el código que pertenece a la búsqueda del número primo y el que comprueba si un número lo es. Las variables se mezclan y las instrucciones también.
Dificulta la mantenibilidad. Si se detecta un error en la función que comprueba si un número es primo o se desea mejorarlo, las modificaciones de- ben propagarse por todos y cada uno de los bloques repetidos. Cuantas más repeticiones, más difícil será introducir cambios.
Es propenso a errores. Ello es consecuencia de la baja legibilidad y mantenibilidad. Una corrección o mejora que no se ha propagado correctamente por todos los fragmentos repetidos puede generar nuevos errores. La corrección de éstos, a su vez, puede generar otros nuevos, y así indefinidamente.

Al final, la repetición de bloques de código conduce a códigos inestables con un coste de mantenimiento más elevado en cada revisión. Afortunada- mente, todos estos inconvenientes se pueden salvar mediante el uso de funciones.
Si nos fijamos en el pseudocódigo que hemos concebido inicialmente, vemos que, de forma natural, hemos detectado la funcionalidad "comprobar si un número es primo", que hemos expresado mediande la fórmula "es_primo". Más adelante, cuando hemos implementado dicha funcionalidad, ha aparecido una variable que podríamos considerar la entrada del subprograma: "número", y otra que se podría considerar su resultado: "esPrimo". En la im- plementación final, el programa principal ha hecho uso de ambas variables para comunicarse con el subprograma, pasando el número a comprobar a través de la variable "número" y obteniendo el resultado mediante la variable "encontrado" (que es el nuevo nombre que, por razones de legibilidad, he- mos dado a la variable "esPrimo").
De todo esto, se derivan dos conclusiones importantes:

La lógica del subprograma es completamente independiente de la del pro- grama principal. Es decir, al programa principal poco le importa cómo se comprueba si un número es primo, ni que variables se utilizan para hacer dicha comprobación.

Programa y subpgorama se comunican mediante una interfaz basada en variables bien definida.
Llegados a este punto, podemos declarar lo que se llama una función. Una función no es más que una agrupación de código que, a partir de uno o más valores de entrada, o, valga la redundancia, en función de ellos, genera un resultado.
Veamos pues la función “esPrimo”:

static boolean esPrimo(int número) { int divisor;
divisor = 2;
while (número % divisor != 0 && divisor < número) { divisor ++;
}
return divisor == número; // resultado: true o false
}

Al margen del uso del modificador static, cuyo significado se estudiará en el capítulo 2, una función se declara escribiendo el tipo de resultado que de- vuelve (en este caso boolean), su nombre (“esPrimo”) y, encerrados entre paréntesis, sus parámetros de entrada separados por comas (“número” de tipo int). Como se aprecia en el código, los parámetros de entrada se definen de la misma forma que las variables: en primer lugar se escribe su tipo y, luego,
su nombre. El código de la función que sigue a este bloque declarativo va en- cerrado entre llaves.

Podemos observar que al final de la función aparece una sentencia nueva: return(devolver). Cuando se alcanza un return, termina la ejecución de la función, y se establece como resultado de la misma el valor de la expresión que sigue al return. La sentencia return siempre termina en punto y coma.
Una vez definida la función, podemos llamarla desde cualquier parte del código. Por ejemplo, la línea siguiente:

primo = esPrimo(19);
determina si el número 19 es primo. Dicho de otra manera, asigna a la va- riable “primo” (de tipo boolean) el resultado de la función “esPrimo” para el valor 19. La llamada a una función también termina en punto y coma. Es importante observar que, al ejecutarse la función, el parámetro “núme- ro” valdrá 19. De la misma forma, al terminarla, se asignará a la variable “primo” el valor de la expresión que hay a la derecha del return:

También es importante destacar que cuando se llama a una función pasa a eje- cutarse el código de ésta, y no se vuelve al código que la ha llamado hasta que no se alcanza un return.

static boolean esPrimo(int número) { int divisor;
divisor = 2;
while (número % divisor != 0 && divisor < número) { divisor ++;
}
return divisor == // resultado: true o false

}
primo = esPrimo(19);

Si rescribimos el programa “BuscarPrimo” usando funciones, nos queda así:

public class BuscarPrimo2 {
static boolean esPrimo(int número) {
int divisor;
divisor = 2;
while (número % divisor != 0 && divisor < número) { divisor ++;
}
return divisor == número; // resultado: true o false
}

public static void main(String[] args) {int inicio, fin; // inicio y fin del rango int número;
boolean encontrado;
// inicio y fin del intervalo de búsqueda inicio = 10000;
fin = 10013;
System.out.println("Buscando el primer número " +
" primo entre " + inicio + " y " + fin + "...");
//comprueba si el primer número del intervalo es
//primo
número = inicio; // número que hay que comprobar encontrado = esPrimo(número); // resultado
//mientras no se haya encontrado un número primo y
//haya más números en el intervalo
while (! es_primo && número <= fin) {
// comprueba el siguiente número número ++;
encontrado = esPrimo(número); // resultado
}
if (encontrado) {
//si ha encontrado un número primo lo muestra por
//pantalla
System.out.println(número");
}
else {
System.out.println("No hay ningún número primo " + "en el intervalo");
}
}
}

Incluso podemos simplificar el recorrido de búsqueda aún más:
número = inicio;
while (número <= fin && ! esPrimo(número)) { número ++;
}
Cuando se trabaja con funciones, deben tenerse en cuenta los aspectos si- guientes:

Los parámetros de una función actúan a todos los efectos como variables. Podemos leerlos y operar con ellos como si fueran variables.

Los parámetros de una función son una copia de los parámetros de entra- da. Si pasamos una variable como parámetro a una función, y dentro del código de la función se modifica el parámetro, el valor de la variable no se verá alterado.

Al espacio entre llaves que encierra el código de la función se le llama “ám- bito de la función”. Las variables declaradas dentro de la función sólo pue- den verse dentro del ámbito de la función. Es decir, no se puede acceder a ellas desde fuera de la función. Lo mismo es aplicable a los parámetros.

Una función se caracteriza a partir de su nombre y el tipo de los parámetros de entrada. Si cambia alguno de ellos, estamos ante funciones diferentes. Esto significa que puede haber funciones con el mismo nombre pero con parámetros distintos. A esta característica, que no todos los lenguajes so- portan, se le llama “sobrecarga de funciones”. A partir del número y el tipo de los parámetros de entrada en una llamada, se averiguará la función a la que se llama.

Una función puede que no devuelva ningún resultado. En este caso, el tipo de la función será void (vacío), y estaremos ante lo que se llama un procedimiento. Un ejemplo de procedimiento és la función main, que es la primera función que se ejecuta al iniciar un programa.

Bibliografía
Oracle, "The Java(tm) Tutorials", http://download.oracle.com/javase/tutorial/. 
Oracle, "Java(tm) 2 Platform Standard Edition 5.0 API Specification", http:// download.oracle.com/javase/1.5.0/docs/api/.
Personalización SIG, Albert Gavarró Rodríguez , Universidad Oberta  de Catalunya, España 2011