martes, 6 de noviembre de 2012

Tan recursivos pues!

El tema que me lleva hoy a escribir es la idea de recursión. Bueno, es uno de esos temas difíciles de explicar la primera vez y requiere algunas claridades frente a como funcionan las invocaciones a métodos.

¿Cómo funciona un método?

Cuando uno invoca un método, se "abre" espacio en la computadora para ejecutar un cierto conjunto de instrucciones (el contenido del método). Cuando se realizan muchas invocaciones, mucha memoria RAM del computador está siendo usada para ello. En una versión gráfica, es como si creáramos una caja que nos resuelve un problema dadas unas entradas y asegurando unas salidas.

              +-----------------------+
              |     Metodo!           |
         .    |                       |      .
   ........   |                       |   .....
   Entradas   |                       |  Salidas
              |                       |
              |                       |
              +-----------------------+


De esta manera, un método que tiene la responsabilidad de sumar dos números recibiría (entrada) como entrada dos elementos del conjunto de número enteros y devolvería (salida) un elemento del conjunto de números enteros. Para resolver dicha operación se crearía una "caja" simple que resolvería esa operación.

Cuando un método llama otro método, se abren varias "cajas" que esperan resultados unas de las otras, recordemos que un método tiene una responsabilidad definida. Cuando la resolución de un problema es compleja, se debe romper el problema en partes más sencillas, así mismo cuando un método se está complicando, es importante partirlo en métodos más simples. Cuando un método llama a otro, se crea algo llamado "pila de llamados".

  
  +---------+
  | Jugar   |
  +---------+
  | Menu    |
  +---------+
  | Main    |
  +---------+

La anterior pila de llamados representa un programa que inició con un llamado al método Main (como todo programa en Java), a su vez este llamó a un método que se llama Menú y a su vez este llamó a un método que se llama Jugar. En este momento la pila de llamados tiene en su punta el método jugar, que posiblemente es el que está ejecutando. Cuando este método termine, el método menú recibirá su respuesta y así pasará cuando termine el método Menú. Cada método tiene su espacio para guardar cosas, entre ellas las variables que se declaran dentro de él, sus parámetros y demás cosas.

Ahora sí, a lo que vinimos

Resulta que una recursión es un llamado de un método a sí mismo para la resolución sucesiva de un problema. En términos de la pila de llamados, se generarían espacios del mismo método. Supongamos una función que simplemente se llama a sí misma:

public static int metodo(int parametro)
{
   return metodo(parametro+1);
}

Resulta que el método de nuestro ejemplo crearía un número infinitos de entradas en la pila de llamados (teóricamente, porque en la práctica esa pila tiene límites y al sobrepasarlos se genera un error llamado StackOverflow o volcado de pila). Esto se vería más o menos así: (si el primer llamado fúe metodo(1))

     ...
 +---------+
 |metodo(3)|
 +---------+
 |metodo(2)|
 +---------+
 |metodo(1)|
 +---------+


Resulta que el llamado metodo(1) está esperando que el llamado metodo(2) termine, al igual que este último espera por metodo(3) y metodo(3) por metodo(4) y así sucesivamente. Respecto al ejemplo de Main->Menú->Jugar no cambia mucho, excepto por el hecho de que todas las componentes de la pila tienen el mismo nombre. En el código de ejemplo, estos llamados nunca terminarán porque siempre se genera un llamado nuevo. Esto resultará en un StackOverflow... Para detenerlo necesitamos un caso donde no se hagan llamados recursivos, a esto lo llamaremos posteriormente un caso base. Modifiquemos el código del anterior ejemplo para que eso pase cuando el parámetro llegue a un cierto valor, por ejemplo M.

public static int metodo(int parametro)
{
    if(parametro == M)  
    {
        return 0;
    } 
    return metodo(parametro+1);
}

En ese caso los llamados recursivos se expanderían hasta que el valor de la variable parametro llegue a M, cuando llegue, se retornará cero a todos los métodos anteriores. Aunque este método ya no tiene un StackOverflow, es un método sin mayor utilidad, pero revisemos el siguiente:

public static int factorial(int parametro)
{
   if(n==1)
    {
        return 1;
    }
    else
    {
        return parametro* factorial(parametro-1);
    }
}


En este ultimo caso, tenemos un caso en que se detiene (cuando n sea 1) y uno donde expande el llamado (n diferente de 1). De esta manera tenemos un caso base y un caso inductivo. Espero les sirva!

jueves, 1 de noviembre de 2012

Lectura...pero de archivos

En este post me voy a dedicar a revisar una forma sencilla de leer y escribir sobre un archivo de texto, pero primero lo primero....

Archivos... Para quien no sabe qué es uno

Bueno, pues en general los archivos son bloques de datos ordenados que se pueden almacenar de manera secuencial en un dispositivo magnético, óptico o similar. Son los que nos llenan el disco duro del computador!. Básicamente hay dos tipos de archivos, los archivos texto y los archivos binarios.

Los archivos de texto esencialmente guardan eso: texto. Se podría pensar que guardan una colección de Strings separados por el carácter de cambio de linea y para leerlos literalmente recorreremos el archivo como si de una colección de cadenas de caracteres se tratara.

Los archivos binarios permiten guardar algunas otras cosas, normalmente representaciones de los objetos que trabajamos en un programa "como los trajeron al mundo", osea en binario. En este post me dedicaré a los primeros...

Nada mejor que un ejemplo

Lo primero que vamos a hacer es leer un archivo sencillo con personas y números de teléfono. Suponga que el archivo contactos.txt tiene el siguiente contenido:

Pepito Perez;4243430
Fulano Rodríguez;6508765

Para leerlo vamos a necesitar varias cosas...
1. La clase File: La clase File de java es una representación de un archivo en un sistema de archivos (¿De verdad?). Las carpetas (directorios) tambien pueden ser representados con esta clase. Entre otras uno puede instanciarla con un archivo para crearlo, saber su carpeta, listar los archivos que están en ella, etc. La usaremos para representar el archivo especifico que vamos a leer.
2. La clase FileReader: Esta clase permite abrir el archivo, es la clase que sabe acceder al disco para abrir un archivo.
3. La clase BufferedReader: Esta clase nos genera un lector con el que podamos leer "pedazo a pedazo" el archivo. Nos provee métodos para leer las líneas del archivo y saber si se acabó.

Miremos el siguiente código fuente:

public ArrayList<Persona> cargarArchivo ()  
      {  
           try   
           {  
                ArrayList<Persona> resultado = new ArrayList<Persona>();  
                File archivo = new File("../data/contactos.txt");  
                BufferedReader lector = new BufferedReader(new FileReader(archivo));  
                String linea = "";  
                int i=0;  
                while (( linea = lector.readLine()) != null )  
                {  
                     String[] partes = linea.split(";");  
                     Persona p = new Persona(partes[0], new Integer(partes[1]));  
                     resultado.add(p);  
                }
                lector.close();
                return resultado;  
           }   
           catch (FileNotFoundException e)   
           {  
                System.out.println("Archivo no encontrado");  
                return null;  
           }   
           catch (IOException e)   
           {  
                System.out.println("Error de lectura");  
                return null;  
           }  
           System.out.println("Carga del archivo exitosa!");  
           return new ArrayList<Persona>();  
      }  

Vamos por partes, dijo Jack el destripador....

El método que está en el ejemplo anterior carga las personas que están en el archivo en un ArrayList y los retorna. Algunas cosas que ver en este código.

1. las clausulas try, catch: En principio estas clausulas no tienen nada que ver con el tema de este post, de hecho son mecanismos que tiene java para manejo de Excepciones, en resumen una excepción es un error y estas clausulas sirven para saber que hacer con él. El bloque de código que se escribe dentro del try simplemente se realizará (aquí va lo que nos interesa), la clausula catch sirve para atrapar ciertos tipos de error, como pueden ver hay dos de ellas, una para error "no encontré el archivo" y otra para "error de lectura". El contenido de esos bloques es código que se ejecutará en caso que esos errores se presenten.

2. File: En el ejemplo se crea un nuevo archivo, el parámetro que se le envía podría ser solamente el nombre del archivo, con lo que buscaría en archivo en la ubicación donde la clase está corriendo (en la misma carpeta). En el ejemplo se usa el comodín ".." que significa "una carpeta arriba". después la carpeta data y el nombre del archivo. 

3. Los lectores. En el ejemplo se instancia un BufferedReader con un FileReader con un File adentro. De esta manera tendremos acceso por fin a lo que necesitamos.

4. El método readLine: Hace lo que dice, lee una linea y luego la próxima en cada llamado. retornará null si ya se acabó el archivo, por eso está en esos términos la condición del ciclo while.

5. Split: Es una utilidad que nos da la clase String, sirve para romper un String en partes dada una cadena, en este caso la usaremos para romper un "renglón" del archivo en dos partes, la que tiene el nombre y la que tiene el número de teléfono. El resultado del método es un arreglo de String con cada parte.

6. Close: Al final sería decente cerrar el archivo que se está usando... por programas mal hechos que no usan esa instrucción es que a veces no podemos sacar nuestras memorias USB de un computador, porque un programa se quedo con un archivo abierto!

En una próxima ocasión escribiré acerca de como crear y escribir en archivos de texto!

lunes, 22 de octubre de 2012

Enter the Matrix

La dimensión desconocida ...

Después del asunto de trabajar con arreglos y pensando un poco en el asunto de la dimensión de una variable, retomo el hecho de que las variables podrían eventualmente tener más de una dimensión , en este caso la idea de arreglo se generaliza de manera libre.

¿Pensaron alguna vez en la idea de hacer un arreglo de arreglos?
¿Qué tal un arreglo de arreglos de arreglos?

Si la pregunta es si se puede, en la mayoría de lenguajes de programación SE PUEDE!. El asunto es que al tratar de modelar esta estructura inevitablemente estamos ampliando las dimensiones de una variable. Si una variable entera (solita, como la traje yo al mundo) tiene dimensión cero y un arreglo tiene dimensión 1 (el espacio donde se mueven sus posiciones), una matriz podría considerarse como una variable que tiene dimensión superior a 1, por ejemplo 2 (Lo que implica dos indices de posición o coordenadas)


         0       1      2       3       4

     +------++------++------++------++------+
  0  |  ' ' ||  ' ' ||  ' ' ||  ' ' ||  ' ' |
     |      ||      ||      ||      ||      |
     +------++------++------++------++------+
     +------++------++------++------++------+
  1  |  ' ' ||  ' ' ||  ' ' ||  ' ' ||  ' ' |
     |      ||      ||      ||      ||      |
     +------++------++------++------++------+
     +------++------++------++------++------+
  2  |  ' ' ||  'O' ||  'S' ||  'O' ||  ' ' |
     |      ||      ||      ||      ||      |
     +------++------++------++------++------+
     +------++------++------++------++------+
  3  |  ' ' ||  ' ' ||  ' ' ||  ' ' ||  ' ' |
     |      ||      ||      ||      ||      |
     +------++------++------++------++------+


Extiende la declaración!

La idea inicial que habíamos usado previamente para asuntos dimensionales tenía que ver con el uso del operador [] que nos permitía acceder a posiciones cualquiera de un conjunto en una dimensión (arreglo). La idea cuando aparecen multiples dimensiones es exactamente la misma, pero requiere del uso de más operadores [] para identificar las demás coordenadas. En el caso de dos dimensiones, se requiere uno para trabajar las filas y uno para trabajar las columnas donde se ubica un elemento dentro del conjunto. El anterior ejemplo hace una matriz bidimensional de caracteres, cualquier parecido con la conformación de una sopa de letras o un Scrabble es pura coincidencia...:)

Por extensión, la sintaxis para declarar una matriz tiene la misma estructura que la que usa un arreglo.


char [] arregloLetras = new char[10];
char [][] matrizLetras = new char[15][15];
char [][][] cuboLetras = new char[10][10][10];

En la primera linea se declaro un conjunto de caracteres de tamaño 10, en el segundo ejemplo se declara una matriz de caracteres de tamaño 15X15.  char[][] actua como el tipo (matriz de caracteres), matrizLetras es el nombre de la variable y new char[15][15] crea las variables necesarias para hacer la matriz con 15 X 15 variables de tipo char. Esto se podría interpretar tambien como declarar un arreglo de tamaño 15 donde cada posición es un arreglo de tamaño 15 de caracteres, por eso el uso de dos operadores [] seguidos. El último ejemplo declara una matriz de caracteres de tamaño 10X10X10 (tres dimensiones).

En ese orden de ideas, char es la palabra que designa el tipo "caracter", char[] designa el tipo "conjunto de caracteres" y char[][] designa el tipo "matriz de caracteres" o "conjunto de conjuntos" de caracteres. El ultimo ejemplo se podría comprender como una extensión de lo anterior, osea char[][][] sería una "matriz tridimensional de caracteres" o un "conjunto de conjuntos de conjuntos de caracteres".

Asignación y consulta

La asignación y consulta de una matriz requiere el uso del operador [] así como pasa en los arreglos, pero al tratarse de un conjunto de dos dimensiones, se requiere de su uso 2 veces. De esta manera, las instrucciones: 


matrizLetras[0][0]='x';
matrizLetras[1][5]='y';
matrizLetras[2][3]='z';

Modifican posiciones específicas de la matriz, la primera línea modifica la posición <0,0> de la matriz y asigna el valor 'x'. Los indices de la matriz (valores que se ponen dentro del operador []) se consideran como filas(para el primer indice) y columnas (para el segundo), así en la segunda instrucción se cambia la posición fila = 1, columna = 5 asignando el valor 'y'. En la tercera instrucción se modifica la posición <2,3> de la matriz y se asigna el valor 'z'.

Así mismo, se puede consultar un valor de una matriz mediante el operador []. Para imprimir un valor de la matriz basta con usarlo de igual forma:

System.out.println(matrizLetras[0][0]);
System.out.println(matrizLetras[1][5]);
System.out.println(matrizLetras[2][3]);

De esta manera, estas tres líneas imprimirán en la pantalla los valores 'x', 'y' y 'z' de acuerdo con lo anterior.

Ciclos y matrices - Recorridos

Los indices de una matriz evidentemente son números enteros, por ende se podrían poner dentro del operador [] valores variables. Uno de los usos más importantes tiene que ver con el recorrido de matrices. El recorrido de matrices se suele utilizar cuando hay que pasar por todas las posiciones de una matriz para hacer algo específico. 

 
for(int i=0; i<matrizLetras.length; i++)  
 {  
      for(int j=0; j<matrizLetras[i].length; j++)  
      {  
           matrizLetras[i][j]= '_';  
      }  
 }  

En este ejemplo, hemos usado un primer ciclo que recorra todas las filas de la matriz. Nótese que llamado length tiene ese propósito. En el segundo ciclo, se utiliza el operador length pero para cada posición i de la matriz (según el razonamiento, usar el operador [] una sola vez en una matriz bidimensional, nos entregaría un arreglo y a ese arreglo es al que le estamos pidiendo el tamaño). En resumen, el primer ciclo funciona tantas veces como filas tenga la matríz y cada una de esas veces un segundo ciclo funciona tantas veces como columnas tenga la matriz. Así, en este ejemplo se recorren 15 filas y por cada una de ellas se recorren 15 columnas, osea que la instrucción matrizLetras[i][j] sucederá 15X15 veces (225).

En el anterior ejemplo se asigna el caracter '_' a todas las posiciones de la matriz sin excepción. Si se quisieran hacer recorridos parciales se podría jugar con el ciclo para que no toda posición fuera tenida en cuenta. Para ello se pueden variar los inicios del ciclo o su condición. Un ejemplo de ello es recorrer solo una fila de la matriz, para ello se hace un ciclo solo para las columnas y se deja constante la fila:


      int filaconstante=2;  
      for(int j=0; j<matrizLetras[filaconstante].length; j++)  
      {  
           matrizLetras[filaconstante][j]= 'x';  
      }  
      int columnaconstante=2;  
      for(int i=0; i<matrizLetras[columnaconstante].length; i++)  
      {  
           matrizLetras[i][columnaconstante]= 'y';  
      }  
      for(int i=0; i<matrizLetras[columnaconstante].length; i++)  
      {  
           matrizLetras[i][i]= 'd';  
      }  

En el segundo ciclo se recorre solo una columna y el ciclo varía las filas. En el tercer ciclo se recorre la diagonal de la matriz, osea las posiciones <0,0>, <1,1>.... etc.

martes, 16 de octubre de 2012

Arreglos...¿Y eso que?

El asunto de arreglos siempre ha generado expectativa entre mis estudiantes debido a que este tema suele recordarles que ciertos conceptos sobre uso y declaración de variables no están tan claros como creen. En realidad los arreglos no dejan de ser más que un tipo de variable con la particularidad que permite representar conjuntos de tamaño definido.

Inicios, comparando con lo que ya sabemos

Un día vimos como de la nada se podía crear una variable y darle un valor. Esa declaración de variable requiere inicialmente de un tipo (p.e int, double, char, String, Scanner, etc) y de un nombre para la recién bautizada variable. Eso equivale a separar un espacio en la memoria del computador para almacenar un valor y etiquetarlo con un nombre.

int variable = 5; 


  +-------+
  |       |
  |  5    |
  |       |
  `.......'
   miVariable

En la anterior instrucción especificamos un tipo (int, osea entero) , un nombre para la variable (miVariable) y un valor inicial (5). En el caso de los arreglos no es demasiado diferente.

int[] conjuntoEntero = new int[9];
conjuntoEntero[0]=3;
conjuntoEntero[1]=1;
...
conjuntoEntero[8]=20;


    +-----+-----+-----+------+-----+----+----+-----+-------+
    |  3  |  1  | 15  |  11  | 10  | -1 |  2 |  4  |  -20  |
    |     |     |     |      |     |    |    |     |       |
    +-----+-----+-----+------+-----+----+----+-----+-------+
       0     1     2      3     4    5    6     7      8

Aquí también especificamos un tipo (int[], osea arreglo de enteros), un nombre para la variable (conjuntoEntero) y un valor inicial (evidentemente toca posición por posición). Lo primero que notamos es que no podemos intervenir sobre todo el conjunto al mismo tiempo, se hace posición por posición. Para este propósito se usa el operador [] que nos permite acceder a una posición dentro del arreglo. En este caso se puede hacer lo mismo que con una variable.

conjuntoEntero // es una variable de tipo "arreglo de enteros"
conjuntoEntero[0] // es una variable de tipo "entero"

Ciclos y arreglos...recorriendo un conjunto

Un arreglo tiene un tamaño en principio definido, pero no necesariamente lo conocemos. Una de las cosas típicas que se hace con un arreglo es recorrerlo con diferentes propósitos, por ejemplo sumar sus elementos.
Así se realizaría dicho procedimiento:

int suma = 0;
for ( int i = 0; i< conjuntoEntero.length; i++)
{
 suma = suma + conjuntoEntero[i];
}

La variable suma almacena entonces un cero inicialmente y será incrementada dependiendo del valor de cada posición del arreglo. El ciclo funcionaría entonces desde i =0 hasta el tamaño del arreglo (nótese el llamado a  length que me da su tamaño) y tomaría "el i-ésimo" valor del arreglo y lo suma en la variable suma (valga la redundancia). El índice que estoy aplicando en el arreglo es el mismo valor que uso en el ciclo!!


Conclusiones varias

Los arreglos son tipos de variables también, dicho de una manera matemática los tipos que habíamos usado antes son variables de dimensión cero (int, char, String, etc.), los arreglos son tipos de variables de dimensión 1 (int[], char[], String[]) y evidentemente existen tipos de variables de dimensiones superiores a 1 (matrices). La idea detrás de un arreglo es representar un conjunto de datos de tamaño fijo y eso es lo que se especifica en su declaración.

Cada elemento de un arreglo es del tipo que se declaró previamente. Si el conjunto es de enteros, uno de sus elementos es entero (¿Obvio no?). El operador de indirección ([]) se utiliza para referirse a uno de los elementos. Para hacer operaciones con todo el conjunto se suele usar un ciclo.

La idea detrás de un ciclo...

El concepto de ciclo y el por qué

La idea detrás de un ciclo comienza cuando teníamos que realizar ciertos procesos repetidas veces. Aún habiendo algunos mecanismos alternos para llevar esta tarea a cabo, hay ciertos programas que requieren realizar un procedimiento un número grande de veces o peor aún, un número indefinido de veces.

Una forma un poco inocente de abordar este problema sería planteando en principio un conjunto seguido de instrucciones:

  • Instrucción
  • Instrucción
  • Instrucción
  • Instrucción
  • ...(hasta completar N veces)
Aún así el problema no se resuelve, porque si tuviéramos que ejecutar una instrucción una cantidad enorme de veces, la cantidad de líneas de código se volvería inmanejable, por otro lado, esta solución tampoco es viable si no conocemos ese numero N de veces que esa instrucción debe ejecutarse.

El ciclo viene de la idea de especificar una instrucción y una cantidad de veces que podría estar indefinida, consideremos una estructura así:

Ejecute N veces: Instrucción

No lo sé de primera mano, pero posiblemente hay lenguajes que tenían ciclos de esta manera, sin embargo hay problemas para los cuales no es suficiente utilizar una estructura tan simple. Por ejemplo, hay problemas que simplemente no tienen definido un número de veces como este:

Muestre el menú de opciones hasta que el usuario seleccione la opción salir

Bueno, literalmente este problema no tiene un número de opciones definido ya que el usuario en cuestión podría usar indefinidamente el susodicho menú de opciones y por ende jamas terminar. Esto implica que no necesariamente un ciclo debe tener un numero definido de iteraciones (llamemos así la cantidad de veces que un ciclo funciona). Para solucionar esto, muchos lenguajes optaron por soluciones de este estilo:

Mientras la condición X se cumpla, ejecute la instrucción Y

Esta forma es muchísimo más general y corresponde a la implementación de ciclos que varios lenguajes de programación poseen.

Bueno, y eso en JAVA ¿Cómo se hace?

Las instrucciones repetitivas (ciclos) en JAVA requieren en principio de la definición de una expresión booleana (osea, con valor true o false) que defina si el ciclo seguirá iterando. También requiere del conjunto de instrucciones que va a ejecutar repetidas veces. La estructura es muy similar a la que usa un condicional:

while ( expresionBooleana )
{
 //instrucciones a repetir
}

La palabra reservada while, se utiliza para definir un ciclo, le sigue un paréntesis que encierra la expresión booleana que controla el ciclo y un par de llaves que encierran el codigo que ejecutará de manera repetida. Veamos un pequeño ejemplo:

int i=0;
while ( i<10 )
{
 System.out.println("Valor de i:"+i);
}

En las anteriores instrucciones he declarado una variable entera llamada i, cuyo valor inicial es cero. He usado la palabra while y he puesto como condición i<10. (Léase "mientras i sea menor que 10"). Dentro de las instrucciones del ciclo he llamado al método que me permite mostrar texto en la pantalla e intento mostrar el valor de i.
Aún así hay un serio inconveniente con este ciclo.... la variable entera i tiene valor inicial cero, el ciclo por ende debería funcionar porque i cumple la condición de ser menor que 10, también se mostrará el valor de i en la pantalla, pero el valor de i nunca está variando en el ciclo. Esto significa que este programa tiene dos problemas: 1. Que siempre va a imprimir "Valor de i:0" y 2. Que nunca va a terminar de hacerlo, porque la condición se cumplirá eternamente (a menos que usted "mate" el proceso o apague el computador).
Una versión correcta de este ciclo sería:

int i=0;
while ( i<10 )
{
 System.out.println("Valor de i:"+i);
 i++;
}

Ahora este ciclo funciona y funcionará las veces que son necesarias (10 en este caso) porque la instrucción i++ garantiza que en algún momento la condición del ciclo deje de ser cierta y por ende el ciclo deje de iterar. Este programa mostrará en pantalla:

  • Valor de i: 0
  • Valor de i: 1
  • Valor de i: 2
  • Valor de i: 3
  • Valor de i: 4
  • Valor de i: 5
  • Valor de i: 6
  • Valor de i: 7
  • Valor de i: 8
  • Valor de i: 9
El anterior ciclo es un ciclo que tiene una cantidad constante de iteraciones, pero en general los ciclos podrían ser escritos con comportamientos diferentes. En general un ciclo requiere de una condición que evalúe verdadero cuando se quiere que el ciclo funcione y evalúe falso cuando no. También requiere de que dentro del ciclo hayan instrucciones que modifiquen el estado de esa condición para lograr que termine en algún momento.

La instrucción for

Es muy común el ciclo que hemos usado anteriormente, especialmente si se trata de recorrer conjuntos de tamaño especifico (por ejemplo las letras de una palabra, las posiciones de un arreglo o de una matriz). Es tan común el uso de este ciclo que algunos lenguajes implementaron una estructura de ciclo que permitiera lo siguiente:

Instrucción de inicialización, Mientras la condición X se cumpla, ejecute Y y Ejecute la instrucción de avance. 

El mismo while del ejemplo anterior se escribe así en un for:


for (int i=0; i<10 ;i++)
{
 System.out.println("Valor de i:"+i);
}

Nótese que la declaración de la variable i se movió al paréntesis del for, al igual que la instrucción de avance del ciclo i++. Este for hace exactamente lo mismo que el while anterior, solamente reorganiza las instrucciones con el fin de hacer un poco más organizada la escritura. El paréntesis de un for tiene entonces una instrucción de inicialización que sucederá antes de comenzar el ciclo, una condición del ciclo y una instrucción de avance que sucederá al final de cada iteración. Dentro de sus llaves lleva las instrucciones a repetir.

Algunas conclusiones

Los ciclos son mecanismos de control de un programa que permiten repetir instrucciones, para usarlos se debe tener una cierta seguridad de saber en que casos el ciclo debe iterar o no y también se debe considerar que el ciclo debe garantizar una terminación (aunque hay excepciones para esta última norma). Esta terminación se garantiza haciendo que alguna de las instrucciones del ciclo afecte la condición del mismo con el fin de hacerla falsa en algún momento.

La implementación de un ciclo en JAVA puede realizarse por medio de las instrucciones while o for. Los dos formatos permiten representar los mismos ciclos de manera indiferente (es cuestión de gustos usar uno o el otro, aquí entrarían a jugar criterios de legibilidad del código u otros).