martes, 28 de enero de 2014

Primeras interacciones con el usuario en JAVA

Uno de los temas que no he tratado en este blog es el manejo de la interacción con el usuario. Antes que enfrentarnos a interfaces gráficas, es necesario aprender un par de instrucciones que nos permitirán interactuar con el usuario por medio de la consola de comandos.

¿Qué es la consola?


Una consola de comandos es una interfaz de usuario de solo texto, donde el usuario escribe comandos y el programa los procesa. Muchas aplicaciones usan este tipo de interfaces, especialmente porque no todos los entornos son necesariamente gráficos (por ejemplo en los servidores).

Una consola de comandos simplemente despliega texto y el usuario escribe texto. nada más que eso.


Bueno ¿Y qué hago?


Para poder interactuar con el usuario, necesitamos considerar las preguntas que queremos hacerle y los datos que queremos que nos provea (las entradas de nuestro programa). Por ejemplo, si queremos hacer una aplicación que suma, resta, multiplica y divide, necesitaremos las siguientes entradas:

  • La operación que se requiere
  • El primer operando
  • El segundo operando
Estas tres cosas son preguntas que le haremos al usuario. Piensen en un formulario de una página web, hay campos de texto donde uno pone la información que se requiere. Así mismo se trabajará en una aplicación de consola.

Sacando información por la pantalla


Gracias al cielo no nos toca pensar en como desplegar el texto. Java viene con un par de librerías que nos ayudarán para lograrlo y solamente necesitamos llamar un método que imprime en la pantalla una cadena de caracteres:


System.out.println("Hola");

La anterior instrucción (obviamente dentro de algún método, por ejemplo un main) debería imprimir la cadena hola seguida de un cambio de línea (o sea que lo siguiente que se escriba aparecerá más abajo). Si se quiere obviar el cambio de línea se puede invocar el método:

System.out.print("Hola sin cambio de línea");

Con estos textos se pueden hacer las preguntas necesarias al usuario, pero queda faltando como capturar lo que el usuario escribe en el teclado...

Capturando lo que el usuario escribe


Para poder capturar información del teclado, se requiere crear una variable de tipo Scanner (no profundizaré en este tipo, pero es una variable de un tipo "lector" que puede leer varias cosas, inclusive nuestro teclado). A esta variable le llamaremos un método que puede capturar lo que el usuario escribe hasta que presiona ENTER. El método que llamamos depende del tipo de dato que queremos pedir. Así, hay metodos para pedir numeros , booleanos y cadenas (no hay para pedir un solo caracter, al menos en JAVA)

Siendo así instanciaremos una variable de tipo Scanner y le daremos una referencia al teclado

Scanner teclado = new Scanner(System.in);

Y luego a esa variable le pediremos que nos dé el número que el usuario escriba. El programa se quedará esperando que el usuario lo haga

int numero = teclado.nextInt();

En este programa, la variable número quedará con el valor del entero que escribe el usuario. Así mismo hay métodos para pedir cadenas (nextLine), pedir números Double o Float (nextDouble y nextFloat) y booleanos (nextBoolean):


int numero = teclado.nextInt(); //pide entero
float flotante = teclado.nextFloat(); //pide flotante
double doble = teclado.nextDouble(); //pide un double (doble presición)
boolean b = teclado.nextBoolean(); //pide un booleano, espera que el usuario escriba "true" o "false"
String s = teclado.nextLine(); //pide una cadena de caracteres


Un ejemplo 


Con estas instrucciones podemos entonces trabajar nuestra mini calculadora.... es necesario condicionar la operación basado en lo que nuestro usuario nos dice que hagamos. El tema de condicionales se trata en el post Sobre Condicionales - Actuando en consecuencia , pero para este ejemplo basta saber que la instrucción if nos permite seleccionar la operación correcta dependiendo del operador que el usuario nos dió.

import java.util.Scanner;


public class CalculadoraBasica
{

 /**
  * @param args
  * @param
  * @return
  * Description
  */
 public static void main(String[] args)
 {
  //Crear una variable de tipo Scanner que nos permita pedir al usuario lo que necesitamos
  Scanner teclado = new Scanner(System.in);
  //Preguntar al usuario una a una las entradas que requerimos
  //operador
  System.out.println("Por favor escriba el operador(+,-,*,/)");
  String operador = teclado.nextLine();
  //operando 1
  System.out.println("Por favor escriba el primer operando");
  double operando1 = teclado.nextDouble();
  //operando 2
  System.out.println("Por favor escriba el segundo operando");
  double operando2 = teclado.nextDouble();
  
  //ahora podemos ejecutar la operación seleccionada. utilizaremos un condicional 
  //para seleccionar la operacion y una variable double para almacenar la respuesta
  double respuesta = 0;
  if(operador.equals("+"))
  {
   respuesta = operando1 + operando2;
  }
  else if(operador.equals("-"))
  {
   respuesta = operando1 - operando2;
  }
  else if(operador.equals("*"))
  {
   respuesta = operando1 * operando2;
  }
  else if(operador.equals("/"))
  {
   respuesta = operando1 / operando2;
  }
  
  //por ultimo podemos mostrar al usuario la respuesta
  
  System.out.println(operando1+" "+operador+" "+operando2+" = "+respuesta);
 }

}

Espero que esta entrada les sea util :)...Con esto podremos hacer aplicaciones sencillas que interactuen con el usuario mediante textos.

jueves, 29 de agosto de 2013

Sobre condicionales - Actuando en consecuencia

En esta entrada quiero tratar el tema de instrucciones condicionales. Las instrucciones condicionales a la larga son instrucciones que el programador decide si se ejecutan o no en ciertos casos del problema que se está atendiendo. Básicamente uno asigna unas acciones a cada caso posible que se quiera considerar.

Árbol de decisión


Una buena manera de aproximarse a reconocer los escenarios posibles de un problema es armar un árbol de decisión. Un árbol de decisión tiene como objetivo representar los casos y sub- casos asociados a un problema por medio de una gráfica; hay muchos formatos de caso de decisión que van desde los arboles de decisión binarios lógicos (preguntas de si y no), los arboles de decisión probabilísticos (que tienen probabilidades asociadas a los casos) y los arboles de decisión n-arios casuales (que vamos a utilizar en este escrito). Algunas definiciones

Un nodo de un árbol de decisión es un caso: Suena obvio ¿cierto?, pues NO lo es. Normalmente estamos tentados a escribir en los nodos cosas que no son casos del problema, sino consecuencias o soluciones al caso. También se suele etiquetar el caso con un número para distinguirlo.


.................
|               |
| Caso 1        |
|               |
|...............|

Un nodo puede tener un padre: 

Si un nodo tiene un padre, significa que el nodo es un sub-caso de su padre. Se puede afirmar que si el caso hijo se presenta es porque el caso de su padre también se presentó. La relación entre estos casos es un AND (sucedieron los dos).
 .................
 |               |
 | Caso 1        |
 |               |
 |...............|
         |
         |
         |
       \ | /
        \./
 .................
 |               |
 | Caso 1.1      |
 |               |
 |...............|


Un nodo puede tener un hermano: 

Si un nodo padre tiene dos o más nodos hijos (sub-casos), estos nodos hijos se consideran hermanos. En un árbol de decisión se consideran casos hermanos mutuamente excluyentes, o sea relacionados por un XOR (sucede uno o el otro, nunca más de uno). Dicho de otra manera, no pueden suceder estos dos casos al tiempo.

 .................
 |               |
 | Caso 1        |
 |               |
 |...............|
         |--._
         |    `-..
         |        ``-._
       \ | /           `-.._    .
        \./                 `-._|
 .................     ......::::.......
 |               |     |               |
 | Caso 1.1      |     | Caso 1.2      |
 |               |     |               |
 |...............|     |...............|

El nodo raíz (que no tiene padre) se considera como el escenario general de todo el problema.

¿Para qué sirve esto?

Diversas situaciones requieren tener en cuenta todos los casos posibles, especialmente para poder tomar una determinación ante ellos. Los problemas que valen la pena, tienen un número de casos que no es manejable a menos que se tenga un modelo de representación y por eso es útil un árbol de decisión. A la hora de programarlo, se hace más importante aún, porque el arbol se reflejará en nuestra estructura de condicionales.

El condicional en JAVA - IF


La instrucción para la programación de condicionales en JAVA se llama IF y permite especificar la estructura de casos y las instrucciones a ejecutar en cada uno de ellos. La primera forma estructural del if es similar a lo siguiente:

if (condicion_caso)
{
   //instrucciones
   operacion();
}

El paréntesis encierra una expresión que evalúa un valor booleano (true si el caso se da, false si no). Luego va un agrupador (corchetes) con las instrucciones que se ejecutarán en ese caso. El funcionamiento es simple, si la condición se da, se llama al método operacion; en caso contrario no sucede nada.

La siguiente forma estructural utiliza la cláusula ELSE

if(condicion_caso)
{
    operacion1();
}
else
{
    operacion2();
}

En este caso, de darse la condición se llamará al método "operacion1", en caso contrario (si la condición no se cumple) se invocará el método "operación2". La cláusula ELSE funciona en este caso como decir "todos los otros casos" Esto se asemeja a un arbol de decisión así:

    .................
    |               |
    | Problema      |
    |               |
    |...............|
            |--._
            |    `-..
            |        ``-._
          \ | /           `-.._    .
           \./                 `-._|
    .................     ......::::.......
    |               |     |               |
    | Caso 1        |     | En cualquier  |
    |               |     | otro caso     |
    |...............|     |...............|



A una claúsula else se le puede adicionar también un if, de modo que se pueden especificar varios casos hermanos de manera puntual. Por ejemplo:

if(caso1)
{
    operacion1();
}
else if(caso2)
{
    operacion2();
}
else if(caso3)
{
    operacion3();
}
else
{
    operacion4();
}


Aquí se especifican cuatro casos diferentes y una operación diferente para cada uno de ellos. También se puede usar el else con esta estructura, haciendo que si ninguno de los casos se da (los casos 1,2 y 3) entonces se ejecutará la operación 4. Esto se asemeja a un árbol de decisión como este:

     .................
     |               |
     | Problema      |
     |               |
     |...............|
           /`-.._
          /   \ `:--.._
         /     `.  `-..``-.._
        /        \     `-._  `'--.__
       '          `.       `-._     `--.._
  +-------+    +-------+  +----`--+  +----'--+
  |       |    |       |  |       |  |       |
  | Caso1 |    | Caso2 |  | Caso3 |  | Otros |
  |       |    |       |  |       |  |       |
  +-------+    +-------+  +-------+  +-------+


Sin embargo, el if es una estructura jerárquica del lenguaje, por ello es posible que dentro de la operación de cada uno de los casos se ponga otra estructura if (anidar un if dentro de otro). El equivalente a esta operación es crear hijos en los arboles de decisión. Por ejemplo:

if(caso1)
{
    if(caso11)
    {
        operacion11();
    }
    else if(caso12)
    {
        operacion12();
    }
}
else if(caso2)
{
    if(caso21)
    {
        operacion21();
    }
    else
    {
        operacion22();
    }
}
else if(caso3)
{
    if(caso31)
    {
        if(caso311)
        {
            operacion311();
        }
        else if (caso312)
        {
            operacion312();
        }
    }
    else if(caso32)
    {
        operacion12();
    }
}
else
{
    operacion4();
}

Nótese que en esta estructura if hay varios casos anidados unos dentro de otros. El anterior ejemplo sería similar a evaluar un árbol de decisión similar al siguiente:

     .................
     |               |
     | Problema      |
     |               |
     |......`........|
          .'   `-:`---..___
         /        `-.   ``-`:-==...__
        /            `-.        ``-..ii`--...__
      .'                `-.            ``--.._ ```--...__
     /                     `-.                ``--..__   ```--...__
  +-------+                 +-`-.---+             +---``--+   +----'--+
  |       |                 |       |             |       |   |       |
  | Caso1 |                 | Caso2 |             | Caso3 |   | Otros |
  |       |                 |       |             |       |   |       |
  +---+`-._                 +---+`.-+             +--.'`.-+   +-------+
       |   `-.                .'   `-.              /    `.
  +----+--+ +-`-.=--+    +---+---+ +--`.---+  +---.'--+ +--`.---+
  |       | |       |    |       | |       |  |       | |       |
  | Caso11| | Caso12|    | Caso21| | Otros |  | Caso31| | Caso32|
  |       | |       |    |       | |       |  |       | |       |
  +-------+ +-------+    +-------+ +-------+  +---\---+ +-------+
                                                .' `.
                                               /     \
                                         +----+--+ +--`.---+
                                         |       | |       |
                                         |Caso311| |Caso312|
                                         |       | |       |
                                         +-------+ +-------+

Espero que este post les sea de utilidad!

miércoles, 14 de agosto de 2013

Variables y Operadores en JAVA

Volviendo a las bases de JAVA, este post está dedicado a la declaración de variables, a su uso, a los operadores que se pueden usar y demás detallitos importantes al iniciar con JAVA.

Noción de Variable


En general una variable en un espacio en memoria que reservamos para guardar un valor específico. Podemos imaginar una variable como una caja donde guardamos valores, la cual tiene un nombre y además un tipo (el tipo de dato que queremos almacenar). Así, se esperaría que podamos almacenar números, letras, palabras, valores booleanos y otros cuantos más. 

   calificacion
  |-----------.
  |           |
  |    5.0    |
  |           |
  |...........|


En resumen, tenemos que una variable tiene un nombre, un tipo y un valor. Es de esperar que el valor sea del tipo que la variable es y también es de esperar que no se puedan crear variables con el mismo nombre, porque Java se confundiría si no fuera así.

Algunos tipos comunes en java


Crear una variable implica definir de que tipo será. En java se permite el uso de ciertos tipos básicos que nos permiten representar:

- Números
- Caractéres
- Booleanos
- Cadenas de Caracteres
- Bytes

Estos no son los únicos tipos de variable que Java permite, de hecho el lenguaje permite la definición de tipos de parte del usuario (clases). Sin embargo en este post nos dedicaremos a los tipos más básicos.

Tipos

Java puede representar valores booleanos, números enteros, números reales, caracteres y cadenas de caracteres, sin embargo tienen algunas limitaciones dependiendo del tipo que se use. Algunos de los tipos que java permite utilizar son:

  • int (entero): Permite representar números enteros desde -2,147,483,648 hasta 2,147,483,647
  • short (entero pequeño): Permite representar números enteros desde -32,768 hasta 32,767
  • long (entero grande): Permite representar números enteros desde -9,223,372,036,854,775,808 hasta 9,223,372,036,854,775,807
  • float (número en notación de punto flotante): Permite representar números reales (con cifras decimales) con una precisión de 32 bits. El rango de valores posibles es un tema que no trataré aquí.
  • double (número en notación de punto flotante): Permite representar números reales (con cifras decimales) con una precisión de 64 bits. El rango de valores posibles es un tema que no trataré aquí.
  • boolean (valor booleano): Permite representar valores verdadero (true) o falso (false)
  • char (caractér): Permite representar caracteres según la codificación Unicode de 16 bits (UTF-16) con una capacidad de 65,535 caracteres (suficientes para representar los letras, números, simbolos especiales y bastantes caracteres de idiomas como el chino, árabe y demás que no usan el alfabeto inglés)
  • String (cadena de caracteres): Permite representar cadenas de caracteres, el tamaño máximo es dependiente de la cantidad de memoria RAM de la máquina donde se ejecuta, en la práctica suficiente para la mayoría de usos.
Declaración (Creación de una variable)

Para crear una variable en Java, simplemente se debe especificar el tipo y el nombre de la variable a crear seguidos de un punto y coma (;).

int entero;
short enteroPeque;
String cadena;
float calificacion;
double numeroDecimal;
long numeroGrandote;

En todo caso, la idea es que el nombre de una variable nos diga algo acerca de su significado. Si estamos programando un sistema de calificaciones, probablemente nombremos una variable "nota", entre más claros sean nuestros nombres de variables, más fácil será comprender el programa que estamos haciendo.

Asignación (Cambiar valor de una variable)


Las variables no sirven de mucho si no podemos manipular sus valores, por eso existe un operador que nos permite hacer precisamente eso. Sin más , aquí unos ejemplos:


int i; //creo una variable entera
i=0; //le cambio el valor a cero
short s = 0; //en la misma línea creo una variable entera y le asigno el valor de cero
char caracter = 'c'; //creo una variable caracter y le doy valor con la letra 'c' , nótese el uso de las comillas simples
String palabra = ""; //creo una variable cadena de caracteres y le doy valor vacío, nótese el uso de las comillas dobles
String otraPalabra = "Hola!"; //creo una variable cadena y le doy el valor "Hola!"
float f = 4.0;
boolean valor = true;
double d = 4.0;
double d2 = d;

El operador de asignación requiere de una variable creada, también se puede hacer que una variable cambie de valor al valor de otra variable, como se hace en las últimas líneas del ejemplo. En general, a la izquierda del operador de asignación (=) debería estar la variable a modificar y a la derecha el valor que se le asignará.


Operadores

Los operadores se utilizan para hacer cálculos con los valores de las variables, más ampliamente, sirven para intervenir sobre ellas. Los operadores matemáticos básicos son funcionales de una manera muy similar al uso que se les da en el álgebra. Sobre los operadores es importante recordar que tienen unas entradas (dominio) y unas salidas (rango). Ahora algunos ejemplos:


int i =0; // creo un entero, le asigno cero
int j =2; // creo un entero, le asigno dos
int suma = i+j; //creo un entero, le asigno la suma de i y j, o sea 2
int resta = 10-j;//creo un entero, le asigno la resta de 10 y j, o sea 8
int multiplicacion = 2*2; //creo un entero, le asigno la multiplicación de 2 y 2, o sea 4
int division = 4/2; //creo un entero, le asigno la división de 4 y 3, o sea 4
int modulo = 11 % 2; //creo un entero , le asigno el módulo entre 11 y 2, o sea 1

Aparte de las operaciones típicas, aparecen algunas otras que funcionan con números:


int i =0; //creo un entero con valor cero
i++; //aumento el entero en uno , o sea queda con valor 1
i+=2;//aumento el entero en dos, o sea queda con valor 3
i = i+3; //aumento el entero en tres, o sea queda con valor 6
i*= 2; //multiplico el entero por dos, o sea queda con valor 12
i/=2; //divido el entero en dos, o sea queda con valor 6
i--; //decremento el entero en uno, o sea queda con valor 5
i-=2; //decremento el entero en dos, o sea queda con valor 3

Las operaciones anteriores aplican con números decimales, con excepción del módulo (debido a que su resultado es entero). En el caso de la división, se considera siempre una división decimal, para divisiones enteras se requiere interpretar el resultado de esta división como un entero, obviando su parte decimal.

También las operaciones booleanas básicas se pueden realizar en java:

boolean valor = true; //creo un booleano y le asigno el valor verdadero
boolean otro = false; //creo un booleano y le asigno el valor falso
boolean andlogico = valor && otro; //creo un booleano y le asigno el resultado de la operacion AND 
boolean orlogico = valor || otro; //creo un booleano y le asigno el resultado de la operacion OR  
boolean negacion = !valor; //creo un booleano y le asigno el resultado de la operacion NOT

Los Strings soportan el operador + para efectos de concatenar cadenas con otros tipos o entre ellas:

 int i = 2; 
 String cadena = "ho"+"la"+" "; //queda con valor "hola"
 String cadena2 = cadena + "mundo"; //queda con valor "hola mundo"
 String cadena3 = cadena2 +" "+ i; // queda con valor "hola mundo 2"



Los operadores relacionales también funcionan en java, aquí unos ejemplos:

boolean iguales = 2==2; //queda en true, porque 2 es igual a 2
boolean diferentes = 2!=2; //queda en false, porque 2 NO es diferente de 2
boolean mayor = 2>3; //queda en false porque 2 NO es mayor que 3
boolean mayorigual = 2>=3; //queda en false porque 2 NO es mayor o igual a 3
boolean menor = 2<=3 //queda en false porque 2 es menor o igual a 2



Ojalá sea una buena introducción ... Hasta un próximo post :)


viernes, 28 de junio de 2013

Algunos tips para manejo de Strings en Java!

El manejo de cadenas de caracteres en java es considerablemente más sencillo que con otros lenguajes, gracias al cielo que los desarrolladores de java se tomaron en serio la idea de hacer una clase String decente. Esta entrada en mi blog tiene la intención de dar algunos tips básicos de manejo de esta clase.

Primero lo primero


Un String es una cadena de caracteres, un tipo y una clase en java. Se utiliza para representar palabras, frases, etc. Un String puede ser vacío "", puede tener solo una letra "a", o "puede tener toda una frase". Sobra decirlo, pero la manera de declarar un String es:
    
    String s;
    s = "hola";
    String x = "otro String"; 

Operadores sobre Strings


Los Strings soportan algunas operaciones, la más utilizada es la concatenación, que permite "pegar" un String con otro (+), con un número o con una letra. La otra operación obvia es la asignación (=). Ahora un ejemplo de ello:

   
   int numero = 4; 
   String cadenaCompuesta = "Esta es"+" una "+"cadena "+'c'+'o'+"mpuesta"; //aquí pego cadenas con letras
   String otraCadena = "Leere este post al menos "+numero+" veces"; //aquí pego cadenas con números
   String cadena = "";
   cadena+="algo"; //aquí agrego una palabra a lo que ya había
   cadena+=" mas"; //aquí agrego otra palabra a lo que ya había

Métodos de uso común


Los Strings tienen algunos métodos que se pueden usar para facilitarle la vida a quien programa, estas son algunas funciones comunes:

- charAt: dado un número, retorna el caracter ubicado en la posición enviada como parámetro
- contains: dada una cadena, retorna true si esta contenida en otra
- length: retorna la longitud
- endsWith: retorna true si la cadena termina con un sufijo enviado como parámetro
- equals: retorna true si dos cadenas son iguales
- indexOf: retorna la posición de la primera ocurrencia de una cadena enviada como parámetro
- isEmpty: retorna true si el tamaño de la cadena es cero
- lastIndexOf: retorna la posición de la última ocurrencia de una cadena enviada como parámetro
- replace: reemplaza una cadena por otra
- startsWith: retorna true si la cadena inicia con un prefijo enviado como parámetro
- subString: retorna una subcadena dada posición de inicio y una longitud
- toLowerCase: convierte la cadena a minúsculas
- toUpperCase: convierte la cadena a mayúsculas

Ahora unos ejemplos de como funcionan los anteriores métodos...


                String s = "Sonido";
		char c = s.charAt(0);//S
		char c2 = s.charAt(1);//o
		boolean b = s.contains("oni");//true
		boolean b2 = s.contains("xx");//false
		int i = s.length();//6
		boolean b3 = s.endsWith("do");//true
		boolean b4 = s.equals("Sonido");//true
		boolean b5 = s.equals("sonido");//false
		int i2 = s.indexOf("on");//1
		boolean b6  = "".isEmpty();//true
		boolean b7 = s.isEmpty();//false
		int i3 = s.lastIndexOf("o");//5
		String s2 = s.replace("ido", "pa");//Sopa
		boolean b8 = s.startsWith("Son");//true
		String s3 = s.substring(1, 3);//on
		String s4 = "SoNiDo".toLowerCase();//sonido
		String s5 = "SoNido".toUpperCase();//SONIDO


jueves, 9 de mayo de 2013

Excepto por la Excepción....

¿Por qué excepciones?


Resulta que las excepciones son mecanismos que emplean los lenguajes de programación para manejar los posibles errores que puede tener una aplicación que estamos construyendo. Muchas veces, de manera intuitiva definimos ciertas convenciones para expresar que un procedimiento no cumplió su objetivo. Consideremos el siguiente algoritmo de búsqueda:
 

 public static int buscarPersona(int id, Persona[] conjuntoPersonas)
 {
  for (int i = 0; i < conjuntoPersonas.length; i++) 
  {
   if(id == conjuntoPersonas[i].getId())
   {
    return i;
   }
  }
  return -1;
 }

La anterior búsqueda retornaría la posición del arreglo donde se encuentra la persona que estamos buscando, sin embargo retornará -1 si esa persona no es encontrada en el arreglo. En este caso estamos utilizando el valor -1 como una convención que significa "No pude encontrar la persona en cuestión", lo cual es un comportamiento no esperado en la ejecución del método que estamos diseñando.

Así como el ejemplo anterior, los programadores nos valemos de un montón de convenciones para decir si una ejecución de un procedimiento fué exitosa o no; sin embargo manejar los casos de error de esta manera puede ser confuso cuando la complejidad de nuestros procedimientos es mayor. Además, las convenciones que utilizamos solo tienen significado para quien programó el procedimiento, razón por la cual quien llame nuestro método de búsqueda podría interpretar ese -1 como un resultado válido, lo cual puede desencadenar efectos inesperados (por ejemplo que el usuario de este método acceda a la posición -1 de ese arreglo).

La excepción salvando el día

Bueno, vamos a modificar el código de nuestra búsqueda para que avise que hubo un error de manera explícita, las excepciones son precisamente eso. 

 
public static int buscarPersona(int id, Persona[] conjuntoPersonas)throws Exception
{
 for (int i = 0; i < conjuntoPersonas.length; i++) 
 {
  if(id == conjuntoPersonas[i].getId())
  {
   return i;
  }
 }
 throw new Exception("Persona no encontrada");
}

Ahora nuestro método persona tiene dos cambios, en su encabezado (firma del método) aparece la palabra throws que indica que nuestro método podría lanzar una excepción, seguido de esta palabra se escribe el tipo de excepción que puede lanzar, en este caso la excepción más básica representada en la clase Exception. También se modifica el lugar donde retornamos -1 y en vez de ello creamos y lanzamos la excepción que anunciamos, en este caso una que lleva como mensaje : "Persona no encontrada". Para ello usamos la palabra reservada throw e instanciamos un objeto de tipo Exception. La palabra throw interrumpe el flujo del programa, razón por la cual cualquier línea después no será ejecutada.

El lado del cliente (versión sin excepción)

Ahora supongamos que usamos la primera versión de nuestro método de búsqueda (la que no usa excepciones). Nuestro código se vería más o menos así:

public static void modificarPersona ( int id , String nuevoNombre, Persona[] conjuntoPersonas )
{
 int posicion = buscarPersona(id, conjuntoPersonas);
 if(posicion != -1)
 {
  Persona p = conjuntoPersonas[posicion];
  p.setNombre(nuevoNombre);
 }
}

Este método le cambia el nombre a una persona, previamente la busca usando nuestro método del ejemplo anterior. Nótese el if horrible que hace que no se pueda modificar la persona en caso que la búsqueda devuelva -1. Quien lee este código está obligado a saber que ese -1 significa "no lo encontró", lo cual empeora si no es la misma persona quien escribe los dos métodos, no hay documentación o peor aún el método buscarPersona está en otra clase de la cual no se dispone del código fuente.

Sin embargo, podría ser peor...

public static void modificarPersona ( int id , String nuevoNombre, Persona[] conjuntoPersonas )
{
 int posicion = buscarPersona(id, conjuntoPersonas);

 Persona p = conjuntoPersonas[posicion];
 p.setNombre(nuevoNombre);
 
}

En este caso ni siquiera se toma en cuenta que el método podría fallar, por ende podríamos estar accediendo a la posición -1 del arreglo y generando un error grave en nuestra aplicación. ¿Quién tiene la culpa?

El lado del cliente (con excepción) y las cláusulas try, catch y finally

Bueno, ahora usemos la versión decente (con excepción) del método de búsqueda. Una de las ventajas de usar excepciones es que el compilador de java nos obliga a tratarlas, evitando que se pase por alto el caso de error. Revisemos una versión de nuestro método cliente usando excepciones.

public static void modificarPersona ( int id , String nuevoNombre, Persona[] conjuntoPersonas )
{
 int posicion;
 try 
 {
  posicion = buscarPersona(id, conjuntoPersonas);
  Persona p = conjuntoPersonas[posicion];
  p.setNombre(nuevoNombre);
  
 } 
 catch (Exception e) 
 {
  e.printStackTrace();
  //esta linea imprime en la consola la
  //traza de llamados que llevaron al error
 }
}

La cláusula try encierra las instrucciones que pueden lanzar excepciones, de hecho si un método es llamado sin la mediación de esta palabra reservada, no podrá ser ejecutado (el compilador nos dará un error a menos que el método cliente también lance excepción mediante la palabra throws ). La cláusula catch nos sirve para ejecutar algunas instrucciones que sucederán si alguna de las lineas dentro del try lanza una expcepción. La idea es escribir dentro del catch las instrucciones que manejarán el error, en el ejemplo anterior imprimiremos la traza de llamados en la consola, también podríamos escribir un archivo de registro de errores o lanzar una nueva excepción. Con un nuevo lanzamiento nuestro código podría quedar así...

public static void modificarPersona ( int id , String nuevoNombre, Persona[] conjuntoPersonas )throws Exception
{
 int posicion;
 try 
 {
  posicion = buscarPersona(id, conjuntoPersonas);
  Persona p = conjuntoPersonas[posicion];
  p.setNombre(nuevoNombre);
 } 
 catch (Exception e) 
 {
   throw new Exception("Hubo un error en la modificacion de la persona: "+e.getMessage());
 }
}

En esta versión, además de capturar la excepción lanzamos una nueva que dice un mensaje más explícito. Teniendo en cuenta que nuestro método de búsqueda podría ser usado por más de un cliente, el mensaje original podría no ser suficiente, en este caso le damos más contexto diciendo que no solamente la búsqueda falló, sino que además fué invocada con la intención de modificar una persona.

Existe la posibilidad de ejecutar algunas líneas de código independientes de la existencia de una excepción o no dentro de una cláusula try. La cláusula finally ejecuta su contenido siempre, inclusive si no se usa un try , lo cual podría ser una buena práctica para aquellas cosas que no se deben dejar de hacer en ningun caso (por ejemplo cerrar un archivo o una conexión a la red). Aquí un ejemplo de su uso leyendo un archivo:


public void openFile()
{
 FileReader reader = null;
 try 
 {
  reader = new FileReader("someFile");
  int i=0;
  while(i != -1){
   i = reader.read();
   System.out.println((char) i );
  }
 } 
 catch (IOException e) 
 {
  e.printStackTrace();
 } 
 finally 
 {
  if(reader != null)
  {
   try 
   {
    reader.close();
   } 
   catch (IOException e) 
   {
    e.printStackTrace();
   }
  }
  System.out.println("--- File End ---");
 }
}
Independientemente de si sucede la excepción (en este caso IOException), se intentará cerrar el archivo que se está trabajando. Nótese que en este caso la invocación al método close también podría generar una excepción que requiere ser capturada igualmente.

Las clases Exception, Error y Throwable.

Como vimos en los primeros ejemplos, es relativamente típico usar la clase Exception, sin embargo dicha clase es una clase genérica y deberíamos usar hijas de ella (por medio de herencia) para clasificar los posibles tipos de excepción que nuestra aplicación sabe lanzar. En general es una buena práctica definir varios tipos de excepciones que sean dicientes del error que está sucediendo o del componente que lanza la excepción. Por ejemplo, se podrían separar las excepciones de lógica de las excepciones de interfaz de usuario y eso ya sería un buen avance.

Si revisamos la jerarquia de herencia de la clase Exception nos encontraremos con que es una extensión (hereda de) la clase Throwable y que existe una clase Error que es hermana de Exception. Normalmente se trabajan excepciones cuando el flujo del programa principal puede ser restaurado y se puede continuar trabajando, la clase Error se utilizará entonces cuando el problema sea crítico y no se pueda continuar usando la aplicación (por ejemplo cuando no hay memoria disponible). Esa elección depende principalmente del desarrollador, sin embargo cási todos los casos prácticos se pueden resolver usando solo excepciones.



Por otro lado está la clase Throwable, la cual es la única clase que puede ser "lanzada" mediante la cláusula throw, razón por la cual Exception y Error son hijas suyas. Si queremos personalizar excepciones o errores, les recomendaría que extiendan directamente estas dos clases.

Algunas excepciones conocidas

Para terminar este post, quisiera contarles algunas excepciones que se suelen utilizar o que son comunes en el desarrollo. Aquí una lista:


ArrayIndexOutOfBoundsException Se intenta acceder a una posición inválida en un arreglo o matriz
ClassCastException Se intenta hacer cast de tipo con un objeto que no es de ese tipo
NegativeArraySizeException Se intenta crear un arreglo de tamaño negativo (suena increible, pero si hicieron una excepción para eso es porque hay quien lo intenta)
NullPointerException Se intenta acceder a una variable cuyo valor es nulo
StringIndexOutOfBoundsException Lo mismo que un ArrayIndexOutOfBoundsException pero dentro de un String
ConcurrentModificationException Se intenta hacer una modificación y lectura de un elemento al mismo tiempo, típicamente aparece cuando se eliminan elementos de una lista que se esta recorriendo en un mismo ciclo
FileNotFoundException Se intenta abrir un archivo que no existe, típico error de ruta
IOException Error en la lectura o escritura, normalmente cuando se trabaja con archivos.
ConnectException Se intenta una conexión imposible, típico si se intenta conectar a un servidor caído o a una URL incorrecta

martes, 5 de marzo de 2013

Clases y Objetos - ¿Cómo?, ¿Por qué?, ¿Para qué?

Este post tiene como idea mostrar un poco del funcionamiento de las clases y los objetos en JAVA desde una perspectiva nada formal y más bien práctica. Cuando yo aprendí a trabajar con JAVA era inmigrante de C++ por lo cual aprendí un poco "a las patadas" y aunque después de varios años de trabajar con JAVA ya  me considero un programador muy decente, quiero aprovechar este post para contarles algunas cosas que me ayudaron en ese proceso de aprender solo.

Clases y Objetos ¿Qué es eso?


Bueno, primero lo primero... Básicamente los lenguajes orientados por objetos tienen la idea de que los que escriben aplicaciones trabajen con conceptos más parecidos al mundo real. La forma más acertada que encontraron en su época fue el uso de variables de "tipos personalizados" para dejar de hablar de números , bytes, caracteres y cadenas; de hecho en lenguajes como C++ el concepto de cadena de caracteres estuvo relegado a librerías que ayudaban a usarlos, pero quienes tuvimos que sufrir con C (a secas) vimos como tocaba manejar las cadenas de caracteres como arreglos dinámicos desde una variable tipo char*. Bueno, en java al menos se tomaron la molestia de implementar una clase String muy decente...

Volviendo al asunto de los tipos, se quería que los programadores pensaran en conceptos más abstractos, como una Persona, un Directorio, un Automóvil  etc. La forma de implementarlos estos conceptos se pensó en términos de variables compuestas personalizables (muy al estilo de un struct en C). En principio una clase es una definición de un tipo de dato compuesto que además de las variables internas que lo definen (atributos), tiene unos métodos (procedimientos, funciones) que también lo caracterizan. En teoría un objeto tiene unas propiedades y comportamientos que lo hacen diferente de los demás (atributos y métodos).

Si volvemos al concepto de variable, una variable debería tener al menos un tipo y un nombre y además se le puede asignar un valor. La idea con un objeto realmente no cambia mucho....

        
 int i = 0;
 Persona p = new Persona("Daniel Santamaría", 4243430);

En la primera línea vemos la declaración de una variable entera que se llama i y que le estamos asignando el valor cero. En la segunda línea vemos una estructura similar en su sintaxis, una variable de tipo persona que se llama p (la variable) y le estamos asignando una persona. En este caso hablamos de un hipotético objeto persona que estamos usando y que posiblemente tiene "por dentro" una variable String para representar el nombre y un long que representará el número de teléfono. La palabra reservada "new" se utiliza en varios lenguajes de programación (C#, JAVA, C++, PHP, entre otros) para crear un nuevo objeto (separar memoria y ponerlo allí).

Definición de una clase


Para que esto sea posible, en algún lado de nuestro programa es necesario definir ¿Qué es una clase persona?. Para ello se usa un archivo aparte y la palabra reservada "class".


public class Persona
{
 private String nombre;
 private long telefono;
 
 public Persona(String n, long t)
 {
  this.nombre = n;
  this.telefono = t;
 }
}

La anterior porción de código (probablemente ubicada en un archivo Persona.java) define una clase Persona con dos atributos (nombre y teléfono). El uso de la palabra "private" para declarar dichas variable permite que esta variable solamente pueda ser accedida al interior de la clase persona (no accesible desde otras clases). El uso de esta palabra reservada y otras (public, protected, etc) no será tratado en este texto. Sin embargo es importante decir que : "Es muy importante que los atributos de una clase sean privados", ya que de no serlo, otras clases podrían interferir en la correcta operación del objeto. La idea de fondo es que cada clase "se encargue de lo suyo"...

También hay allí un método, específicamente un método constructor. El constructor se encarga normalmente de inicializar el estado de un objeto, sea dando valores por defecto o recibiéndolos como parámetro. Nótese también el uso de la palabra "this" con la que se hace explícito el hecho de que quiero trabajar con la variable que es atributo de este mismo objeto. Esto resuelve inconsistencias, por ejemplo si dentro del método se declara una variable con el mismo nombre.

El molde y la galleta - Diferencia entre clase y objeto


La definición de una clase permite decirle al lenguaje ¿Qué es una persona? (en el ejemplo anterior). Bueno, eso es una clase y se refiere al concepto literal de clase :


  •  "Orden en que, con arreglo a determinadas condiciones o calidades, se consideran comprendidas diferentes personas o cosas." (Tomada del diccionario de la real academia de la lengua española)

El objeto vendría siendo una persona en particular, o sea una instancia particular de la clase persona. Esa instancia es básicamente una variable ya creada con valores específicos, única e irrepetible. En resumen la clase se comporta como el "molde" y el objeto como "la galleta" que se puede hacer con el molde. Uno mira con que la rellena, pero sigue siendo una galleta.

Métodos, especialmente los "getters" y "setters"


Como había dicho anteriormente es de suprema importancia que los atributos estén bajo el control de la clase que los contiene. La clase Persona del ejemplo es la única autorizada para cambiar el nombre de una persona de manera directa, sin embargo se puede hacer que un método permita modificar o consultar esos valores (de lo contrario la clase sería casi que inútil). Esto es lo que hace un método get(pedir valor) o set (cambiar valor). Lo interesante de estos métodos es que en ellos se puede controlar lo que sucede al hacer el cambio, notificar al objeto si es necesario, realizar validaciones, etc. Por ejemplo:


public class Persona
{
 private String nombre;
 private long telefono;
 
 public Persona(String n, long t)
 {
  this.nombre = n;
  this.telefono = t;
 }
 public String darNombre()
 {
  return this.nombre;
 }
 public long darTelefono()
 {
  return this.telefono;
 }
 public void cambiarNombre(String n)
 {
  if(!n.equals(""))
  {
   this.nombre = n;
  }
 }
 public void cambiarTelefono(long t)
 {
  if(t!=0)
  {
   this.telefono = t;
  }
 }
}

En este ejemplo he puesto 4 métodos más, los dos primeros retornan el valor del atributo, los dos siguientes permiten cambiar el valor del atributo, pero bajo algunas restricciones, por ejemplo el nombre no se puede cambiar si el valor enviado como parámetro es un String vacío, así mismo no se podrá cambiar el teléfono por un cero. Estas restricciones son tontas, pero podrían ser tan complejas como se requiera, por ello la importancia de mantener ese control dentro de la misma clase.

Desde afuera de la clase, estos métodos podrían ser llamados usando el operador punto (.):


 Persona p = new Persona("Daniel Santamaría", 4243430);
 p.cambiarNombre("Pepito Perez");
 p.cambiarTelefono ( 5554455 );
 System.out.printLn(p.darNombre());

Listo! con ello podrán crear clases simples.... Ahora faltan las relaciones entre clases...

Relaciones entre clases - ¿Quién tiene Qué?

Cuando uno modela clases, espera tener varias (obvio) y algunas de ellas tendrán que relacionarse para modelar contextos más complejos. La relación más común entre clases es la contenencia (agregación o composición), básicamente modela el hecho de que un concepto esta compuesto por otro. Consideremos el caso de una agenda de teléfonos, la cual contiene un conjunto de personas. En un diagrama de clases UML se representaría de la siguiente forma:

El diagrama representa una relación de agregación entre Agenda y Persona, la flecha en el diagrama significa que literalmente una Agenda puede tener de 0 a N Personas (significado del simbolo *). En ese caso, si miramos la implementación de la clase Agenda, necesitaremos tener una variable que pueda guardar N personas, por ejemplo un arreglo.


public class Agenda 
{
 private int numeroPersonas =0;
 public static final int MAX_PERSONAS = 100;
 private Persona[] personas = new Persona[MAX_PERSONAS];
 
 public void agregarPersona(Persona p)
 {
  this.personas[numeroPersonas]=p;
  this.numeroPersonas++;
 }
 public Persona[] listarPersonas()
 {
  return this.personas;
 }
 //.... otros métodos de la clase
}

Nótese que se incluyeron algunos atributos extra para la administración de la colección, los cuales no están en el diagrama. Si somos rígidos, deberían estar en él, pero desde el punto de vista conceptual, solamente requeríamos modelar el conjunto de personas. Por otro lado aparece un atributo "personas" que es un arreglo de personas, este sería quien representa la relación existente entre las dos clases, pero no necesariamente puede tratarse de un arreglo existiendo diversas estructuras de datos que podrían cumplir la misma función (por ejemplo listas, arboles, pilas, etc.)

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!