08 - POO I: Clases y objetos

La Programación Orientada a Objetos (POO) es un paradigma de la programación donde se organiza el código en unidades denominadas clases que contienen propiedades (o atributos) y métodos.

En realidad, al ser Java un lenguaje totalmente orientado a objetos, ya hemos utilizado este paradigma desde el principio. Fíjate en la primera línea del código de nuestra primera aplicación:

public class holaMundo {

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

Lo primero que hacemos es definir una clase llamada holaMundo mediante la palabra reservada class (más adelante veremos lo que significa public). La idea es encapsular (esconder) el código de nuestra aplicación en clases y que estas se comuniquen entre sí mediante métodos.

Piensa, por ejemplo, en una televisión. Podemos interactuar con ella a través de ciertos métodos definidos (encender o apagar la TV, subir o bajar el volumen, cambiar de canal…), pero no podemos ver como funcionan esos procesos internamente (los circuitos electrónicos que permiten ejecutar esos procesos están ocultos). La POO funciona de forma parecida. Escondemos los detalles de implementación de nuestras clases y creamos métodos para que las clases puedan interactuar entre ellas.

Cuando hablamos de POO, existen una serie de características que se deben cumplir. Las características más importantes de la POO son:

  • Abstracción: Es el proceso por el cual definimos las características más importantes de un objeto, sin preocuparnos de cómo se escribirán en el código del programa. En la POO la herramienta más importante para soportar la abstracción es la clase. Básicamente, una clase es un tipo de dato que agrupa las características comunes de un conjunto de objetos. Por ejemplo, si pensamos en una clase Vehículo que agrupa las características comunes de todos ellos, a partir de dicha clase podríamos crear objetos como Coche y Camión . Entonces se dice que Vehículo es una abstracción de Coche y de Camión.
  • Modularidad: Una vez que hemos representado el escenario del problema en nuestra aplicación, tenemos como resultado un conjunto de objetos software a utilizar. Este conjunto de objetos se crean a partir de una o varias clases. Cada clase se encuentra, normalmente, en un archivo diferente, por lo que la modularidad nos permite modificar las características de la clase que define un objeto, sin que esto afecte al resto de clases de la aplicación.
  • Encapsulación: También llamada ocultamiento de la información. La encapsulación o encapsulamiento es el mecanismo básico para ocultar la información de las partes internas de un objeto a los demás objetos de la aplicación. Con la encapsulación un objeto puede ocultar la información que contiene al mundo exterior, o bien restringir el acceso a la misma para evitar ser manipulado de forma inadecuada. Por ejemplo, pensemos en un programa con dos objetos, un objeto Persona y otro Coche . Persona comunica con el objeto Coche para llegar a su destino, utilizando para ello las acciones que Coche tenga definidas como por ejemplo conducir. Es decir, Persona utiliza Coche pero no sabe cómo funciona internamente, sólo sabe utilizar sus métodos o acciones.
  • Jerarquía: Mediante esta propiedad podemos definir relaciones de jerarquías entre clases y objetos. Las dos jerarquías más importantes son la jerarquía es un llamada generalización o especialización y la jerarquía es parte de, llamada composición o agregación. Conviene detallar algunos aspectos: La generalización o especialización, también conocida como herencia, permite crear una clase nueva en términos de una clase ya existente (herencia simple) o de varias clases ya existentes (herencia múltiple). Por ejemplo, podemos crear la clase CochedeCarreras a partir de la clase Coche , y así sólo tendremos que definir las nuevas características que tenga. La composición, permite agrupar objetos relacionados entre sí dentro de una clase. Así, un Coche está formado por Motor , Ruedas , Frenos y Ventanas.
  • Polimorfismo: Esta propiedad indica la capacidad de que varias clases creadas a partir de una antecesora realicen una misma acción de forma diferente. Por ejemplo, pensemos en la clase Animal y la acción de expresarse. Nos encontramos que cada tipo de Animal puede hacerlo de manera distinta, los Perros ladran, los Gatos maullan, las Personas hablamos, etc. Dicho de otra manera, el polimorfismo indica la posibilidad de tomar un objeto (de tipo Animal , por ejemplo), e indicarle que realice la acción de expresarse. Esta acción será diferente según el tipo de animal del que se trate.

Como hemos visto, las clases son las definiciones generales de los objetos de nuestra aplicación. Por ejemplo, podemos definir una clase TV que contendrá los métodos (encender, apagar, subir volumen…) y atributos (canal, volumen…) que van a contener todos los objetos creados a partir de ella, pero no se refiere a ninguna TV en concreto. Cuando instanciamos una clase (creamos una instancia de ella), creamos un objeto. En nuestro caso, una TV en concreto. Por ejemplo, podemos crear un objeto TV Samsung QLed 4k QN85A derivado de la clase TV.

Los objetos tienen unas características fundamentales que los distinguen:

  • Identidad: Es la característica que permite diferenciar un objeto de otro. De esta manera, aunque dos objetos sean exactamente iguales en sus atributos, son distintos entre sí. Puede ser una dirección de memoria, el nombre del objeto o cualquier otro elemento que utilice el lenguaje para distinguirlos. Por ejemplo, dos vehículos que hayan salido de la misma cadena de fabricación y sean iguales aparentemente, son distintos porque tienen un código que los identifica.
  • Estado: El estado de un objeto viene determinado por una serie de parámetros o atributos que lo describen, y los valores de éstos. Por ejemplo, si tenemos un objeto Coche , el estado estaría definido por atributos como Marca, Modelo, Color, Cilindrada, etc.
  • Comportamiento. Son las acciones que se pueden realizar sobre el objeto. En otras palabras, son los métodos o procedimientos que realiza el objeto. Siguiendo con el ejemplo del objeto Coche, el el comportamiento serían acciones como: arrancar(), parar(), acelerar(), frenar(), etc.

En otras palabras, una clase es una plantilla o prototipo donde se especifican:

  • Los atributos comunes a todos los objetos de la clase.
  • Los métodos que pueden utilizarse para manejar esos objetos.

Si existe una clase, podemos crear objetos en nuestro programa a partir de esas clases.

Cuando creamos un objeto a partir de una clase se dice que hemos creado una instancia de la clase. A efectos prácticos, objeto e instancia de clase son términos similares. Es decir, nos referimos a objetos como instancias cuando queremos hacer hincapié que son de una clase particular.

Los objetos se crean a partir de las clases, y representan casos individuales de éstas.

Por ejemplo, cuando vimos los tipos de datos en Java, hablamos del tipo String, que no es un tipo de dato primitivo. En realidad, String es una clase predefinida de Java que define el comportamiento de todas las cadenas de texto en Java. Cuando creamos una variable de tipo String, estamos creando un objeto (instancia) de esa clase:

public class ClaseString {
    
    public static void main(String[] args) {
        String mensaje;
        
        mensaje = "Hola mundo";
        System.out.println(mensaje.length());
    }
}

En el ejemplo anterior, creamos un objeto llamado mensaje que pertenece a la clase String. Una vez creado, podemos utilizar los métodos definidos en la clase String, como length().

Como veremos a continuación, normalmente creamos los objetos con la palabra reservada new. En el caso de la clase String podemos obviar este paso, aunque en realidad el código debería ser:
String mensaje = new String();

Vamos a ver como crear clases en Java. Lo primero, será crear un archivo llamado Main que contendrá nuestra clase principal de la aplicación con el método main():

public class Main {

    public static void main(String[] args) {
        
    }
}

Como hemos dicho, Java es totalmente OO, con lo que todo nuestro código tiene que estar contenido dentro de una clase. Además, existe un método especial, main(), que será el punto de entrada a nuestra aplicación. Es decir, cuando ejecutemos nuestra aplicación, lo primero que hará será ejecutar ese método main() de nuestra clase principal. Ésto no ocurre en otros lenguajes de programación que no son totalmente OO.

Como norma general, cuando creamos aplicaciones en Java siempre crearemos la misma clase Main con el método main().

Para crear una nueva clase, pincha con el botón derecho sobre la carpeta donde quieres crear la clase y selecciona New Java Class:

Ponle como nombre Coche (por convención, los nombres de las clases empiezan con mayúscula mientra que los objetos se escriben todo en minúsculas). VSC te habrá creado un nuevo archivo llamado Coche.java con el siguiente código (olvídate, por ahora, de la primera línea de código donde se define el paquete):

package ejemplos.T08.clasesBasico;

public class Coche {
    
}

Ya hemos creado nuestra clase Coche. A partir de ahora, podemos utilizarla siempre que creemos antes un objeto de esa clase.

Las clases tienen propiedades que serán comunes a todos los objetos (su definición, no su valor). Por ejemplo, en la clase Coche tendríamos propiedades como marca, modelo, numeroBastidor… Cualquier objeto creado de esa clase, tendrá esas propiedades con un valor determinado.

¿Recuerdas las variables globales? Eran variables que estaban accesibles desde cualquier punto del código. En realidad, lo que definíamos eran, precisamente, propiedades de clase. Vamos a crear algunas propiedades en nuestra clase:

public class Coche {
    String marca, modelo, color;
    int numeroBastidor, velocidad = 0;
}

Listo, ya tenemos nuestra clase con sus propiedades. Vamos a intentar acceder a las propiedades del coche desde nuestra clase Main():

public class Main {

    public static void main(String[] args) {
        System.out.println(Coche.marca);
    }
}

¿Por qué está dando un error? Básicamente, lo único que hemos hecho es definir una clase, pero no hemos creado ningún objeto todavía de esa clase, con lo que no podemos acceder a sus propiedades. Como dijimos antes, una clase es simplemente una definición (una plantilla), pero hasta que creemos un objeto de esa clase, es como si no existiera para nuestra aplicación.

Puede que estés pensando cómo es posible que podamos ejecutar el método main() de nuestra clase principal si no hemos creado ningún objeto cuando arranca la aplicación. Ésto se debe a que dicho método es estático (palabra reservada static) entre otras cosas, lo que nos permite ejecutar métodos de una clase sin crear ningún objeto. Más adelante ya veremos cómo hacerlo

Antes de poder utilizar métodos y propiedades de una clase, tenemos que crear un objeto derivado de ella.

Para la creación de un objeto hay que seguir los siguientes pasos:

  • Declaración: Definir el tipo de objeto (la clase a la que pertenece).
  • Instanciación: Creación del objeto utilizando el operador new.

Podemos juntar ambos pasos en uno, como hacemos con la declaración de listas, conjuntos… (en realidad, al igual que la clase String los arrays, list, set… son clases predefinidas en Java):

public class Main {

    public static void main(String[] args) {
        Coche coche1 = new Coche();
    }
}

A partir de ahora, ya podemos acceder a las propiedades del objeto. Para acceder a sus propiedades (o métodos), tenemos que escribir el nombre de nuestro objeto seguido por un punto y la propiedad (o método):

    public static void main(String[] args) {
        Coche coche1 = new Coche();

        coche1.marca = "Kia";

        System.out.println("La marca de mi coche es " + coche1.marca);
    }

La marca de mi coche es Kia

Las funciones que vayamos creando en nuestras clases, son lo que llamamos métodos de las clases, y es el mecanismo por el que podremos interactuar con los objetos. Por ejemplo, vamos a crear un método acelerar() que recibirá un entero que indicará en cuanto aumenta la velocidad del coche:


public class Coche {
    String marca, modelo, color;
    int numeroBastidor, velocidad = 0;

    public void acelerar(int aumento){
        velocidad += aumento;
    }
}

Ahora, desde nuestra clase Main podemos usar ese método para aumentar la velocidad del coche:

public class Main {

    public static void main(String[] args) {
        Coche coche1 = new Coche();

        System.out.println("La velocidad del coche es " + coche1.velocidad);
        coche1.acelerar(20);
        System.out.println("La velocidad del coche es " + coche1.velocidad);
    }
}

La velocidad del coche es 0
La velocidad del coche es 20

Fíjate que ahora ya no añadimos la palabra static a nuestros métodos de clase. Ésto es así porque antes de utilizar el método creamos el objeto, con lo que ya no necesitamos indicarle a Java que ese método se puede utilizar sin necesidad de crear un objeto
Otra cosa importante que veremos en el siguiente tema es la visibilidad. Tanto los métodos como las propiedades de una clase deben llevar visibilidad (public, private o protected). Si no le ponemos visibilidad, por defecto será public. Por ahora, vamos a crear todos nuestros métodos con public. Más adelante ya cambiaremos la visibilidad de nuestros métodos (y, sobre todo, de nuestras propiedades) al tipo correcto.

Ejercicio 1

Crea un nuevo proyecto Java. Además de la clase principal (puedes llamarla Main o App), crea una nueva clase llamada Coche con los atributos marca, modelo, color, numeroBastidor y velocidad. Los 3 primeros serán String y los dos últimos de tipo int. La velocidad inicial será de 0.

En la clase principal, crea un nuevo coche, asígnale valores a sus atributos y muestra por pantalla la frase “Mi coche es un {marca} {modelo} de color {color} con número de bastidor {numeroBastidor}”, sustituyendo las variables encerradas entre llaves por los valores que le hayas dado a las propiedades al crear el coche. Ejemplo:

Mi coche es un Kia Niro gris con número de bastidor 123456

Ejercicio 2

Crea en la clase Coche un método llamado toString() que devuelva la frase“{marca} {modelo} de color {color} con número de bastidor {numeroBastidor}” (igual que antes, sustituyendo las variables por los valores reales del objeto).

Utiliza ese método para mostrar por pantalla los detalles del coche desde la clase principal.

En realidad, el método toString() es un método heredado de la clase Object (no te preocupes por ahora del concepto de herencia). Lo que estamos haciendo es sobreescribir el método para devolver la cadena que queramos (puede que veas la anotación @Override encima de la declaración de ese método en muchas clases. Cuando veamos el tema de herencia y sobreescritura de métodos ya lo entenderás). Normalmente se utiliza para mostrar los objetos como cadenas de caracteres.

Ejercicio 3

Crea dos métodos en la clase Coche que permitan aumentar o reducir la velocidad del coche en 5. Utiliza ambos métodos por pantalla y muestra la velocidad para comprobar que funcionan correctamente.

Ejercicio 4

Modifica los métodos anteriores para que se le pueda pasar la velocidad como parámetro y no sea fija de 5.

Ejercicio 5

Crea una nueva clase llamada Flota. Esta clase tendrá una propiedad que será un listado de coches. Crea un método para añadir un coche al listado y otro para eliminarlo. Al primer método se le pasará un objeto de tipo Coche y al segundo un número de bastidor.

Crea un último método que muestre por pantalla el listado de coches de la flota.

Añade 3 coches diferentes, muestra el listado, elimina uno de los coches y vuelve a mostrar el listado de coches que quedan en la flota.

Ejercicio 5.b

Elimina el método que muestra por pantalla el listado de coches. En su lugar, añade el método toString() a Flota para devolver una cadena con el listado de coches que tiene la flota.

Acuérdate que la clase String es inmutable. Utiliza la clase StringBuilder para poder añadir cadenas a un String.

Ejercicio 6

Modifica el método eliminarCoche() anterior para que devuelva false en caso de no encontrar el coche con ese número de bastidor en la flota. A la hora de eliminar un coche desde la clase principal, muestra por pantalla la frase “No se encuentra el coche en la flota” si no existe un coche con ese número de bastidor.

Ejercicio 7

Haz que la aplicación pida el número de bastidor a eliminar de la flota de coches. Si se elimina el coche correctamente, muestra por pantalla la frase “Se ha eliminado el coche con número de bastidor {numeroBastidor}”. Si no se encuentra el coche, la aplicación deberá mostrar la frase “No existe ningún coche con número de bastidor {numeroBastidor}”.

Ejercicio 8

Crea una nueva clase llamada Conductor. Esta clase tendrá como atributos String nombre, el nombre del conductor, y Coche asignado, el coche que ha sido asignado al conductor.

Crea un método donde se le pasará un numero de bastidor y una flota de vehículos. El método comprobará que el coche con ese número de bastidor existe en esa flota y se lo asignará al conductor. Para buscar el coche en la flota, crea un método nuevo en la clase Flota que recibirá un número de bastidor y devolverá el objeto coche si lo encuentra o null en caso contrario.

Crea otro método que muestre el coche asignado al conductor con la frase “Coche asignado a {nombre}: {coche}”. En tu clase principal, crea 2 conductores, asígnale uno de los coches a uno de los conductores y muestra por pantalla el coche asignado al conductor.

Ejercicio 9

Modifica el método que muestra el coche asignado para que muestre la frase “{nombre} no tiene asignado ningún coche” si ese conductor no tiene asignado ningún coche.

Ejercicio 10

Crea otro método en la clase Conductor para eliminar el coche asignado.

Ejercicio 11

Haz que el método anterior devuelva false si el conductor no tiene asignado ningún coche, y usa el método para mostrar por pantalla “Este conductor no tiene asignado ningún coche” si se intenta eliminar un coche a un conductor que no tiene asignado ninguno.

Ejercicio 12

Modifica el método que asigna un coche a un conductor para que reciba por parámetro un objeto de la clase Coche y no el número de bastidor. Desde la clase principal, deberás buscar ese coche en la flota de vehículos antes de asignárselo al conductor.

  • clase/daw/prog/2eval/poo_clases.txt
  • Última modificación: 2022/12/04 10:28
  • por cesguiro