Principios fundamentales de la programación orientada a objetos en Java

Convenciones para la denominación de componentes

En Java, el conjunto de caracteres para nombrar variables, métodos, clases y otros elementos se conoce como identificador. Las reglas para identificadores válidos incluyen el uso de letras (a-z, A-Z), dígitos (0-9), guiones bajos (_) y el signo de dólar ($). No pueden empezar con un dígito y no deben coincidir con palabras reservadas. El lenguaje distingue entre mayúsculas y minúsculas.

Las convenciones de nomenclatura promueven la legibilidad:

  • Paquetes: Todos los términos en minúsculas, p. ej., org.ejemplo.proyecto.
  • Clases e Interfaces: Cada palabra comienza con mayúscula (estilo CamelCase), p. ej., CalculadoraCientifica.
  • Variables y Métodos: La primera palabra en minúsculas y las siguientes en CamelCase, p. ej., obtenerUsuarioActual.
  • Constantes: Todas las letras en mayúsculas con guiones bajos entre palabras, p. ej., MAXIMO_INTENTOS.

Conceptos centrales de la programación orientada a objetos (POO)

La POO organiza el código en torno a clases, que actúan como plantillas para crear objetos. Una clase define la estructura (atributos) y el comportamiento (métodos) comunes a un tipo de entidad. Un objeto es una instancia concreta de una clase, con valores de atributos específicos.

Gestión de memoria en la JVM

La Máquina Virtual de Java (JVM) gestiona varias áreas de memoria durante la ejecución:

  • Pila (Stack): Almacena variables locales y marcos de pila para cada método en ejecución. Es privada por hilo.
  • Montículo (Heap): Área compartida donde se asignan todos los objetos y arrays. Es el foco principal del recolector de basura (GC).
  • Área de Métodos (Metaspace en JDK 8+): Almacena metadatos de clases, constantes y variables estáticas. Compartida por todos los hilos.
  • Contador de Programa: Mantiene la dirección del bytecode actual que se está ejecutando para cada hilo.
  • Pila de Métodos Nativos: Soporta la ejecución de métodos escritos en otros lenguajes (p. ej., C).
  • Memoria Directa: Memoria fuera del heap gestionada por la JVM, usada por NIO para operaciones de E/S eficientes.

Creación y ciclo de vida de los objetos

La asignación de memoria para un objeto sigue un proceso específico. Al instanciar una clase con new, la JVM:

  1. Carga la información de la clase (solo una vez por clase).
  2. Asigna espacio en el montículo para el nuevo objeto.
  3. Inicializa los campos del objeto: primero con valores por defecto (0, null, false), luego con los valores asignados en la declaración y finalmente mediante el constructor invocado.
  4. Devuelve la referencia (dirección de memoria) del objeto creado.
// Constructor
public Vehiculo(String marca, int anio) {
    this.marca = marca;
    this.anioFabricacion = anio;
}

}

// Creación de una instancia Vehiculo miCoche = new Vehiculo("AutoMarca", 2023);


</div>La referencia `miCoche` en la pila apunta al objeto `Vehiculo` almacenado en el montículo.

#### Alcance de las variables

Las variables se dividen en dos categorías principales según su alcance:

1. **Atributos (Variables de instancia/estáticas):** Declaradas a nivel de clase. Su alcance abarca toda la clase. Tienen valores por defecto si no se inicializan.
2. **Variables locales:** Declaradas dentro de un método o bloque. Su alcance se limita a ese bloque. Deben inicializarse explícitamente antes de su uso, ya que no tienen valor por defecto.

<div class="code-example">```
public class EjemploAlcance {
    // Atributo (variable de instancia)
    int contadorGlobal = 0;

    public void procesar() {
        // Variable local
        int valorTemporal = 10;
        System.out.println(valorTemporal); // Válido
        // System.out.println(otraLocal); // No válida, no está declarada aquí
    }
}

Los constructores son métodos especiales que inicializan un objeto al momento de su creación. Deben tener el mismo nombre que la clase y no especifican un tipo de retorno. Si no se define ningún constructor, el compilador proporciona uno público sin argumentos por defecto.

// Constructor por defecto
public Punto() {
    this.x = 0;
    this.y = 0;
}

// Constructor parametrizado
public Punto(int x, int y) {
    this.x = x; // 'this' se refiere al objeto actual
    this.y = y;
}

}


</div>### Los tres pilares de la POO en Java

#### 1. Encapsulamiento

El encapsulamiento consiste en ocultar los datos internos de un objeto y proporcionar un acceso controlado a través de métodos públicos (getters y setters). Esto protege la integridad de los datos.

<div class="code-example">```
public class CuentaBancaria {
    private double saldo; // Encapsulado

    public double getSaldo() {
        return saldo; // Solo lectura pública
    }

    public void depositar(double cantidad) {
        if (cantidad > 0) {
            this.saldo += cantidad; // Control interno
        }
    }
}

La herencia permite que una clase (subclase o clase hija) adquiera los atributos y métodos de otra clase (superclase o clase padre), promoviendo la reutilización de código. Se establece con la palabra clave extends.

class Perro extends Animal { public void ladrar() { System.out.println("¡Guau!"); } }

Perro miPerro = new Perro(); miPerro.nombre = "Fido"; miPerro.comer(); // Heredado de Animal miPerro.ladrar();


</div>**Reglas clave:** Java soporta herencia simple. Los métodos y atributos privados del padre no son directamente accesibles en la subclase. Si la superclase no tiene un constructor sin parámetros, la subclase debe llamar explícitamente a un constructor válido del padre usando `super()`.

#### 3. Polimorfismo

El polimorfismo permite que una entidad (método u objeto) tome múltiples formas. Se manifiesta principalmente de dos maneras:

- **Sobrecarga (Compile-time):** Múltiples métodos con el mismo nombre pero diferentes parámetros dentro de la misma clase.
- **Sobreescritura (Runtime):** Una subclase proporciona una implementación específica para un método ya definido en su superclase.

El polimorfismo de objetos es fundamental. Una referencia de tipo padre puede apuntar a un objeto de tipo hijo (**conversión ascendente**). En tiempo de ejecución, se invoca el método sobreescrito en la subclase.

<div class="code-example">```
class Figura {
    public double calcularArea() { return 0; }
}

class Circulo extends Figura {
    private double radio;
    public Circulo(double r) { this.radio = r; }
    @Override
    public double calcularArea() { return Math.PI * radio * radio; }
}

class Rectangulo extends Figura {
    private double ancho, alto;
    public Rectangulo(double a, double h) { ancho = a; alto = h; }
    @Override
    public double calcularArea() { return ancho * alto; }
}

// Uso polimórfico
Figura f1 = new Circulo(5);
Figura f2 = new Rectangulo(4, 6);
System.out.println(f1.calcularArea()); // Invoca el de Circulo
System.out.println(f2.calcularArea()); // Invoca el de Rectangulo


</div>**Propieadd importante:** La resolución de métodos en tiempo de ejecución se basa en el **tipo real del objeto** (runtime type), mientras que la resolución de atributos se basa en el **tipo de la referencia** (compile-time type).

#### Vinculación dinámica

La vinculación dinámica es el mecanismo que permite que la invocación de un método se resuelva en tiempo de ejecución basándose en la clase del objeto, no en el tipo de la refernecia. Esto es lo que hace posible el polimorfismo de sobreescritura.

<div class="code-example">```
class Padre {
    int valor = 10;
    public int obtenerValor() { return valor; }
}

class Hijo extends Padre {
    int valor = 20;
    @Override
    public int obtenerValor() { return valor; }
}

Padre ref = new Hijo();
// Vinculación dinámica: llama a Hijo.obtenerValor()
System.out.println(ref.obtenerValor()); // Imprime 20
// Resolución de atributo: usa el tipo de la referencia (Padre)
System.out.println(ref.valor); // Imprime 10

Etiquetas: java POO herencia polimorfismo jvm

Publicado el 6-7 17:57