26 de septiembre de 2018

Bucles (Loops)

Cuando ejecutamos nuestros pequeños ejemplo, se están ejecutando una única vez. La sentencia que se ejecuta no vuelve a llamarse más. En esta entrada aprendemos los distintos bucles que tiene Java que nos van a permitir iterar (repetir) un fragmento de código las veces que necesitemos.

Repetir algo es una de las partes que vimos más generales a la hora de programar. Ya sólo nos faltará ver la Entrada (Input) en la siguiente sesión.

En general vamos a tener tres partes a la hora de definir un bucle:
  1. Condición para ejecutar el código del cuerpo del bucle
  2. Actualización de valor/es que influyen en la condición
  3. Código a ejecutarse que no es propio del control del bucle
Con estas ideas vamos a ver las distintas formas de declarar un bucle

Los bucles nos permiten repetir fragmentos de código mediante el control de una expresión condicional



Bucle while

Su estructura es:
  while (condicion) {
    // cuerpo con actualizacion
  }
Su funcionamiento es evaluar la condición marcada y ejecutar el código de su cuerpo si se cumple. El código del cuerpo debe influir de alguna forma en la siguiente evaluación de la condición o se quedaría infinitamente iterando o nunca lo haría.

Vemos un ejemplo muy gráfico basado en la curiosa conjetura de Collatz:
  public static void collatz(int n) {
    while (n != 1) {
        System.out.println(n);
        if (n % 2 == 0) { // n es par
            n = n / 2;
        } else {          // n es impar
            n = n * 3 + 1;
        }
    }
  }

Bucle for

Su estructura es:
  for (inicio; condicion; actualizo) {
    // cuerpo
  }
Este bloque centraliza el control del bucle en su primera línea. Cada una de las expresiones se dedica a:
  • inicio: Normalmente declara un estado inicial. Se ejecuta sólo cuando se inicia el bucle.
  • condicion: Se evaluará cada iteración del bucle.
  • actualizo: Se ejecutará al final de cada iteración y servirá para influir en la condicion.
En este ejemplo hacemos una cuenta atrás desde un número:
  for (int i = 3; i > 0; i--) {
    System.out.println(i);
  }
  System.out.println("!BOOM!");

Bucle do-while

Su estructura es:
  do {
    // cuerpo
  } while (condicion);
Se puede ver como un caso especial de while. La diferencia es que la condición se evaluará al final de la iteración. Esto implica que al menos se ejecutará una vez el cuerpo hasta llegar a la condición.
  do {
    System.out.println("Itero");
  } while (false);
En el ejemplo vemos que el cuerpo se ejecutará aunque la condición sea false desde el principio. Es importante terminar toda la declaración con punto y coma ";" ya que podríamos pensar que no hace falta como en los otros dos casos, pero sería un error de compilación (Eclipse nos avisará).

Diferencias entre los distintos bucles

Ahora que tengo varias formas de iterar ¿Cuál uso?
Puedes seguir estos consejos para elegir el bucle más idóneo:
  • for: cuando sabes cuantas veces vas a tener que iterar porque tiene un numero finito de elementos.
  • while: cuando no sabes cuántas veces tienes que iterar, incluido ninguna.
  • do-while: cuando no sabes cuántas veces tienes que iterar pero al menos será una.
Los bucles en general están formados por una situación inicial, una condición que va evolucionando y un código a ejecutar en su cuerpo. Cuanto más sepamos del problema a solucionar mejor decidiremos el bucle a emplear


Controlar el bucle con break y continue

Hay dos palabras reservadas que se usan mucho con los bucles: break y continue.
  • break: termina la ejecución del bucle y sale directamente
  • continue: termina la ejecución de la iteración y vuelve a evaluar la condición
En el siguiente ejemplo vemos que continue hace que se salte los números pares y que break termina la ejecución en 7 en vez de llegar al 9:
  for (int i = 0; i < 10; i++) { // de 0 a 9
    
      if(i == 7) { // termina cuando es 7
          break;
      }
      if(i % 2 == 0) { // pasa al siguiente si es par
          continue;
      }
    
      System.out.println("i = " + i);
  }

Conversiones for y while

Todo for puede convertirse en while y viceversa pero no es aconsejable. Si miras los ejemplos verás que todo funciona bien, pero a simple vista no queda bien y hace el código menos legible. Por eso es importante elegir bien qué estructura usar.
  for (int i = 0; i < 10; i++) {
  }

  // equivale a

  int i = 0; // inicio
  while (i < 10) { // condicion
      i++; // actualizo
  }
A la inversa sería tan simple como borrar el inicio y actualizo del for y en condición usar la del while:
  while (condicion) {
    // ...
  }

  // equivale a

  for (;condicion;) {
  }

24 de septiembre de 2018

Condiciones (if-then-else)

Todo el código que hemos visto va siguiendo unos pasos fijos cada vez que lo ejecutamos. Las condiciones nos van a permitir decidir qué código se ejecutará en función de los valores que tengamos durante la ejecución.

Os recuerdo que tomar una decisión es una de las partes más generales que vimos cuando aprendimos qué es programar.

De manera general una cláusula condicional se expresa de la siguiente forma:
  if (condicion) {
    // rama se cumple
  } else {
    // rama NO se cumple
  }
  • Ejecuta la "rama se cumple" si se cumple la condición o la rama "NO se cumple" si no se cumple la misma condición.
  • El fragmento else no es obligatorio si no se necesita su rama.
La cláusula if-then-else nos permite decidir el código a ejecutar en función de una condición


Este es un ejemplo básico:
  public static void main(String[] args) {
      int saldo = 100;
      String resultado;
    
      if (esRico(saldo)) {
          resultado = "Soy rico";
      } else {
          resultado = "NO soy rico";
      }
    
      System.out.println("¡" + resultado + "! Mi saldo es: " + saldo);
  }

  public static boolean esRico(int saldo) {
      return saldo >= 1000;
  }
Los bloques que marcan las ramas están encerrados entre llaves como de costumbre, pero si la rama sólo consta de una única sentencia se puede optar por omitir las llaves. Sin embargo hay que prestar especial atención a la indentación (las distintas sangrías que tienen las sentencias en función de la profundidad de su bloque) para no confundir líneas sucesivas como formando parte del bloque. En ese caso nos quedaría así:
  if (esRico(saldo))
      resultado = "Soy rico";
  else
      resultado = "NO soy rico";
O incluso podéis verlo así:
  if (esRico(saldo)) resultado = "Soy rico";
  else resultado = "NO soy rico";
No obstante, si repasáis la guía de estilo que marcamos para el curso, recomienda siempre poner las llaves.

Encadenar y Anidar condiciones

Si queremos tener condiciones más complejas se pueden encadenar. En este caso se puede continuar el comienzo de la rama else con otro if. Vamos a ver más claro este punto con un ejemplo:
  public static void main(String[] args) {
      int saldo = 100;
      String resultado;
    
      if (esRico(saldo)) {
          resultado = "Soy rico";
      } else if (esPobre(saldo)) {
          resultado = "Soy pobre";
      } else {
          resultado = "Voy tirando";
      }
    
      System.out.println("¡" + resultado + "! Mi saldo es: " + saldo);
  }

  public static boolean esRico(int saldo) {
      return saldo >= 1000;
  }

  public static boolean esPobre(int saldo) {
      return saldo <= 0;
  }
Si queremos ir afinando desde condiciones más generales a otras más precisas, se pueden anidar condiciones dentro de las ramas. Personalmente suelo usar el anidamiento y comentar las ramas si es necesario (ver en el código). La parte que cambia es la siguiente:
  if (esRico(saldo)) { // Soy rico
      resultado = "Soy rico";
  } else { // NO soy rico
      if (esPobre(saldo)){ // Soy pobre
          resultado = "Soy pobre";
      } else {
          resultado = "Voy tirando";
      }
  }
La composición de condiciones es algo habitual. Se puede ver como un modo de filtrar valores para no tener que evaluar una sentencia de nuevo como si formáramos una única condición concatenándolas usando los operadores lógicos (!esRico && !esPobre). Al final vemos una demostración.

El encadenamiento y anidamiento de condiciones nos sirven para filtrar y separar mejor el código


En el libro se puede ver otro uso de la palabra clave return. Como dijimos anteriormente se usa en los métodos con retorno para terminar la ejecución y devolver el valor. En este caso simplemente finaliza la ejecución. Personalmente creo que no es necesario y que hace más difícil seguir el código. Recomiendo tener un único punto de salida de los métodos.

Demostrando los operadores lógicos cortocircuitados

Como os adelanté, veríamos exactamente una demostración de ese cortocircuito. En este código podemos ver que se cortocircuita al no haber salidas de consola que se producirían si se evaluara !esPobre. En el vídeo se observa claramente:
  public static void main(String[] args) {
      int saldo = 1000;
      System.out.println("¿Voy Tirando? " + vaTirando(saldo));
  }

  public static boolean esRico(int saldo) {
      System.out.println("Comprobando RICO");
      return saldo >= 1000;
  }

  public static boolean esPobre(int saldo) {
      System.out.println("Comprobando POBRE");
      return saldo <= 0;
  }

  public static boolean vaTirando(int saldo) {
      return !esRico(saldo) && !esPobre(saldo);
  }

21 de septiembre de 2018

Métodos con retorno

Hasta ahora hemos declarado sólo métodos sin retorno (void methods) porque en la parte del resultado al definirlos siempre hemos usado la palabra reservada void.

Desde esta entrada vamos a empezar a hacer cosas más chulas ya que veremos los métodos con retorno (value methods), los cuales sólo difieren de los void en dos aspectos de su declaración:
  1. Declaran el tipo del valor que devuelven
  2. Utilizan la palabra reservada return para indicar el fin de la ejecución del método y devolver el valor. Cualquier código posterior en su ámbito no se ejecutará.
Los métodos con retorno declaran el tipo del valor que devuelven con la sentencia return que finaliza su ejecución


No se puede declarar un método con retorno sin estas dos modificaciones. De hecho nuestro IDE nos alertará de que falta la sentencia return si le indicamos un tipo de retorno pero no la hemos añadido para salir del cuerpo.


Esta diferencia implica unos cambios importantes:
  • Ahora los métodos value nos pueden devolver un resultado con el que podemos trabajar tras su ejecución.
  • Los cambios que generaban los métodos void se hacían sobre algún elemento externo (como hicimos con la consola añadiendo líneas). Esto se conoce como efectos colaterales (side effects) y va en contra de la programación funcional (functional programming).
  • La programación funcional es coherente ya que una invocación con los mismos argumentos siempre nos producirá el mismo resultado.
  • Es una buena práctica programar lo que podamos de esta forma para reutilizar más nuestro código y mejorar el mantenimiento (pues es mucho más fácil de depurar).

Los métodos con retorno producen valores con los que trabajar después y facilitan la programación funcional


Empecemos con un ejemplo sencillo:
    public static double suma(double a, double b) {
        double suma = a + b;
    
        return suma;
    }
Este código admite dos argumentos de tipo double y devuelve su suma. También es válido simplemente con:
    public static double suma(double a, double b) {
        return a + b;
    }
Ya que la variable suma no la necesitamos localmente y sólo necesitamos devolver el valor de la expresión a + b. En vuestros primeros códigos no tengáis pegas en añadir variables innecesarias si os va a servir para ir comprobando paso a paso que vuestro código funciona correctamente. Lo vamos a ver en la práctica de esta sesión, pero antes ¿Os atrevéis a hacer un método que reste los dos valores?


    public static double resta(double minuendo, double sustraendo) {
        double resta = minuendo - sustraendo;
    
        return resta;
    }
Si habéis dejado los nombres de a y b, sería conveniente dejar claro cuál es el minuendo y cuál el sustraendo pues no es una operación conmutativa. Cuanto más claros sean los nombres de los parámetros más fácil será usarlos.

Vamos a ver otro ejemplo típico: preguntar por un boolean. Cuando trabajemos con objetos será normal preguntar si es/estaAlgo (imaginad un objeto con color que puede ocultarse, podríamos preguntar si estaOculto o si esVerde).

Veamos este código:
    public static boolean esPositivo(double numero) {
        return numero > 0;
    }
El método esPositivo nos devolverá true si es mayor que cero, si no devolverá false. El nombre de este tipo de métodos siempre lo expresaremos en positivo (mejor esPositivo que noEsPositivo, eso lo expresariamos como !esPositivo)

Intentad hacer ahora uno que nos diga si un entero es par.


    public static boolean esPar(int numero) {
        int resto = numero % 2;
    
        return resto == 0; // Resumiendo sería "return (numero % 2) == 0;"
    }
Hay que fijarse que el tipo del parámetro es más adecuado que sea int y la forma más sencilla de calcularlo es usando el operador % para ver si el resto es cero.

Vamos a expresar los nombres de los métodos en positivo y con nombres de argumentos lo más claros posibles


Para terminar vamos a ver una práctica más larga que nos aporta el libro en la que tendréis que ir comprobando paso a paso que todo está saliendo bien, reutilizar todo el código que podáis y que sea funcional.

Práctica:

En el vídeo he ido haciendo paso a paso este método.


public static double area(double xc, double yc, double xp, double yp) {
    double area;
    double radio;
    
    radio = distancia(xc, yc, xp, yp);
    area = Math.PI * cuadrado(radio);
    
    return area;
}

private static double cuadrado(double radio) {
    return radio * radio;
}

private static double distancia(double xc, double yc, double xp, double yp) {
    double distancia;
    double sumaDeLosCuadrados;
    double cuadradoX;
    double cuadradoY;
    double restaX;
    double restaY;
    
    restaX = resta(xc, xp);
    restaY = resta(yc, yp);
    cuadradoX = cuadrado(restaX);
    cuadradoY = cuadrado(restaY);
    sumaDeLosCuadrados = suma(cuadradoX, cuadradoY);
    distancia = Math.sqrt(sumaDeLosCuadrados);

    return distancia;
}

18 de septiembre de 2018

Parámetros / Argumentos - Visibilidad y ámbito (scope)

Vamos a utilizar la declaración de parámetros y el paso de argumentos para personalizar el método que hicimos en la entrada anterior y poder decir el número de líneas nuevas que queremos añadir. También veremos los conceptos de visibilidad y ámbito (scope).

Con los parámetros podemos personalizar la ejecución de un método


En el ejemplo del libro se crea un nuevo método "tresLineas" que repite la invocación del método "nuevaLinea" tres veces. Ahora podemos cambiar el código para hacer una sola llamada a este nuevo método, pero ¿Y si queremos añadir dos, cuatro o diez líneas en vez de tres? ¿Hacemos otro método para cada caso? No, vamos a ver cómo personalizar las invocaciones usando argumentos.

Diferencia entre parámetros y argumentos

Antes que nada y para no seguir pensando que cada vez uso una palabra distinta hay que tener en cuenta estas definiciones:
  • Parámetros: Son cada una de las variables locales al método que se declaran en su firma entre paréntesis.
  • Argumentos: Son cada uno de los valores que corresponden a cada parámetro en la invocación de un método.
Después del siguiente ejemplo se ve claramente cuáles son los parámetros y cuáles los argumentos.

Modificamos nuestro código para que acepte un entero que indique el número de líneas que queremos añadir.

NOTA: He añadido un bucle (bloque for) para poder hacer el código. Este concepto y su uso se verá en otra sesión más adelante. Por ahora simplemente copiamos el código y ya aprenderemos a leerlo y escribirlo.
    public static void main(String[] args) {
        System.out.println("Hola Mundo");
        nuevaLinea(2);
        System.out.println("Adios Mundo");
    }
    
    public static void nuevaLinea(int numeroLineas) {
        for (int i = 0; i < numeroLineas; i++) {
            System.out.println("|--------------------|");
        }
    }
Pero alguno podrá pensar que la cadena que he dejado para rellenar la nueva línea es un "magic number". Vamos a añadir otro parámetro para que podamos decirle el texto que debe aparecer en cada nueva línea. El código queda así:
    public static void main(String[] args) {
        System.out.println("Hola Mundo");
        nuevaLinea(2, "|--------------------|");
        System.out.println("Adios Mundo");
    }
    
    public static void nuevaLinea(int numeroLineas) {
        nuevaLinea(numeroLineas, "");
    }
    
    public static void nuevaLinea(int numeroLineas, String texto) {
        for (int i = 0; i < numeroLineas; i++) {
            System.out.println(texto);
        }
    }
En el vídeo se puede ver cómo modifico el código sobre la marcha y se entiende mejor que encontrar el código final completo.


Después de la demostración podemos ver claramente la diferencia entre parámetro y argumento (los números de línea se refieren al último fragmento de código en esta página):
  1. Parámetros:
    1. línea 7: (int numeroLineas)
    2. línea 11: (int numeroLineas, String texto)
  2. Argumentos:
    1. línea 3: los valores 2 y "|--------------------|"
    2. línea 8: los valores numeroLineas y ""
    3. tambien líneas 2 y 4: "Hola Mundo" y "Adios Mundo"
Resumiendo: los parámetros se declaran parecido a las variables mientras que los argumentos son los valores que se asignan a esos parámetros al invocar el método.

Los parámetros se declaran pero los argumentos se asignan a esos parámetros al invocar el método


Visibilidad y ámbito (scope)

Ahora que ya tenemos distintos bloques de código podemos ver qué es una variable local y dónde está permitido su uso. En el vídeo hago la demostración. Simplemente debe quedarnos claro que una variable se puede usar sólo dentro del bloque de código, toda vez que se ha declarado, y se usará normalmente hasta que otra con un ámbito (scope) más cercano la "ensombrezca" (shadowing). Este último concepto de shadowing lo veremos cuando hablemos de constructores en las Clases.

Una variable se puede usar sólo dentro del bloque de código y si ya se ha declarado

17 de septiembre de 2018

Métodos

Por ahora conocemos cómo usar las variables y sus operadores para trabajar con ellas. Lo malo es que todo nuestro código está dentro del método "main" y lo único que hemos hecho es llamar a la impresión por consola. Vamos a ver cómo declarar más métodos, sus partes y qué se entiende por la firma de un método (signature).

Según la especificación Java 8, un método declara un código ejecutable que puede ser invocado con un número fijo de valores como argumentos. Nos va a permitir reutilizar fragmentos de código que podremos personalizar con los argumentos y que pueden proporcionarnos un resultado.

Los métodos nos permiten reutilizar código y obtener resultados personalizados por los argumentos que usemos


Un método tiene la siguiente estructura:
  1. Modificadores
  2. Cabecera del método con:
    1. Tipo de resultado que nos devolverá (el tipo)
    2. Declarador de método con:
      1. Identificador (nombre, en formato lowerCamelCase y, por convención, comenzando con un verbo)
      2. Parámetros (incluido su tipo)
  3. Cuerpo del método
En total hay algunas partes más, pero por ahora nos quedamos con estas que son las que nos hacen falta por ahora.

Vamos a verlo con el ejemplo del método "main" usado hasta ahora:



Firma del método (signature)

La firma del método es el conjunto nombre y parámetros (incluyendo el tipo) y no puede haber dos métodos con la misma firma en el mismo código.

Para usar un método nos interesa su firma ya que lo invocaremos llamándolo por su nombre y poniendo entre paréntesis los argumentos que encajen con sus parámetros.

En la próxima entrada hablaremos de ámbito y visibilidad y de los parámetros. Aquí simplemente mencionar que dos métodos pueden diferenciarse en los parámetros aunque tengan el mismo nombre y sería válido. Es lo que se llama sobrecarga.

Usaremos un método invocándolo con su nombre y poniendo entre paréntesis los argumentos que encajen con sus parámetros


Vamos a crear un método muy simple que imprima una nueva línea en nuestra consola. Nos creamos una nueva clase "EjemploMetodos" y usamos el siguiente código:
    public static void main(String[] args) {
        System.out.println("Hola Mundo");
        nuevaLinea();
        System.out.println("Adios Mundo");
    }

    public static void nuevaLinea() {
        System.out.println();
    }
Podéis ver el vídeo para ver mejor en vivo su definición e invocación:


14 de septiembre de 2018

Operadores lógicos

Para terminar la explicación básica de los operadores en Java, vamos a ver los operadores lógicos. Estos operadores nos van a permitir realizar operaciones con valores boolean.

Los operadores lógicos en Java son:
  1. Y (puerta lógica AND): &
  2. O (puerta lógica OR): |
  3. Y condicional: &&
  4. O condicional: ||
  5. Negación (puesta lógica NOT): !
Los 4 primeros son operadores binarios y el último es unario (niega un valor).

Los operadores lógicos realizan operaciones sobre valores boolean actuando como puertas lógicas


 Para los que no lo conozcan, el funcionamiento de las puertas lógicas es:
  • AND: El resultado será verdadero (true) sólo si ambos operandos son true. En el resto de casos se evalúa a falso (false)
  • OR: El resultado será false sólo si ambos operandos son false. En el resto de casos se evalúa a true.
  • NOT: Devuelve lo contrario.

¿Qué diferencia hay en los operadores condicionales?

A los operadores Y/O condicionales se les llama también "cortocircuito". La diferencia entre éstos y su versión no cortocircuitada se ve a la hora de evaluar la expresión. Veamos un ejemplo con Y condicional:
resultado = condicion1 && condicion2 && condicion3
En este ejemplo si condicion1 es true se seguirá evaluando si condicion2 también lo es, pues para saber el resultado hay que llegar hasta el final o hasta que una condición sea false. Si se encuentra una condición false entonces no hace falta seguir evaluando pues el resultado final va a ser false.

Si lo vemos para O condicional:
resultado = condicion1 || condicion2 || condicion3
Aquí pasa algo parecido pero a la inversa. Se parará de evaluar en cuanto se encuentre que una condición es true pues el resultado final será true independientemente del resto de condiciones.

Los operadores lógicos cortocircuitados evitan la evaluación restante de la expresión cuando conocen el valor resultante global


A primera vista podría simplemente parecer una diferencia de rendimiento (si evito operaciones irá todo más rápido), pero lo fundamental es si queremos que se evalúe una condición o no. Ahora mismo es pronto para ver la diferencia. Lo veremos cuando lleguemos a Clases y se explique el concepto de null.

Este código servirá para ver un ejemplo de las puertas lógicas:
boolean v = true;
boolean f = false;

System.out.println(v + " & " + v + " = " + (v & v));
System.out.println(v + " & " + f + " = " + (v & f));
System.out.println(v + " | " + f + " = " + (v | f));
System.out.println(f + " | " + f + " = " + (f | f));
System.out.println("!" + v + " = " + !v);
System.out.println("!" + f + " = " + !f);
Su salida es:

true & true = true
true & false = false
true | false = true
false | false = false
!true = false
!false = true


Echaréis en falta probar los operadores cortocircuitados. Precisamente son los que se utilizan habitualmente, pero en un ejemplo tan sencillo sin haber visto métodos es difícil demostrar el corte que se produce, por eso se ve cuando hemos visto métodos y aprendemos las condiciones.

Operadores de comparación

En esta entrada vamos a ver los operadores de comparación que usaremos para comparar valores. Vamos a emplearlos con decimales para empezar a usarlos y de paso veremos los errores de redondeos que no vimos en los operadores numéricos.

NOTA: En este punto nos vamos a desviar del índice del libro que sirve de guía. En Think Java el orden es ver la entrada de teclado, métodos sin retorno, condicionales y lógica, métodos con retorno y bucles. Yo prefiero el orden que he marcado en la programación del curso para tener todos los operadores juntos y terminar este parte con la entrada de teclado y poder hacer un programita chulo al final para verlo todo junto. Simplemente advertir que hasta que lleguemos a Arrays no vamos a seguir el orden del libro.

Ya hemos visto y usado el operador de asignación y los operadores numéricos habituales. Los operadores de comparación en Java son:
  1. Menor que: <
  2. Mayor que: >
  3. Menor o igual que: <=
  4. Mayor o igual que: >=
  5. Igual que: ==
  6. Distinto que: !=

Todos ellos operadores binarios y con sus operandos formarán una expresión que devolverá un valor de tipo boolean. Este tipo boolean sólo tiene dos valores posibles: verdadero o falso (true o false). Es la base para trabajar con condiciones que veremos en su sesión correspondiente.

Los operadores de comparación son binarios y devuelven un valor de tipo boolean (verdadero o falso)


Con este código vamos a tener un ejemplo de cada uno y podéis seguirlo en el vídeo:
float a = 2.1f;
double b = 3.9;

System.out.println(a + " < " + b + " : " + (a < b));
System.out.println(a + " > " + b + " : " + (a > b));
System.out.println(a + " <= " + b + " : " + (a <= b));
System.out.println(a + " >= " + b + " : " + (a >= b));
System.out.println(a + " == " + b + " : " + (a == b));
System.out.println(a + " != " + b + " : " + (a != b));
Su salida es:

2.1 < 3.9 : true
2.1 > 3.9 : false
2.1 <= 3.9 : true
2.1 >= 3.9 : false
2.1 == 3.9 : false
2.1 != 3.9 : true

Incluso si prefiris leer el blog a ver un vídeo, os aconsejo ver la demostración de los errores de redondeos y la comparación entre tipos decimales distintos. Esta demostración se aprecia mejor en vivo y no la he escrito en esta entrada.

Debemos tener especial cuidado cuando comparemos valores numéricos de distinto tipo debido al redondeo


Hay que tener en cuenta que estos operadores funcionan con valores numéricos pero no con todos los tipos que hemos visto hasta ahora (por ejemplo no podemos ver si un String es mayor que otro, pero sí podemos ver si es el mismo)

NOTA: Si quieres leer más sobre la compatibilidad entre tipos como en el ejemplo del vídeo entre int y double, puedes ver todas las conversiones que contempla la especificación de Java.

13 de septiembre de 2018

Operadores - Numéricos

Ya hemos visto el operador de asignación. Es uno de los 38 que contiene la especificación de Java 8. En esta entrada veremos los operadores numéricos. Estos operadores junto con otras expresiones, nos van a permitir realizar cálculos, que os recuerdo son una de las partes más generales en las que dividiamos cualquier programa cuando aprendimos qué era programar. En el libro que estamos siguiendo da muchos ejemplos de redondeos y diferencias entre distintos tipos numéricos como int, float o double.

Ahora mismo no voy a entrar en esa profundidad pues en general no influyen demasiado. Al final de esta entrada sí que voy a llamaros la atención en aspectos a tener en cuenta por experiencia personal. Por ahora vamos a ver cuales son los operadores numéricos y unos ejemplos.

Los operadores numéricos permiten hacer cálculos aritméticos. Existen diferencias usándolos con operandos enteros o decimales


Según la especificación de Java los operadores numéricos son (para enteros y decimales):
  1. Suma: +
  2. Resta/Cambio de signo: -
  3. Multiplicación: * (asterisco)
  4. División: /
  5. Módulo (resto): %
  6. Incremento: ++
  7. Decremento: --
Para enteros hay otros a nivel bit que es más raro su uso y no los veremos en esta entrada.

Los 5 primeros se usan con dos operandos. Cuando un operador utiliza dos operandos se conoce como operador binario.

Los operadores 6 y 7 se usan un sólo operando, son unarios (sólo necesitan un operando para incrementar o decrementar) y cambia su comportamiento si lo usamos antes o después de la variable como se ve en el vídeo.

El operador "-" también puede usarse como unario (para cambiar el signo)

El operador "+" podemos usarlo con una cadena de texto para que nos concatene todo en una sola cadena.


Este es un ejemplo para cada uno:
int operando1 = 2;
int operando2 = 3;
int resultado;
System.out.print("Sumo los operandos = ");
resultado = operando1 + operando2;
System.out.println(resultado);
System.out.print("Resto los operandos = ");
resultado = operando1 - operando2;
System.out.println(resultado);
System.out.print("Niego el operando1 = ");
resultado = -operando1;
System.out.println(resultado);
System.out.print("Multiplico los operandos = ");
resultado = operando1 * operando2;
System.out.println(resultado);
System.out.print("Divido los operandos = ");
resultado = operando1 / operando2; // Cociente
System.out.println(resultado);
System.out.print("Resto de dividir los operandos = ");
resultado = operando1 % operando2; // Resto
System.out.println(resultado);
// Comprobamos que los operandos no han cambiado hasta ahora
System.out.println("Imprimo los operandos: op1 = "
        + operando1 + " y op2 = " + operando2);
System.out.print("Incremento el operando1 = ");
System.out.println(++operando1); // Cambio antes de devolver el valor
System.out.print("Decremento el operando1 = ");
System.out.println(operando1--); // Cambio despues de devolver el valor
System.out.print("Imprimo el operando1 = ");
System.out.println(operando1); // Ahora vemos el cambio
Su salida por consola es:

Sumo los operandos = 5
Resto los operandos = -1
Niego el operando1 = -2
Multiplico los operandos = 6
Divido los operandos = 0
Resto de dividir los operandos = 2
Imprimo los operandos: op1 = 2 y op2 = 3
Incremento el operando1 = 3
Decremento el operando1 = 3
Imprimo el operando1 = 2 

Si bien podría haber puesto los ejemplos con números directamente, quiero que desde el principio os acostumbréis a usar variables.

Este uso de los operadores se llama expresión. Una expresión es un fragmento de código que devuelve un valor. El concepto de sentencia que explicamos anteriormente suele usar varias expresiones. El mejor ejemplo son las líneas 23 y 24. Las dos líneas contienen una única sentencia que va obteniendo los sucesivos resultados de concatenar el texto dos a dos hasta que tiene una única cadena de texto que imprime por pantalla.

Es interesante ver cómo las variables no cambian sus valores usando los primeros operadores. Esto es porque no les asignamos nuevo valor. El valor sólo se asigna a la variable "resultado". Sin embargo con el incrementador y decrementador sí que cambia el propio valor de su único operador, pero lo hace antes de devolver el nuevo valor si los usamos a la izquierda de la variable y después si usamos el operador a la derecha.

Las expresiones devuelven un valor, pero no implican un cambio del valor en sus variables.


Conclusiones a tener en cuenta

Como prometí al principio, aquí os dejo mis conclusiones al respecto:
  1. Redondeo de enteros: Esto si es normal encontrarlo. Si hago una división de enteros me redondeará a un entero y puedo tener grandes errores inesperados (sobre todo si es por debajo de 1). Por esto cuando tengamos que realizar operaciones con decimales usaremos float (que ocupa menos memoria) o double (si queremos tener una gran precisión).
  2. Respetar los datos que tenemos: Si leemos un dato externo que tiene el tamaño double y lo convertimos a float vamos a perder precisión. Si ese dato tiene esa precisión debemos valorar el por qué, si lo obviamos puede que confrontando datos con otros procesos haya discrepancias (imaginad una aplicación que sume millones de cantidades decimales distintas). Solo reduciremos el tamaño del dato cuando el error es asumible y no influye en el resultado final.
  3. Comparar decimales del mismo tipo: Lo mejor es comparar los datos del mismo tipo, en caso contrario uno de los dos se "convertirá" al otro tipo produciendo un pequeño error de ajuste. Esto producirá probablemente que una simple comparación de igualdad falle aunque asignemos "el mismo valor". Puedes ver la demostración en la siguiente entrada de operadores de comparación.
  4. Indicar los valores decimales float poniendo una "f" al final (ej: 5.3f), si no lo tomará como un double y nos saldrá un error.
  5. Existen unas clases que encapsulan los valores numéricos primitivos. Si no nos hace falta un valor como objeto (por ejemplo Collection nos obliga a tipos por referencia) se sacará más rendimiento usando tipos primitivos. Este punto puede no entenderse, pero quedará claro cuando veamos Clases, Objetos y Colecciones.

Si tenéis vosotros algún consejo más, os pido que lo dejéis en los comentarios.

12 de septiembre de 2018

Variables (definición y uso)

Las variables son un identificador (nombre) que apunta a un espacio de memoria. Son una parte fundamental del código fuente ya que permite referirnos a valores de cualquier tipo con un nombre que significa algo para nosotros como persona que lee y entiende mejor palabras que sólo ceros y unos.

Simplificándolo un poco: una variable es el nombre que le damos a un valor.


Los valores que tomen pueden ser muy variados (texto, números, audio, archivos, etc...) y es lo que vamos a conocer como "tipo" de variable. Para usar variables primero tenemos que "declararlas" (se refiere a cuando las creamos). Java esta especificado como un lenguaje fuertemente tipado así que para declarar variables vamos a tener que definir su identificador y su tipo.

Las variables constan de tipo e identificador y nos servirán para dar nombre a un valor


Declaración de variable

La sintaxis de una declaración es la siguiente:
String texto;
Acabamos de declarar una variable con identificador (nombre) "texto" y tipo String.

Otros tipos pueden ser int, char, boolean, Collection, etc... Se puede observar que unas veces empezamos con minúsculas y otras con mayúsculas. Recordad que ya dijimos que Java es "case sensitive" (sensible a las mayúsculas). Más adelante diremos las diferencias, por ahora simplemente vamos a escribirlo como en los ejemplos.

Este es un ejemplo de declaración de varias variables:
String nombre;
String primerApellido;
int hora, minutos;
Vemos que todos los identificadores empiezan con una letra en minúscula y que si están formados por más de una palabra se pone en mayúsculas el comienzo de las siguientes (no se permiten espacios). Esto es una convención de Java y se conoce como CamelCase (en este caso lowerCamelCase)

Declaramos las variables con tipo e identificador usando lowerCamelCase


Se pueden declarar dos variables del mismo tipo separando sus identificadores por coma (hora y minuto).

Este ejemplo podríamos entenderlo como las variables que necesitaríamos para definir una cita ¿Cómo añadirías otra para el lugar de encuentro?

Asignación de valor

Ahora ya podemos utilizarla para hacer referencia a valores. Esto se conoce como "asignar" un valor.

Para asignar un valor a una variable utilizamos el operador de asignación "=" como en el siguiente código:
primerApellido = "García";
hora = 10;
minutos = 0; 
Llamo la atención sobre el valor "García" (entre comillas dobles: String es texto) y los valores para hora y minutos (sin comillas: int es número). Si lo hiciéramos al revés nos saldría un error porque en la asignación hay que respetar los "tipos" de datos declarados anteriormente (String e int)

Asignamos valores a las variables respetando su tipo


También es importante no confundir el operador de asignación "=" con el operador de igualdad "==" que se verá más adelante.

Ahora ya estamos preparados para escribir en consola nuestro mensaje "Hola Mundo con variable":
String mensaje = "Hola Mundo con variable";
System.out.print(mensaje);
Desde este momento trabajamos nuestro código con variables y no usaremos valores fijos (conocidos como "magic numbers") que nos compliquen el mantenimiento o reutilización del código.

Debemos usar variables y erradicar los "magic numbers"


En la siguiente entrada vamos a ver más operadores y trabajar con variables.

11 de septiembre de 2018

Mostrando más mensajes (print/println)

Como mostrar "Hola Mundo" es chulo, pero sólo un poco, vamos a ver cómo ampliar nuestro programa añadiendo más datos a la salida. Os recuerdo que la salida (output) es una de las partes más genéricas en las que dividíamos cualquier programa cuando aprendimos qué es programar.


Si cogemos el archivo Fin.java de la sesión anterior y añadimos este código:
System.out.println("Cruel");
System.out.print("Texto en otra línea");
Lo ejecutamos y vemos que nos queda:

Adios MundoCruel
Texto en otra línea

NOTA: Podemos usar atajos de Eclipse para no tener que escribir expresiones tan parecidas. Usar System.out.println(); es muy habitual y tiene el atajo syso.

Fijaros que hemos usado dos métodos distintos: print y prinln

La diferencia es que se añada el texto a una nueva línea o se permanezca en la misma que se escribió.

Cada línea que realiza una acción básica se llama sentencia, terminan en punto y coma (;) y ocupan una línea


También es importante ver cómo se organiza el código. Por ahora simplemente conocer estos puntos:
  1. Cada línea que realiza una acción básica se llama sentencia
  2. Todas las sentencias terminan en punto y coma (;)
  3. Al terminar una sentencia se sigue en una nueva línea
Todos los textos van "entrecomillados" y se conocen como String. String en inglés es traducido como cadena y representa que un texto es una cadena de caracteres.

Vemos que no hay espacio en "MundoCruel", ya que cuando termina de escribir uno, empieza a escribir otro. Si queremos un espacio en medio hay que añadirlo.

Los textos van "entrecomillados" y se conocen como String


Ahora bien, hay texto "especial" que no podemos escribir como un caracter (un salto de línea, una tabulación, unas comillas dobles, etc...) y para solucionarlo contamos con las secuencias de escape.

Las secuencias de escape empiezan con el caracter de escape "\" que indica que se esta usando una secuencia de escape. Las más habituales son:
  • \n = nueva línea
  • \\ = caracter "\" (backslash)
  • \" = comillas dobles dentro del String
  • \' = comillas simples dentro del String (en algunos lenguajes como JavaScript pueden delimitarse los Strings con cualquiera de las dos)
  • \r = retorno de carro
  • \t = tabulación
De esta forma podemos escribir en una sola sentencia todo el texto anterior e incluso adornarlo con unas comillas dobles que antes nos hubiera cortado la cadena:
System.out.print("Adios \"Mundo Cruel\"\nTexto en otra línea\n\tY otra linea más 'tabulada' usando \\t");
Que nos da la salida:

Adios "Mundo Cruel"
Texto en otra línea
    Y otra más tabulada usando \t

NOTA: Ya te habrás fijado en las tabulaciones que se usan según vamos entrando en bloques de código (nada en raíz, una en la declaración de "main" y dos en las sentencias dentro de éste). La forma de disponer el código para hacerlo más legible y uniforme se conoce como guía de estilo. Google ha publicado varias, una de ellas para Java, y se recomienda su uso en proyectos que vayamos a compartir con más programadores.

Práctica:

¿Os atrevéis a escribir el código necesario usando lo visto para imprimir por consola el listado con todas las secuencias de escape?

5 de septiembre de 2018

¿Cómo no? Hola mundo

Para ejecutar un programa en Java vamos a necesitar compilar código que cumpla con nuestro algoritmo y que de alguna forma se le indique a la JVM dónde tiene que empezar.

Utilizamos el método "main" para decirle a la JVM por donde empezar


Para ello vamos a usar el método "main" y nuestra aplicación simplemente mostrara el mensaje "Hola Mundo" por consola.

En el siguiente vídeo muestro un ejemplo de "Hola Mundo" de toma de contacto con la sintaxis de Java y su ejecución utilizando Eclipse.


Este video es la continuación del anterior donde explicábamos cómo instalar Eclipse.

El código completo es:
public class Inicio {

    public static void main(String[] args) {
        System.out.println("Hola Mundo");
    }

}
Es importante copiarlo tal cual pues Java es "case sensitive" (distingue entre mayúsculas y minúsculas). Esto quiere decir que, si por ejemplo escribimos "system" o "SYSTEM" en vez de "System", Eclipse nos dará un error porque no lo reconocerá.

Podemos ver varias palabra clave como:
  • public: que nos dice que la visibilidad del método es pública
  • class: que nos dice que es una clase
  • static: que nos dice que no es necesario tener una instancia para utilizarlo
  • void: que nos dice que el método no tiene ningún valor de retorno
Ahora mismo todo esto nos sonará a chino. En esta sesión simplemente hay que quedarse con que utilizamos el método "main" para decirle a la JVM por donde empezar.

Podemos tener varios archivos con distintos métodos main. Vamos a elegir cómo arrancarlos con las configuraciones de ejecución o si creamos un .jar ejecutable se lo indicaremos en el manifiesto cuando lo exportemos. Esto último lo veremos más adelante.

4 de septiembre de 2018

Instalación del Entorno de Desarrollo Integrado (IDE) - Eclipse

Para programar vamos a necesitar una aplicación que nos facilite la creación del código fuente (con asistente de contenido, control de versiones, refactorización, etc...), nos lo compile, ayude a la depuración y se encargue de desplegar los componentes en su caso.

Estas tareas pueden hacerse por separado, pero un Entorno de Desarrollo Integrado (IDE) se encarga de todo de manera conjunta.

Para el curso he elegido Eclipse que es el que personalmente más me gusta y tiene un gran soporte, pero podéis oír hablar de otro como NetBeans o IntelliJ IDEA.

Eclipse se descarga directamente de la página oficial.

Un IDE nos ayudará con todas las tareas implicadas en la programación. En el curso usaremos Eclipse.


Las versiones cambian "a menudo". Recomiendo no ser un loco de cambiar todo cada vez que salga una versión, pero siempre que tengas que crearte un entorno nuevo por cambio de puesto de trabajo, equipo, etc... coge la última versión.

Es muy importante elegir la versión apropiada (32 o 64 bits) a nuestra máquina virtual de Java (JVM). ¡Ojo! Me refiero a nuestra versión instalada de Java (JRE), no a la arquitectura del micro, ni del Sistema Operativo.

Eclipse se ejecuta con Java, así que debemos tenerlo instalado y saber su versión.

Para saberlo simplemente ejecuta en un terminal/consola el comando:

java -version

En el video podemos ver el ejemplo en vivo.


En mi caso es una versión de 32 bits porque en la versión de 64 bits aparece explícitamente escrito (no se ve en mi pantallazo)

Una vez que sabemos esto toca elegir qué paquete de todos los posibles de Eclipse vamos a usar. Yo siempre elijo el paquete "Eclipse IDE for Java EE Developers". Suele ser la que más descargas tiene. Lo importante es que tenga las herramientas para Java y Git.

Para comenzar podría utilizarse un paquete más reducido como el "Eclipse IDE for Java Developers", pero como llegaremos a un punto del curso en que utilizaremos la parte Enterprise de Java pues mejor coger el que me valga para todo.

En mi caso me descargo la versión Windows 32 bits de "Eclipse IDE for Java EE Developers".

Una ventaja de Eclipse es que es gratuito. Aprovecho para pedirte que dones para mantener estos proyectos. Puedes coger la mínimo (yo pagué sólo 5€). Si necesitan más ayuda puede que en unos años te pidan seguir apoyando (a mi me pasó con Wikipedia). El software libre es una joya que hay que mimar.

He decidido dar estos consejos para elección del IDE (en vez de un link) pues la página de descargar de Eclipse suele cambiar y puede que en un futuro no sea como se ve en el video o el link se corte.

Voy a descomprimir el contenido descargado en C:/desarrollo. Usaré esta carpeta para todo lo que vaya añadiendo en el curso.

La primera vez que abrimos Eclipse nos va a pedir que elijamos el "Espacio de Trabajo (workspace)". Creamos una carpeta "workspace" en la carpeta "desarrollo" y marcamos la casilla para que no nos vuelva a preguntar. Los workspaces sirven para tener orden cuando cambiamos entre proyectos por ejemplo. Por ahora sólo vamos a usar uno.

Descargo todo en la carpeta de trabajo C:/desarrollo. Dentro tengo otra "workspace" que nos servirá para organizarnos


Eclipse también intentará conectarse a Internet para comprobar actualizaciones u obtener documentación, si nos conectamos por un proxy habrá que indicarle los datos de conexión (en esta versión debería coger los datos del navegador por defecto).

Opciones de Configuración personal

Estas son unas recomendaciones de configuración de Eclipse (pero es una preferencia personal):
  1. Usar un tema oscuro: Por el bien de nuestros ojos vamos a Window -> Preferences => General -> Appearance y seleccionamos "Dark" entre las opciones de "Theme"
  2. Potenciar el Asistente de Contenido: Esto facilitará la ayuda de opciones a la hora de escribir código. Vamos a Window -> Preferences => Java –> Editor –> Content Assits y  en el grupo "Auto Activacion" ponemos en "Auto activation triggers for Java" todas las letras en mayúsculas y minúsculas incluyendo un punto al final

    QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm.

    Así se activará la ayuda con cualquier tecla habitual que usemos. Si ponemos "Auto activation delay (ms)" a 200 será bastante rápida la ayuda.
  3. Dejar el IDE en ingles: Aunque se puede cambiar el lenguaje del IDE con proyectos como Babel, yo recomiendo dejarlo en inglés porque si alguna vez buscamos algo en Internet con un problema que tengamos, lo normal es que nos guíen con los nombres en inglés y me parece más fácil encontrar la solución así.

Ya podemos empezar

Sólo queda crear nuestro primer proyecto:
  1. Vamos a File –> New –> Java Project
  2. Le damos un nombre "Hola Mundo" por ejemplo.
  3. Por defecto dejamos el JRE que sugiere (será el que nos salió con java -version)
  4. Para ver el código de ejemplo puedes ir a la primera entrada de programación "Hola Mundo".

Compárteme

Entradas populares