Programación orientada a objetos (OOP)
Hasta ahora hemos trabajado principalmente con variables, condiciones, bucles, arrays y cadenas.
A medida que los programas crecen, puede resultar complicado organizar toda la información utilizando únicamente variables y funciones.
La Programación Orientada a Objetos (POO) es un paradigma de programación que permite organizar el código utilizando clases y objetos.
La idea principal es representar dentro del programa diferentes elementos que tienen:
- Datos, que llamaremos atributos.
- Comportamientos/Acciones, que representaremos mediante métodos.
Por ejemplo, si estamos creando un programa para gestionar una tienda, podríamos tener objetos como:
Producto
Cliente
Pedido
Cada uno de estos objetos podría tener sus propios datos y comportamientos.
Un Producto podría tener:
nombre
precio
stock
Y podría tener métodos/acciones como:
mostrarInformacion()
vender()
De esta forma, la información relacionada con cada elemento queda organizada.
Conceptos clave
- Clase: Una clase es una plantilla o molde que define la estructura y el comportamiento de los objetos a representar. Por ejemplo, una clase
Cochepodría definir cómo se crean, qué caracaterísticas tienen y cómo se comportan los objetos tipo coche. - Objeto: Un objeto es una instancia concreta de una clase. Representa un ente en concreto que puede tener atributos (datos) y comportamientos (métodos) de su clase. Por ejemplo, un objeto podría ser una instancia de la clase
Cochellamada "coche1" con valores específicos en los atributos de la clase como color "rojo" y modelo "Audi Q2", y métodos como acelerar y frenar. - Atributos: Los atributos son variables que almacenan datos en un objeto. Cada objeto tiene su propio conjunto de valores de atributos. En el ejemplo del coche, los atributos podrían ser "color" y "modelo".
- Métodos: Los métodos son funciones que definen el comportamiento de un objeto. Los objetos pueden invocar métodos para realizar acciones o calcular valores. En el ejemplo del coche, los métodos podrían ser "acelerar" y "frenar".
- Encapsulación: La encapsulación es el principio de ocultar los detalles internos de una clase y proporcionar una interfaz pública para interactuar con los objetos. Esto promueve la seguridad y el mantenimiento del código. Lo veremos al final de la lección.
Beneficios
- Reutilización de Código: La POO permite reutilizar código a través de la creación de clases y la creación de objetos a partir de esas clases. Esto reduce la duplicación de código y facilita el mantenimiento.
- Abstracción: Permite modelar conceptos del mundo real en un software de manera más natural y abstracta. Los objetos y clases reflejan objetos y conceptos reales.
- Modularidad: El código se divide en objetos y clases, lo que facilita la organización y comprensión del software. Los cambios en una clase tienen un impacto limitado en el resto del programa.
- Herencia: La POO permite crear nuevas clases basadas en clases existentes, heredando sus atributos y métodos. Esto promueve la reutilización y la extensión del código.
- Polimorfismo: Permite que los objetos de diferentes clases se comporten de manera similar al definir métodos con el mismo nombre en clases diferentes. Facilita la gestión de objetos de manera genérica.
Clases y objetos
Antes de empezar a crear clases y objetos es importante entender la diferencia entre ambos conceptos.
Una clase es como un molde o plantilla.
Un objeto es un elemento creado a partir de ese molde.
Podemos imaginar una clase como la definición de "ser persona".
¿Qué es ser una persona?
Por ejemplo podemos definir que toda persona tiene nombre, apellido, edad, color de ojos...
La clase Persona definiría todos esos atributos y comportamientos que para nosotros defininen a cualquier persona, pero no es ninguna persona en concreto.
A partir de la definición de Persona (la clase), podemos crear tantos objetos de dicha clase como queramos. Podemos crear el objeto "juan" dando valor a los atributos definidos en la clase:
nombre: Juan
apellido: García
edad: 35
color de ojos: marrón
En Java ocurre algo parecido:
Clase Persona
↓
┌───┴────┐
↓ ↓
Persona 1 Persona 2 ...
La clase define qué características y comportamientos tendrá una persona.
Después podemos crear tantos objetos Persona como necesitemos.
Clases
Una clase se define utilizando la palabra clave class.
Por ejemplo:
public class Persona {
}
Acabamos de crear una clase llamada Persona en el fichero Persona.java.
Por sí sola, esta clase todavía no hace demasiado.
Dentro de ella podemos definir sus atributos y métodos.
Atributos
Los atributos son variables que pertenecen a un objeto.
Representan los datos o características del objeto.
Por ejemplo, una persona puede tener:
nombre
edad
Los atributos se definen nada más empezar la clase indicando tipo nombreVariable;.
public class Persona {
String nombre;
int edad;
}
Ahora nuestra clase Persona tiene dos atributos:
nombreedad
Métodos
Los métodos representan comportamientos o acciones que puede realizar un objeto.
Por ejemplo, una persona podría:
saludar()
mostrarEdad()
Podemos definir estos métodos dentro de la clase:
public class Persona {
String nombre;
int edad;
public void saludar() {
System.out.println("Hola");
}
}
Ahora cualquier objeto de la clase Persona podrá utilizar el método saludar().
Objetos
Una clase es solamente una plantilla.
Para utilizarla necesitamos crear objetos.
Para crear un objeto utilizamos la palabra clave new. Generalmente los objetos concretos se crearán dentro del programa main.
Para crear un objeto de tipo Persona:
Persona persona1 = new Persona();
Podemos dividir esta línea en varias partes:
Persona persona1 = new Persona();
↓ ↓ ↓
Tipo Variable Crear objeto
Persona indica el tipo de objeto.
persona1 es el nombre de la variable que almacenará el objeto.
new Persona() crea una nueva instancia de la clase.
Crear varios objetos
Podemos crear tantos objetos como necesitemos.
Persona persona1 = new Persona();
Persona persona2 = new Persona();
Persona persona3 = new Persona();
Los tres objetos pertenecen a la misma clase:
Persona
│
├── persona1
├── persona2
└── persona3
Pero cada objeto tiene sus propios atributos.
Acceder a los atributos
Podemos acceder a los atributos de un objeto utilizando el operador ..
Por ejemplo:
Persona persona1 = new Persona();
persona1.nombre = "Ana";
persona1.edad = 25;
Ahora el objeto contiene:
persona1
├── nombre → "Ana"
└── edad → 25
Podemos consultar esos valores:
System.out.println(persona1.nombre);
System.out.println(persona1.edad);
Salida:
Ana
25
Acceder a los métodos
Del mismo modo que podemos acceder a los atributos, podemos llamar a los métodos utilizando ..
Por ejemplo:
persona1.saludar();
Esto ejecutará el método saludar() perteneciente al objeto persona1.
:pencil: NOTA: los métodos se llaman con paréntesis al final
()mientras que los atributos solamente indicamos su nombre.
Constructores
Hasta ahora hemos creado objetos de esta forma:
Persona persona1 = new Persona();
Y posteriormente dábamos un valor concreto a sus atributos con el ..
Pero podemos hacer que, al crear un objeto, sea obligatorio proporcionar determinados datos.
Para ello utilizamos los constructores.
Un constructor es un método especial que se ejecuta automáticamente cuando creamos un objeto, permitiéndonos indicar en la creación del objeto los valores de los atributos tal que así:
Persona persona1 = new Persona("Anna", 25);
Persona persona2 = new Persona("Gema", 32);
El constructor se crea justo después de la definición de los atributos tal que así:
public class Persona {
String nombre;
int edad;
public Persona(String nombre, int edad) {
this.nombre = nombre;
this.edad = edad;
}
}
Su sintaxis es siempre:
public class NombreClase {
tipo1 atributo1;
tipo2 atributo2;
public NombreClase(tipo1 parametro1, tipo2 parametro2) {
this.atributo1 = parametro1;
this.atributo2 = parametro2;
}
}
Ahora veremos cómo funciona.
this
La palabra clave this hace referencia al objeto actual.
Puede resultar especialmente útil cuando los parámetros de un método o constructor tienen el mismo nombre que los atributos.
Observa:
public class Persona {
String nombre;
int edad;
public Persona(String nombre, int edad) {
this.nombre = nombre;
this.edad = edad;
}
}
Tenemos dos variables llamadas nombre:
this.nombre → atributo del objeto
nombre → parámetro del constructor
Por lo tanto:
this.nombre = nombre;
significa:
El atributo nombre del objeto
=
El parámetro nombre recibido
Al escribir:
Persona persona1 = new Persona("Anna", 25);
Es como decirle:
this.nombre = "Anna";
this.edad = 25;
¿Por qué necesitamos this?
Podríamos utilizar nombres diferentes:
public Persona(String nombrePersona, int edadPersona) {
nombre = nombrePersona;
edad = edadPersona;
}
Esto funcionaría correctamente.
Pero normalmente es más habitual utilizar el mismo nombre para el atributo y el parámetro:
public Persona(String nombre, int edad) {
this.nombre = nombre;
this.edad = edad;
}
De esta forma el código resulta más claro y fácil de mantener.
Métodos dentro de una clase
Una clase puede tener varios métodos.
Por ejemplo:
public class Persona {
// Atributos
String nombre;
int edad;
// Constructor
public Persona(String nombre, int edad) {
this.nombre = nombre;
this.edad = edad;
}
// Métodos
public void saludar() {
System.out.println("Hola, soy " + nombre);
}
public void mostrarInformacion() {
System.out.println("Nombre: " + nombre);
System.out.println("Edad: " + edad);
}
}
Podemos crear un objeto y utilizar sus métodos:
Persona persona1 = new Persona("Ana", 25);
persona1.saludar();
persona1.mostrarInformacion();
Salida:
Hola, soy Ana
Nombre: Ana
Edad: 25
Observa que dentro de los métodos podemos utilizar directamente los atributos del objeto:
public void saludar() {
System.out.println("Hola, soy " + nombre);
}
Métodos que devuelven valores
Los métodos también pueden devolver información.
Por ejemplo, podemos crear un método que devuelva el nombre:
public String obtenerNombre() {
return nombre;
}
Y otro que devuelva la edad:
public int obtenerEdad() {
return edad;
}
Podemos utilizar estos métodos desde el main:
Persona persona1 = new Persona("Ana", 25);
System.out.println(persona1.obtenerNombre());
System.out.println(persona1.obtenerEdad());
Salida:
Ana
25
static
La palabra clave static indica que algo pertenece a la clase, en lugar de pertenecer a cada objeto individual.
Esta diferencia es muy importante.
Hasta ahora hemos trabajado con atributos que pertenecen a cada objeto.
Por ejemplo:
public class Persona {
String nombre;
int edad;
}
Si creamos dos personas:
Persona persona1 = new Persona();
Persona persona2 = new Persona();
persona1.nombre = "Ana";
persona2.nombre = "Luis";
Cada objeto tiene su propio nombre.
persona1 → Ana
persona2 → Luis
Esto ocurre porque nombre no es static.
Atributos static
Imaginemos ahora que queremos almacenar un dato que sea común para todas las personas.
Por ejemplo, queremos saber cuántas personas hemos creado.
Podemos utilizar un atributo static:
public class Persona {
String nombre;
static int cantidadPersonas = 0;
}
Este atributo pertenece a la clase Persona, no a cada persona individual.
Podemos incrementarlo desde el constructor:
public Persona(String nombre) {
this.nombre = nombre;
cantidadPersonas++;
}
Ahora podemos crear varios objetos:
Persona persona1 = new Persona("Ana");
Persona persona2 = new Persona("Luis");
Persona persona3 = new Persona("María");
La variable cantidadPersonas tendrá el valor:
3
Esto ocurre porque los tres objetos comparten el mismo atributo static. Es decir, cantidadPersona no tiene un valor diferente para cada objeto de tipo Persona que se cree, sino que existe un único atributo cantidadPersona perteneciente a la clase en sí.
Acceder a un atributo static
Como un atributo static pertenece a la clase, podemos acceder a él utilizando el nombre de la clase en lugar del nombre del objeto creado:
System.out.println(Persona.cantidadPersonas);
Salida:
3
No necesitamos crear un objeto adicional para acceder a este atributo, sino que accedemos a él directamente con el nombre de la clase Persona..
Incorrecto:
Persona persona1 = new Persona("Mario", 22);
System.out.println(persona1.cantidadPersonas);
Correcto:
System.out.println(Persona.cantidadPersonas);
Métodos static
Los métodos también pueden ser static.
Un método static pertenece a la clase y puede utilizarse sin crear un objeto.
Por ejemplo:
public class Calculadora {
public static int sumar(int a, int b) {
return a + b;
}
}
Podemos utilizar el método directamente desde la clase:
int resultado = Calculadora.sumar(5, 3);
System.out.println(resultado);
Salida:
8
No hemos necesitado hacer:
Calculadora calculadora = new Calculadora();
Esto es posible porque sumar() es static.
main y static
Ahora podemos entender mejor por qué el método main tiene la palabra static.
Cuando escribimos:
public static void main(String[] args) {
}
el método main es static.
Esto permite que Java pueda ejecutar el programa sin tener que crear previamente un objeto de la clase que contiene main.
Podemos recordar:
public
↓
Puede ser accesible desde fuera
static
↓
Pertenece a la clase
void
↓
No devuelve ningún valor
main
↓
Punto de entrada del programa
static y atributos de los objetos
Hay que tener cuidado al trabajar con static.
Un método static pertenece a la clase, por lo que no puede acceder directamente a los atributos normales de un objeto.
Por ejemplo:
public class Persona {
String nombre;
public static void mostrarNombre() {
System.out.println(nombre);
}
}
Esto no funcionará correctamente.
¿Por qué?
Porque nombre pertenece a cada objeto.
En cambio:
mostrarNombre()
pertenece a la clase.
Java no sabe de qué objeto debería obtener el nombre.
Un método static sí puede acceder directamente a atributos que también sean static.
public class Persona {
static int cantidadPersonas = 0;
public static void mostrarCantidad() {
System.out.println(cantidadPersonas);
}
}
Ahora ambos pertenecen a la clase Persona.
Clases y ficheros
Una duda muy habitual cuando empezamos a trabajar con clases es:
¿Las clases van en el mismo fichero que el main o en un fichero separado?
La respuesta depende de cómo estemos organizando el programa, pero en proyectos reales lo habitual es mantener las clases en ficheros separados.
Por ejemplo:
Proyecto
│
├── Main.java
├── Persona.java
├── Producto.java
└── Coche.java
Cada clase tiene su propio fichero.
Esto permite mantener el código organizado y facilita su mantenimiento.
El fichero Main
Normalmente tenemos una clase que contiene el método:
public static void main(String[] args)
Por ejemplo:
public class Main {
public static void main(String[] args) {
Persona persona = new Persona("Ana", 25);
persona.mostrarInformacion();
}
}
El main es el punto de entrada del programa.
Desde él podemos crear objetos y utilizar sus métodos.
El fichero de la clase
La clase Persona podría estar en un fichero llamado:
Persona.java
Y contener:
public class Persona {
private String nombre;
private int edad;
public Persona(String nombre, int edad) {
this.nombre = nombre;
this.edad = edad;
}
public void mostrarInformacion() {
System.out.println("Nombre: " + nombre);
System.out.println("Edad: " + edad);
}
}
Después podemos utilizar esta clase desde Main.
Llamar a una clase desde Main
Si las dos clases están en el mismo paquete, normalmente podemos utilizar directamente la clase.
public class Main {
public static void main(String[] args) {
Persona persona = new Persona("Ana", 25);
persona.mostrarInformacion();
}
}
Si la clase se encuentra en otro paquete, debemos importarla.
Por ejemplo:
import personas.Persona;
Después podremos utilizarla:
Persona persona = new Persona("Ana", 25);
Ejemplo completo
Vamos a juntar todos los conceptos vistos hasta ahora.
Tenemos una clase Persona con:
- Dos atributos.
- Un constructor.
this.- Métodos.
- Un atributo
static.
public class Persona {
private String nombre;
private int edad;
static int cantidadPersonas = 0;
public Persona(String nombre, int edad) {
this.nombre = nombre;
this.edad = edad;
cantidadPersonas++;
}
public String obtenerNombre() {
return nombre;
}
public int obtenerEdad() {
return edad;
}
public void mostrarInformacion() {
System.out.println("Nombre: " + nombre);
System.out.println("Edad: " + edad);
}
}
Ahora podemos utilizar la clase desde Main:
public class Main {
public static void main(String[] args) {
Persona persona1 = new Persona("Ana", 30);
Persona persona2 = new Persona("Luis", 25);
persona1.mostrarInformacion();
System.out.println();
persona2.mostrarInformacion();
System.out.println();
System.out.println("Personas creadas: " + Persona.cantidadPersonas);
}
}
Salida:
Nombre: Ana
Edad: 30
Nombre: Luis
Edad: 25
Personas creadas: 2
Errores frecuentes
Confundir una clase con un objeto
La clase:
Persona
es la plantilla.
El objeto:
Persona persona1 = new Persona("Ana", 25);
es una instancia concreta de esa clase.
Podemos crear muchos objetos a partir de una misma clase.
Olvidar new
Incorrecto:
Persona persona1;
Aquí solamente hemos declarado una variable.
Todavía no hemos creado ningún objeto.
Para crear el objeto:
Persona persona1 = new Persona("Ana", 25);
Confundir el constructor con un método
El constructor:
public Persona(String nombre, int edad) {
}
tiene el mismo nombre que la clase.
Además, no tiene tipo de retorno.
No debemos escribir:
public void Persona(String nombre, int edad) {
}
Eso sería un método, no un constructor.
Olvidar this
Si utilizamos el mismo nombre para los atributos y los parámetros:
public Persona(String nombre, int edad) {
this.nombre = nombre;
this.edad = edad;
}
necesitamos utilizar this para distinguir el atributo del parámetro.
Utilizar static sin necesidad
No debemos convertir todos los atributos y métodos en static.
Utilizaremos static cuando el dato o comportamiento pertenezca a la clase en general, y no a un objeto concreto.
Por ejemplo:
nombre
edad
pertenecen a cada persona.
Mientras que:
cantidadPersonas
puede ser un dato compartido por todas las personas.