La utilización de clases y objetos (ó instancias) es uno de los pilares de la programación orientada a objetos (POO). Debemos aprender a emplear ambos términos de una forma exacta.
Los objetos en programación pueden representar cosas que tenemos en la vida real, pero nunca serán elementos de la vida real.
Sin embargo nos pueden servir para representar esos elementos concretos de una forma abstracta dentro de un programa. La abstracción es algo esencial en la programación orientada a objetos.
Es la parte de un programa en Java donde se definen las propiedades y operaciones que van a tener los ejemplares (objetos, instancias) de dicha clase. Con una clase se define un nuevo tipo de datos (Scanner, String, Coche).
Se puede entender que una clase son los planos o el molde, con el que se indican todas las características o forma para poder crear un objeto (ejemplar) de esa clase.
modificadores class nombreClase{ declaracionesDeMiembros; }
Dentro de una clase, como hemos visto hasta ahora, solo podemos definir dos cosas: métodos y variables.
Cada uno de los elementos de una clase (variables o métodos) se conoce como miembros de la clase.
public class Coche{ //Atributos: definen el estado del objeto String matricula; int numRuedas; String marca; float kilometros; //Métodos: operaciones del objeto void arracar(){ } int acelerar(int intensidad){ }
Además pueden existir otro tipo de miembros de una clase (métodos o variables) que son estáticos static
. A este tipo de miembros se les conoce como métodos o variables de clase. Se tratan unos puntos más adelante.
Las variables locales
(de ámbito local) son aquellas que no son miembros de la clase, sino que están dentro del cuerpo de un método o bloque de código (entre corchetes). Si recordamos, una variable de ámbito local, solo puede ser accedida desde el mismo ámbito en el que se declara, o en ámbitos inferiores. O sea, dentro de los corchetes (bloque) en el que se declara o en bloques creados dentro del bloque en el que se declara.
Un objeto es la materialización de una clase en un elemento o ejemplar de dicha clase. Existe en la memoria del ordenador (se crea) y tiene las propiedades (variables miembro) y las operaciones (métodos miembro) que se han indicado en la definición de la clase.
Un objeto tiene un estado propio, que viene definido por el estado de sus atributos. Si creamos dos objetos de la misma clase, pueden tener estados distintos.
Se puede entender un objeto como el resultado de construir un ejemplar a partir de los planos o molde (clase) que hemos definido anteriormente.
Crear un ejemplar (objeto) de una clase se conoce como instanciar una clase. Para crear un objeto o instancia de una clase, debemos llamar o ejecutar su constructor.
Se necesita usar el operador new
para llamar al constructor de una clase. Se ha usado en otras ocasiones:
Scaner input; //llamada al constructor Scanner() //el constructor recibe un parámetro con el flujo de entrada input = new Scanner(System.in); String cadena; //llamada al constructor String() que crea una cadena vacía. cadena = new String(); //llamada al constructor String(String cadena) //crea una cadena con el valor del parámetro cadena = new String("fernando");
La llamada a un constructor devuelve siempre la referencia al objeto creado en memoria (heap).
La declaración del constructor de una clase tiene una sintaxis parecida a la de los métodos, pero:
modificadores nombre_clase(){ //instrucciones } //Ejemplo public class Coche{ private String matricula; private int km; //Constructor de la clase Coche public Coche(){ //instrucciones que se realizan al crear un objeto Coche } }
Los constructores también pueden recibir parámetros, pero recordamos que nunca devuelven un valor.
public Coche(String cod_matricula){ matricula = cod_matricula; }
Y así puedo crear instancias de la clase Coche utilizando los constructores que he definido:
public static void main(String[] args){ Coche miCoche = new Coche(); Coche miOtroCoche = new Coche("FDS-1234"); }
Concluyendo, ¿qué hace el operador new cuando precede al constructor de una clase? Devuelve el valor de la referencia del objeto que se acaba de crear:
// Declaro una variable de la clase Coche Coche miVariable; // Asigno la referencia de un objeto Coche a la variable miVariable = new Coche();
La variable, no contiene el objeto sino la referencia (dirección) al lugar de la memoria en el que ha guardado.
Se conoce como el constructor que no recibe parámetros. Si en una clase no definimos explícitamente un constructor, Java creará implícitamente un constructor llamado no-args que tiene el siguiente aspecto:
public Coche(){ }
De este modo aunque no definamos un constructor siempre podremos instanciar una clase.
Los constructores se utilizan para inicializar los atributos de una clase que necesiten ser inicializados para ser usados, por ejemplo: arrays, listas, creación de objetos, etc.
public class Alumno{ private String nombre; private int[] notas; private File foto; public Alumno(){ notas = new int[6]; } public Alumno(String nombre, String rutaFoto){ this.nombre = nombre; notas = new int[6]; foto = new File(rutaFoto); } }
Los atributos de la clase (variables de instancia) nunca se inicializan al ser declarados. Esto no se aplica a las variables static
(variables de clase) , ya que existen aunque no se llame al constructor.
public class Alumno{ //Incorrecto y poco estético private String nombre = ""; private int[] notas = new int[10];
Toda variable que no es de tipo primitivo es, sin lugar a dudas, una variable de referencia.
Las variables de referencia son todas las variables cuyo tipo de datos es una clase. No contienen al objeto en sí, sino una referencia (dirección) al objeto creado en la memoria del ordenador (heap). Este es uno de los puntos más importantes del lenguaje Java.
Para obtener la referencia de un nuevo objeto usamos su constructor:
//Creo 2 variables de referencia, vacías. Coche coche1; Coche coche2; //ahora creo un objeto y guardo su dirección (referencia) en la variable coche1 coche1 = new Coche(); //Cambio el valor del campo matrícula del objeto referenciado coche1.setMatricula("ASD-1234"); //Ahora asigno a la variable coche2 el valor de coche1 coche2 = coche1; //En este momento ambas variables tiene la dirección del mismo objeto //y (coche1 == coche2) es true //Muestro la matrícula que asigné al objeto desde coche1 System.out.println(coche2.getMatricula()); //Asigno a coche2 otro valor con la referencia de un nuevo objeto coche2 = new Coche(); //Ahora coche2 tiene un valor distinto: almacena otra dirección // y está referenciando a un nuevo objeto
El valor null
representa el valor de referencia nulo (“sin valor”). Se usa para indicar la ausencia de referencia a un objeto.
Toda variable contiene un valor y cuando se le pasa un parámetro a un método, se le pasa un valor, ya sea un valor de referencia o un valor primitivo.
En otros lenguajes existe el paso de parámetros por referencia frente al paso por valor. Java solo permite el paso de parámetros por valor (valores de referencia o primitivos).
Para indicar desde qué clases se puede acceder a los miembros de otra clase se usan los llamados modificadores de visibilidad.
Usan al declarar los miembros de una clase (atributos, métodos, o constructores) y definen desde qué otras clases puedo acceder a sus miembros.
public
: cualquier miembro público es accedido desde cualquier clase en cualquier paquete.private
: los miembros privados solo se pueden acceder desde dentro de la clase que los contiene.protected
: los miembros protegidos solo se pueden usar desde dentro de la propia clase o desde dentro de sus subclases (herencia de clases).
Por convenio, los atributos de una clase se deben definir como private
.
public class Coche{ private int matricula; public int getMatricula(){ return matricula; } } //Desde el método main de otra clase public static void main(String[] args){ Coche unCoche = new Coche(); unCoche.matricula; //No puedo acceder, es private unCoche.getMatricula(); //Puedo acceder, es public }
Como se acaba de comentar, los atributos de una clase se deben declarar private
por convenio.
Entonces se necesitan métodos públicos para acceder a los atributos y poder modificarlos. Estos métodos se conocen como getters y setters.
private String nombre; private int edad; private boolean casado; public String getNombre(){ return nombre; } public void setNombre(String nombre){ this.nombre = nombre; } ...
Estos métodos se nombran como get cuando quiero obtener el valor, y como set cuando quiero establecerlo. En el caso de los atributos booleanos en lugar de get, se suele indicar is, aunque no afecta al funcionamiento.
La mayoría de IDE's permiten generar getters y setters.
En Eclipse cuando tengamos el cursor del ratón en el editor de una clase, hacemos click derecho y vamos al menu source
→ Generate Getters and Setters
.
La palabra this
se usa dentro de un método o de un constructor en una clase y representa el objeto actual: el objeto cuyo método o constructor está siendo llamado.
this
contiene una referencia al objeto actual.
Hay 2 razones para usarlo:
this
para diferenciarlos. Este es su uso principal.this
, para llamar al constructor.public class Persona{ private String nombre; private int edad; private int altura; //Uso this para saber que uno el el atributo //Y el otro el parámetro public void setNombre(String nombre){ this.nombre = nombre; } public Persona(String nombre, int edad){ this.nombre = nombre; this.edad = edad; } //Pero tambien lo puedo usar para llamar a un constructor ya definido public Persona(String nombre, int edad, altura){ this(nombre,edad); this.altura = altura; } }
El concepto de sobrecarga (overload) consiste en crear varios métodos con el mismo nombre. Aplicado a constructores (todos tienen el mismo nombre) consiste en tener varios constructores. Es un aspecto que se trató en el tema de métodos estáticos
Para que pueda sobrecargar constructores o métodos, cada uno debe diferenciarse de otro en el numero, o tipo de los parámetros que recibe.
Java solo nos permite crear más de un método o constructor con el mismo nombre si el número de parámetros o su tipo, es diferente a los de los demás métodos. El valor de retorno no se toma como dato diferenciador.
Ejemplo de sobrecarga de método indexOf()
de la clase String:
El modificador static se aplica principalmente a variables o a métodos de una clase. Ya lo hemos visto aplicado a métodos.
Variables miembro estáticas: Una variable estática no se asocia a una instancia de una clase (objeto), sino a la clase misma. Esto es: no hay una copia de la variable estática por cada objeto de la clase, sino una sola copia por clase. A estas variables se les conoce como variables de clase.
Aunque se creen varios objetos de una misma clase, el valor de sus variables estáticas es el mismo para todos los objetos, y si se modifican, se modifican para todos. Solo hay una copia en memoria.
Métodos miembro estáticos: Al igual que las variables estáticas, los métodos estáticos no dependen de un objeto de la clase, y por eso se pueden llamar sin necesidad de crear un objeto de la clase. Se les llama método de clase.
Dado que no es necesario crear un objeto para llamar a estos métodos, no puedes acceder a variables miembro de la clase que no sean estáticas. Esto tiene sentido, ya que si las variables no son estáticas dependen de un objeto, y como hemos dicho, no es necesario un objeto para llamar al método.
Desde el punto de vista del lugar donde se declaran existen dos tipos de variables:
Las variables miembro son inicializadas automáticamente, de la siguiente forma:
Las variables miembro pueden inicializarse con valores distintos de los anteriores en su constructor. Las variables locales (de ámbito local) no se inicializan automáticamente. Se les debe asignar un valor antes de ser usadas. Los atributos estáticos solo pueden inicializarse en su declaración (no en un constructor).
Cuando ya no se necesita un objeto simplemente puede dejar de referenciarse. No existe una operación explícita para 'destruir' un objeto o liberar el área de memoria usada por él.
Un objeto deja de referenciarse cuando no hay ninguna variable de referencia dentro del programa que contenga su valor de referencia:
Coche miCoche = new Coche(); miCoche = null; //He perdido el valor de la referencia al objeto //Otro ejemplo: Coche miCoche = new Coche(); miCoche = new Coche(); //Pierdo la referencia del objeto anterior
Una variable solo puede contener un valor, y cuando no hay ninguna variable que guarde la referencia a un objeto, lo habré perdido para siempre.
La liberación de memoria la realiza el recolector de basura (garbage collector), que es una función de la JVM (Java Virtual Machine). El recolector revisa toda el área de memoria del programa y determina qué objetos pueden ser borrados de la memoria porque ya no tienen referencias activas que los apunten.
El recolector de basura actúa cuando la JVM lo determina (tiene un mecanismo de actuación no trivial). No existe un momento concreto en que las áreas de memoria son liberadas, sino que lo determina en cada momento la JVM en función de sus necesidades de espacio.
La clase Object
es la clase raíz de la cual heredan todas las clases. Esta herencia es implícita, no es necesario indicarlo en la subclase (mediante extends). Puede que una clase no herede de Object, pero tendrá una superclase que sí lo hace.
La clase Object define una serie de métodos miembro que son heredados por todas las clases. Algunos de estos métodos se redefinen (o sobrescriben) en las clases que definamos.
La programación orientada a objetos (POO) es un paradigma de programación basado en unos pilares. Entre ellos podemos encontrar 4 principales: abstracción, encapsulamiento, herencia y polimorfimo.
La abstracción se puede entender como la representación de entidades (pueden ser de la vida real) mediante elementos de un programa. Definimos esos elementos mediante las clases (Coche, Vehiculo, Persona, Pasajero, etc) y los usamos mediante sus objetos.
Además, estos elementos realizan una serie de operaciones, pero nosotros no tenemos por qué saber cómo se realizan están operaciones: el cómo se hace, formará parte de la clase, donde se definen cómo se hacen las cosas. A nosotros una clase nos ofrece una interfaz (métodos) mediante la cual realizar dichas operaciones, sin tener que preocuparnos de entender cómo se hacen o de implementar su funcionamiento cada vez que las usemos. Todo esto se conoce como abstracción.
El concepto de encapsulamiento es otro pilar fundamental de la POO y está ligado a la abstracción. Consiste en aislar los miembros (propiedades y operaciones) del acceso exterior (ponerlos dentro de una clase). Así evitamos que otras partes del programa pueden modificar las propiedades de un objeto de forma errónea, y crearemos una interfaz para trabajar con el objeto y acceder a sus propiedades. Esta interfaz son los métodos. Es la forma en la que el objeto nos permite trabajar con él, sin poder tener acceso a su interior (implementación de operaciones, valor de las propiedades).
La herencia y el polimorfismo se verán más adelante ya que también están ligados entre sí.
Las siguientes palabras clave se irán viendo a medida que trabajemos con nuestras clases y ampliando conocimientos:
extends
: Se usa para indicar cuál es la clase desde la que se hereda.super
: similar a this
, pero contiene una referencia a la superclase.abstract
: modificador para clases o métodos, se usa en las clases abstractas.final
: se aplica tanto a variables, clases o métodos, e indica que no se permiten modificar.interface
: similar a la palabra class
, define una clase abstracta pura, se conocen como interfaces.implements
: similar a extends
, se usa para indicar de qué interfaces se hereda.© 2024 Fernando Valdeón