Programacion Orientada a Objetos

La POO tiene varios pilares para asegurar la simplicidad de código y su reutilización, y aunque diversos autores señalan diversos pilares, en este documento se considerarán los cuatro que son comunes en la mayoría de textos, estos son: Abstracción, Encapsulamiento, Herencia y Polimorfismo, las dos primeras están más relacionadas con la búsqueda de códigos simples y las dos siguientes con la reutilización.
Abstracción
Es el pilar de la POO, que permite identificar las características y comportamientos de un objeto y con los cuales se construirá la clase (plantilla).  Esto quiere decir que a través de este pilar o fundamento es posible reconocer los atributos y métodos de un objeto.
La imagen muestra en texto de color negro algunas de las características del objeto: color, tamaño, marca, son características porque todos los controles pueden tenerlas y cada objeto de este tipo tomará valores que le identifiquen; el objeto de la imagen tiene los siguientes valores: 
color: negro
tamaño: 15
marca: samsung
De igual forma, se identifican los comportamientos que en la imagen aparecen como textos de color blanco
Encapsulamiento
Es la característica de la POO que permite el ocultamiento de la complejidad del código, pertenece a la parte privada de la clase y que no puede ser vista desde ningún otro programa.
En el gráfico se representa como la complejidad del código queda oculta dentro de una caja fuerte impidiendo que quien lo utilice sin observar los detalles de cada una de las líneas que permiten el cumplimiento de una acción específica.
En realidad, el encapsulamiento está relacionado con el acceso a un código desde el código de otra clase; sin embargo en términos generales, esta representación gráfica es conveniente para comprender el concepto de encapsulamiento.
Herencia
Es el pilar más fuerte que asegura la reutilización de código, ya que a partir de esta característica es posible reutilizar (heredar) las características y comportamientos de una clase superior llamada clase padre, a sus clases hijas, denominadas clases derivadas. Esto implica que una vez desarrollado el código de una clase base, su código puede ser reutilizado por las clases derivadas.
En el gráfico, Persona es la clase Padre, que tiene como características: CI, nombre, dirección, fechaNac, genero, entre otros; y Estudiante y Profesor son las clases "Hijas", que heredan las características de la clase padre y a su vez establecen las propias de su clase.  Esto implica que no se deberán volver a definir, sino que por el simple hecho de heredarlas ya es posible utilizarlas y en el caso de los comportamientos ejecutarlos o modificarlos si es necesario.
Polimorfismo
A través de esta característica es posible definir varios métodos o comportamientos de un objeto bajo un mismo nombre, de forma tal que es posible modificar los parámetros del método, o reescribir su funcionamiento, o incrementar más funcionalidades a un método.
En el gráfico se observa que todas son figuras geométricas por lo que pueden incluirse en una clase Padre, por lo que la clase deberá tener el método Área(), este método podrá ser reescrito tantas veces como figuras existan, con los parámetros correspondientes en cada clase derivada: Circulo, Triangulo y Rectángulo, o reescrita en la clase base.


¿Qué son las clases en Java?


Las clases en Java (Java Class) son plantillas para la creación de objetos, en lo que se conoce como
programación orientada a objetos, la cual es una de los principales paradigmas de desarrollo de
software en la actualidad.

¿Qué es una clase?
Las clases en Java son básicamente una plantilla que sirve para crear un objeto.
Si imaginásemos las clases en el mundo en el que vivimos, podríamos decir que la clase “persona
es una plantilla sobre cómo debe ser un ser humano. Todos y cada uno de nosotros, los seres humanos,
somos objetos de la clase “persona“, ya que todos somos personas. La clase “persona” contiene
la definición de un ser humano, mientras que cada ser humano es una instancia u objeto de dicha clase.

CUÁL ES LA ESTRUCTURA Y COMPONENTES DE UNA CLASE EN JAVA?

Bienvenido a este nuevo artículo donde aprenderás la estructura y componentes de una clase en Java.

Una clase es el núcleo principal de un programa en Java, en un programa en Java todo gira entorno
a las clases tanto los pequeños programas que diseñamos en nuestros primeros pasos cuando estamos
empezando a programar así como en un gran y complejo sistema transaccional que tiene un banco,
en Java todo es clases. Es por eso que este artículo vamos a ver cuales son los componentes que tiene
una clase de tal manera que estén correctamente diseñadas, a continuación detallo la estructura
que debe tener una clase Java.

PAQUETES EN UNA CLASE

Los paquetes son una forma de organizar las clases en un programa Java, todas las clases que guardan
cierto utilidad semejante se agrupan en un paquete, un paquete es la primera línea de código que debe
estar en un clase Java, sólo puede existir un sólo paquete en una clase caso contrario la clase
no compilará, cuando no se crea un paquete con un nombre específico para una clase el compilador
crea uno por defecto llamado default package, en estos casos las clases que estén dentro de este paquete
por defecto no van a tener la línea de código que indica al paquete que pertenecen.

SENTENCIA IMPORT

Una sentencia import debe estar a continuación de la sentencia del nombre del paquete al que pertenece
la clase, recordar que en una clase Java puede existir más de una sentencia import, una sentencia
import indica que se va usar un recurso que se encuentra en otro paquete, el recurso puede ser: clases,
interfaces, clases abstractas, variables static etc.

QUE ES UNA CLASE?

Una clase es una plantilla genérica la cual va permitir a futuro crear instancias (objetos) particulares
de esa clase, las clases no son ejecutadas directamente si no que son compiladas y son convertidas a
archivos .class que si son ejecutadas por la máquina virtual de Java (JVM).
Las clases en Java siempre deben anteceder con la palabra class seguido del nombre (es aconsejable
que el nombre de una clase sea un sustantivo y esté en singular) de la clase seguido de la apertura y
cierre de llaves que delimitan el cuerpo de la clase.

CONSTRUCTOR DE UNA CLASE

El constructor de una clase deberá llamarse con el mismo nombre de la clase, su modificador de acceso
será public y no debe retornar ningún valor, además que puede o no tener parámetros,
el objetivo de un constructor es inicializar los atributos de la clase, si en una clase no existe
un constructor el compilador automáticamente crea uno y asigna valores por defecto a sus atributos.
Los valores por defecto para un objeto son null, para números es cero, para valores booleanos es false y
para caracteres es ‘\u0000’.

ATRIBUTOS O PROPIEDADES DE UNA CLASE


Como mencioné anteriormente un clase es una plantilla de la cual se puede crear objetos,
un clase debe implementar sus características que se les conoce como propiedades o atributos,
imaginemos que estamos diseñando un clase que va permitir crear objetos de tipo vehículos,
no todos los vehículos van a tener las mismas características, es posible que existan vehículos de color
rojo, azul, blanco etc, este tipo de características las definimos como atributos o
propiedades de una clase.
Puesto que los atributos de una clase guardan datos deben definir el tipo de dato que van almacenar,
por ejemplo, int, boolean, long, byte, char, etc. o algún tipo de dato definido por el programador que
puede ser un objeto.
La implementación de los atributos de una clase se pueden ver como sigue:
1
2
3
4
class Vehiculo{
  String color;
  float motor;
}

MÉTODOS DE UNA CLASE


Una clase también implementa un comportamiento, siguiendo el ejemplo de la clase vehículo
este puede tener varios comportamientos como por ejemplo: arrancar, frenar, parar etc.,
el comportamiento de una clase se define con la creación de métodos, un método debe contener
el tipo de retorno + nombre del método (se recomienda que este siempre sea en verbo) +
apertura y cierre de paréntesis  a continuación la apertura y cierre de llaves donde internamente
estará el cuerpo del método.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Vehiculo{
  String color;
  float motor;
 
  public void arrancar(){
  }
  public int frenar(){
   return 1;
  }
 
  public void frenar(int param1, string param2){
   
  }
}
Un método puede también contener argumentos o parámetros los mismos que debe ir dentro
de los paréntesis, cada argumento debe tener definido su tipo y de haber varios deben estar separados
por comas.
En el ejemplo para comparar puse una analogía con el caso de una clase vehículo pero ya en la
práctica se puede usar por ejemplo una clase llamada CuentaBancaria que por ejemplo puede tener
como atributos el tipo de cuenta, nombre del beneficiario y comportamiento como por ejemplo
realizarDeposito(), realizarRetiro(), todo dependerá de los requerimientos que tengamos.

Parámetro y argumento

Los parámetros o argumentos son una forma de intercambiar información con el método.
Pueden servir para introducir datos para ejecutar el método (entrada) o para obtener o modificar datos
tras su ejecución (salida).

15.1 DECLARACIÓN DE PARÁMETROS

Los parámetros se declaran en la cabecera de la declaración de los métodos.
Al declararse el parámetro, se indica el tipo de dato y el identificador correspondiente.
Los parámetros o argumentos de un constructor o de un método pueden ser de cualquier tipo,
ya sean tipos primitivos o referencias de objetos (en este caso debe indicarse el identificador de la clase
correspondiente). Ejemplos de declaraciones de cabeceras de métodos:
// Sin parametros

public double devuelve()
 {
   return ...;
}
// Un parametro, x de tipo double

public void asigna(double x) {
   ...
}
// Dos parametros, a y b de tipo int
public int elMayor(int a, int b) {
   ...
}
// Un parametro, v, array real
public static double sumatorio (double [] v) {
   ...
}
// Un parámetro de la clase Fecha
public boolean caducado (Fecha fechaLimite) {
   ...
}
El identificador del parámetro se emplea sólo dentro del método para hacer referencia al argumento
correspondiente y puede coincidir con el de un atributo de la misma clase. En tal caso,
se dice que oculta a la variable miembro. Esta técnica suele emplearse en los constructores para
inicializar una instancia. Por ejemplo en la clase Circulo:
public class Circulo {

   int x, y, radio;

   public Circulo(int x, int y, int radio) {
       ...
   }
}
La clase Circulo tiene tres atributos o variables miembro x, y y radio y un constructor con tres
argumentos con los mismos identificadores que facilitan los valores iniciales a los atributos
respectivamente. Los identificadores de los parámetros ocultan a las variables miembro dentro
del cuerpo del constructor, de forma que x, y y radio dentro del constructor hacen referencia
a los parámetros y no a las variables miembro. Para acceder a las variables miembro,
es necesario emplear la palabra reservada this que referencia a la instancia que está siendo
inicializada por el constructor.
public class Circulo {
   int x, y, radio;
   public Circulo(int x, int y, int radio) {
       this.x = x;         // this.x hace referencia al atributo x
                           // x hace referencia al parametro x
       this.y = y;         // this.y hace referencia al atributo y
                           // y hace referencia al parametro y
       this.radio = radio;
   }
}
Normalmente dentro del cuerpo del método de una clase puede hacerse referencia directa a las
variables miembro de las instancias. Salvo en el caso del ejemplo anterior en el que las variables
miembro están ocultas. Por otro lado, los parámetros de un mismo método no puede compartir
el mismo identificador (no pueden coincidir) con el de una variable local.
El siguiente código genera un error de compilación:
public void asigna(double x) {
   // Un parametro, x de tipo double
   double x; // Error de compilacion
   ...
}

15.2 USO ALTERNATIVO DE THIS COMO REFERENCIA AL PARÁMETRO IMPLÍCITO

En la clase CuentaBancaria se implementa un método transferencia que transfiere el saldo
de una cuenta origen (parámetro formal explícito) a otra cuenta (parámetro implícito):
/**

* Declaracion de la clase CuentaBancaria
* Ejemplo de declaracion de variables

* metodos estaticos y uso de this

*/
public class CuentaBancaria {

   // Atributos o variables miembro
   private double saldo;
   public static int totalCuentas=0;

   // Metodos

   public CuentaBancaria() {
       this(0.0);
   }

   public CuentaBancaria( double ingreso ) {
       saldo = ingreso;
       incCuentas();
   }

   public double saldo() {
       return saldo;
   }

   public static void incCuentas () {
       totalCuentas++;
   }


   // Transfiere todo el dinero de la cuenta origen a la actual
   public void transferencia( CuentaBancaria origen ) {
       saldo += origen.saldo;
       origen.saldo=0;
   }
}

Métodos con retorno y sin retorno

Un metodo con retorno te devuelve un dato, mientras que si no tiene retorno no te devuelve nada.
Te pongo un ejemplo:

Tienes un objeto al que vamos a llamar "caja", y que tiene unicamente un contador llamado "cosas".
Ahora vamos a crear dos metodos. Uno lo vamos a llamar "cuentaCosas" y otro
"vaciar". "cuentaCosas" te dice cual es el valor del contador interno,
te devuelve el numero de "cosas". "vaciar" pone el contador "cosas" a 0.

"cuentaCosas" es un metodo CON retorno. Obtienes un dato (que puede ser un valor numerico
o un objeto o cualquier cosa).
"vaciar" es un metodo SIN retorno. Puede modificar un dato interno al objeto, por ejemplo.

Métodos de acceso (getter y setter)

Setters & Getters
Los Setters & Getters son métodos de acceso lo que indica que son siempre declarados públicos,
y nos sirven para dos cosas:

Setters: Del Inglés Set, que significa establecer, pues nos sirve para asignar un valor inicial a un
atributo, pero de forma explícita, además el Setter nunca retorna nada (Siempre es void),
y solo nos permite dar acceso público a ciertos atributos que deseemos el usuario pueda modificar.

Getters: Del Inglés Get, que significa obtener, pues nos sirve para obtener (recuperar o acceder)
el valor ya asignado a un atributo y utilizarlo para cierto método.

Entonces veamos en un ejemplo, si tenemos una Clase de nombre Estudiante, cómo serían sus
Setters & sus Getters?

public class Estudiante
{
       private String nombre;
       private String apellido;
       private String correo;

       public String getNombre()
       {
               return nombre;
       }
       public void setNombre(String nombre)
       {
               this.nombre = nombre;
       }

       public String getApellido()
       {
               return apellido;
       }
       public void setApellido(String apellido)
       {
               this.apellido = apellido;
       }

       public String getCorreo()
       {
               return correo;
       }
       public void setCorreo(String correo)
       {
               this.correo = correo;
       }
}

Y en el main la implementación sería:

public class Principal
{
       public static void main(String args[])
       {
           Estudiante estudiante1 = new Estudiante();
           // Ingreso los datos con los Setter a la nueva instancia de
               // Estudiante, estos datos pueden ingresarse desde teclado
               // o a través de una GUI
           estudiante1.setNombre("María");
           estudiante1.setApellido("Paucar");
           estudiante1.setCorreo("maria.paucar@epn.edu.ec");

           // Para obtener los datos uso los Getter
               // y los podemos imprimir en Consola o a una GUI
           System.out.println(estudiante1.getNombre());
           System.out.println(estudiante1.getApellido());
           System.out.println(estudiante1.getCorreo());
       }
}

Tipos de métodos

Un método es una abstracción de una operación que puede hacer o realizarse con un objeto.
Una clase puede declarar cualquier número de métodos que lleven a cabo operaciones
de lo más variado con los objetos. En esta sección los métodos se clasifican en dos grupos:
los métodos de instancia y los métodos de clase.

MÉTODOS DE INSTANCIA


Las clases pueden incluir en su declaración muchos métodos o no declarar ninguno.
Los métodos pueden clasificarse en métodos de instancia y métodos de clase.
Los métodos de instancia operan sobre las variables de instancia de los objetos pero
también tienen acceso a las variables de clase. La sintaxis de llamada a un método de instancia es:

// Llamada tipica a un metodo de instancia
idReferencia.idMetodo(parametros);
Todas las instancias de una clase comparten la misma implementación para un método
de instancia. La instancia que hace la llamada al método es siempre un parámetro o
argumento implícito. Dentro de un método de instancia, el identificador de una variable
de instancia hace referencia al atributo de la instancia concreta que hace la llamada
al método (suponiendo que el identificador del atributo no ha sido ocultado por el de
un parámetro).
En el ejemplo anterior en la declaración de la clase CuentaBancaria,
los métodos saldo y transferencia son métodos de instancia.
public double saldo() {
   return this.saldo;
}


public void transferencia( CuentaBancaria origen ) {
  this.saldo += origen.saldo;
  origen.saldo = 0;
}
Ejemplos de llamada a estos métodos dentro de PruebaCuentaBancaria:
CuentaBancaria c1 = new CuentaBancaria();
CuentaBancaria c2 = new CuentaBancaria(20.0);

c1.transferencia(c2);
System.out.println("Cuenta con: " + c1.saldo() + " euros");

MÉTODOS DE CLASE


En principio, los métodos de clase no operan sobre las variables de instancia de los objetos.
Los métodos de clase pueden trabajar con las variables de clase pero no pueden acceder
a las variables de instancia declaradas dentro de la clase,
a no ser que se crea una nueva instancia y se acceda a las variables de instancia
a través del nuevo objeto. Los métodos de clase también pueden ser llamados precediendolos
con el identificador de la clase, sin necesidad de utilizar el de una instancia.
IdClase.idMetodo(parametros); // Llamada tipica a un metodo de clase
La palabra static determina la declaración de un método de clase. Por defecto,
si no se indica la palabra static, el método declarado se considera un método de instancia.
En el ejemplo anterior en la declaración de la clase CuentaBancaria, el método incCuentas
es un método de clase.
public static void incCuentas () {
   totalCuentas++;
}
Un ejemplo de llamada a este método dentro de PruebaCuentaBancaria sería:
CuentaBancaria.incCuentas();
Las diferencias entre los métodos de instancia y los de clase se resumen aquí:
Diferencias entre los métodos de instancia y los métodos de clase
Figura 14.1 Diferencias entre los métodos de instancia y los métodos de clase
Los métodos de clase o estáticos se pueden considerar equivalentes a las rutinas (globales)
de otros lenguajes de programación como Pascal o C.
Como ejemplos típicos de métodos estáticos pueden indicarse los métodos de Java
correspondientes a las funciones matemáticas sin, cos, exp, pow... de la clase java.lang.Math.
Las llamadas a estos métodos se realizan anteponiendo el identificador de la clase Math
al identificador del método: Math.sin(angulo)...
Métodos estáticos de la clase <code>Math</code>

Constructor en POO

En programación orientada a objetos (POO), un constructor es una subrutina cuya misión es
inicializar un objeto de una clase. En el constructor se asignan los valores iniciales del
nuevo objeto.
Se utiliza para crear tablas de clases virtuales y poder así desarrollar el polimorfismo,
una de las herramientas de la programación orientada a objetos. Al utilizar un constructor,
el compilador determina cual de los objetos va a responder al mensaje (virtual)
que hemos creado. Tiene un tipo de acceso, un nombre y un paréntesis.

En Java es un método especial dentro de una clase, que se llama automáticamente cada vez
que se crea un objeto de esa clase.
Posee el mismo nombre de la clase a la cual pertenece y no puede devolver ningún valor
(ni siquiera se puede especificar la palabra reservada void). Por ejemplo, si añadiéramos
a la clase SSuma un constructor, tendríamos que llamarlo también SSuma.
Cuando en una clase no se escribe propiamente un constructor, Java asume uno por defecto.

Sobrecarga de métodos

Sobrecarga de métodos y de constructores

La firma de un método es la combinación del nombre y los tipos de los parámetros o argumentos.

La sobrecarga de métodos es la creación de varios métodos con el mismo nombre pero
con diferente lista de tipos de parámetros. Java utiliza el número y tipo de parámetros
para seleccionar cuál definición de método ejecutar.

Java diferencia los métodos sobrecargados con base en el número y tipo de parámetros o
argumentos que tiene el método y no por el tipo que devuelve.

Tambien existe la sobrecarga de constructores: Cuando en una clase existen constructores
múltiples, se dice que hay sobrecarga de constructores.

Ejemplo

/* Métodos sobrecargados */
int calculaSuma(int x, int y, int z){
   ...
}
int calculaSuma(double x, double y, double z){
   ...
}

/* Error: estos métodos no están sobrecargados */
int calculaSuma(int x, int y, int z){
    ...
}
double calculaSuma(int x, int y, int z){
   ...
}

Ejemplo

/* Usuario4.java */

class Usuario4
{
    String nombre;
    int edad;
    String direccion;

    /* El constructor de la clase Usuario4 esta sobrecargado */
    Usuario4( )
    {
       nombre = null;
       edad = 0;
       direccion = null;
    }

    Usuario4(String nombre, int edad, String direccion)
    {
       this.nombre = nombre;
       this.edad = edad;
       this.direccion = direccion;
    }

    Usuario4(Usuario4 usr)
    {
       nombre = usr.getNombre();
       edad = usr.getEdad();
       direccion = usr.getDireccion();
    }

    void setNombre(String n)
    {
       nombre = n;
    }

    String getNombre()
    {
       return nombre;
    }

    /* El metodo setEdad() está sobrecargado */
    void setEdad(int e)
    {
       edad = e;
    }

    void setEdad(float e)
    {
       edad = (int)e;
    }

    int getEdad()
    {
       return edad;
    }

    void setDireccion(String d)
    {
       direccion = d;
    }

    String getDireccion()
    {
       return direccion;
    }
}

Ejemplo

/* ProgUsuario4.java */

class ProgUsuario4
{
  void imprimeUsuario(Usuario4 usr)
  {
    // usr.nombre equivale en este caso a usr.getNombre()
    System.out.println("\nNombre: " + usr.nombre );
    System.out.println("Edad: " + usr.getEdad() );
    System.out.println("Direccion: " + usr.getDireccion() +"\n");
  }

  public static void main(String args[])
  {
     ProgUsuario4 prog = new ProgUsuario4( );
     /* Se declaran dos objetos de la clase Usuario4 */
     Usuario4 usr1,usr2;

     /* Se utiliza el constructor por omisión */
     usr1 = new Usuario4( );
     prog.imprimeUsuario(usr1);

    /* Se utiliza el segundo constructor de Usuario4 */
     usr2 = new Usuario4("Eduardo",24,"Mi direccion");
     prog.imprimeUsuario(usr2);

    /* Se utiliza el tercer constructor de Usuario4 */
     usr1 = new Usuario4(usr2);

     usr1.setEdad(50);
     usr2.setEdad(30.45f);

     prog.imprimeUsuario(usr1);
     prog.imprimeUsuario(usr2);
  }
}

Modificadores de acceso


Modificadores de acceso: public, private, protected y default

Los modificadores de acceso, como su nombre indica, determinan desde qué clases
se puede acceder a un determinado elemento.
En Java tenemos 4 tipos: public, private, protected y el tipo por defecto,
que no tiene ninguna palabra clave asociada, pero se suele conocer como default o
package-private.
Si no especificamos ningún modificador de acceso se utiliza el nivel de acceso por defecto,
que consiste en que el elemento puede ser accedido sólo desde las clases
que pertenezcan al mismo paquete.
El nivel de acceso public permite a acceder al elemento desde cualquier clase,
independientemente de que esta pertenezca o no al paquete en que se encuentra el elemento.
private, por otro lado, es el modificador más restrictivo y especifica que los elementos
que lo utilizan sólo pueden ser accedidos desde la clase en la que se encuentran.
Este modificador sólo puede utilizarse sobre los miembros de una clase y
sobre interfaces y clases internas, no sobre clases o interfaces de primer nivel,
dado que esto no tendría sentido.
Es importante destacar también que private convierte los elementos en privados para otras clases,
no para otras instancias de la clase. Es decir, un objeto de una determinada clase puede
acceder a los miembros privados de otro objeto de la misma clase, por lo que algo
como lo siguiente sería perfectamente válido:
  • class MiObjeto {  
  •  private short valor = 0;  
  •  
  •  MiObjeto(MiObjeto otro) {  
  •    valor = otro.valor;  
  •  }
  • }  
El modificador protected, por último, indica que los elementos sólo pueden ser accedidos
desde su mismo paquete (como el acceso por defecto) y desde cualquier clase que extienda
la clase en que se encuentra, independientemente de si esta se encuentra en el mismo paquete o no.
Este modificador, como private, no tiene sentido a nivel de clases o interfaces no internas.
Los distintos modificadores de acceso quedan resumidos en la siguiente tabla:

La misma clase
Otra clase del mismo paquete
Subclase de otro paquete
Otra clase de otro paquete
public
X
X
X
X
protected
X
X
X

default
X
X


private
X




Método Abstracto

Un método abstracto es un método declarado pero no implementado, es decir, es un método
del que solo se escribe su nombre, parámetros y tipo devuelto pero no su código.

Los métodos abstractos se escriben sin llaves {} y con ; al final de la declaración.
Por ejemplo:
public abstract area();
Un método se declara como abstracto porque en ese momento (en esa clase) no se
conoce cómo va a ser su implementación.
Por ejemplo: A partir de una clase Polígono se pueden derivar las clases Rectángulo y Triángulo.
Ambas clases derivadas usarán un método área.
Podemos declararlo en Figura como abstracto y dejar que cada clase lo implemente
según sus necesidades.
Al incluir el método abstracto en la clase base se obliga a que todas las clases derivadas
lo sobrescriban con el mismo formato utilizado en la declaración.
Si una clase contiene un método abstracto se convierte en clase abstracta y
debe ser declarada como tal.

La forma general de declarar un método abstracto en Java es:
[modificador] abstract tipoDevuelto nombreMetodo([parámetros]);

Clase abstracta

Una clase que declara la existncia de metodos per no la implementacion de dichos metodos,
se considera una clase abstracta.

Una clase abstracta puede contener metodos no abstratos pero al menos uno de los metodos
debe ser abstracto.

Para declarar una clase o metodo como abstractos, se utiliza la palabra reservada abstract.

abstract class Dibujar
{
abstract void miMetodo(int var1, int var2);
String otroMetodo()
{
// instrucciones del metodo
}
}

Una clase abstracta no se puede instanciar (es decir no se pueden volver en objetos)
pero si se puede heredar y las clases hijas seran las encargadas de agregar
la funcionalidad a los metodos abstractos.

Interface en Java

Una interface es una variante de una clase abstracta con la condicion de que todos sus
metodos deben ser abstractos. Si la interface va a tener atributos, estos deben llevar
las palabras reservadas static final y con un valor inicial ya que funcionan como constantes
por lo que, por convencion, su nombre va en mayusculas.

interface Nomina
{
public static final String EMPRESA  = "Patito, S.A.";
public void detalleDeEmpleado(Nomina obj);
}

Una clase implementa una o mas interfaces (seáradas  por comas) con la palabra reservada
implements con el uso de interfaces se puede "simular" la herencia multiple que Java no soporta.

class Empleado implements Nomina
{
...
}

Existen varias diferencias entre una clase abstracta y una interfaz:

Una clase abstracta puede heredar de una sola clase (abstracta o no) mientras que una interfaz puede extender varias interfaces de una misma vez. Una clase abstracta puede tener métodos que sean abstractos o que no lo sean,
mientras que las interfaces sólo y exclusivamente pueden definir métodos abstractos. En java concretamente (ya que has puesto la etiqueta Java), en las clases abstractas la palabra
abstract es obligatoria para definir un método abstracto (así como la clase).
Cuando defines una interfaz, esta palabra es opcional ya que se infiere en el concepto de interfaz.
En una clase abstracta, los métodos abstractos pueden ser public o protected.
En una interfaz solamente puede haber métodos públicos. En una clase abstracta pueden existir variables static, final o static final
con cualquier modificador de acceso (public, private, protected o default).
En una interfaz sólo puedes tener constantes (public static final).



Array

En Java los arrays son objetos y, para crearlos, se puede utilizar el operador new.
EJEMPLO { ArrayDeNumeros } Habiendo declarado un array de números enteros:
int[] numeros;
Para crearlo con 5 elementos, se puede escribir:
numeros = new int[5];
Ambas sentencias se pueden escribir en una sola línea:
int[] numeros = new int[5];
Al crear un array de esta forma, Java asigna valores por defecto a sus elementos:
  • 0 si son números enteros o reales.
  • '\u0000' si son caracteres.
  • false si son datos lógicos.
  • null si son objetos.
Para comprobar que los valores iniciales del array numeros son ceros,
se puede escribir el siguiente código:
public class ArrayDeNumeros
{
   public static void main(String[] args)
   {
       int[] numeros = new int[5];
       int i;

       for(i=0; i<=4; i++)
       {
           System.out.println(numeros[i]);
       }
   }
}
Nota: fíjese que, para recorrer el array se ha utilizado un índice (i), cuyos valores mínimo
y máximo son, respectivamente, 0 y el número de elementos del array menos 1
(en este caso 5-1, es decir 4).
Al compilar y ejecutar el programa anterior, en la pantalla se verá:
Ejecución del programa ArrayDeNumeros escrito en Java, donde se muestran los valores de un array de enteros.

Colecciones

Una colección representa un grupo de objetos. Esto objetos son conocidos como elementos.
Cuando queremos trabajar con un conjunto de elementos, necesitamos un almacén donde
poder guardarlos. En Java, se emplea la interfaz genérica Collection para este propósito.
Gracias a esta interfaz, podemos almacenar cualquier tipo de objeto y podemos usar una serie
de métodos comunes, como pueden ser: añadir, eliminar, obtener el tamaño de la colección…
Partiendo de la interfaz genérica Collection extienden otra serie de interfaces genéricas.
Estas subinterfaces aportan distintas funcionalidades sobre la interfaz anterior.

Tipos de colecciones


Set

La interfaz Set define una colección que no puede contener elementos duplicados.
Esta interfaz contiene, únicamente, los métodos heredados de Collection añadiendo
la restricción de que los elementos duplicados están prohibidos.
Es importante destacar que, para comprobar si los elementos son elementos duplicados
o no lo son, es necesario que dichos elementos tengan implementada, de forma correcta,
los métodos equals y hashCode. Para comprobar si dos Set son iguales,
se comprobarán si todos los elementos que los componen son iguales sin importar
en el orden que ocupen dichos elementos.
Dentro de la interfaz Set existen varios tipos de implementaciones realizadas
dentro de la plataforma Java. Vamos a analizar cada una de ellas:

HashSet: este implementación almacena los elementos en una tabla hash.
Es la implementación con mejor rendimiento de todas pero no garantiza
ningún orden a la hora de realizar iteraciones.
Es la implementación más empleada debido a su rendimiento y a que,
generalmente, no nos importa el orden que ocupen los elementos.
Esta implementación proporciona tiempos constantes en las operaciones básicas siempre
y cuando la función hash disperse de forma correcta los elementos dentro de la tabla hash.
Es importante definir el tamaño inicial de la tabla ya que este tamaño marcará el rendimiento
de esta implementación.

TreeSet: esta implementación almacena los elementos ordenándolos en función de sus valores.
Es bastante más lento que HashSet. Los elementos almacenados deben implementar
la interfaz Comparable. Esta implementación garantiza, siempre, un rendimiento de log(N)
en las operaciones básicas, debido a la estructura de árbol empleada para almacenar los
elementos.
LinkedHashSet: esta implementación almacena los elementos en función del orden de inserción.
Es, simplemente, un poco más costosa que HashSet.
Ninguna de estas implementaciones son sincronizadas; es decir, no se garantiza el estado del Set si dos o más hilos acceden de forma concurrente al mismo. Esto se puede solucionar empleando una serie de métodos que actúan de wrapper para dotar a estas colecciones de esta falta de sincronización:
Java
1
2
3
Set set = Collections.synchronizedSet(new HashSet());
SortedSet sortedSet = Collections.synchronizedSortedSet(new TreeSet());
Set set = Collections.synchronizedSet(new LinkedHashSet());

Una vez explicados los distintos tipos de Set, veremos cómo se crean y
mostraremos sus diferencias en los tiempos de inserción. Como hemos visto anteriormente,
el más rápido debería ser HashSet mientras que, por otro lado, el más lento debería ser TreeSet.
Vamos a comprobarlo con el siguiente código:
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
final Set hashSet = new HashSet(1_000_000);
final Long startHashSetTime = System.currentTimeMillis();
for (int i = 0; i < 1_000_000; i++) {
   hashSet.add(i);
}
final Long endHashSetTime = System.currentTimeMillis();
System.out.println("Time spent by HashSet: " + (endHashSetTime - startHashSetTime));
final Set treeSet = new TreeSet();
final Long startTreeSetTime = System.currentTimeMillis();
for (int i = 0; i < 1_000_000; i++) {
   treeSet.add(i);
}
final Long endTreeSetTime = System.currentTimeMillis();
System.out.println("Time spent by TreeSet: " + (endTreeSetTime - startTreeSetTime));
final Set linkedHashSet = new LinkedHashSet(1_000_000);
final Long startLinkedHashSetTime = System.currentTimeMillis();
for (int i = 0; i < 1_000_000; i++) {
   linkedHashSet.add(i);
}
final Long endLinkedHashSetTime = System.currentTimeMillis();
System.out.println("Time spent by LinkedHashSet: " +
(endLinkedHashSetTime - startLinkedHashSetTime));
A continuación, el resultado de los tiempos obtenidos:
Java
1
2
3
Time spent by HashSet: 46
Time spent by TreeSet: 194
Time spent by LinkedHashSet: 74
Los tiempos obtenidos demuestran que, efectivamente, el tiempo de inserción es menor en
HashSet y mayor en TreeSet. Es importante destacar que la inicialización del tamaño inicial
del Set a la hora de su creación es importante ya que, en caso de insertar un gran número
de elementos, podrían aumentar el número de colisiones y; con ello, el tiempo de inserción.

List

La interfaz List define una sucesión de elementos. A diferencia de la interfaz Set,
la interfaz List sí admite elementos duplicados. A parte de los métodos heredados de Collection,
añade métodos que permiten mejorar los siguientes puntos:
  • Acceso posicional a elementos: manipula elementos en función de su posición en la lista.
  • Búsqueda de elementos: busca un elemento concreto de la lista y devuelve su posición.
  • Iteración sobre elementos: mejora el Iterator por defecto.
  • Rango de operación: permite realizar ciertas operaciones sobre ragos de elementos dentro de la propia lista.
Dentro de la interfaz List existen varios tipos de implementaciones realizadas dentro
de la plataforma Java. Vamos a analizar cada una de ellas:
  • ArrayList: esta es la implementación típica. Se basa en un array redimensionable
  • que aumenta su tamaño según crece la colección de elementos.
  • Es la que mejor rendimiento tiene sobre la mayoría de situaciones.
  • LinkedList: esta implementación permite que mejore el rendimiento en ciertas ocasiones.
Esta implementación se basa en una lista doblemente enlazada de los elementos,
teniendo cada uno de los elementos un puntero al anterior y al siguiente elemento.

Ninguna de estas implementaciones son sincronizadas; es decir,
no se garantiza el estado del List si dos o más hilos acceden de forma concurrente al mismo.
Esto se puede solucionar empleando una serie de métodos que actúan de wrapper
para dotar a estas colecciones de esta falta de sincronización:
Java
1
2
List list = Collections.synchronizedList(new ArrayList());
List list = Collections.synchronizedList(new LinkedList());
A continuación, vamos a ver cómo se crean los distintos tipos de interfaces:
Java
1
2
final List arrayList = new ArrayList();
final List linkedList = new LinkedList();
El cuándo usar una implementación u otra de List variará en función de la situación
en la que nos encontremos. Generalmente, ArrayList será la implementación que usemos
en la mayoría de situaciones. Sobretodo, varían los tiempos de inserción, búsqueda y eliminación
de elementos, siendo en unos casos una solución más óptima que la otra.

Map

La interfaz Map asocia claves a valores. Esta interfaz no puede contener claves duplicadas y;
cada una de dichas claves, sólo puede tener asociado un valor como máximo.
Dentro de la interfaz Map existen varios tipos de implementaciones realizadas
dentro de la plataforma Java. Vamos a analizar cada una de ellas:

HashMap: este implementación almacena las claves en una tabla hash.
Es la implementación con mejor rendimiento de todas pero no garantiza ningún
orden a la hora de realizar iteraciones.
Esta implementación proporciona tiempos constantes en las operaciones básicas siempre
y cuando la función hash disperse de forma correcta los elementos dentro de la tabla hash.
Es importante definir el tamaño inicial de la tabla ya que este tamaño marcará
el rendimiento de esta implementación.

TreeMap: esta implementación almacena las claves ordenándolas en función de sus valores.
Es bastante más lento que HashMap. Las claves almacenadas deben implementar la interfaz
Comparable. Esta implementación garantiza, siempre, un rendimiento de log(N)
en las operaciones básicas, debido a la estructura de árbol empleada para almacenar
los elementos.

LinkedHashMap: esta implementación almacena las claves en función del orden de inserción.
Es, simplemente, un poco más costosa que HashMap.
Ninguna de estas implementaciones son sincronizadas; es decir,
no se garantiza el estado del Map si dos o más hilos acceden de forma concurrente al mismo.
Esto se puede solucionar empleando una serie de métodos que actúan de wrapper para
dotar a estas colecciones de esta falta de sincronización:
Java
1
2
3
Map map = Collections.synchronizedMap(new HashMap());
SortedMap mortedMap = Collections.synchronizedSortedMap(new TreeMap());
Map map = Collections.synchronizedMap(new LinkedHashMap());
A continuación, vamos a ver cómo se crean los distintos tipos de interfaces:
Java
1
2
3
final Map> hashMap = new HashMap>();
final Map> treeMap = new TreeMap>();
final Map> linkedHashMap = new LinkedHashMap>();
El cuándo usar una implementación u otra de Map variará en función de la situación
en la que nos encontremos. Generalmente, HashMap será la implementación que usemos
en la mayoría de situaciones. HashMap es la implementación con mejor rendimiento
(como se ha podido comprobar en el análisis de Set), pero en algunas ocasiones
podemos decidir renunciar a este rendimiento a favor de cierta funcionalidad
como la ordenación de sus elementos.

Comentarios

Entradas populares