"Te voy a hablar de los « modificadores de acceso ». Ya te hablé de ellos una vez, pero la repetición es un pilar del aprendizaje."
Puedes controlar el acceso (visibilidad) que tienen otras clases a los métodos y variables de tu clase. Un modificador de acceso responde a la pregunta «¿Quién puede acceder a este método/variable?». Puede especificar solo un modificador para cada método o variable.
1) Modificador « público ».
Se puede acceder a una variable, método o clase marcada con el modificador público desde cualquier parte del programa. Este es el mayor grado de apertura: no hay restricciones.
2) Modificador « privado ».
Solo se puede acceder a una variable, método o clase marcada con el modificador privado en la clase donde se declara. El método o la variable marcada se oculta de todas las demás clases. Este es el grado más alto de privacidad: accesible solo por su clase. Dichos métodos no se heredan y no se pueden invalidar. Además, no se puede acceder a ellos en una clase descendiente.
3) « Modificador por defecto ».
Si una variable o método no está marcado con ningún modificador, se considera que está marcado con el modificador "predeterminado". Las variables y los métodos con este modificador son visibles para todas las clases del paquete donde se declaran, y solo para esas clases. Este modificador también se denomina acceso " paquete " o " paquete privado ", lo que sugiere el hecho de que el acceso a variables y métodos está abierto a todo el paquete que contiene la clase.
4) Modificador « protegido ».
Este nivel de acceso es ligeramente más amplio que el paquete . Se puede acceder a una variable, método o clase marcada con el modificador protected desde su paquete (como "paquete") y desde todas las clases heredadas.
Esta tabla lo explica todo:
Tipo de visibilidad | Palabra clave | Acceso | |||
---|---|---|---|---|---|
Tu clase | Tu equipaje | descendiente | Todas las clases | ||
Privado | privado | Sí | No | No | No |
Paquete | (sin modificador) | Sí | Sí | No | No |
Protegido | protegido | Sí | Sí | Sí | No |
Público | público | Sí | Sí | Sí | Sí |
Hay una forma de recordar fácilmente esta tabla. Imagina que estás escribiendo un testamento. Estás dividiendo todas tus cosas en cuatro categorías. ¿Quién puede usar tus cosas?
quien tiene acceso | modificador | Ejemplo |
---|---|---|
solo yo | privado | diario personal |
Familia | (sin modificador) | Fotos de familia |
Familia y herederos | protegido | finca familiar |
Todos | público | Memorias |
"Es muy parecido a imaginar que las clases en el mismo paquete son parte de una familia".
"También quiero contarles algunos matices interesantes sobre los métodos de anulación".
1) Implementación implícita de un método abstracto.
Digamos que tienes el siguiente código:
class Cat
{
public String getName()
{
return "Oscar";
}
}
Y decidió crear una clase Tiger que hereda esta clase y agregar una interfaz a la nueva clase
class Cat
{
public String getName()
{
return "Oscar";
}
}
interface HasName
{
String getName();
int getWeight();
}
class Tiger extends Cat implements HasName
{
public int getWeight()
{
return 115;
}
}
Si solo implementa todos los métodos faltantes que IntelliJ IDEA le indica que implemente, más adelante podría terminar dedicando mucho tiempo a buscar un error.
Resulta que la clase Tiger tiene un método getName heredado de Cat, que se tomará como la implementación del método getName para la interfaz HasName.
"No veo nada terrible en eso".
"No es tan malo, es un lugar probable para que se produzcan errores".
Pero puede ser aún peor:
interface HasWeight
{
int getValue();
}
interface HasSize
{
int getValue();
}
class Tiger extends Cat implements HasWeight, HasSize
{
public int getValue()
{
return 115;
}
}
Resulta que no siempre se puede heredar de múltiples interfaces. Más precisamente, puede heredarlas, pero no puede implementarlas correctamente. Mira el ejemplo. Ambas interfaces requieren que implementes el método getValue(), pero no está claro qué debería devolver: ¿el peso o el tamaño? Es bastante desagradable tener que lidiar con esto.
"Estoy de acuerdo. Quieres implementar un método, pero no puedes. Ya heredaste un método con el mismo nombre de la clase base. Está roto".
"Pero hay buenas noticias".
2) Ampliar la visibilidad. Cuando hereda un tipo, puede expandir la visibilidad de un método. Así es como se ve:
codigo Java | Descripción |
---|---|
|
|
|
Hemos ampliado la visibilidad del método de protected a public . |
Código | Por qué esto es «legal» |
---|---|
|
Todo es estupendo. Aquí ni siquiera sabemos que la visibilidad se ha ampliado en una clase descendiente. |
|
Aquí llamamos al método cuya visibilidad se ha ampliado.
Si esto no fuera posible, siempre podríamos declarar un método en Tiger: En otras palabras, no estamos hablando de ninguna violación de seguridad. |
|
Si se cumplen todas las condiciones necesarias para llamar a un método en una clase base ( Cat ), entonces ciertamente se cumplen para llamar al método en el tipo descendiente ( Tiger ). Porque las restricciones en la llamada al método eran débiles, no fuertes. |
"No estoy seguro de haberlo entendido completamente, pero recordaré que esto es posible".
3) Restringir el tipo de devolución.
En un método anulado, podemos cambiar el tipo de retorno a un tipo de referencia reducido.
codigo Java | Descripción |
---|---|
|
|
|
Anulamos el método getMyParent y ahora devuelve un Tiger objeto. |
Código | Por qué esto es «legal» |
---|---|
|
Todo es estupendo. Aquí ni siquiera sabemos que el tipo de devolución del método getMyParent se ha ampliado en la clase descendiente.
Cómo funcionaba y funciona el «código antiguo». |
|
Aquí llamamos al método cuyo tipo de devolución se ha reducido.
Si esto no fuera posible, siempre podríamos declarar un método en Tiger: En otras palabras, no hay violaciones de seguridad y/o violaciones de conversión de tipos. |
|
Y todo funciona bien aquí, aunque ampliamos el tipo de variables a la clase base (Cat).
Debido a la anulación, se llama al método setMyParent correcto. Y no hay nada de qué preocuparse al llamar al método getMyParent , porque el valor de retorno, aunque de la clase Tiger, todavía se puede asignar a la variable myParent de la clase base (Cat) sin ningún problema. Los objetos Tiger se pueden almacenar de forma segura tanto en variables Tiger como en variables Cat. |
"Sí. Lo tengo. Al anular métodos, debe tener en cuenta cómo funciona todo esto si pasamos nuestros objetos a un código que solo puede manejar la clase base y no sabe nada sobre nuestra clase" .
"¡Exactamente! Entonces la gran pregunta es ¿por qué no podemos reducir el tipo de valor devuelto cuando anulamos un método?"
"Es obvio que en este caso el código de la clase base dejaría de funcionar:"
codigo Java | Explicación del problema |
---|---|
|
|
|
Sobrecargamos el método getMyParent y redujimos el tipo de su valor de retorno.
Todo está bien aquí. |
|
Entonces este código dejará de funcionar.
El método getMyParent puede devolver cualquier instancia de un objeto, porque en realidad se llama en un objeto Tiger. Y no tenemos un cheque antes de la asignación. Por lo tanto, es muy posible que la variable myParent de tipo Cat almacene una referencia de cadena. |
"¡Maravilloso ejemplo, Amigo!"
En Java, antes de llamar a un método, no se verifica si el objeto tiene dicho método. Todas las comprobaciones se realizan en tiempo de ejecución. Y una llamada [hipotética] a un método faltante probablemente haría que el programa intentara ejecutar un código de bytes inexistente. En última instancia, esto conduciría a un error fatal y el sistema operativo cerraría el programa a la fuerza.
"Vaya. Ahora lo sé".
GO TO FULL VERSION