Modificadores sin acceso en Java

M

Introducción

Modificadores son palabras clave que nos permiten ajustar el acceso a nuestra clase y sus miembros, su alcance y comportamiento en determinadas situaciones. Por ejemplo, podemos controlar qué clases / objetos pueden acceder a ciertos miembros de nuestra clase, si una clase puede ser heredada o no, si podemos anular un método más tarde, si deberíamos anular un método más tarde, etc.

Palabras clave modificadoras se escriben antes de la variable / método / clase (retorno) tipo y nombre, por ejemplo private int myVar o public String toString().

Los modificadores en Java se dividen en uno de dos grupos: acceso y no acceso:

  • Acceso: public, private, protected.
  • Sin acceso: estático, final, abstracto, sincronizado, volátil, transitorio y native.

native no se trata con más detalle a continuación, ya que es una palabra clave simple que marca un método que se implementará en otros lenguajes, no en Java. Funciona junto con la interfaz nativa de Java (JNI). Se usa cuando queremos escribir secciones de código críticas para el rendimiento en lenguajes más amigables con el rendimiento (como C).

¿Quiere obtener más información sobre los modificadores de acceso, en lugar de la falta de acceso? Si es así, consulte nuestro artículo Modificadores de acceso en Java.

Modificadores sin acceso

Estos tipos de modificadores se utilizan para controlar una variedad de cosas, como las capacidades de herencia, si todos los objetos de nuestra clase comparten el mismo valor de miembro o tienen sus propios valores de esos miembros, si un método se puede anular en una subclase, etc.

En la siguiente tabla se puede encontrar una breve descripción de estos modificadores:

Descripción general del nombre del modificador
estáticofinalabstractosincronizadovolátiltransitorio

El miembro pertenece a la clase, no a los objetos de esa clase.
Los valores de las variables no se pueden cambiar una vez asignados, los métodos no se pueden anular, las clases no se pueden heredar.
Si se aplica a un método, debe implementarse en una subclase, si se aplica a una clase, contiene métodos abstractos.
Controla el acceso de subprocesos a un bloque / método.
El valor de la variable siempre se lee de la memoria principal, no de la memoria de un hilo específico.
El miembro se omite al serializar un objeto.

El modificador estático

los static modifier hace que un miembro de la clase sea independiente de cualquier objeto de esa clase. Hay algunas características a tener en cuenta aquí:

  • Variables declarado static se comparten entre todos los objetos de una clase (ya que la variable esencialmente pertenece a la clase misma en este caso), es decir, los objetos no tienen sus propios valores para esa variable, en cambio, todos comparten uno solo.
  • Variables y métodos declarado static se puede acceder a través del nombre de la clase (en lugar de la referencia de objeto habitual, p. ej. MyClass.staticMethod() o MyClass.staticVariable), y se puede acceder a ellos sin que se cree una instancia de la clase.
  • static los métodos solo pueden usar static variables y llamar a otras static métodos, y no puede referirse a this o super de cualquier forma (una instancia de objeto puede que ni siquiera exista cuando llamamos a static método, entonces this no tendría sentido).

Nota: Es muy importante tener en cuenta que static las variables y los métodos no pueden accederstatic (instancia) variables y métodos. Por otro lado, nostatic las variables y los métodos pueden acceder static variables y métodos.

Esto es lógico, ya que static los miembros existen incluso sin un objeto de esa clase, mientras que los miembros de instancia existen solo después de que se ha instanciado una clase.

Variables estáticas

Para las variables, usamos static si queremos que la variable sea común / compartida para todos los objetos.

Echemos un vistazo a cómo static las variables se comportan de manera diferente a las variables de instancia regulares:

class StaticExample {
    public static int staticInt = 0;
    public int normalInt = 0;
    
    // We'll use this example to show how we can keep track of how many objects
    // of our class were created, by changing the shared staticInt variable
    public StaticExample() {
        staticInt++;
        normalInt++;
    }
}
// No instances of StaticExample have been created yet
System.out.println(StaticExample.staticInt); // Prints: 0
// System.out.println(StaticExample.normalInt); // this won't work, obviously

// Let's create two instances of StaticExample
StaticExample object1 = new StaticExample();
// We can refer to static variables via an object reference as well, 
// however this is not common practice, we usually access them via class name
// to make it obvious that a variable/method is static
System.out.println(object1.staticInt); // Prints: 1
System.out.println(object1.normalInt); // Prints: 1

StaticExample object2 = new StaticExample();
System.out.println(object2.staticInt); // Prints: 2
System.out.println(object2.normalInt); // Prints: 1

// We can see that increasing object2's staticInt 
// increases it for object1 (and all current or future objects of that class)

object1.staticInt = 10;
object1.normalInt = 10;
System.out.println(object2.staticInt); // Prints: 10
System.out.println(object2.normalInt); // Prints: 1 (object2 retained its own value for normalInt as it depends on the class itself)

Métodos estáticos

El ejemplo más común de uso static es el main() método, se declara como static porque se debe llamar antes de que exista ningún objeto. Otro ejemplo común es el Math clase ya que usamos los métodos de esa clase sin hacer una instancia de ella primero (como Math.abs()).

Una buena forma de pensar static métodos es “¿Tiene sentido utilizar este método sin crear primero un objeto de esta clase?” (por ejemplo, no es necesario crear una instancia del Math clase para calcular el valor absoluto de un número).

Se pueden utilizar métodos estáticos para acceder y modificar static miembros de una clase. Sin embargo, se usan comúnmente para manipular parámetros de métodos o calcular algo y devolver un valor.

Estos métodos se conocen como métodos de utilidad:

static int average(int num1, int num2) {
    return (num1+num2)/2;
}

Este método de utilidad se puede utilizar para calcular el promedio de dos números, por ejemplo.

Como se mencionó anteriormente, el Math la clase se usa a menudo para llamar static métodos. Si miramos el código fuente, podemos notar que en su mayoría ofrece métodos de utilidad:

public static int abs(int i) {
    return (i < 0) ? -i : i;
}

public static int min(int a, int b) {
    return (a < b) ? a : b;
}

public static int max(int a, int b) {
    return (a > b) ? a : b;
}

Bloques estáticos

También hay un static bloquear. UN static El bloque se ejecuta solo una vez cuando la clase se instancia por primera vez (o una static miembro, incluso si la clase no está instanciada), y antes del resto del código.

Agreguemos un static bloquear a nuestro StaticExample clase:

class StaticExample() {
    ...
    static {
        System.out.println("Static block");
    }
    ...
}
StaticExample object1 = new StaticExample(); // "Static block" is printed
StaticExample object2 = new StaticExample(); // Nothing is printed

Independientemente de su posición en la clase, static Los bloques se inicializan antes que cualquier otro bloque no estático, incluidos los constructores:

class StaticExample() {
    public StaticExample() {
        System.out.println("Hello from the constructor!");
    }

    static {
        System.out.println("Hello from a static block!");
    }
}

La instanciación de esta clase daría como resultado:

Hello from a static block!
Hello from the constructor!

Si es múltiple static los bloques están presentes, se ejecutarán en su orden respectivo:

public class StaticExample {
    
    static {
        System.out.println("Hello from the static block! 1");
    }
    
    public StaticExample() {
        System.out.println("Hello from the constructor!");
    }
    
    static {
        System.out.println("Hello from the static block! 2");
    }
}

La instanciación de esta clase daría como resultado:

Hello from the static block! 1
Hello from the static block! 2
Hello from the constructor!

Importaciones estáticas

Como ya se mencionó, es mejor llamar static miembros prefijados con el nombre de la clase, en lugar del nombre de la instancia. Además, en algunos casos, nunca instanciamos realmente una clase con static métodos, como el Math class, que ofrece numerosos métodos de utilidad relacionados con las matemáticas.

Dicho esto, si usamos una clase ‘ static miembros a menudo, podemos importar miembros individuales o todos ellos usando un static import. Esto nos permite omitir el prefijo de sus llamadas con el nombre de la clase:

package packageOne;

public class ClassOne {
    static public int i;
    static public int j;

    static public void hello() {
        System.out.println("Hello World!");
    }
}
package packageTwo;

static import packageOne.ClassOne.i;

public class ClassTwo {
    public ClassTwo() {
        i = 20;
    }
}

O, si quisiéramos importar todos static miembros de ClassOne, podríamos hacerlo así:

package packageTwo;

import static packageOne.ClassOne.*;

public class ClassTwo {
    public ClassTwo() {
        i = 20;
        j = 10;
    }
}

Lo mismo se aplica a los métodos:

package packageTwo;

import static packageOne.ClassOne.*;

public class ClassTwo {
    public ClassTwo() {
        hello();
    }
}

Ejecutar esto daría como resultado:

Hello World!

Esto puede no parecer tan importante, pero ayuda cuando llamamos a muchos static miembros de una clase:

public int someFormula(int num1, int num2, int num3) {
    return Math.ceil(Math.max(Math.abs(num1), Math.abs(num2))+Math.max(Math.abs(num2), Math.abs(num3)))/(Math.min(Math.abs(num1), Math.abs(num2))+Math.min(Math.abs(num2), Math.abs(num3)));
}

// Versus...
import static java.lang.Math.*;
public int someFormula(int num1, int num2, int num3) {
    return ceil(max(abs(num1), abs(num2))+max(abs(num2), abs(num3)))/(min(abs(num1), abs(num2))+min(abs(num2), abs(num3)));
}

El modificador final

La palabra clave final puede tener uno de tres significados:

  • para definir constantes con nombre (variables cuyos valores no pueden cambiar después de la inicialización)
  • para evitar que se anule un método
  • para evitar que una clase sea heredada

Constantes nombradas

Añadiendo el final El modificador a una declaración de variable hace que esa variable sea invariable una vez que se inicializa.

los final El modificador se usa a menudo junto con el static modificador si estamos definiendo constantes. Si solo aplicamos static a una variable, todavía se puede cambiar fácilmente. También hay una convención de nomenclatura vinculada a esto:

static final double GRAVITATIONAL_ACCELERATION = 9.81;

Variables como estas a menudo se incluyen en clases de utilidad, como la Math class, acompañado de numerosos métodos de utilidad.

Aunque, en algunos casos, también garantizan sus propias clases, como Constants.java:

public static final float LEARNING_RATE = 0.3f;
public static final float MOMENTUM = 0.6f;
public static final int ITERATIONS = 10000;

Nota: cuando usas final con las variables de referencia de objeto, tenga cuidado con el tipo de comportamiento que espera. Considera lo siguiente:

class MyClass {
    int a;
    int b;

    public MyClass() {
        a = 2;
        b = 3;
    }
}
    final MyClass object1 = new MyClass();
    MyClass object2 = new MyClass();

La variable de referencia object1 es de hecho final y su valor no puede cambiar, pero ¿qué significa eso para las variables de referencia de todos modos? Esto significa que object1 ya no puede cambiar a qué objeto apunta, pero podemos cambiar el objeto en sí. Esto es algo que a menudo confunde a la gente:

    // object1 = object2; // Illegal!
    object1.a = 5; // Perfectly fine

Los parámetros del método también se pueden declarar final. Esto se usa para asegurarnos de que nuestro método no cambie el parámetro que recibe cuando es llamado.

Las variables locales también se pueden declarar final. Esto se usa para asegurarse de que la variable reciba un valor solo una vez.

Evitar la anulación

Si especifica el final modificador al definir un método, ninguna subclase futura no puede anularlo.

class FinalExample {
    final void printSomething() {
        System.out.println("Something");
    }
}
class ExtendsFinalExample extends FinalExample {
    // This would cause a compile-time error
    //void printSomething() {
    //  System.out.println("Some other thing");
    //}
    
    // However, you are perfectly free to overload this method
    void printSomething(String something) {
        System.out.println(something);
    }
}

Una pequeña ventaja de declarar métodos verdaderamente finales como final es un ligero aumento de rendimiento cada vez que llamamos a este método. Por lo general, Java resuelve las llamadas a métodos dinámicamente en tiempo de ejecución, pero con métodos declarados final, Java puede resolver una llamada al mismo en tiempo de compilación, o si un método es realmente pequeño, puede simplemente hacer llamadas en línea a ese método ya que “sabe” que no será anulado. Esto elimina la sobrecarga asociada con una llamada a un método.

Prevenir la herencia

Este uso de final es bastante sencillo, una clase definida con final no se puede heredar. Esto, por supuesto, declara implícitamente todos los métodos de esa clase como finales también (no se pueden anular si la clase no se puede heredar en primer lugar).

final class FinalExample {...}

El modificador abstracto

los abstract El modificador se usa para definir métodos que se implementarán en una subclase más adelante. La mayoría de las veces se usa para sugerir que alguna funcionalidad debe implementarse en una subclase o (por alguna razón) no se puede implementar en la superclase. Si una clase contiene un abstract método, también debe ser declarado abstract.

Nota: No puede crear un objeto de un abstract clase. Para hacer eso, debe proporcionar una implementación para todos los abstract métodos.

Un ejemplo sería si tuviéramos una clase simple llamada Employee que encapsula datos y métodos para un empleado. Digamos que no a todos los empleados se les paga de la misma manera, a algunos tipos de empleados se les paga por hora y a otros se les paga un salario fijo.

abstract class Employee {
    int totalHours; // In a month
    int perHour;    // Payment per hour
    int fixedRate;  // Fixed monthly rate
    ...
    abstract int salary();
    ...  
}
class Contractor extends Employee {
    ...
    // Must override salary if we wish to create an object of this class
    int salary() {
        return totalHours*perHour; 
    }
    ...
}
class FullTimeEmployee extends Employee {
    ...
    int salary() {
        return fixedRate; 
    }
    ...
}
class Intern extends Employee {
    ...
    int salary() {
        return 0; 
    }
    ...
}

Si una subclase no proporciona una implementación para todos abstract métodos en la superclase, debe declararse como abstract también, y no se puede crear un objeto de esa clase.

Nota: abstract se usa mucho con polimorfismo, por ejemplo, diríamos ArrayList<Employee> employees = new ArrayList();, y añadir Contractor, FullTimeEmployeey Intern se opone a ello. Aunque no podemos crear un objeto del Employee class, todavía podemos usarlo como un tipo de variable de referencia.

El modificador sincronizado

Cuando dos o más subprocesos necesitan usar el mismo recurso, de alguna manera debemos asegurarnos de que solo uno de ellos tenga acceso a él a la vez, es decir, necesitamos sincronizarlos.

Esto se puede lograr de varias maneras, y una forma simple y legible (aunque con un uso algo limitado) es mediante el synchronized palabra clave.

Un concepto importante que debe comprender antes de ver cómo utilizar esta palabra clave es el concepto de monitor. Cada objeto en Java tiene su propio monitor implícito asociado. Un monitor es un bloqueo “mutuamente excluyente”, lo que significa que solo un hilo puede “poseer” un monitor a la vez. Cuando un hilo entra en el monitor, ningún otro hilo puede entrar hasta que sale el primer hilo. Esto es lo que synchronized hace.

Los subprocesos están más allá del alcance de este artículo, por lo que me centraré en la sintaxis de synchronized solamente.

Podemos sincronizar el acceso a métodos y bloques de código. La sincronización de bloques de código funciona proporcionando una instancia de objeto con la que queremos sincronizar el acceso y el código que queremos realizar relacionado con ese objeto.

class SynchronizedExample {

    ...
    SomeClass object = new SomeClass();
    ....

    synchronized(object) {
         // Code that processes objects
         // only one thread at a time
    }
    
    // A synchronized method
    synchronized void doSomething() {
         ...
    }
    ...
}

El modificador volátil

los volatile El modificador le dice a Java que una variable puede ser cambiada inesperadamente por alguna otra parte del programa (como en la programación multiproceso), y que el valor de la variable siempre se lee desde la memoria principal (y no desde la memoria caché de la CPU), y que cada cambio en el volatile La variable se almacena en la memoria principal (y no en la caché de la CPU). Teniendo esto en cuenta, volatile solo debe usarse cuando sea necesario, ya que leer / escribir en la memoria cada vez es más costoso que hacerlo con la memoria caché de la CPU y solo leer / escribir en la memoria cuando sea necesario.

En términos simplificados, cuando un hilo lee un volatile valor variable, se garantiza que leerá el valor escrito más recientemente. Básicamente, un volatile variable hace lo mismo que synchronized métodos / bloques, simplemente no podemos declarar una variable como synchronized.

El modificador transitorio

Cuando una variable se declara como transient, eso significa que su valor no se guarda cuando el objeto se almacena en la memoria.
transient int a; significa que cuando escribimos el objeto en la memoria, el contenido de “a” no se incluirá. Por ejemplo, se utiliza para asegurarnos de que no almacenamos información privada / confidencial en un archivo.

Cuando intentamos leer un objeto que contiene transient variables, todas transient los valores de las variables se establecerán en null (o valores predeterminados para tipos primitivos), sin importar cuáles fueran cuando escribimos el objeto en el archivo. Otro ejemplo de uso sería cuando el valor de una variable debe derivarse de otros datos (como la edad actual de alguien) y no es parte del estado del objeto persistente.

Nota: Algo muy interesante ocurre cuando usamos transient y final juntos. Si tenemos un transient final variable que se evalúa como una expresión constante (cadenas o tipos primitivos), la JVM siempre la serializará, ignorando cualquier potencial transient modificador. Cuando transient final se usa con variables de referencia, obtenemos el comportamiento predeterminado esperado de transient.

Conclusión

Los modificadores son palabras clave que nos permiten ajustar el acceso a nuestra clase y sus miembros, su alcance y comportamiento en determinadas situaciones. Proporcionan rasgos fundamentales para nuestras clases y sus miembros. Todo desarrollador debe conocerlos a fondo para aprovecharlos al máximo.

Como ser consciente de que protected el control de acceso se puede omitir fácilmente, o el transient final modificador cuando se trata de expresiones constantes.

 

About the author

Ramiro de la Vega

Bienvenido a Pharos.sh

Soy Ramiro de la Vega, Estadounidense con raíces Españolas. Empecé a programar hace casi 20 años cuando era muy jovencito.

Espero que en mi web encuentres la inspiración y ayuda que necesitas para adentrarte en el fantástico mundo de la programación y conseguir tus objetivos por difíciles que sean.

Add comment

Sobre mi

Últimos Post

Etiquetas

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con tus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, aceptas el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Más información
Privacidad