CodeGym /Blog Java /Random-ES /Conceptos de programación orientada a objetos en Java
Autor
Alex Vypirailenko
Java Developer at Toshiba Global Commerce Solutions

Conceptos de programación orientada a objetos en Java

Publicado en el grupo Random-ES
Una de las mayores fortalezas de Java es la programación orientada a objetos (POO). Esa es la razón por la que este lenguaje se ha vuelto tan popular y es muy adecuado para proyectos de cualquier tamaño. ¿Qué es la programación orientada a objetos? No es magia, pero puede parecer mágico si realmente te involucras. OOP se trata de cómo construir su software. Es un concepto, o más bien un montón de conceptos de OOP en Java, que le permiten crear algunas interacciones y relaciones específicas entre los objetos de Java para desarrollar y usar software de manera efectiva. Conceptos de programación orientada a objetos en Java - 1La programación orientada a objetos clásica incluye 3 + 1 conceptos principales. Comencemos con los clásicos.

El objeto

Los objetos Java, así como los objetos del mundo real, tienen dos características: estado y comportamiento.

Por ejemplo, un objeto humano tiene estado (nombre, género, dormido o no…) y comportamiento (estudia Java, camina, habla…). Cualquier objeto Java almacena su estado en campos y expone su comportamiento a través de métodos.

Encapsulación

La encapsulación de datos oculta datos internos del mundo exterior y accede a ellos solo a través de métodos expuestos públicamente. ¿Qué significa eso? ¿Qué datos? ¿Escondiendose de quien? Ocultar significa restringir el acceso directo a los miembros de datos (campos) de una clase.

Cómo funciona en Java:

  1. Los campos se hacen privados.
  2. Cada campo de una clase obtiene dos métodos especiales: un getter y un setter. Los métodos getter devuelven el valor del campo. Los métodos de establecimiento le permiten cambiar el valor del campo de forma indirecta pero permitida.

Ejemplo de encapsulación en código Java:


public class Student {
private int age;
private String name;

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

public class Test{
public static void main(String[] args) {
Student firstStudent = new Student();
firstStudent.setName("John");
// The name field is private, so you can no longer do this:  firstStudent.name = "John"; 
}
}

¿Por qué debería usar la encapsulación?

La razón principal es facilitar el cambio de código. Imagine que tiene una solicitud para una escuela de hockey y hay una clase de Estudiante de hockey con dos campos que almacenan el nombre y la edad del estudiante cuando se inscribió en la escuela. Algo como esto:

public class HockeyStudent {
public String name;
public  int ageOfEnrollment;
}
El campo ageOfEnrollment es público, sin getters ni setters... Esta clase es utilizada por muchas otras clases, y todo estaba bien hasta que un desarrollador decidió que un solo campo int no era suficiente. Algunos jugadores de hockey en una cohorte son casi un año mayores que sus compañeros, por lo que sería más conveniente dividirlos en dos grupos según el mes en que nacieron. Entonces, el campo ageOfEnrollment debe cambiarse a una matriz int (int[][]) : el primer número es para años completos y el segundo es para meses. ¡Ahora necesita refactorizar todo el código que usa la clase Student ! Pero si su edad de inscripciónel campo es privado y tienes getters y setters, entonces todo es más fácil. Si el requisito para establecer la edad de un estudiante cambia, simplemente actualice la lógica en el método de establecimiento setAgeOfEnrollment() y sus clases pueden continuar usando Student sin ningún problema. Este ejemplo es algo artificial, pero espero que explique por qué usar la encapsulación es una gran idea.

Herencia

Este principio es más fácil de entender incluso sin ninguna experiencia práctica. No te repitas (DRY) podría ser el lema del concepto de herencia. La herencia le permite crear una clase secundaria que hereda los campos y métodos de la clase principal sin redefinirlos. Claro, puede anular los campos y métodos de la clase principal en la clase secundaria, pero no es una necesidad. Además, puede agregar nuevos estados y comportamientos en la clase secundaria. Las clases principales a veces se denominan superclases o clases base, y las clases secundarias se conocen como subclases. La palabra clave extends de Java se utiliza para implementar el principio de herencia en el código.

Cómo funciona en Java:

  1. Cree la clase principal.
  2. Cree la clase secundaria usando la palabra clave extends .
  3. En el constructor de la clase Child, use el método super(parentField1, parentField2, ...) para establecer los campos de los padres.

Un constructor es un método especial que se utiliza para inicializar un objeto recién creado. Un constructor tiene el mismo nombre que su nombre de clase. Hay dos tipos de constructores: predeterminado (constructor sin argumentos) y constructor parametrizado. Una clase debe tener al menos un constructor (tiene el constructor predeterminado si no se han definido otros constructores) y puede tener muchos de ellos.

Cada vez que creas un nuevo objeto, llamas a su constructor. En el ejemplo anterior, haces esto en esta línea:


Student firstStudent = new Student();

Utiliza la nueva palabra clave para llamar al constructor predeterminado de la clase Student : tudent() .

Algunas reglas:

  1. Una clase solo puede tener un padre.
  2. Una clase principal puede tener muchas clases secundarias.
  3. Una clase secundaria puede tener sus propias clases secundarias.

Ejemplo de herencia en código Java

Vamos a crear una clase de teléfono .

public class Phone {
    int price;
    double weight;

// Constructor
public Phone(int price, double weight) {
        this.price = price;
        this.weight = weight;
    }

    void orderPhone(){
        System.out.println("Ordering phone...");
    }
}
Por supuesto, hay diferentes tipos de teléfonos, así que vamos a crear dos clases secundarias: una para teléfonos Android y otra para iPhone. Luego agregaremos algunos campos y métodos que el padre no tiene. Y usaremos super() para llamar a los constructores para inicializar los campos que tiene la clase principal.

Ejemplo de herencia en Java


public class Android extends Phone {

// Some new fields     
String androidVersion;
int screenSize;

    String secretDeviceCode;

// Constructor 
    public Android(int price, double weight, String androidVersion, int screenSize, String secretDeviceCode) {
        super(price, weight); // Android inherits Phone’s fields

        //this - reference to the current object
        //super - reference to the parent object

        this.androidVersion = androidVersion;
        this.screenSize = screenSize;
        this.secretDeviceCode = secretDeviceCode;
    }

	// New Android-specific method, does not exist in the Phone class 
    void installNewAndroidVersion() {
        System.out.println("installNewAndroidVersion invoked...");

    }

}

public class IPhone extends Phone {
   
    boolean fingerPrint;

    public IPhone(int price, double weight, boolean fingerPrint) {
        super(price, weight);
        System.out.println("IPhone constructor was invoked...");
        this.fingerPrint = fingerPrint;
    }

    void deleteIPhoneFromDb() {
        System.out.println("deleteIPhoneFromDb invoked...");
    }

@Override // This is about polymorphism, see below
void orderPhone(){
        System.out.println("Ordering my new iPhone and deleting the old one...");
    }
}
Entonces, para repetir: en Java, la herencia le permite extender una clase con clases secundarias que heredan los campos y métodos de la clase principal. Es una excelente manera de lograr la reutilización del código.

Polimorfismo

El polimorfismo es la capacidad de un objeto para transformarse, tomando diferentes formas o actuando de diferentes maneras. En Java, el polimorfismo generalmente ocurre cuando se usa una referencia de clase principal para referirse a un objeto de clase secundaria.

Qué significa eso y cómo funciona en Java:

¿Qué es el polimorfismo en Java? En general, eso significa que puede usar el mismo nombre de método para diferentes propósitos. Hay dos tipos de polimorfismo en Java: anulación de métodos (polimorfismo dinámico) y sobrecarga de métodos (polimorfismo estático).

Anulación de método

Puede anular el método de una clase principal en una clase secundaria, obligándolo a funcionar de manera diferente. Vamos a crear una clase padre Músico con un método play() .

Ejemplo de polimorfismo en código Java


   public class Musician {
    String name;
    int age;

    // Default constructor
    public Musician() {
    }

    // Parameterized constructor
    public Musician(String name, int age) {
        this.name = name;
        this.age = age;
    }

    void play() {
        System.out.println("I am playing my instrument...");
    }
}
Diferentes músicos usan diferentes instrumentos. Vamos a crear dos clases secundarias: Pianista y Violinista . Gracias al polimorfismo, cada uno ejecutará su propia versión del método play() . Al anular, puede usar la anotación @Override , pero no es necesario.

public class Pianist extends Musician {
    
    String favoritePianoType;

    public Pianist(String name, int age, String favoritePianoType) {
        super(name, age);
        this.favoritePianoType = favoritePianoType;
    }


    @Override
void play(){
        System.out.println("I am playing the piano...");
    }
}
El violinista puede ser solista o miembro de una orquesta. Tengamos eso en cuenta al anular nuestro método play() .

public class Violinist extends Musician { 
    boolean isSoloist; 

public Violinist(String name, int age, boolean isSoloist) {
            super(name, age);
            this.isSoloist = isSoloist;
        }


    @Override
void play(){
if (isSoloist) 
        System.out.println("I am playing the violin solo...");
else 
System.out.println("I am playing the violin in an orchestra...");

    }
}
Vamos a crear una clase Demo , en la que crearemos tres objetos, una instancia de cada una de las clases creadas anteriormente. Veremos qué resultados obtenemos.

public class Demo {
  public static void main(String[] args) {
  Musician musician = new Musician();
  Violinist violinist = new Violinist("John", 32, true);
  Pianist pianist = new Pianist("Glen", 30, "Acoustic"); 

  System.out.println("Musician said:");
  musician.play();
  System.out.println("Violinist said:");
  violinist.play();
  System.out.println("Pianist said:");
  pianist.play();
    }
}
Esto es lo que obtenemos:

Musician said:
I am playing my instrument...
Violinist said:
I am playing the violin solo…
Pianist said:
I am playing the piano...
Todo violinista y pianista es músico, pero no todo músico es violista o pianista. Eso significa que puede usar el método de reproducción del músico si no necesita crear uno nuevo. O puede llamar al método del padre desde el hijo usando la palabra clave super . Hagámoslo en el código de Pianist:

public class Pianist extends Musician {

    String favoritePianoType;
    
    @Override
    void play(){
        super.play();
        System.out.println("I am playing the piano...");
    }
}
Ahora llamemos a nuestro método main() en la clase Demo . Aquí está el resultado:

Musician said:
I am playing my instrument...
Violinist said:
I am playing the violin solo...
Pianist said:
I am playing my instrument...
I am playing the piano...

Sobrecarga de métodos

La sobrecarga de métodos significa usar varios métodos con el mismo nombre en la misma clase. Deben ser diferentes en cuanto al número, orden o tipos de sus parámetros. Supongamos que un pianista puede tocar un piano acústico y un piano eléctrico. Para tocar una eléctrica, el músico necesita electricidad. Vamos a crear dos métodos play() diferentes. El primero sin parámetros, para un piano acústico, y el segundo con un parámetro que indica si hay electricidad disponible.

public class Pianist extends Musician {

    String name;
    int age;
    String favoritePianoType;

    @Override
    void play(){
        super.play();
        System.out.println("I am playing the piano...");
    }
    void play(boolean isElectricity){
        if (isElectricity) {
            System.out.println("The electricity is on.");
            System.out.println("I am playing the piano...");
        }
        else System.out.println("I can't play this without electricity.");
    }
}
Por cierto, puedes usar el primer método play() dentro del segundo método play(booleano) de esta manera:

void play(boolean isElectricity){
        if (isElectricity) {
            System.out.println("The electricity is on.");
            play();
        }
        else System.out.println("I can't play this without electricity.");
    }
Agreguemos algunas líneas a nuestra clase Demo para demostrar nuestra sobrecarga:

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

        Musician musician = new Musician();
        Violinist violinist = new Violinist("John", 23, true);
        Pianist pianist = new Pianist("Glen", 30, "Acoustic"); 

        System.out.println("Musician said:");
        musician.play();
        System.out.println("Violinist said:");
        violinist.play();
        System.out.println("Pianist said:");
        pianist.play();
        System.out.println("The pianist will now try the electric piano:");
        pianist.play(true);
        System.out.println("The electricity has been shut off. Now when trying the electric piano, the pianist says:");
        pianist.play(false);
    }
}
Aquí está el resultado:

Musician said:
I am playing my instrument...
Violinist said:
I am playing the violin solo...
Pianist said:
I am playing my instrument...
I am playing the piano...
The pianist will now try the electric piano:
The electricity is on.
I am playing my instrument...
I am playing the piano...
The electricity has been shut off. Now when trying the electric piano, the pianist says:
I can't play this without electricity.
Java sabe qué método debe usarse según sus parámetros y el tipo de objeto. Eso es polimorfismo.

Abstracción

Cuando definimos una clase, estamos tratando de construir un modelo de algo. Por ejemplo, supongamos que estamos escribiendo un videojuego llamado MyRacer con diferentes autos de carreras. Un jugador puede elegir uno de ellos y luego actualizarlo o comprar uno diferente. Entonces… ¿Qué es un coche? Un automóvil es algo bastante complicado, pero si estamos tratando de crear un videojuego de carreras (a diferencia de un simulador de conducción), entonces no necesitamos describir los miles de engranajes y juntas que contiene. Necesitamos su modelo, velocidad punta, características de maniobrabilidad, precio, color… Y quizás eso sea suficiente. Ese es el modelo de un auto para nuestro juego. Más adelante en MyRacer 2, supongamos que decidimos agregar neumáticos que afectan el manejo en la carretera. Aquí el modelo es diferente, porque agregamos más detalles. Dejar' s define la abstracción de datos como el proceso de identificar solo las características importantes (o necesarias) de un objeto e ignorar cualquier detalle irrelevante. Hay diferentes niveles de abstracción. Por ejemplo, si usted es un pasajero en un autobús, necesita saber cómo es su autobús y hacia dónde se dirige, pero no necesita saber cómo conducirlo. Si usted es un conductor de autobús, no necesita saber cómo crear un nuevo autobús, solo necesita saber cómo conducirlo. Pero si usted es un fabricante de autobuses, necesita ir a un nivel más bajo de abstracción, porque los detalles del diseño del autobús son muy importantes para usted. Espero entiendas lo que quiero decir. necesita saber cómo es su autobús y hacia dónde se dirige, pero no necesita saber cómo conducirlo. Si usted es un conductor de autobús, no necesita saber cómo crear un nuevo autobús, solo necesita saber cómo conducirlo. Pero si usted es un fabricante de autobuses, necesita ir a un nivel más bajo de abstracción, porque los detalles del diseño del autobús son muy importantes para usted. Espero entiendas lo que quiero decir. necesita saber cómo es su autobús y hacia dónde se dirige, pero no necesita saber cómo conducirlo. Si usted es un conductor de autobús, no necesita saber cómo crear un nuevo autobús, solo necesita saber cómo conducirlo. Pero si usted es un fabricante de autobuses, necesita ir a un nivel más bajo de abstracción, porque los detalles del diseño del autobús son muy importantes para usted. Espero entiendas lo que quiero decir.

Cómo funciona en Java:

Construyamos cuatro niveles de abstracción en Java, o más bien en programación orientada a objetos, desde el más bajo (el más específico) hasta el más alto (el más abstracto).
  1. El nivel más bajo de abstracción es un objeto específico. Es una entidad con un conjunto de características que pertenecen a una clase específica. Tiene valores de campo específicos.

  2. Una plantilla para crear objetos es una clase. Es una descripción de un conjunto de objetos con propiedades y estructura interna similares.

  3. Una clase abstracta es una descripción abstracta de las características de un conjunto de clases (actúa como plantilla para la herencia de otras clases). Tiene un alto nivel de abstracción, por lo que es imposible crear objetos directamente desde una clase abstracta. Solo las clases secundarias de las clases abstractas se pueden usar para crear objetos. Una clase abstracta puede incluir métodos con una implementación, pero esto no es un requisito.

  4. Una interfaz es una construcción del lenguaje de programación Java que contiene solo métodos públicos abstractos y campos constantes estáticos (estático final). En otras palabras, ni las clases abstractas ni las interfaces se pueden usar para generar objetos.

Por cierto, en Java 8 o posterior, las interfaces pueden tener no solo métodos abstractos y constantes, sino también métodos predeterminados y estáticos. En Java, una interfaz define un comportamiento, mientras que una clase abstracta se usa para crear una jerarquía. Una interfaz puede ser implementada por múltiples clases.

Ejemplo de una interfaz en código Java


interface Human {
	public void struggle();
	public void protect();
}

interface Vulcan {
	int angleOfPointyEars; 
	public void turnOffEmotions(boolean isOn);
	public void telepathy();
}
Puede implementar más de una interfaz

The Spock class implements Human and Vulcan {
public void struggle() {
System.out.println("I am struggling...");
}
	public void protect() {
System.out.println("You are under my protection!”);
}
public void turnOffEmotions(boolean isOn){
If (isOn) {
System.out.println("I am turning off my emotions.");
isOn= !isOn;
}
}
	public void telepathy() {
System.out.println("Connecting to your brain...");
}

}
Para estudiantes principiantes, que cubre todos los conceptos principales de la programación orientada a objetos en Java. Además de los 4 principios principales de programación orientada a objetos, Java también tiene asociación, agregación y composición. Puede llamarlos "principios OOP adicionales". Se merecen su propio artículo aparte.
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION