En PC Resumen hablaremos de las herencias en Java. La herencia es una de las herramientas fundamentales de la programación orientada a objeto porque permite la creación de clasificaciones jerárquicas. Mediante la herencia el programador puede crear una clase general que defina las características comunes de un conjunto de términos relacionado. Esta clase puede ser heredada por otras clases más específicas, cada una de las cuales añadirá nuevas características.

En Java, una clase que es heredada llama superclase y la clase que hereda denomina subclase.

Para heredar una clase sólo tenemos que incorporar la palabra clave extends. Vemos el siguiente programa el cual crea una superclase llamada A y una subclase llamada B. Fíjese en la utilización de la palabra clave extends.

En PC Resumen hablaremos del control de acceso en Java. El encapsulamiento relaciona los datos con el código que las manipula, pero además el encapsulamiento proporciona otro atributo importante: el control de acceso. Con el encapsulamiento se puede controlar el acceso de miembros de una clase a las partes del programa. Controlando el acceso se puede prevenir un uso indebido. Por ejemplo, si sólo se permite el acceso a los datos a través de métodos bien definidos, se impide una mala utilización de estos datos. Por lo tanto, cuando se implementa correctamente una clase, se crea una caja negra que puede utilizarse pero el funcionamiento interno no está abierto a la actuación externa.

El especificador de acceso determina cómo puede accederse a un miembro de la clase modificando su declaración. Java proporciona un conjunto de especificadores de acceso. Algunos aspectos de control están más relacionados con la herencia o los paquetes y esto ya se verá más adelante. Ahora, comenzaremos examinando el control de acceso tal y como se aplica a una sola clase.

Los especificadores de acceso son public, protected y private. El especificador protected sólo tiene sentido cuando interviene la herencia. Ahora veremos los otros dos.

Cuando a un miembro de una clase se le aplica el especificador público, se puede acceder a este miembro para cualquier código del programa. Cuando el miembro especifica private, únicamente será accesible a través de otros miembros de la clase. Cuando no se utiliza ningún especificador de acceso, el miembro de una clase se predeterminado público dentro de su paquete, pero no se puede acceder desde fuera de su paquete. Más adelante ya hablaremos de los paquetes.

Hasta ahora, todas las clases desarrolladas han sido públicas, pero esto no es lo más habitual. Normalmente, se quiere restringir el acceso a los datos de una clase, sólo permitiendo el acceso a través de los métodos. A veces también, se definen métodos privados para una clase.

El especificador de acceso precede al resto de especificaciones de tipo de un miembro. Vemos un programa de ejemplo:

class Test {
    int a; // acceso predeterminado.
    public int b; // acceso público.
    private int c; // acceso privado.

    // métodos para acceder a c.

    void ECTS (int i) { // Se establece el valor de c.
        c = y;
    }
   
    int getc () {// Se obtiene el valor de c.
        return c;
    }
}

class AccessTest {
    public static void main (String args []) {
        Test ob = new Test ();
 
        // Estos son correctos y se puede acceder directamente a a y b.

        ob.a = 10;
        ob.b = 20;

        // Esto no es correcto

        // ob.c = 100; // Error!

        // Se debe acceder a c a través de sus métodos.

        ob.setc (100); // OK
        System.out.println ( "a, b, y c:" + ob.a + "" +
        ob.b + "" + ob.getc ());
    }
}

Si elimináramos el comentario de la línea // ob.c = 100; sería imposible compilar el programa porque habría violación de acceso.

Aunque los métodos normalmente proporcionan una acceso a los datos definidos por una clase, esto no tiene por qué ser siempre así. De todas formas, la mayoría de clases reales, será necesario permitir operaciones sobre los datos sólo a través de sus métodos. Esto es especialmente importante cuando se involucran cuestiones de herencia.

Static

Puede haber ocasiones en las que se quiera definir un miembro de clase que se utilizará independientemente de cualquier objeto de esta clase. Normalmente, sólo se puede acceder a un miembro de la clase utilizando un objeto de esta clase, pero también es posible crear un miembro que pueda usarse a sí mismo sin referencia a una instancia específica. Para crear un miembro de este tipo, es necesario que la declaración vaya precedida de la palabra clave static. Cuando un miembro se declara static, puede accederse a él antes de que se haya creado ningún objeto de su clase, y sin referencia a ningún objeto. Se pueden declarar static tanto métodos como variables. El ejemplo más común es el método main el que se declara static porque hay que llamar antes de que exista ningún objeto. Las variables de instancia declaradas como static son básicamente variables globales. Cuando se declaran los objetos de clase, no se hace ninguna copia de las variables static. En cambio, todas las instancias de clase comparten la misma variable.

Los métodos declarados static tienen algunas restricciones:

  • Sólo pueden llamar a otros miembros declarados static
  • Sólo pueden acceder a datos static
  • De ninguna manera poder hacer referencia a este o a super (esta palabra está relacionada con la herencia y la veremos más adelante).

Si lo que se necesita es hacer un cálculo en lugar de inicializar las variables static, puede declararse un bloque static que se ejecute una sola vez, cuando se carga la clase por primera vez. Vemos el siguiente ejemplo:

class UsStatic {
    static int a = 3;
    static int b;
    static void meth (int x) {
        System.out.println ( "x =" + x);
        System.out.println ( "a =" + a);
        System.out.println ( "b =" + b);
    }
    static {
        System.out.println ( "Static bloque inicializado.");
        b = a * 4;
    }
    public static void main (String args []) {
        meth (42);
    }
}

Todas las sentencias static ejecutan en cuando se carga la clase UsStatic. En primer lugar, se le asigna 3 a la variable a, después ejecuta el bloque static y finalmente se llama al main.

Fuera de la clase en el que están definidos, los métodos y variables static pueden usarse independientemente de cualquier objeto. Para hacerlo sólo es necesario especificar el nombre de la clase seguido del operador punto.

Final

Puede declararse una variable como final para prevenir que su contenido sea modificado. Esto significa que debe inicializar una variable final en el momento de ser declarada. De hecho estamos declarando constantes (lo que haríamos con #define).
Un convenio muy común es el de elegir identificadores en mayúsculas para las variables final. Esta palabra también se puede aplicar a los métodos, pero su significado es muy diferente y ya lo veremos cuando hablamos de herencia.

En PC Resumen hablaremos de como pasar objetos como parámetros en Java.

Hasta el momento sólo hemos utilizado tipos simples como parámetros de los métodos pero es común y perfectamente correcto, pasar objetos a los métodos.

Uno de los usos más comunes de los parámetros de objetos implica a los constructores. Muy a menudo, se quiere construir un nuevo objeto de forma que sea igual a otro objeto ya existente. Para hacerlo, se debe definir un constructor que tome un objeto de su clase como parámetro. Por ejemplo, la siguiente versión de Caja permite que un objeto inicialice a otro:

class Caja {
    double anchura;
    double altura;
    double profundidad;
 
    // Se construye una copia idéntica del objeto

    Caja(Caja ob) {  // Pasamos un objeto al constructor
        anchura = ob.amplada;
        altura = ob.alçada;
        profundidad = ob.profunditat;
    }

    // Constructor que se utiliza cuando se especifican todas las dimensiones
    
    Caja(double w, double h, double d) {
        anchura = w;
        altura = h;
        profundidad = d;
    }

    // Constructor que se utiliza cuando no se especifican dimensiones

    Caja() {
        anchura = -1; // Usar -1 para indicar
        altura = -1; // una caja
        profundidad = -1; // no inicializada.
    }

    // Constructor que se utiliza cuando se crea un cubo

    Caja(double len) {
        anchura = altura = profundidad = len;
    }

    // Cálculo y regreso del volumen.

    double volumen() {
        return ancho * altura * profundidad;
    }
}

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

        // Se crean diferentes cajas

        Caja cajaMia1 = new Caja(10, 20, 15);
        Caja cajaMia2 = new Caja();
        Caja cubo = new Caja(7);
        Caja clon = new Caja(cajaMia1);
    
        ......................
    }
}

Paso de argumentos

En Java cuando se pasa un tipo simple a un método, se pasa por valor. Por tanto, lo que le pueda pasar al parámetro que recibe el argumento, no tiene efecto fuera del método.

Cuando se pasa un objeto a un método, la situación cambia para que los objetos se pasan por referencia. Debemos tener en cuenta que cuando se crea una variable del tipo de clase, lo que se está creando únicamente es una referencia a un objeto. Por lo tanto, cuando se pasa esta referencia a un método, el parámetro que lo recibe hará referencia al mismo objeto al que se refiere el argumento. Los cambios en el objeto dentro del método afectarán al objeto utilizado como argumento.

Retorno de objetos

Un método puede devolver cualquier tipo de datos, incluyendo los tipos de clase creados por el programador. Por ejemplo en el siguiente programa, el método incrDeu() devuelve un objeto en el que el valor a es diez veces mayor que en el objeto que llama al método:

class Test {
    int a;
    
    Test(int i) {
        a = i;
    }

    Test incrDeu() {
        Test temp = new Test(a+10);
        return temp;
    }
}

class RetOb {
    public static void main(String args[]) {
        Test OB1 = new Test(2);
        Test OB2;
        OB2 = ob1.incrDeu();
        System.out.println( "ob1.a:" + ob1.a);
        System.out.println( "ob2.a:" + ob2.a);
        OB2 = ob2.incrDeu();
        System.out.println( "ob2.a después del segundo incremento: " + ob2.a);
    }
}

Cada vez que se llama a incrDeu(), se crea un nuevo objeto y una referencia de este objeto se devuelve a la rutina que hace la llamada.

El programa anterior muestra otro punto interesante. Dado que todos los objetos se crean dinámicamente utilizando el operador new, no hay que preocuparse de si un objeto desaparece cuando finaliza el método en el que fue creado. El objeto continuará existiendo siempre y cuando en algún lugar del programa exista una referencia a él. Cuando ya no haya referencias este será eliminado.

En PC Resumen hablaremos de que es la sobrecarga de métodos. A Java, dentro de una misma clase pueden definirse dos o más métodos con el mismo nombre, aunque la declaración de sus parámetros sea diferente. Cuando esto ocurre, se dice que los métodos están sobrecargado.

Cuando se llama a un método sobrecargado, Java utiliza el tipo y / o número de argumentos como guía para determinar qué versión del método debe ejecutar. Por lo tanto, los métodos sobrecargados deben ser diferentes en el tipo y / o en el número de parámetros. Por ejemplo:

class OverloadDemo {
    void test() {
        System.out.println("No parámetros");
    }

    // Sobrecarga el método test con un parámetro entero.

    void test(int a) {
         System.out.println("en: " + a);
    }

    // Sobrecarga el método test con dos parámetros enteros.
    
    void test(int a, int b) { 
        System.out.println("a y b:" + a + "" + b);
    }

    // Sobrecarga el método test con un parámetro doble.

    double test(double a) {
        System.out.println("double a: " + a);
        return a * a;
    }
}

class Overload {
    public static void main(String args[]) {
        OverloadDemo ob = new OverloadDemo();
        double Resultado;

        // Llama a todas las versiones del test().
        
        ob.test();
        ob.test(10);
        ob.test(10, 20);
        Resultado = ob.test(123.25);
        System.out.println("Resultado de ob.test(123.25): " + Resultado);
    }
}

La importancia de la sobrecarga radica en que permite relacionar los métodos a los que puede accederse mediante el uso de un nombre común. Cuando se sobrecarga un método, cada versión de este método puede realizar la actividad que queramos. No existe ninguna regla que establezca que los métodos sobrecargados deben relacionarse entre sí, pero desde un punto de vista de estilo, la sobrecarga implica la relación.

La sobrecarga en los constructores

Además de los métodos normales de sobrecarga, también se puede sobrecargar un método constructor. De hecho, es la situación más frecuente. Antes hemos creado un método constructor para la clase Caja, que tenía tres parámetros. Esto significa que todas las declaraciones de los objetos Caja deben pasar tres argumentos al constructor. Esto hace que si queremos una caja sin saber cuáles son las dimensiones, o en el caso de que quisiéramos inicializar un cubo especificando sólo un valor que utilizaríamos para las tres dimensiones, no lo podríamos hacer.

La solución a estos 'problemas' es muy sencilla, se trata únicamente de sobrecargar el constructor de forma que gestione estas situaciones. Por ejemplo:

class Caja {
    double anchura;
    double altura;
    double profundidad;

    // Constructor que se utiliza cuando se especifican todas las dimensiones

    Caja(double w, double h, double d) {
        anchura = w;
        altura = h;
        profundidad = d;
    }

    // Constructor que se utiliza cuando no se especifican dimensiones

    Caja() {
        anchura = -1; // Usar -1 para indicar
        altura = -1; // una caja
        profundidad = -1; // no inicializada.
    }

    // Constructor que se utiliza cuando se crea un cubo

    Caja (double len) {
        anchura = altura = profundidad = len;
        }

    // Cálculo y regreso del volumen.
  
    double volumen () {
        return ancho * altura * profundidad;
    }
}

En PC Resumen hablaremos de los constructores de clases en Java

Puede ser una tarea bastante pesada inicializar todas las variables de una clase cada vez que se crea una instancia. Incluso cuando se añaden funciones como setDim(), es más sencillo y preciso realizar todas las inicializaciones cuando el objeto se crea por primera vez. Como el proceso de inicialización es tan común, Java permite que los objetos se inicializa cuando se crean. Esta inicialización automática se lleva a cabo mediante un constructor.

Un constructor inicializa un objeto inmediatamente después de su creación; tiene el mismo nombre de la clase y es sintácticamente similar a un método. Una vez que se define, se llama automáticamente al constructor justo después de que se haya creado el objeto y antes de que termine el operador new. Los constructores resultan un tanto extraños porque no devuelven nada, ni siquiera vacío. Esto se debe a que el tipo implícito que devuelve el constructor es el mismo tipo de la clase. La función del constructor es inicializar el estado interno de un objeto de forma que el código que crea una instancia tenga una inicialización completa que podrá ser utilizada por ejemplo el objeto de forma inmediata.