Introducción A Java Para Desarrollo Android

En este artículo repasarás los conceptos básicos del lenguaje Java con el fin de recordar y fortalecer tus conocimientos.

Esto permitirá acercarte mucho más al Desarrollo Android y evitar que te pierdas en algunos tutoriales donde se usan funcionalidades de Java.

Cabe aclarar que esta no es una guía para enseñar Algoritmia o un curso profundo de programación Java.

Simplemente quiero resaltar aquellos aspectos que serán de vital importancia estudiar antes de crear aplicaciones Android.

Asumiré que ya tienes instalado el JDK y que conoces las potencialidades de la máquina virtual de Java y como esta permite ejecutar secuencias en gran variedad de plataformas.

1. Ejemplo: Aplicación HelloWorld En Java

Comencemos recordando la estructura general de un programa Java.

Recrearemos el famoso ejemplo «Hello World», donde el objetivo principal es imprimir una sola línea de caracteres en la consola. Para ello ve a un editor de texto como bloc de notas y pega el siguiente código.

public class PruebaJava {
    public static void main(String args[]) {
        System.out.print("Recordando Java...");
    }
}

Ahora guarda el archivo con extensión .java con el nombre de «PruebaJava.java» en algún directorio de fácil localización.

Guardar Fichero Con Extensión Java

Ahora abre tu consola de comandos y escribe la siguiente línea para compilar el archivo:

javac PruebaJava.java

Con ello, ya está listo el archivo PruebaJava.class. Lo que significa que ya puedes ejecutar el programa con el comando:

java PruebaJava

Si todo ha salido bien, entonces podrás ver en consola el mensaje.

"Recordando Java..."

Este ejemplo muestra la creación de una clase sencilla con un método main(). Recuerda que este método actúa como el punto de inicio de la aplicación. En él se ejecutan las acciones fundamentales de la app. Si no lo incluyeras, la ejecución arrojaría un error.

La impresión del texto se logra con el método System.out.println(), solo incluyes el texto que deseas proyectar encerrado en comillas dobles (" ").

2. Paquetes De Clases

Los paquetes son estructuras de directorios que agrupan tus recursos Java para evitar colisiones de nombres. Por ejemplo, si tuvieses una clase en el paquete com.herprogramacion.modelo.Archivo no habría problema en tener una clase con el mismo nombre en com.herprogramacion.utilidades.Archivo.

Como ves, esta división aísla las características de ambas clases y así puedes nombrarlas igual.

Las siguientes son características fundamentales para crear un sistema de paquetes:

  • El nombre de los paquetes debe ser en minúsculas para evitar conflictos con los nombres de las clases, las cuales usan notación CamelCase. Nada de nombres como Com.FinaNzas.CuENTAs.
  • Solamente los paquetes exclusivos del lenguaje pueden comenzar con la palabra java o javax.
  • Asegura que la estructura de los paquetes sean únicos. Generalmente se usa como convención el dominio de internet de las compañías a la inversa. En mi caso sería com.hermosaprogramacion.
  • Existen casos donde el nombre de la compañía no funciona. Por ejemplo: cuando el dominio usado contiene una palabra reservada; cuando hay caracteres especiales como '-'; o si el paquete comienza con dígitos numéricos. Para solucionarlo antepón un guion bajo para complementar '_'.Convenciones Para Nombres De Paquetes En Java

3. Apuntar Tu Código Con Comentarios

Los comentarios son bloques de texto significativo que serán ignorados por el compilador Java. Al igual que otros lenguajes como C++, Php, Javascript, etc., estos permiten detallar las características de tu código, con el fin de comprender su funcionamiento por toda persona interesada en el desarrollo.

Es posible realizar comentarios de línea simple, donde solo se escribe una línea comentada a través de barras verticales //.

// Ejecución del bucle interno

O también comentar múltiples líneas si incluyes el texto dentro de /*  */.

/*
Este conjunto de instrucciones
genera una serie de números aleatorios
con el fin de sumar sus magnitudes
 */

Comentarios TODO en Java— Existen IDEs que permiten la creación de comentarios tipo TODO. Con ellos se puede apuntar sobre el código tareas pendientes, preguntas sin resolver, optimizaciones por realizar, etc.

Su propósito es dinamizar el código para que el programador recuerde eventos necesarios que completarán el desarrollo rápidamente.

Por ejemplo…

En Android Studio los comentarios TODO se escriben de la forma //TODO: [comentarios].

//TODO-james: Recuerda crear una prueba unitaria para el resultado del parsing para esta petición.

Se escribe la palabra TODO( en Español sería «Por hacer») y luego especificas la acción. Si deseas añades el nombre de la persona encargada.

La ventaja es que podrás ver en forma de lista todos los comentarios TODO dentro del proyecto o el archivo actual.

Comentarios TODO En Android Studio

4. Tipos De Datos Primitivos

El manejo de la información en Java es soportado en varios tipos de datos elementales.

Al igual que en otros lenguajes, Java soporta números enteros, flotantes, booleanos, caracteresstrings, etc.

La siguiente es una tabla con las palabras reservadas que representan los tipos de datos primitivos principales:

Tipo de dato Descripción
boolean Representa los estados binarios true  (1) y false (0)
byte Representa a un número de 8 bits, cuyo rango se encuentra entre -128 y 127
short Número entero de 16 bits, cuyo rango se encuentra entre -37768 y 32762
int Es capaz de contener un número entero con signo de 32 bits, cuyo rango es {-231, 231-1}
long Representa un número entero de 64 bits con signo, con rango de {-263, 263-1}
float Tipo de dato para el manejo de números de punto flotante con una precisión sencilla de 32bits
double Tipo de dato para el manejo de números de punto flotante con un doble precisión a 64bits
char Es un tipo de dato que representa un solo carácter Unicode de 16 bits

5. Declarar Conjuntos De Valores Con Arrays

Los arrays son objetos que contienen múltiples valores de un solo tipo. La cantidad de elementos que contenga debe ser predefinida para que sea posible relacionar un índice a la posición.

Indices y Tamaño De Un Array

 

La anterior ilustración muestra la representación visual de un array con un tamaño de 10(length). Los índices están basados en cero, por lo que el primer elemento está en la posición 0 y el ultimo en la posición tamaño-1.

El siguiente es un ejemplo donde creamos un array de enteros con tamaño 5. Luego asignamos valores de ejemplo e imprimimos su contenido.

PruebaArray.java

class PruebaArray {
    public static void main(String[] args) {
        // Declaración del array
        int[] arrayDeEnteros;

        // Creamos 5 espacios para enteros
        arrayDeEnteros = new int[5];

        arrayDeEnteros[0] = 1;
        arrayDeEnteros[1] = 3;
        arrayDeEnteros[2] = 5;
        arrayDeEnteros[3] = 7;
        arrayDeEnteros[4] = 9;

        System.out.println("arrayDeEnteros[0]: "
                + arrayDeEnteros[0]);
        System.out.println("arrayDeEnteros[1]: "
                + arrayDeEnteros[1]);
        System.out.println("arrayDeEnteros[0]: "
                + arrayDeEnteros[2]);
        System.out.println("arrayDeEnteros[0]: "
                + arrayDeEnteros[3]);
        System.out.println("arrayDeEnteros[0]: "
                + arrayDeEnteros[4]);
    }
}

El resultado sería:

arrayDeEnteros[0]: 1
arrayDeEnteros[1]: 3
arrayDeEnteros[2]: 5
arrayDeEnteros[3]: 7
arrayDeEnteros[4]: 9

Como ves, tanto como la declaración y la inicialización del array requiere el uso de corchetes para indicarle al compilador que será un array.

Si deseas obtener un valor del array en algún índice específico, entonces usa los corchetes con el índice.

Incluso puedes declarar e inicializar en una sola línea. Los valores puedes asignarlos en llaves, separando por comas.

int [] arrayDeEnteros = {1, 3, 5, 7, 9};

6. Uso De Operadores

Los operadores son caracteres especiales para indicar la realización de una acción entre variables.

¿Qué tipo de operaciones?

Si hablamos de operadores aritméticos, entonces ya sabrás a que me refiero: suma, resta, multiplicación, división, módulo.

También operaciones lógicas como el AND, OR, NOT.

O asignación de valores, incrementos, agrupación, entre otros.

La siguiente es una tabla de los operadores en Java. Al momento de usar operadores en tus sentencias debes tener en claro que existe una precedencia, es decir, algunos tienen más prioridad de ejecución que otros. La tabla está agrupa por niveles de importancia.

Operadores Precedencia
sufijo expr++ expr--
unarios ++expr --expr +expr -expr ~ !
multiplicativos * / %
aditivos + -
desplazamiento de bits << >> >>>
relacionales < > <= >= instanceof
igualdad == !=
AND para bits &
OR exclusivo para bits ^
OR inclusivo para bits |
AND lógico &&
OR lógico ||
operador ternario ? :
asignación = += -= *= /= %= &= ^= |= <<= >>= >>>

Como ves, son una gran cantidad de opciones para computar tus datos. Por lo que puedes realizar infinidad de tareas en tus aplicaciones.

7. Estructuras De Decisión

Este tipo de sentencias se usan para desviar el flujo de ejecución de nuestros programas basados en una condición establecida.

La estructura de decisión más simple es la sentencia if. Esta comprueba la veracidad de una expresión para tomar decisiones. Si el valor es verdadero (true), entonces las acciones definidas en su bloque de instrucciones.

if(expresionBooleana){
    /*
    Bloque de instrucciones a ejecutar si la evaluación es true
     */
}

Por ejemplo…

PruebaIf.java

class PruebaIf {
    public static void main(String[] args) {

        int a = 2;
        int b = 3;

        if(a+b >2){
            System.out.println("La suma de ambos números es mayor que 2");
        }
    }
}

El ejemplo anterior comprueba si la suma de las variables a y b es mayor que 2. Si es así, entonces se imprime el mensaje de confirmación.

Pero… ¿Cómo evaluar un resultado adverso?

Usando la sentencia else (en español se lee «de otro modo»).

Al usar else para complementar una sentencia if, es posible controlar el flujo con otro bloque de instrucciones alternativo.

if(expresionBooleana){
    /*
    Bloque de instrucciones a ejecutar si la evaluación es true
     */
}else{
    /*
    Bloque de instrucciones para resultado adverso
     */
}

En el caso del ejemplo anterior, podríamos imprimir el mensaje contrario diciendo que el resultado es menor.

if(a+b >2){
    System.out.println("La suma de ambos números es mayor que 2");
}else{
    System.out.println("La suma de ambos números es menor que 2");
}

Ahora… ¿qué pasa si tienes más de un resultado a controlar?

En vez de usar else, usa la variación else if para evaluar múltiples condiciones. Por ejemplo, si existiese una variable llamada comprasTotales, la cual contiene las compras totales de un cliente específico y se deseara imprimir el grupo al que pertenece este cliente según el momento, podríamos usar la variación else if.

if (comprasTotales < 200) {
    System.out.println("Cliente Moderado");
} else if (comprasTotales >= 200 && comprasTotales < 1000) {
    System.out.println("Cliente Común");
} else if (comprasTotales >= 1000) {
    System.out.println("Cliente Estrella");
}

La sentencia switch en Java— Existen casos donde la sentencia if debe expandirse a varios else if para comprobar la igualdad de un valor en específico. Esto puede ser un poco tedioso en la redacción del código.

La sentencia switch existe para simplificar este tipo de situaciones. Esta estructura de decisión múltiple permite comprobar varios casos para ejecutar las instrucciones correctas.

switch(dato){
    default:
        // Acciones por defecto
    case valor1:
        // Acciones para valor 1
        break;
    case valor2:
        // Acciones para valor 2
        break;
    //...
    case valorN:
        // Acciones para valor N
        break;
}

La sentencia case determina el valor que comprobarás. Si se cumple, entonces se ejecutan las instrucciones contenidas. Una vez terminan, se cierra cada caso con la sentencia break.

default contiene las instrucciones a realizar para aquellos valores que no fueron especificados particularmente.

Por ejemplo… analicemos como se contarían los votos de una elección.

String opcionElegida = "Carlos Valdez";

switch (opcionElegida) {
    default:
        System.out.println("Opción elegida inválida");
        break;
    case "Ramiro Córdoba":
        votosRamiroCordoba++;
        break;
    case "Susana Feliciano":
        votosSusanaFeliciano++;
        break;
    case "Michael Stewart":
        votosMichaelStewart++;
        break;
    case "Camila Rengifo":
        votosCamilaRengifo++;
        break;
    case "Carlos Valdez":
        votosCarlosValdez++;
        break;
}

8. Bucles En Java

Un bucle es una estructura de repetición que permite ejecutar un mismo conjunto de instrucciones varias veces.

Veamos cuáles tenemos a disposición.

1. while: El bucle while ejecuta una y otra vez las mismas acciones mientras la condición que se le ha establecido sea verdadera. Es la mejor opción, si no sabes con exactitud la cantidad de iteraciones necesarias.

while(expresionBooleana){
    /*
    Bloque de instrucciones
     */
}

El siguiente ejemplo muestra cómo sumar los números del 1 al 10.

PruebaWhile.java

class PruebaWhile {
    public static void main(String[] args) {

        int suma = 0;
        int contador = 1;

        while (contador <= 10) {
            suma += contador;
            contador++;
        }

        System.out.println("La suma es: " + suma);
    }
}

El código anterior mostraría el siguiente resultado:

La suma es: 55

2. do-while: Este bucle es una variación del anterior. Además de repetir las instrucciones mientras la condición sea verdadera, permite ejecutar la primera instrucción sin comprobar la condición.

Para ello es necesario incluir las acciones dentro de la sentencia do y al final comprobar con la sentencia while

do{
    /*
    Conjunto de instrucciones
     */
}
while (expresionBooleana);

El ejemplo anterior podríamos traducirlo al bucle do-while de la siguiente forma:

EjemploDoWhile.java

class PruebaDoWhile {
    public static void main(String[] args) {

        int suma = 0;
        int contador = 1;

        do {
            suma += contador;
            contador++;
        }
        while (contador <= 10);

        System.out.println("La suma es: " + suma);
    }
}

3. for: El bucle for se encarga de repetir las instrucciones hasta la condición impuesta en sus argumentos llegue a ser falsa. Es la mejor opción si sabes la cantidad de veces que deseas ejecutar una condición.

for (inicialización; condicionDeParada; incremento) {
    /*
    Conjunto de instrucciones
     */
}

La anterior sintaxis muestra tres partes del bucle:

  • inicialización: Inicia la cuenta del bucle. Esto ocurre una sola vez.
  • condicionDeParada: Expresión booleana que se relaciona con la inicialización para saber en qué momento se detendrá el bucle.
  • incremento: Expresión llamada al final de cada iteración, la cual permite incremento o decremento de un valor.

A continuación traduzco el mismo ejemplo de la suma de números al bucle for.

PruebaFor.java

class PruebaFor {
    public static void main(String[] args) {

        int suma = 0;
        for (int contador = 1; contador <= 10; contador++) {
            suma += contador;
        }

        System.out.println("La suma es: " + suma);
    }
}

4. for-each: El bucle for-each (en español «para cada uno») permite ejecutar un mismo bloque de instrucciones por cada elemento de una lista de valores. Es ideal para recorrer estructuras de datos.

for (<tipo> nombre : estructura){
    /*
    Conjunto de acciones
     */
}

Por ejemplo… imprimamos el mismo array visto con anterioridad.

class PruebaForEach {
    public static void main(String[] args) {

        int[] arrayDeEnteros = {1, 3, 5, 7, 9};

        for (int entero : arrayDeEnteros) {
            System.out.println(entero);
        }
    }
}

La variable entero es usada para guardar el elemento actual de la iteración. Esto permite que imprimamos cada elemento del array .

9. Definición De Clases En Java

Debido a que Java es un lenguaje orientado a objetos, es indispensable tener en claro cómo usar clases y objetos.

Una clase es la abstración de las características de un objeto en particular. Esta define las características (atributos) que posee y los comportamientos que tendrá (métodos).

En código, podemos comparar una clase con una especie de plantilla de la cual podemos fabricar objetos.

Para declarar una clase debes usar la palabra reservada class y luego definir un bloque de código que comprenda sus atributos y métodos.

Estructura De Una Clase En Java

Por ejemplo… la siguiente es una clase que describe a un cliente.

public class Cliente {
    /*
    Atributos
     */
    private int idCliente;
    private String nombre;
    private String telefono;
    private float saldo;

    // Método que modifica al campo saldo
    public void acumularSaldo(float excedente) {
        saldo += excedente;
    }
}

Restringir el alcance con modificadores de acceso— Los modificadores de acceso determinan el alcance de atributos y métodos hacia otras clases. Existen 4 posibilidades de implementación.

  • public: Permite que una pieza de código sea visible en todos los niveles.
  • protected: Con este modificador solo las clases que se encuentren en el mimo paquete pueden ver y acceder a este código.
  • sin modificador: Permite que solo las clases del paquete accedan al código. A diferencia de protected, este modificador no permite que las subclases tengan privilegios del código de su superclase.
  • private: Solo la clase donde está contenido el código, puede tener acceso.

Simplificando un poco, observa la siguiente tabla sobre modificadores Java.

Modificador Clase Paquete Subclase Mundo
public Si Si Si Si
protected Si Si Si No
sin modificador Si Si No No
private Si No No No

El modificador final en Java— Declara que un fragmento de código no puede cambiar. Este modificador se puede usar en clases, atributos, métodos, parámetros y variables locales.

Declarar una clase como final asegura que esta no tenga subclases, de lo contrario se produce un error.

public final class PruebaModificadorFinal{

}
public class SubclaseDeFinal extends PruebaModificadorFinal{    

}

El código anterior produciría el siguiente error:

Error cannot inherit from final

Al hacerlo sobre un método, significa que las subclases de una superclase no pueden sobrescribir este método.

public class PruebaModificadorFinal {

    public final void imprimirMensaje() {
        System.out.println("Probando el modificador final en métodos");
    }
}
public class SubclaseDeFinal extends PruebaModificadorFinal {

    public void imprimirMensaje() {
        System.out.println("Intentando sobrescribir método");
    }
}

Intentar sobrescribir produce este error:

Error overriden method is final

Usar final con un atributo obliga a que asignes el valor en la declaración o en todos sus constructores. El compilador lanzará un error si intentas asignar un valor en otro lugar o si quieres asignar por segunda vez.

public class PruebaModificadorFinal {
    final int numero;

    PruebaModificadorFinal() {

    }

}

Mira lo que pasa si no asignas un valor.

Error variable might not have been initializated

Los parámetros con final no se les pueden reasignar un valor distinto al que ya trae hacia el método. Si se pasa por referencia un objeto, se asegura que no cambie la referencia, más no el contenido del objeto.

public class PruebaModificadorFinal {
    int numero;

    PruebaModificadorFinal() {
        asignarNumero(3);
    }

    public void asignarNumero(final int numero) {
        numero = 4;
        // ...
    }

}

Habrá error al intentar asignar un valor al parámetro.

Error Al Asignar Valor A Parámetro Java

Una variable local final debe ser inicializada obligatoriamente solo una vez. Intentar asignar por segunda vez produciría un error.

public class PruebaModificadorFinal {
    int numero;

    PruebaModificadorFinal() {
        sumarNumero(3);
    }

    public void sumarNumero(int numero) {
        final int suma = 0;

        suma = this.numero + numero;
    }

}

La variable suma del método sumarNumero() no permitirá ejecutar la segunda sentencia.

Error cannot assign a value to final variable

El modificador static en Java— Con static podemos declarar atributos y métodos como parte de clase, en vez de ser parte de las instancias.

Hablamos de variables de clase cuando un atributo es marcado con static dentro de una clase. Esto significa que la variable será común para todos los objetos y tendrá una posición fija de memoria.

Cualquier instancia de una clase puede cambiar el valor de un atributo estático, sin embargo también es posible hacerlo sin tener que crear una instancia.

Por ejemplo…

Se tiene la clase Enemigo, la cual tiene atributos como la cantidad de vida, maná, y el ataque. Cada uno de estos atributos varía según la instancia. Pero… ¿cómo saber cuántos enemigos se han creado en total?

Puedes crear una variable de clase que cuente la cantidad. Algo como:

Enemigo.java

public class Enemigo {
    private int vida;
    private int mana;
    private int ataque;

    private static int numeroDeEnemigos = 0;

    public Enemigo(int vida, int mana, int ataque) {
        this.vida = vida;
        this.mana = mana;
        this.ataque = ataque;

        // Incrementar la cantidad de enemigos
        numeroDeEnemigos++;
    }
}

Como ves, numeroDeEnemigos se crea como variable de clase y es incrementada en el constructor. Para consultar su valor, puedes usar el operador punto como se hace normalmente con un atributo, solo que desde el nombre de la clase.

Enemigo.numeroDeEnemigos

Análogamente podemos crear métodos de clase. Este tipo de métodos se pueden invocar directamente desde la clase sin tener que crear una instancia. Sin embargo pueden ser invocados por objetos, solo que no es recomendable porque no aclara su naturaleza.

Invocar Método Estático Desde Objeto

Los métodos estáticos son útiles para obtener el valor de las variables de clase. Para obtener la variable numeroDeEnemigos podrías usar el siguiente método.

public static int obtenerNumEnemigos(){
    return numeroDeEnemigos;
}

Luego accederíamos a través de la clase de la siguiente forma:

Enemigo.obtenerNumEnemigos();

Otro propósito lo podemos ver en los métodos de clase para utilidades. Por ejemplo, el método de clase asList() de la clase Arrays, permite convertir un array en una lista, sin tener que crear una instancia.

Declaración de constantes en Java— Las constantes son elementos que no pueden cambiar su valor a lo largo de programa. Para declararlas usa la combinación de los modificadores final y static.

public static final float GRAVEDAD_TIERRA= 9.9f;

Esta constante podrá usarse en todo el proyecto. Por convención de nombres se espera que escribamos nombres en mayúsculas y separando las palabras con guiones bajos.

10. Encapsulación Con Métodos set y get

La encapsulación es un concepto de la programación orientada a objetos que se refiere a limitar la visibilidad de los miembros de un objeto.

Esto significa que añades protección a los datos de tus objetos para mantener segura la estabilidad e integridad de una unidad funcional en la aplicación.

Una de las formas de aplicar esta teoría es usar métodos set y get.

Normalmente cuando inicias creando tus primeras clases, accedes a los atributos de las instancias de la siguiente forma:

miObjeto.atributo1 = 2;
miObjeto.atributo2 = "Cadena";

Usas el operador punto para obtener o asignar su valor de forma pública.

El papel que juegan los métodos set y get es restringir el acceso directo de tus atributos con el modificador private y en su lugar construir método public que obtengan y asignen su valor.

public class PruebaSetYGet{
    private int atributo1;
    private String atributo2;
    private float atributo3;

    public int getAtributo1() {
        return atributo1;
    }

    public void setAtributo1(int atributo1) {
        this.atributo1 = atributo1;
    }

    public String getAtributo2() {
        return atributo2;
    }

    public void setAtributo2(String atributo2) {
        this.atributo2 = atributo2;
    }

    public float getAtributo3() {
        return atributo3;
    }

    public void setAtributo3(float atributo3) {
        this.atributo3 = atributo3;
    }
}

El código anterior muestra la existencia de tres atributos privados. Por cada uno existe un método que comienza con las palabras en inglés «get» y «set», donde get*() obtiene el valor del atributo con la sentencia return y set*() asigna un nuevo valor a través de un parámetro.

Así evitas el contacto directo y solo debes llamar a los métodos.

miObjeto.setAtributo1(2);
String atributo2 = miObjeto.getAtributo2();

Cuando uses Android Studio podrás generar estos métodos automáticamente al presionar ALT + INSERT.

Generar Métodos get Y set En Android Studio

Luego seleccionas los atributos a los cuales deseas generar los métodos get y set.

Seleccionar Atributos Para Generar Métodos get y set

11. El Operador new Para Crear Objetos En Java

Una vez tienes tu clase definida, ya es posible instanciar objetos que comiencen a contribuir a la aplicación. La creación de un nuevo objeto se lleva a cabo del operador new.

Para crear un nuevo objeto de la clase Cliente sería:

Cliente clienteActual = new Cliente();

Uso de constructores— Si deseas personalizar la inicialización de los objetos de una clase, puedes declarar una variación del constructor por defecto.

Un constructor es un mecanismo para crear tus objetos. Tienen casi la misma forma de un método. La diferencia está en que no tiene tipo de retorno especificado y su nombre es el mismo de la clase.

El siguiente es un constructor que permite asignar valores a todos los campos del cliente.

public class Cliente {
    /*
    Atributos
     */
    private int idCliente;
    private String nombre;
    private String telefono;
    private float saldo;

    public Cliente(int idCliente, String nombre, String telefono, float saldo) {
        this.idCliente = idCliente;
        this.nombre = nombre;
        this.telefono = telefono;
        this.saldo = saldo;
    }

    // Método que modifica al campo saldo
    public void acumularSaldo(float excedente) {
        saldo += excedente;
    }
}

12. Herencia De Clases

La herencia es el proceso de transmitir atributos y métodos de una clase a otra. Donde la clase que se toma como base se le denomina superclase y la que hereda se le llama subclase.

Usa la palabra reservada extends para declarar que una clase hereda de otra.

Veamos un ejemplo entre una clase llamada Estudiante, la cual hereda de Persona.

Persona.java

public class Persona {
    String nombre;
    String telefono;

    public Persona(String nombre, String telefono) {
        this.nombre = nombre;
        this.telefono = telefono;
    }

    public void obtenerInformacion() {
        System.out.println("Nombre: " + nombre);
        System.out.println("Teléfono: " + telefono);
    }
}

Estudiante.java

public class Estudiante extends Persona {
    private float nota1, nota2, nota3;

    public Estudiante(String nombre, String telefono, float nota1, float nota2, float nota3) {
        super(nombre, telefono);
        this.nota1 = nota1;
        this.nota2 = nota2;
        this.nota3 = nota3;
    }

    // Sobrescribir método de la superclase
    public void obtenerInformacion() {
        super.obtenerInformacion();
        System.out.println("Notas: " + nota1 + "," + nota2 + "," + nota3);
    }
}

La referencia super representa a la superclase Persona, este facilita pasar los parámetros necesarios, para la satisfacción de su constructor.

También invocamos el método obtenerInformacion() alojado en la clase padre, para sobrescribir el de la clase Estudiante.

Si creáramos una instancia de ambas clases veremos como el método obtenerInformacion() tiene dos comportamientos diferentes.

Main.java

public class Main {

    public static void main(String args[]) {
        Persona persona = new Persona("Carlos", "3445569");
        persona.obtenerInformacion();

        persona = new Estudiante("Arliz", "3445678", 3.4f, 4.5f, 1.0f);
        persona.obtenerInformacion();
    }

}

Lo anterior produciría el siguiente resultado:

Nombre: Carlos
Teléfono: 3445569
Nombre: Arliz
Teléfono: 34456678
Notas: 3.4, 4.5, 1.0

13. Concepto De Clases Abstractas

Una clase abstracta es aquella de la cual no se pueden crear instancias directamente. Sin embargo, se pueden crear subclases a partir de ellas. Definir una clase de este tipo requiere usar la sentencia abstract en la declaración.

Este concepto permite organizar las clases semejantes. También obliga a los programadores a sobrescribir métodos importantes que garanticen el correcto funcionamiento de un patrón.

Dentro de una clase abstracta puede que existan 0 o más métodos abstractos. Un método abstracto es aquel que se declara pero no se implementa, con el fin de que las subclases si lo hagan.

abstract int calcularIndice(int medida);

Por ejemplo… 

Tenemos la clase Objeto3D para representar aquellos componentes que tienen vértices, índices y una textura definida. Con esta clase podemos definir subclases como Cubo, Esfera y Piramide. Aunque las tres son objetos tridimensionales, requieren un renderizado distinto según su geometría. Por lo que el método para renderizar debe ser abstracto y por ende la clase Objeto3D.

abstract public class Objeto3D {
    List<Vertice> vertices;
    List<Indices> indices;
    Textura textura;

    void crearGeometria() {
        //...
    }

    void texturizar() {
        // ...
    }

    // Método abstracto
    abstract void renderizar();

}

public class Cubo extends Objeto3D {
    void renderizar() {
        // Implementación personalizada
    }
}

public class Esfera extends Objeto3D {
    void renderizar() {
        // Implementación personalizada
    }
}

14. Crear Interfaces En Java

Las interfaces son un mecanismo para crear un tipo sin definir su implementación. Son similares a las clases, solo que estás contienen constantes, firmas de métodos sin implementar, métodos estáticos, métodos por defecto y tipos anidados.

Las clases normales pueden heredar los métodos de las interfaces. Sin embargo no se considera Herencia si no Implementación.

Para crear una interfaz usa la palabra reservada interface. Al implementarla sobre una clase usa implements.

public interface NombreInterfaz{
    // Constantes...
    // Firmas de métodos...
    // Tipos...
}

Una interfaz no puede ser instanciada. Su propósito es proveer un camino para forzar polimorfismo en una clase, exigiendo que se especifique un comportamiento sin importar la forma en que se haga.

También es importante recalcar que una clase puede implementar varias interfaces. A lo que se le llama herencia múltiple.

Conceptualmente una interfaz es un medio para comunicar dos elementos.

Por ejemplo…

Un médico puede comprender la información de la presión arterial de un paciente a través de un estetoscopio. En este caso, el estetoscopio sería una interfaz.

Otro ejemplo sería relacionar un Leon con estados como ser Mamifero y Carnivoro. Ambas condiciones son una interfaz que permite comunicar la supervivencia del león con recursos y situaciones naturales, como es comer carne o alimentarse de leche.

public interface Mamifero{
    void amamantar();
}

public interface Carnivoro{
    void desgarrar();
}

public class Leon implements Mamifero, Carnivoro{

    // Otras acciones dignas de un león

    public void amamantar() {
        // Acciones y condiciones para amamantar
    }

    @Override
    public void desgarrar() {
        // Acciones y condiciones para desgarrar
    }
}

15. Manejar Errores Con Excepciones

Una excepción es un evento inusual que ocurre en la ejecución de un programa, el cual interrumpe el flujo de instrucciones. Frecuentemente las condiciones que provocan el evento son errores.

Cuando esto sucede, se crea un objeto del tipo Exception que contiene los datos del error. Luego es pasado al sistema para informar del inconveniente.

Las expceciones se basan en la clase Throwable, la cual tiene implementado el comportamiento para interrumpir el flujo en caso de que se presenten condiciones específicas.

Excepciones: La Clase Throwable

Cuando ocurren problemas graves en el proceso de la máquina virtual de Java esta lanza objetos de tipo Error.

Por otro lado, si el problema no es tan grave para el sistema, se lanzan objetos del tipo Exception o alguno de sus herederos (RuntimeException, NullPointerException, IllegalAccessException, etc).

Por ejemplo…

Si tuvieses un método que solo asigna números positivos a un atributo sería conveniente comprobar antes que el parámetro satisfaga la condición. Algo como:

PruebaExcepciones.java

public class PruebaExcepciones {

    int valor;

    public static void main(String args[]) {


        for (int i = 1; i < 9; i++) {
            PruebaExcepciones ejemplo = new PruebaExcepciones();
            if (i != 7)
                try {
                    ejemplo.asignarValor(i);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            else
                try {
                    ejemplo.asignarValor(i * -1);
                } catch (Exception e) {
                    e.printStackTrace();
                }

        }
    }

    public void asignarValor(int numero) throws Exception {
        if (numero > 0)
            valor = numero;
        else
            throw new Exception("No es posible asignar el número negativo " + numero);
    }
}

El bucle escrito en el main cuenta desde 1 a 8, donde se asigna el valor del índice al atributo de un objeto PruebaExcepciones nuevo. Como truco, al llegar a 7 multiplicamos por -1 para producir un número negativo.

El método asignarValor() es marcado para lanzar excepciones con la sentencia throws junto al tipo de excepción. Luego hacemos efectivo el lanzamiento si el parámetro número es negativo.

Para manejar la excepción se usa un bloque try-catch, donde try recibe las instrucciones a procesar y catch realiza las acciones en caso de que haya atrapado la excepción. En nuestro caso imprimimos la pila de seguimiento sobre la excepción con printStackTrace(), la cual indica las capas sucesivas donde ocurrió el inconveniente.

El resultado al ejecutar sería:

Stack Trace En Excepción

16. Colecciones De objetos En Java

Las colecciones son objetos que agrupan elementos asociados en una misma estructura. Hablo de listas, mapas, conjuntos, colas, etc.

La librería de las colecciones trae consigo componentes que generan el comportamiento de las unidades que  agruparán elementos. Veamos la definición de algunos:

  • Collection: Representa la naturaleza de una colección como un conjunto de elementos agrupados, sin tener en cuenta características como su orden o duplicidad. Esta interfaz permite obtener elementos, obtener el tamaño total de la colección, añadir elementos, eliminar elementos e iterar sobre ellos.
  • List: Una lista es una colección ordenada que puede contener duplicados. Para obtener sus elementos se usa un sistema de índices basado en cero. Permite acceder a los elementos a través de sus índices sin tener que recorrerla manualmente. Facilita la obtención del índice de cada elemento. Incluso permite operar solo un segmento de la lista.
  • Set: Un conjunto es una colección desordenada que no puede contener elementos duplicados. Permite realizar todas las acciones comunes de una colección.
  • Map: Un mapa es una colección que asigna claves a los elementos que contiene. A diferencia de una lista que usa índices enteros, un mapa puede asignar strings a cada elemento de información. Basados en esas claves podemos añadir, remover, etc.
  • Iterator: Un iterador es un mecanismo para obtener los elementos de una colección de forma secuencial. Le permite al programador usar el método next() para retornar en el elemento actual. El siguiente es un ejemplo de un bucle for recorriendo una lista de strings.
    for (Iterator<String> i = lista.iterator(); i.hasNext(); ) {
        String valorActual = i.next();
        // ...
    }

Los componentes anteriores producen gran cantidad de clases que nos permiten agrupar datos, como por ejemplo:

  • ArrayList: Lista basada en un array de tamaño variable, creada con base en la interfaz List.
  • HashMap: Tabla hash basada en la implementación de Map. Cumple con todas las características de Map y además permite añadir elementos con claves y valores nulos.

Veamos un ejemplo de ArrayList…

PruebaArrayList.java

import java.util.ArrayList;
import java.util.List;

public class PruebaArrayList {

    public static void main(String args[]) {

        List<String> lista = new ArrayList<String>();
        lista.add("Draven");
        lista.add("Sion");
        lista.add("Teemo");
        lista.add("Yasuo");
        lista.add("Jax");

        for (String elemento : lista) {
            System.out.println(elemento);
        }
    }

}

17. Clases Genéricas En Java O Generics

Los Generics son tipos que permiten crear implementaciones de clases, métodos e interfaces basados en parámetros.

En el ejemplo anterior del ArrayList vimos cómo esta clase permite crear una lista de un tipo específico contenido en cuñas "<>". Dicho tipo se le denomina parámetro y evita que se realice casting de los elementos al momento de acceder a estos.

List<String> lista = new ArrayList<String>();
lista.add("Elemento");
String s = lista.get(0);   // Nos evita el cast String s = (String)list.get(0)

Por ejemplo… si tuviésemos una clase llamada Barco la cual permite calcular características como el peso total o la velocidad máxima, sería de utilidad poder tener precisión según las propiedades del material empleado para su construcción.

En ese caso podríamos usar una clase genérica para satisfacer el enunciado «crear nuevo barco a base de…». Esto haría necesario la existencia de clases para los materiales como Metal, Madera, Aleacion, etc.

public class Barco<T> {
    // Usamos T para abreviar "Tipo"
    private T t;

    public void calcularPesoTotal(){
        // ...
    }
    
    public void obtenerVelocidadMaxima(){
        // ...
    }
}

Al usar el parámetro T, podemos referirnos a un tipo genérico que tendrá cosas en común y así poder producir el mismo resultado sin importar cual se use.

Con la definición de la clase genérica, ya es posible crear barcos de todo material.

Barco<Metal> barcoDeMetal = new Barco<Metal>();
Barco<Madera> barcoDeMadera = new Barco<Madera>();
Barco<Aleacion> barcoDeAleacion = new Barco<Aleacion>();

18. Clases Anidadas En Java

El lenguaje Java permite que los desarrolladores declaren clases dentro de otras clases. Ellas reciben el nombre de clases anidadas, donde la clase que contenedora se le denomina clase externa.

class ClaseExterna {

    class ClaseAnidada {
        
    }
}

Una clase anidada puede ser declarada como estática o no estática y se comporta como un miembro más de la clase externa. La primera es llamada clase anidada estática y la segunda clase interna.

Las clases internas pueden acceder a otros miembros de la clase externa, incluso si estos son privados. En cambio una clase anidada estática no tiene esta oportunidad.

¿Cuáles son las  ventajas de usar clases anidadas?

  • Facilita la agrupación de clases que usarán en un mismo lugar — Aumenta la accesibilidad de una clase a otra para evitar comunicaciones complicadas.
  • Se convierte en otro método para crear encapsulación—Al declarar una clase anidada como un miembro privado, es posible restringir el acceso por otras clases.
  • Orden y mantenibilidad del código— El hecho de haya una clase anidada reduce la creación de más ficheros y mejora la interacción lógica de los miembros.

Veamos un ejemplo de clase anidada estática…

public class ScriptBaseDeDatos {

    public static class ColumnasTablaPerro {
        public static final String ID = "_id";
        public static final String NOMBRE = "nombre";
        public static final String RAZA = "raza";
        public static final String PESO = "peso";
    }
}

El ejemplo anterior muestra una clase externa llamada ScriptBaseDeDatos la cual contiene una clase anidada estática llamada ColumnasTablaPerro. Esta representa las columnas de una tabla perteneciente a una base de datos y contiene como miembros una serie de constantes.

Si quisiéramos acceder algún campo desde algún lugar del proyecto lo haríamos de la siguiente forma:

String id = ScriptBaseDeDatos.ColumnasTablaPerro.ID;

Uso de clases anónimas— Una clase anónima es un tipo de clase interna que se declara y se instancia al mismo tiempo. Esta puede ser declarada dentro de bloque de códigos como el de un método.

Su gran poder está en que en vez de ser tratadas como una declaración, se consideran una expresión. Esto significa que puedes asignarlas a otra expresión existente dentro de un bloque de código.

El ejemplo más común se da al momento de usar escuchas en la interfaz de Android. El siguiente código muestra un botón al cual se le asigna una escucha para manejar los eventos de click.

boton.setOnClickListener(
        new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // Acciones...
            }
        }
);

Como ves, existe un botón al cual se le asigna una nueva interfaz de escucha con el método setOnClickListener(). Este método recibe una instancia del tipo OnClickListener.

Justo en ese parámetro podemos crear una clase anónima que satisfaga este requerimiento con el operador new.

Si te fijas, estamos definiendo el cuerpo de una nueva clase sin nombre (por algo le llaman anónimas), donde sobrescribimos el método onClick().

El compilador no arrojará errores, porque sabe que el cuerpo de esta nueva clase implementada en la línea dará como resultado una instancia OnClickListener.

19. Hilos: Programación Concurrente En Java

Los programas Java soportan la ejecución de múltiples hilos de trabajo. La clase que permite crear este tipo de unidad de procesamiento es Thread.

Para crear un nuevo hilo se debe hacer uso de la interfaz Runnable, donde el código que se ejecutará en el hilo se implementa en el controlador run().

Por ejemplo…

PruebaHilos.java

public class PruebaHilos {

    public static void main(String args[]) {
        new Thread(
                new Runnable() {
                    public void run() {
                        System.out.println("Este método se ejecutó en un hilo");
                    }
                }
        ).start();
    }

}

Una vez has creado un nuevo objeto Thread indicando el componente Runnable, entonces inicias su ejecución con el método start().

Sincronizar hilos en Java con synchronized— La sincronización es un mecanismo para evitar que dos o más hilos generen fallos de ejecución o difuminen la lógica de la aplicación, al acceder a un mismo recurso.

Para evitar este tipo de inconvenientes usaremos la palabra reservada synchronized. Esta sentencia bloquea el control para que solo un hilo tenga acceso al recurso. Así que hasta que un hilo no acabe la operación, el siguiente deberá esperar. Además permite que los hilos otorguen visibilidad a los datos procesados.

class EjemploSincronizacion {
    private int i = 0;

    public synchronized int metodoSincronizado() {
        return i++;
    }

    public int variacionMetodoSincronizado() {
        synchronized (this) {
            return i++;
        }
    }
}

El ejemplo anterior muestra como se sincroniza un método y una sentencia.

El método metodoSincronizado() evita que un hilo adicional lo invoque hasta que el actual lo termine de procesar. Además hace visible el estado actual a todos los hilos.

Dentro de variacionDelMetodoSincronizado() se sincroniza una sentencia. En este caso synchronized recibe el objeto en el que serán protegidas las sentencias. Ambas definiciones funcionan igual.

20. Incluir Anotaciones En El Código

El último tema de este artículo son las anotaciones.

Una anotación es un metadato que no tiene efecto en la lógica del programa.

Algunas utilidades de las anotaciones son:

  • Informar al compilador sobre lugares del código donde deseamos detectar errores o suprimir alertas.
  • Crear código a partir de los datos de la anotación. Esto se da en algunas herramientas como la librería de parsing XML Simple Framework.
  • Análisis en tiempo de ejecución.

Las anotaciones se identifican al ver que inician con el carácter '@'. Por ejemplo @Override, @NonNull, @TargetApi, etc.

Estas se ubican junto al código que se desea marcar, ya sean métodos, parámetros, clase, etc.

@Override
protected void onCreate(Bundle savedInstanceState) {
    // ...
}

Incluso pueden recibir valores para indicar una condición especial.

import android.annotation.TargetApi;
...

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class ActividadPrincipal extends ActionBarActivity {
    // ...
}

Java trae consigo anotaciones predeterminadas que podemos usar para varios propósitos. Veamos algunas:

  • @Deprecated: Anotación que marca una pieza de código como obsoleta, lo que significa que ya no es recomendable usarla de nuevo. Esta anotación permite que el compilador muestre una alerta que le indique al desarrollador que probablemente hay una nueva versión del código o una mejor manera de realizar la tarea.
    Método Con Anotación Deprecated En Java
    Esto produciría el siguiente efecto en Android Studio al utilizar el método.
    Anotación Deprecated En Android Studio
  • @Override: Indica al compilador que el método marcado será sobrescrito con base a la superclase. Es de gran utilidad para diferenciar los métodos y prevenir errores.
    Anotación Override En Android Studio
  • @SuppressWarnings: Declara al compilador que no deseamos que se muestren alertas sobre comportamientos incorrectos.
    Anotación SuppressWarnings En Android Studio
    La ilustración muestra como al añadir la anotación con el atributo «deprecation» el método que antes se marcaba como obsoleto, ahora luce normal.

Si deseas profundizar más sobre el lenguaje Java, te recomiendo el curso Fundamentos De Java. De verdad es muy útil para los recién llegados.

Conclusión

Este artículo te ha proporcionado una visión general de las funcionalidades de Java para Desarrollo Android y como explotar el paradigma orientado a objetos.

La velocidad con que aprendas Desarrollo Android depende de la cantidad de tiempo que emplees para capacitarte por cuenta propia. Por lo que si no te sientes seguro con la programación en Java, puedes tomarte algunas sesiones que te ayuden a esclarecer tus dudas.

Ahora queda comprender los componentes de una aplicación Android para tener en claro como podrás estructurar tus aplicaciones a la hora de elegir una arquitectura.

Únete Al Discord De Develou

Si tienes problemas con el código de este tutorial, preguntas, recomendaciones o solo deseas discutir sobre desarrollo Android conmigo y otros desarrolladores, únete a la comunidad de Discord de Develou y siéntete libre de participar como gustes. ¡Te espero!