Hasta ahora hemos realizado programas relativamente sencillos los cuales no realizaban subprocedimientos o subrutinas las cuales podían ser repetitivas. Con el tema de hoy veremos cómo crearnos bloques de código los cuales se comportan como subalgoritmos dentro de nuestro programa principal: las Funciones Como en cada tema, el código de hoy es el siguiente:

package net.openwebinars;

public class FuncionesProcedimientos {

  // Función
  static int sumar(int x, int y) {
    int resultado = 0;
    resultado = x + y;
    return (resultado);
  }

  // Procedimiento
  static void multiplicar(int x, int y) {
    x *= y;
    System.out.println("> Dentro del procedimiento la x vale " + x);
  }

  static int potencia(int x, int y) {
    int result = 0;
    // Caso Base
    if (y == 0) {
      result = 1;
    } else {
      // Caso general
      result = x * potencia(x, y - 1);
    }
    return (result);
  }

  // Suma los elementos de dos vectores
  // y guarda el resultado en un tercero
  static int[] sumarVectores(int[] a, int[] b) {
    final int TAM_VECTOR = a.length;
    int[] resultado = new int[TAM_VECTOR];
    if (TAM_VECTOR != b.length) {
      for (int i = 0; i < TAM_VECTOR; i++)
        resultado[i] = -1;
    } else {
      for (int i = 0; i < TAM_VECTOR; i++)
        resultado[i] = sumar(a[i], b[i]);
    }    
    return (resultado);
  }

  // Suma los elementos de dos vectores
  // y guarda el resultado en un tercero
  // pasado por argumento
  static void sumarVectores(int[] a, int[] b, int[] resultado) {
    final int TAM_VECTOR = a.length;
    resultado = new int[TAM_VECTOR];
    if (TAM_VECTOR != b.length) {
      resultado = reiniciarVector(resultado);
    } else {
      for (int i = 0; i  Potencia de x elevado a y = " + potencia(x, y));
    System.out.println("> z + (x^y) = " + sumar(z, potencia(x, y)));
    // Suma de vectores 1
    resultados = sumarVectores(vectorA, vectorB);
    System.out.println("> Suma de vectores 1: ");
    mostrarVector(resultados);
    // Suma de vectores 2
    resultados = reiniciarVector(resultados);
    sumarVectores(vectorA, vectorB, resultados);
    System.out.println("> Suma de vectores 2: ");
    mostrarVector(resultados);
    System.out.println("> Sumatorio 1: " + sumatorio(3, 2, 8, 10, 22, 13));
    System.out.println("> Sumatorio 2: " + sumatorio(3, 2, 8));
    System.out.println("> Sumatorio 3: " + sumatorio(3, 2, 8, 10, 22, 13, 90, -20, -40));
  }

}

Subrutina

Podemos definir subrutina como un bloque de código que ejecuta un subalgoritmo mediante el cual podemos resolver una tarea en concreto como por ejemplo ordenar un vector. Dicho bloque podrá ser llamado en cualquier momento en el código principal y tantas veces como queramos. Lo ideal suele ser que algoritmos grandes los descompongamos en subrutinas ya sea para estructurar mejor el código (hacerlo más leíble) como para generalizar tareas que pueden llegar a ser repetitivas. Pongamos un ejemplo. Gestionar un vector se puede dividir en las siguientes subtareas:
  • Insertar Elemento
  • Borrar Elemento
  • Buscar Elemento
  • Ordenar vector de menor a mayor
  • Ordenar vector de mayor a menor
  • Mostrar contenido del mismo
Si para cada una de las tareas mencionadas definimos subrutinas, tendríamos un programa más estructurado. Muy importante: las subrutinas deberían ser generalizadas y específicas al mismo tiempo; si definimos la suma de dos números deberá ser capaz de sumar cualquier número, pero se limitará a sumar números. No sería correcto indicarle a la subrutina que además de sumar números calculase el cuadrado de ambos ya que eso sería otra subrutina distinta. Por ello el nombre de la subrutina debe de ser representativo de lo que hace el algoritmo. De manera general las subrutinas se definen de la siguiente forma en Java:
tipoRetorno nombreSubrutina(tipoRetorno1 alias1, tipoRetorno2 alias2, ..., tipoRetornoN aliasN) {
  // Código
}
Las subrutinas pueden recibir variables (o incluso arrays) como argumento o parámetro . Un argumento no es más que un valor de entrada a la subrutina. Siguiendo con el ejemplo de la suma, al sumar dos números ambos son argumentos de la subrutina de la suma. No obstante, en Java tenemos que indicar en la subrutina el tipo de dato que espera como argumento. La cantidad de argumentos que podemos pasarle a una subrutina es finita (luego veremos una forma de darle flexibilidad a este aspecto). Además de los argumentos, una subrutina puede devolver o retornar un único valor de salida (también podemos devolver más de un valor si en lugar de una variable devolvemos un array). Según la estructura de la subrutina tenemos existen dos tipos principalmente:
  • Procedimientos
  • Funciones
Nota: se suele llamar a las subrutinas como métodos . El problema está en que en la programación orientada a objetos un método es una subrutina propia de un objeto, independientemente de si se trata de un procedimiento o de una función. Por ello a mi no me gusta llamarlas como tal.

Procedimientos

Los procedimientos son subrutinas las cuales nunca retornan un valor de salida. En el código de hoy, las siguientes subrutinas son procedimientos:
  • multiplicar
  • sumarVectores
  • mostrarVector
  • main
En efecto, main también es un procedimiento. En este caso, main es un procedimiento con el vector de String args. En otros lenguajes como C++ el método main puede retornar algún valor que indica el estado en el cual acaba el programa, pero en Java no sucede así. Sin embargo, a diferencia del main si queremos utilizar los otros procedimientos y funciones tendremos que llamarlos directamente en nuestro código de la siguiente forma:
// Sin parámetros
nombreRutina();
// Con parámetros
nombreRutina(argumento1, argumento2, ..., argumentoN);
Podemos entender una subrutina como una sentencia más de forma que podemos utilizarla incluso en bloques if-else. También podemos pasar como parámetro otra subrutina tal y como vemos en el código de hoy:
System.out.println("> z + (x^y) = " + sumar(z, potencia(x, y)));
En este caso estamos pasándole a la subrutina sumar la variable z y el resultado de evaluar la subrutina potencia(x, y). Si queremos verlo de forma matemática para este caso realmente estamos haciendo z + (x^y) . Para indicar que un procedimiento no devolverá valor, tenemos que escribir void en el campo tipoRetorno. De esa forma decimos que no habrá valor de retorno:
static void multiplicar(int x, int y) {
    x *= y;
    System.out.println("> Dentro del procedimiento la x vale " + x);
}
Nota: de momento definiremos las subrutinas escribiendo static al comienzo de su declaración. Más adelante explicaré qué significa hacer eso.

Funciones

Las funciones son similares a los procedimientos. Difieren principalmente en que las funciones siempre retornan un valor, independientemente de que tengan o no parámetros de entrada. En el código de hoy, las siguientes subrutinas son procedimientos:
  • sumar
  • potencia
  • sumarVectores
  • reiniciarVector
  • sumatorio
En todos ellos siempre se recibe uno o más valores y finalmente se devuelve un tercer valor. Para indicar qué tipo de valor devolverá en lugar de escribir void como hacíamos con los procedimientos, ponemos el tipo de dato que devolverá. Por ejemplo:
static int[] reiniciarVector(int[] vector) {
  final int TAM_VECTOR = vector.length;
  int[] resultado = new int[TAM_VECTOR];
  for (int i = 0; i < TAM_VECTOR; i++)
    resultado[i] = -1;
  return (resultado);
}
En este caso estamos indicando que la función reiniciarVector devolverá un array de tipo int. Para devolver un valor en las funciones utilizamos la palabra reservada return seguida de la variable/vector/objeto a devolver. Nota: No es necesario usar paréntesis en el return, pero es buena práctica hacerlo.

Valor y Referencia

Aquellos que vengan de otros lenguajes como C++ se preguntarán si en Java existe lo que se conoce como pasar variables por referencia o valor . Cuando le pasamos un argumento a una subrutina, se dice que se pasa por valor cuando se crea una copia temporal en una variable auxiliar dentro de la subrutina. Dicha copia podemos modificarla dentro de la subrutina, pero la variable indicada como argumento mantendrá el valor que tenía en el momento de llamar a la subrutina.
static void multiplicar(int x, int y) {
  x *= y;
  System.out.println("> Dentro del procedimiento la x vale " + x);
}
En el procedimiento multiplicar modificamos el valor del primer argumento dentro del código. No obstante, cuando mostramos por pantalla los resultados vemos que nos salen cosas distintas:
> Dentro del procedimiento la x vale 10
> Fuera de multiplicar la x vale 2
Esto se debe a que cambiamos el contenido de la variable temporal, pero no el de la variable original. Por eso se le llama pasar parámetros por valor. El caso contrario, pasar parámetros por referencia consiste en modificar directamente el valor de la variable original sin crear variables temporales. Como esto es una mala práctica, Java no lo permite y se limita a pasar siempre las variables y los arrays por valor (en el caso de los objetos siempre se pasan por referencia).

Número de argumentos de una subrutina

Antes comenté que el número de argumentos de una subrutina es siempre finito. Existen ocasiones donde necesitamos declarar una subrutina donde no sepamos cuántos argumentos puede obtener. Java permite definir funciones con argumentos variables mediante la siguiente estructura:
tipoDato ... alias
Haciendo eso estamos indicando que la subrutina podrá recibir una cantidad variable de argumentos. Si nos vamos al código de hoy tenemos una función con dicho comportamiento:
static int sumatorio(int ... n) {
  int resultado = 0;
  for (int i: n)
    resultado += i;
  return (resultado);
}
La función sumatorio suma entre sí todos los números pasados por argumento. Una forma equivalente de ver este código es la siguiente:
static int sumatorio(int[] n) {
  int resultado = 0;
  for (int i: n)
    resultado += i;
  return (resultado);
}
No obstante al llamar a la función tendríamos que pasarle en lugar de tantos números como quisieramos, un vector con los números almacenados:
// Forma 1
sumatorio(3, 2, 8, 10, 22, 13);
// Forma 2
sumatorio(new int[] {3, 2, 8, 10, 22, 13});
De hecho, aunque utilizemos la primera definición de la función, podemos pasarle un array en lugar de variables separadas. Esto se debe a que realmente Java de manera interna transforma esa lista de argumentos en un vector. Es por eso que dentro del código lo interpretamos como tal al utilizar un bucle para recorrerlos. Y hasta aquí llegamos con el capítulo de hoy. No se olviden de que tienen a su disposición el curso de la asignatura en cursos.openwebinars.net donde pueden hacer ejercicios y/o cuestionarios a modo de autoaprendizaje. No duden en preguntar todo aquello que no entiendan y espero verles en el próximo artículo donde aprenderemos que grandes problemas se pueden resolver fácilmente si los dividimos en subproblemas más pequeños . ¡Un saludo!

Listado de capítulos

En esta sección se listan todos los capítulos de este pequeño curso:
  1. Introducción
  2. Hello World!
  3. Variables y Operadores
  4. Control de Flujo