Introducción
Contenido
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()
oMyClass.staticVariable
), y se puede acceder a ellos sin que se cree una instancia de la clase. static
los métodos solo pueden usarstatic
variables y llamar a otrasstatic
métodos, y no puede referirse athis
osuper
de cualquier forma (una instancia de objeto puede que ni siquiera exista cuando llamamos astatic
método, entoncesthis
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:
Te puede interesar:Problema de vendedor ambulante con algoritmos genéticos en JavaHello 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
, FullTimeEmployee
y 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.