Extensión de clases Introducción



Descargar 64,21 Kb.
Fecha de conversión23.03.2017
Tamaño64,21 Kb.

Extensión de clases

Introducción

  • El conjunto de métodos y campos accesibles desde el exterior de una clase, junto con la descripción de la forma en que se espera que esos miembros se comporten se denomina comúnmente el contrato de la clase.
  • El contrato es lo que el diseñador de la clase ha prometido que la clase va a hacer

Introducción

  • La extensión de clases proporciona dos formas de herencia:
    • Herencia de contrato o tipo, por la que una subclase adquiere el tipo de la superclase y se puede utilizar polimórficamente allí donde se pudiera utilizar la superclase.
    • Herencia de implementación, por lo que una subclase adquiere la implementación de la superclase en término de sus campos y métodos accesibles.

Introducción

  • La extensión de clases puede usarse para múltiples propósitos. El más común es la especialización, donde la clase extendida define su nuevo comportamiento y por tanto se convierte en una versión especializada de su superclase.

Introducción

  • La extensión de clases puede involucrar sólo el cambio de la implementación de un método heredado, quizá para hacerlo más eficiente.
  • Siempre que se extiende una clase, se crea una nueva clase con un contrato ampliado.
  • Sin embargo, no se cambia la parte del contrato que se hereda de la clase que se extiende

Introducción

  • Cambiar la forma en la que se implementa el contrato de la superclase es razonable, pero nunca se debe hacerlo de forma en que se viole el contrato
  • La capacidad de extender clases está relacionada con los mecanismos de control de acceso para ampliar la noción del contrato que la clase presenta. Una clase puede presentar dos contratos diferentes: uno para los usuarios de la clase y otro para los extensores de la clase, ambos deben diseñarse cuidadosamente

Introducción

  • Al extender una clase, la herencia de contrato y la herencia de implementación siempre se producen juntas. Sin embargo, pueden definirse nuevos tipos independientes de implementación usando interfaces.
  • Se pueden reutilizar implementaciones existentes, sin afectar al tipo, utilizando manualmente la composición y el reenvío.

Una clase extendida

  • Como ejemplo se muestra una clase de atributos básica diseñada para almacenar parejas nombre-valor. Los nombres de los atributos son cadenas legibles como “color” o “posición”
  • Los valores de los atributos están determinadas por el tipo de atributo, por ejemplo, posición puede tener el valor de cadena de texto que represente la dirección de una calle, un conjunto de valores enteros representando longitud y latitud

Una clase extendida

  • public class Atrib {
  • private final String nombre;
  • private Object valor=null;
  • public Atrib (String nombre){
  • this.nombre=nombre;
  • }
  • public Atrib(String nombre, Object valor) {
  • this.nombre = nombre;
  • this.valor = valor;
  • }

Una clase extendida

  • Un atributo debe tener un nombre, de forma que cada constructor requiere una parámetro de nombre.
  • El nombre debe ser inmutable (y por tanto se marca como final) ya que puede usarse por ejemplo en una lista enlazada.

Una clase extendida

  • Los atributos pueden tener cualquier tipo de valor por lo que dicho valor se almacena en una variable de tipo Object.
  • El valor puede cambiar en cualquier momento
  • Tanto nombre como valor son miembros private, por lo que puede accederse a ellos mediante los métodos apropiados.
  • Esto asegura que el contrato de Atrib se cumple siempre y da al diseñador de Atrib la libertad de cambiar en el futuro los detalles de la implementación sin afectar a los clientes de la clase

Una clase extendida

  • public String getNombre() {
  • return nombre;
  • }
  • public Object getValor() {
  • return valor;
  • }

Una clase extendida

  • public Object setValor(Object nuevoValor){
  • Object valorAnt=valor;
  • valor=nuevoValor;
  • return valorAnt;
  • }
  • public String toString(){
  • return nombre + "='" + valor + “ ";
  • }
  • }

Una clase extendida

  • Todas las clases vistas hasta ahora son clases extendidas, aunque no se declaren como tales.
  • Una clase como Atrib que no extiende explícitamente a otra clase, extiende implícitamente a Object.
  • Object está en la raíz de la jerarquía de clases y declara métodos que son implementados por todos los objetos, como toString
  • Las variables de tipo Object pueden referirse a cualquier objeto, sea éste una instancia de una clase o un array

Una clase extendida

  • La siguiente clase extiende la noción de atributos de color, que podrían ser cadenas de texto para nombrar o describir colores.
  • Podría ser nombres como “crudo”, que podrían buscarse en una tabla o bien, valores numéricos que pueden decodificarse para producir una representación del color más eficiente que llamaremos ColorPantalla (que se supone definida en alguna otra parte)

Una clase extendida

  • La decodificación de una descripción en un objeto ColorPantalla es lo suficientemente costosa como para desear realizarla una sola vez
  • Se extiende por tanto la clase Atrib creando una nueva clase AtribColor que tenga un método que obtenga un objeto ColorPantalla decodificado, implementado de tal forma que la decodificación se realice una sola vez

Una clase extendida

  • public class AtribColor extends Atrib{
  • private ColorPantalla miColor;
  • //el color decodificado
  • public AtribColor (String nombre, Object valor){
  • super(nombre, valor);
  • decodifColor();
  • }
  • public AtribColor(String nombre){
  • this(nombre, "transparente");
  • }

Una clase extendida

  • public AtribColor(String nombre, ColorPantalla valor){
  • super(nombre, valor.toString());
  • miColor=valor;
  • }
  • public Object setValor(ColorPantalla nuevoValor){
  • //se hace que setValor de la superclase actúe primero
  • super.setValor(nuevoValor.toString());
  • ColorPantalla antValor= miColor;
  • miColor= nuevoValor;
  • return antValor;
  • }
  • public Object setValor(Object nuevoValor){
  • //se hace que setValor de la superclase actúe primero
  • Object devValor=super.setValor(nuevoValor);
  • decodifColor();
  • return devValor;
  • }

Una clase extendida

  • //devuelve el objeto ColorPantalla decodificado
  • public ColorPantalla getColor(){
  • return miColor;
  • }
  • //de un valor a ColorPantalla en función de la descripción en getValor
  • protected void decodifColor(){
  • if(getValor()==null)
  • miColor=null;
  • else
  • miColor=new ColorPantalla(getValor());
  • }
  • }

Una clase extendida

  • AtribColor hace todo lo que hace Atrib y añade nuevo comportamiento
  • Object
  • Atrib
  • AtribColor

Una clase extendida

  • La clase extendida AtribColor hace principalmente tres cosas:
    • Proporciona tres constructores: dos para reflejar los de su superclase y uno que acepta directamente un objeto ColorPantalla
    • Modifica y redefine el método setValor de su superclase, para que pueda establecer el objeto de color cuando se cambia el valor
    • Proporciona un nuevo método getColor que devuelve un valor que es la descripción del color decodificada en un objeto ColorPantalla

Una clase extendida

  • Es interesante notar el uso del modificador de acceso protected en el método decodifColor, al marcarlo así puede accederse al mismo en la clase actual o, en una subclase pero, no es visible externamente

Constructores en clases extendidas

  • Los objetos de las clases extendidas contienen variables de estado (campos) heredados de la superclase y campos definidos localmente en la clase.
  • Para construir un objeto de una clase extendida se deben inicializar correctamente ambos conjuntos de campos

Constructores en clases extendidas

  • El constructor en una clase extendida puede tratar con campos locales pero sólo la superclase sabe cómo inicializar correctamente su estado, de forma que se cumpla su contrato.
  • Los constructores de clases extendidas deben delegar la construcción del estado heredado implícitamente o explícitamente invocando a un constructor de la superclase

Constructores en clases extendidas

  • Un constructor de la clase extendida puede invocar directamente a uno de los constructores de la superclase usando otra clase de invocación explícita a un constructor: la invocación del constructor de la superclase, que usa la construcción super, Ej.: primer constructor de la clase AtribColor, al que se le pasa el nombre y el valor al correspondiente constructor de 2 argumentos de la superclase. Entonces se invoca a su propio método decodifColor para conseguir que miColor contenga una referencia al objeto ColorPantalla correcto

Constructores en clases extendidas

  • Puede retrasarse la elección de qué constructor de la superclase va a utilizarse invocando explícitamente a uno de los constructores propios de la clase, usando this en vez de super, como se ve en el 2º constructor de AtribColor. Se asegura que cada atributo de color tiene un color, sino se proporciona un valor de color proporcionamos un valor por defecto de “transparente”.

Constructores en clases extendidas

  • Sino se invoca al constructor de la superclase o a uno de nuestros propios constructores como primera sentencia ejecutable de nuestro propio constructor, se invoca automáticamente al constructor no-arg de la superclase antes de que se ejecute ninguna sentencia del nuevo constructor. Es decir, nuestro constructor se trata como si super() fuese su primera sentencia. Si la superclase no tiene un constructor no-arg, debe invocarse explícitamente a otro constructor.

Constructores en clases extendidas

  • El 3º constructor de AtribColor permite al programador crear un nuevo objeto de tipo AtribColor para especificar al propio objeto ColorPantalla. Los 2 primeros constructores deben convertir sus parámetros a objetos ColorPantalla usando el método decodifColor, lo que posiblemente tiene un trabajo adicional. Cuando el programador tiene ya un objeto ColorPantalla para proporcionarle un valor, es conveniente evitar el trabajo adicional de esa conversión. Este es un ejemplo de cómo al proporcionar un constructor se aumenta la eficiencia, pero no se añaden capacidades nuevas.

Constructores en clases extendidas

  • En este ejemplo AtribColor tiene constructores con las mismas signaturas que los constructores de la superclase. Esto no es en absoluto necesario, más aún es habitual.
  • Los constructores no son métodos y no se heredan

Dependencias de ordenación de constructores

  • Cuando se crea un objeto, se reserva memoria para todos sus campos, incluyendo aquéllos que se heredan de su superclase. Estos campos se inicializan con valores que dependen de sus tipos respectivos (Ej.: 0 para valores numéricos, null para referencias a objetos)

Dependencias de ordenación de constructores

  • Después, la construcción tiene 3 fases:
    • Se invoca al constructor de la superclase
    • Se inicializan los campos usando sus inicializadores y los bloques de inicialización
    • Se ejecuta el cuerpo del constructor
  • 1º se ejecuta la invocación implícita o explícita al constructor de la superclase.

Dependencias de ordenación de constructores

  • Si se usa una invocación explícita a un constructor con this, se sigue la cadena de invocaciones hasta que se encuentra una invocación implícita o explícita a un constructor de la superclase, se invoca entonces al constructor de la superclase.

Dependencias de ordenación de constructores

  • El constructor de la superclase se ejecuta usando las mismas 3 fases. Este procedimiento se aplica recursivamente, finalizándose cuando se alcanza el constructor de Object, ya que en este punto no existe ningún constructor de una superclase. Cualquier expresión evaluada como parte de la invocación explícita a un constructor no se puede referir a ningún miembro del objeto actual

Dependencias de ordenación de constructores

  • En la segunda etapa todos los inicializadores de los campos y los bloques de inicialización se ejecutan en orden el que han sido declarados. En esta etapa se permiten referencias a otros miembros del objeto actual, suponiendo que hayan sido declarados

Dependencias de ordenación de constructores

  • Finalmente, se ejecutan las sentencias reales del cuerpo del constructor. Si ese constructor se invocó explícitamente, tras la finalización de esas sentencias se devuelve el control al constructor que lo invocó y se ejecuta el resto de su cuerpo. Este proceso se repite hasta que se haya ejecutado el cuerpo del constructor especificado como parte de la construcción new

Herencia y redefinición de miembros

  • Cuando se extiende una clase pueden añadirse miembros a dicha clase y también pueden redefinirse los existentes
  • El efecto exacto de redefinir un miembro heredado depende de la clase de miembro

Redefinición

  • En la clase AtribColor se redefine y sobrecarga el método setValor
  • Sobrecargar un método significa proporcionar más de un método (en la misma clase) con el mismo nombre pero con diferente signatura, para distinguirlos

Redefinición

  • Redefinir un método quiere decir sustituir la implementación del método de la superclase con uno propio. Las signaturas deben ser idénticas
  • Sobrecargar un método heredado quiere decir simplemente que hemos añadido un nuevo método con el mismo nombre que un método heredado pero con diferente signatura. Ej. setValor en AtribColor

Redefinición

  • Redefinir un método significa que se sustituye su implementación, de forma que cuando se invoca al método sobre un objeto de la subclase, en realidad se invoca a la versión del método de la subclase.
  • En AtribColor redefinimos el método setValor(Object) de la clase Atrib proporcionando un nuevo método setValor(Object) que utiliza la palabra reservada super para invocar la implementación de la superclase y luego invoca a decodifColor. La referencia super se puede utilizar en invocaciones a métodos para acceder a métodos de la superclase que hayan sido redefinidos en la clase actual

Redefinición

  • Cuando se redefinen métodos, la signatura y el tipo de retorno deben ser los mismos que en la superclase. Aunque dos métodos se diferencien sólo en el tipo de retorno, esto es un error y el compilador rechazará esta clase

Redefinición

  • Los métodos que redefinen a otros poseen sus propios especificadores de acceso. Una subclase puede modificar el acceso a los métodos de la superclase, pero sólo para hacerlos más accesibles. Un método declarado protected en la superclase puede volverse a declarar de esta forma (lo usual) o se puede declarar public pero no puede declararse private ni tener acceso de paquete. Al hacer un método menos accesible que en la superclase se violaría el contrato de ésta, ya que una instancia de la subclase no podría utilizarse en lugar de una instancia de la superclase

Redefinición

  • Los métodos que redefinen a otros pueden también cambiar los modificadores de los otros métodos.
  • Pueden ser final pero obviamente el que está siendo redefinido no
  • Un método de instancia no puede tener la misma signatura que un método estático heredado, ni viceversa. Sin embargo, un método redefinido se puede hacer abstract incluso aunque no lo fuera el método de la superclase

Redefinición

  • En una subclase puede cambiarse el hecho que un parámetro de un método redefinido sea final, ya que el modificador final de un parámetro no es parte de la signatura del método, sino un detalle de la implementación

Ocultación de campos

  • Los campos no pueden redefinirse, sólo pueden ocultarse. Si declaramos un campo en nuestra clase con el mismo nombre de un campo en la superclase, este otro existirá todavía, pero no podrá accederse directamente a él con su nombre simple. Debe usarse super u otra referencia del tipo de nuestra superclase para acceder a ese campo

Acceso a miembros heredados

  • Cuando un método accede a un miembro de un objeto que se ha redefinido en una subclase, ¿a qué miembro se referirá el método?, ¿al de la superclase o al de la subclase?. La respuesta depende de la clase de miembro, de su accesibilidad y de cómo nos referimos a él

Acceso a miembros heredados

  • Al invocar un método mediante una referencia de objeto, la clase real del objeto gobierna la implementación que se utiliza. Cuando se accede a un campo se utiliza el tipo declarado de la referencia.

Acceso a miembros heredados

  • public class SuperMostrar {
  • public String cad="SuperCad";
  • public void mostrar(){
  • System.out.println("Super.mostrar: "+ cad);
  • }
  • }

Acceso a miembros heredados

  • public class ExtendMostrar extends SuperMostrar{
  • public String cad= "ExtendCad";
  • public void mostrar(){
  • System.out.println("Extend.mostrar: "+ cad);
  • }
  • public static void main(String []args){
  • ExtendMostrar ext= new ExtendMostrar();
  • SuperMostrar sup= ext;
  • sup.mostrar();
  • ext.mostrar();
  • System.out.println("sup.cad= "+sup.cad);
  • System.out.println("ext.cad= "+ext.cad);
  • }
  • }

Acceso a miembros heredados

  • Sólo hay un objeto, pero tenemos dos variables que contienen referencias al mismo. Una variable tiene tipo SuperMostrar (la superclase) y la otra tiene tipo ExtendMostrar (la clase real).
  • Veamos la salida del programa cuando se ejecuta

Acceso a miembros heredados

  • Extend.mostrar: ExtendCad
  • Extend.mostrar: ExtendCad
  • sup.cad= SuperCad
  • ext.cad= ExtendCad

Acceso a miembros heredados

  • En el caso del método mostrar, el comportamiento es el esperado. La clase real del objeto, no el tipo de la referencia, gobierna la versión del método que se llama. Cuando tenemos un objeto ExtendMostrar, al invocar a mostrar se llama al método de ExtendMostrar, incluso aunque el acceso sea mediante una referencia declarada con el tipo SuperMostrar. Esto sucede siempre que mostrar se invoca externamente o internamente dentro de otro método de ExtendMostrar o SuperMostrar

Acceso a miembros heredados

  • En el caso del campo cad, el tipo de referencia, no la clase real del objeto, determina a qué campo de la clase se accede. De hecho, cada objeto ExtendMostrar tiene dos campos String, ambos llamados cad, uno de los cuales está oculto por el propio campo diferente de ExtendMostrar llamado cad:

Acceso a miembros heredados

  • Object
  • SuperMostrar cad= “SuperCad”
  • ExtendMostrar cad=”ExtendCad”

Acceso a miembros heredados

  • El campo al que se accede se determina en tiempo de compilación basándose en el tipo de la referencia, utilizado en el acceso
  • Dentro de un método como mostrar, una referencia a un campo siempre se refiere al campo declarado en la clase en la que se declara el método, o bien a un campo heredado si no hay declaración en esa clase. Por tanto en SuperMostrar.mostrar, la referencia a cad se refiere a SuperMostrar.cad mientras que en ExtendMostrar.mostrar la referencia a cad se refiere a ExtendMostrar.cad

Acceso a miembros heredados

  • La redefinición de métodos posibilita la extensión del código existente, reutilizándolo con objetos de funcionalidad ampliada y especializada no previstos por el creador del código original. Pero en lo que se refiere a los campos, es difícil imaginar casos en los que su ocultación sea una característica útil

Acceso a miembros heredados

  • Si un método existente tuviera un parámetro de tipo SuperMostrar y accediera a cad con esa referencia de objeto, se obtendría siempre SuperMostrar.cad, incluso aunque el método manejara realmente un objeto de tipo ExtendMostrar.
  • Si las clases se diseñaran para utilizar un método en vez de un campo para acceder a la cadena de texto, el método redefinido se invocaría en ese caso y se podría devolver ExtendMostrar.cad. Este comportamiento de ocultación es frecuentemente otra razón para que se prefiera definir clases con datos privados a los que se accede sólo mediante métodos que se redefinen y no se ocultan

Accesibilidad y redefinición

  • Un método puede ser redefinido sólo si es accesible. Si un método no es accesible no puede heredarse y sino se hereda no puede redefinirse. Por ejemplo, un método private no es accesible fuera de su propia clase. Si una subclase definiera un método que por coincidencia tuviera la misma signatura y tipo de retorno que el método privado de la superclase, los métodos no estarían relacionados en absoluto y, el método de subclase no redefiniría al método privado de la superclase

Accesibilidad y redefinición

  • En pocas palabras, las invocaciones a métodos privados siempre invocan a la implementación del método declarada en la clase actual.
  • Cuando un método es inaccesible debido a que la superclase y la subclase están en diferentes paquetes, las cosas son más complicadas.

Ocultación de miembros estáticos

  • Los miembros estáticos dentro de una clase no pueden redefinirse. Siempre están ocultos sean campos o métodos. Sin embargo, el hecho de que estén ocultos tiene poco efecto. Un método o campo estático debe ser accedido siempre mediante el nombre de la clase en donde se declara, por lo que el hecho de que quede oculto por la declaración en una subclase tiene poca importancia.
  • Si se usa una referencia para acceder a un miembro estático, como con campos de instancia, se accede siempre basándose en el tipo declarado de la referencia, no en el tipo del objeto al que se referencia

La palabra reservada super

  • La palabra clave super puede utilizarse con todos los métodos no estáticos de una clase. En acceso a campos e invocación de métodos, super actúa como una referencia al objeto actual como una instancia de su superclase.
  • El uso de super es el único caso en el que el tipo de la referencia gobierna la selección de la implementación del método que se utiliza.

La palabra reservada super

  • Una invocación.método siempre utiliza la implementación de método que utilizaría la superclase.
  • No utiliza ninguna implementación redefinida de este método, descendiendo en la jerarquía de clases

Ejemplo

  • public class Ess {
  • //devuelve el nombre de la clase
  • protected String nm(){
  • return "Ess";
  • }
  • }

Ejemplo

  • public class Mas extends Ess{
  • protected String nm(){
  • return "Mas";
  • }
  • protected void imprimeNM(){
  • Ess sref= (Ess)this;
  • System.out.println("this.nm()= "+this.nm());
  • System.out.println("sref.nm()= "+sref.nm());
  • System.out.println("super.nm()= "+super.nm());
  • }
  • }

Ejemplo

  • Aunque sref y super se refieren al mismo objeto usando el tipo Ess, sólo super ignorará la clase real del objeto usando la implementación de la superclase de nm. La referencia sref actuará de la misma forma que this, seleccionando una implementación de nm basada en la clase real del objeto

Ejemplo

  • this.nm()= Mas
  • sref.nm()= Mas
  • super.nm()= Ess

Compatibilidad y conversión de tipos

  • El lenguaje Java es fuertemente tipado, lo que significa que en la mayor parte de los casos comprueba la compatibilidad de tipos en tiempo de compilación. Evita las asignaciones incompatibles prohibiendo cualquier cosa cuestionable

Compatibilidad

  • Cuando se asigna el valor de una expresión a una variable, como parte de un inicializador, de una sentencia de asignación o, implícitamente cuando se asigna un parámetro de un método, el tipo de la expresión debe ser compatible con el tipo de la variable.
  • En el caso de tipos de referencia esto significa que el tipo de la expresión debe ser el mismo que el tipo declarado de la variable o bien un subtipo.

Compatibilidad

  • Ejemplo: cualquier método que espera un objeto Atrib como parámetro aceptará un objeto AtribColor, ya que es un subtipo de Atrib. Esto se llama, compatibilidad de asignaciones. Pero lo contrario no es cierto. No se puede asignar un objeto Atrib a una variable de tipo AtribColor, ni pasar un objeto Atrib como parámetro cuando se espera un objeto de tipo AtribColor

Compatibilidad

  • La misma regla se aplica a la expresión usada en un sentencia return de un método. El tipo de la expresión debe ser compatible desde el punto de vista de la asignación con el tipo de retorno declarado para el método
  • La referencia al objeto null es un caso especial que es compatible con todos los tipos de referencia, incluyendo los arrays. Una referencia a una variable de cualquier tipo se puede asignar siempre a null

Compatibilidad

  • Los tipos que ascienden en la jerarquía de tipos se dice que son más amplios o menos específicos que los tipos que descienden en la jerarquía
  • Los tipos inferiores se dice que son menos amplios o más específicos que sus correspondientes supertipos. Cuando se espera un supertipo y se proporciona un subtipo, tiene lugar una conversión de ampliación. Esta conversión hace que el objeto del subtipo sea tratado como una instancia del supertipo y se pueda comprobar en tiempo de compilación. No se requiere ninguna acción por parte del programador en una conversión de ampliación. Al ir en el otro sentido, es decir, al tomar una referencia a un supertipo y convertirla en una referencia a un subtipo, tiene lugar una conversión de restricción. La conversión de restricción se debe indicar expresamente mediante el operador de conversión de tipo (cast).

Conversión explícita de tipos

  • Se puede usar un operador de conversión de tipo para indicar al compilador que una expresión se debe tratar como si tuviese el tipo especificado por el operador de conversión. El resultado puede ser de un tipo más amplio o menos amplio, aunque generalmente estos operadores se usan en conversiones de restricción. Un operador de conversión de tipo consiste en un nombre de tipo entre () aplicado a una expresión.

Conversión explícita de tipos

  • En el ejemplo anterior se usa un operador de ampliación de tipo en el método imprimeNM para convertir el tipo de this en el tipo de su superclase:
  • Ess sref= (Ess)this;
  • Este operador de conversión de tipo no es necesario, pero permite enfatizar que lo que realmente deseamos es que ese objeto sea tratado como instancia de su superclase. Si intentamos asignar sref a una referencia de tipo Mas, menos amplio, es necesaria una conversión de tipo explícita:
  • Mas mref= (Mas)sref;
  • Incluso aunque conozcamos que el objeto referido es del tipo correcto, el compilador necesita una conversión explícita de tipo.

Conversión explícita de tipos

  • Las conversiones de ampliación se conocen también como conversiones ascendentes, ya que convierten un tipo en otro de un nivel superior de la jerarquía de tipos. Son también unas conversiones seguras, ya que siempre son válidas. Las conversiones de restricción se llaman también conversiones descendentes ya que convierten un tipo en otro de nivel inferior en la jerarquía. Son también conversiones inseguras, ya que pueden no ser válidas
  • Cuando se requiere realizar una conversión de restricción mediante un operador de conversión de tipo, el compilador no supone que la conversión es correcta. Si el compilador puede decidir que la conversión es incorrecta puede ocurrir un error en tiempo de compilación. Si el compilador no puede decidir si la conversión es correcta en tiempo de compilación, se realizará una comprobación en tiempo de ejecución. Si esta comprobación falla debido a que la conversión es incorrecta, se lanza una excepción ClassCastException

Comprobación de tipos

  • Se puede comprobar la clase de un objeto usando el operador instanceof, que produce true si la expresión de la izquierda es compatible desde el punto de vista de la asignación con el nombre de tipo de su derecha y false en caso contrario. Hay que tener en cuenta que como null no es una instancia de ningún tipo, la aplicación de instanceof a null siempre produce un resultado de false. Se puede usar instanceof para realizar una conversión descendente de forma segura, y asegurar que no se lanzará una excepción.
  • if (sref instanceof Mas)
  • mref = (Mas)sref;

Comprobación de tipos

  • Nótese que el operador de conversión de tipo debe usarse todavía, para convencer al compilador que realmente deseamos usar el objeto como una instancia de la subclase
  • La comprobación de tipos con instanceof es particularmente útil cuando un método no requiere un objeto de un tipo más extendido, pero al pasarle ese objeto puede hacer uso de la funcionalidad extendida.

Ejemplo

  • /*un método ordenar podría aceptar como parámetro
  • * un tipo Lista genérico, pero si realmente recibe un
  • * tipo ListaOrdenada no tendría que hacer nada
  • */
  • public static void ordenar(Lista lista){
  • if (lista instanceof ListaOrdenada)
  • return;
  • //sino, ordenar la lista
  • }

Lo que significa realmente protected

  • Que un miembro de una clase sea protected significa que puede ser accedido por clases que extiendan a la clase actual.
  • De forma más concreta, además de ser accesible dentro de la propia clase y dentro del mismo paquete, un miembro protegido también puede ser accedido desde otra clase a través de referencias a objetos que sean al menos del mismo tipo que la clase (es decir, referencias del tipo de la clase o de uno de sus subtipos).

Ejemplo

  • Consideremos la implementación como lista enlazada de una cola, la clase ColaMonoEnlace, con métodos añadir y quitar, para almacenar un objeto en el final de la cola y quitar el objeto de la cabecera de la cola, respectivamente. Los nodos de la cola están formados por Celdas que tienen una referencia a la siguiente celda de la cola y una referencia al objeto almacenado en la celda actual

Ejemplo

  • public class Celda {
  • private Celda siguiente;
  • private Object elemento;
  • public Celda(Object elemento){
  • this.elemento=elemento;}
  • public Celda(Celda siguiente, Object elemento) {
  • this.siguiente = siguiente;
  • this.elemento = elemento;}
  • public Celda getSiguiente() {
  • return siguiente;}
  • public void setSiguiente(Celda siguiente) {
  • this.siguiente = siguiente;
  • }
  • public Object getElemento() {
  • return elemento;
  • }
  • public void setElemento(Object elemento) {
  • this.elemento = elemento;
  • }}

Ejemplo

  • Una cola consiste entonces en una referencia a las celdas de la cabecera y del final, y la implementación de añadir y quitar:
  • public class ColoMonoEnlace {
  • protected Celda cabecera;
  • protected Celda finali;
  • public void añadir (Objeto item){
  • //
  • }
  • public Object quitar(){
  • //
  • }}

protected

  • Hacemos que las referencias cabecera y finali sean protected, de forma que las clases extendidas puedan manejar directamente las celdas enlazadas en vez de usar añadir y quitar, lo que podrá requerir quitar y poner los elementos cada vez

protected

  • Un grupo decide que necesita una cola con prioridades, de forma que los elementos se almacenen en la cola en un orden específico en vez de insertarse siempre en el final. Por tanto define una clase ColaConPrioridades, en otro paquete, que extiende a ColaMonoEnlace y redefine añadir para insertar el objeto en el lugar correcto. La implementación de añadir en la clase ColaConPrioridades puede acceder a los campos cabecera y finali heredados de ColaMonoEnlace ya que el código está en una subclase de ColaMonoEnlace y el tipo de la referencia al objeto usada (this) es el mismo que el de la subclase, ColaConPrioridades, por lo que el acceso a los miembros protected está permitido. Esto es lo que podría esperarse.

protected

  • El grupo que diseña la cola con prioridades necesita una característica adicional. Desea poder fusionar dos colas con prioridades en una sola. En una operación de fusión la cola destino contiene los elementos de ambas colas, y la otra cola con la que se realiza la fusión queda vacía. La operación fusionar podría empezar así:

protected

  • public void fusionar (ColaConPrioridades q){
  • Celda primero= q.cabecera;
  • //....
  • }

protected

  • No estamos accediendo al miembro protected del objeto actual, sino al miembro protected de un objeto pasado como parámetro. Esto está permitido ya que la clase que intenta el acceso es ColaConPrioridades y el tipo de la referencia q es también ColaConPrioridades. Si q fuese una subclase de ColaConPrioridades esto sería todavía válido
  • Posteriormente el grupo determina que hay un nuevo requerimiento: desea poder fundir una ColoMonoEnlace con una ColaConPrioridades. Por tanto define una versión sobrecargada de fusionar que empieza así:

protected

  • public void fusionar (ColaMonoEnlace q){
  • Celda primero=q.cabecera;
  • //...
  • }

protected

  • Pero este código no compila.
  • El problema que aparece es que la clase que intenta acceder al miembro protected es ColaConPrioridades mientras que el tipo de la referencia del objeto que está siendo accedido es ColaMonoEnlace. Como ColaMonoEnlace no es la misma clase ni una subclase de ColaConPrioridades, el acceso no está permitido. Aunque cada ColaConPrioridades es una ColaMonoEnlace, no todos los objetos ColaMonoEnlace son ColaConPrioridades

protected

  • El razonamiento que justifica las restricciones es: cada subclase hereda el contrato de su superclase y expande este contrato de alguna forma. Supongamos que una subclase, como parte del contrato expandido, pone restricciones en los valores de miembros protegidos de la superclase.
  • Si una subclase diferente pudiese acceder a los miembros protegidos de objetos de la primera subclase, podría manejarlos de forma que violase el contrato de la primera subclase, lo que no es permisible.

protected

  • Los miembros static protegidos pueden ser accedidos por cualquier clase extendida. Si cabecera fuese un campo estático, cualquier método (estático o no) de ColaConPrioridades podría acceder al mismo. Esto se permite ya que una subclase no puede modificar el contrato de sus miembros estáticos, y sólo puede ocultarlos, no redefinirlos, por lo que no hay peligro de que otra clase viole el contrato.

protected

  • Los miembros declarados como protected están también disponibles para cualquier código dentro del paquete de la clase. Si estas diferentes clases de colas estuviesen en el mismo paquete, podrían acceder cada una de ellas a los campos cabecera y finali de las otras, como podría también cualquier tipo no relacionado del paquete. Las clases del mismo paquete se supone que son de confianza y no violarán entre sí sus contratos.
  • En la lista “privado, paquete, protegido, público” cada nivel de acceso amplía las clases de código para las que el miembro es accesible

Marcado de clases y métodos como final

  • Marcar un método como final significa que ninguna clase extendida puede redefinir el método para cambiar su comportamiento. En otras palabras, es la versión final de ese método. También pueden marcarse como final clases completas:
  • final class NoSeExtiende{
  • //...
  • }

Marcado de clases y métodos como final

  • Una clase que se marca como final no puede extenderse por otra clase y todos los métodos de una clase final son efectivamente final
  • Las clases y métodos finales pueden mejorar la seguridad. Si una clase es final nadie puede declarar una clase que la extienda, y por tanto, nadie puede violar su contrato. Si un método es final podemos fiarnos de sus detalles de implementación (a menos, por supuesto, que invoque a métodos no finales).

Marcado de clases y métodos como final

  • Por ejemplo podemos usar final en un método denominado validarContraseña para asegurarnos de que hace lo que dice que hace, en vez de ser redefinido para devolver siempre true. O podemos marcar como final la clase que contiene al método de forma que nunca se pueda extender para que se confunda la implementación de validarContraseña

Marcado de clases y métodos como final

  • Marcar un método o una clase como final es una seria restricción en el uso de una clase. Si hacemos que un método sea final debemos pretender realmente que su comportamiento quede completamente fijo. Estamos restringiendo la flexibilidad de nuestra clase para otros programadores que podrían desear usarla como base para añadir funcionalidad a su código. Al marcar una clase completa se evita que cualquiera pueda extender nuestra clase, limitando así su utilidad para otros. Si marcamos algo como final debemos asegurarnos que deseamos crear estas restricciones

Marcado de clases y métodos como final

  • En muchos casos, podemos conseguir la seguridad que buscamos al marcar una clase como final dejando que la clase sea extensible y marcando cada método de la clase como final. De esta forma, podemos fiarnos del comportamiento de esos métodos permitiendo no obstante extensiones que puedan añadir funcionalidad sin redefinir estos métodos. Por supuesto, los campos que son fiables para los métodos final deberían ser final o private. De lo contrario, una clase extendida podría cambiar el comportamiento cambiando esos campos.

Marcado de clases y métodos como final

  • Otra modificación de final es que simplifica las optimizaciones. Cuando se invoca a un método no final, el sistema en tiempo de ejecución determina la clase real del objeto, asocia la invocación del método con la implementación correcta del método para este tipo, e invoca después a la implementación.

Marcado de clases y métodos como final

  • Pero, por ejemplo, si el método getNombre fuera final en la clase Atrib y tuviésemos una referencia a un objeto de tipo Atrib o de un tipo extendido, podría ser posible simplificar los pasos necesarios para invocar al método. En el caso más simple como en getNombre, una invocación se puede sustituir con el cuerpo real del método. Este mecanismo se conoce como inlining (inclusión de líneas).

Marcado de clases y métodos como final

  • Esto hace que las 2 siguientes sentencias se comporten de forma equivalente:
  • System.out.println( "id= " + rosa.nombre);
  • System.out.println( "id= " + rosa.getNombre());

Marcado de clases y métodos como final

  • Aunque las dos sentencias son iguales de eficientes, el método getNombre permite que el campo nombre sea de sólo lectura y nos da los beneficios de la abstracción, permitiéndonos cambiar la implementación
  • Se pueden aplicar las mismas optimizaciones a métodos privados y estáticos, debido a que tampoco pueden ser redefinidos

Marcado de clases y métodos como final

  • Algunas comprobaciones de tipos resultan más rápidos con clases final. De hecho, muchas comprobaciones de tipos se convierten en comprobaciones en tiempo de compilación y, los errores pueden capturarse antes. Si el compilador encuentra una referencia a una clase final, sabe que el objeto referido es exactamente de ese tipo. Se conoce la jerarquía de clases completa para esta clase, por lo que el compilador puede comprobar si cualquier uso es válido o inválido. Con una referencia no final, algunas comprobaciones sólo se pueden realizar en tiempo de ejecución

Métodos y clases abstractos

  • Un concepto extremadamente útil de la programación orientada a objetos es el concepto de clase abstracta. Utilizando clases abstractas podemos declarar clases que definen sólo parte de una implementación, dejando a las clases extendidas la tarea de proporcionar las implementaciones específicas de algunos métodos o, de todos. El opuesto de abstracto es concreto. Una clase que tiene sólo métodos concretos, incluyendo implementaciones de cualquier método abstracto heredado de superclases, es un clase concreta.

Métodos y clases abstractos

  • Las clases abstractas son útiles cuando parte del comportamiento está definido para la mayoría o todos los objetos de un tipo dado, pero algo del comportamiento sólo tiene sentido para clases particulares y no para una superclase general. Una clase así se se declara como abstract, y los métodos no implementados en esa clase se marcan también como abstract

Métodos y clases abstractos

  • public abstract class Figura {
  • public abstract double area();
  • public boolean esMenorQue(Figura obj){
  • if(this.area()
  • return true;
  • else return false;
  • }
  • }

Métodos y clases abstractos

  • Una clase con métodos abstract se debe declarar como abstract. Esta redundancia ayuda al lector a ver rápidamente que la clase es abstract sin recorrerla para ver si algún método de la clase se declara como abstract

Métodos y clases abstractos

  • En el ejemplo, el método área() se debe implementar en cada subclase que no sea a su vez abstract, esto es debido a que no existe implementación en esta clase, sólo hay una declaración

Métodos y clases abstractos

  • public class Circulo extends Figura {
  • private double radio;
  • public Circulo(double radio) {
  • super();
  • this.radio = radio;}
  • public double getRadio() {
  • return radio;
  • }
  • public double area() {
  • return Math.PI*radio*radio;
  • }}

Métodos y clases abstractos

  • public class Test1 {
  • public static void main(String[] args) {
  • Circulo a= new Circulo(1);
  • Circulo b=new Circulo(2);
  • if(a.esMenorQue(b))
  • System.out.println("El círculo de radio "+a.getRadio()+" tiene menor área que el círculo de radio "+b.getRadio());
  • else
  • System.out.println("El círculo de radio "+b.getRadio()+" tiene menor área que el círculo de radio "+a.getRadio());
  • }}

Métodos y clases abstractos

  • Cualquier clase puede redefinir métodos de su superclase declarándolos abstract en ese punto del árbol de tipos. Esta técnica es útil, por ejemplo, cuando la implementación por defecto de una clase es inválida para una parte de la jerarquía de clases
  • No se puede crear un objeto de la clase abstract ya que no habría implementación válida para algunos métodos que podrían ser invocados (si se pueden crear referencias a objetos de clases que la extiendan)

Extensión de clases: ¿cuándo y como?

  • La posibilidad de escribir clases extendidas proporciona una gran parte de los beneficios de la programación orientada a objetos. Cuando se extiende una clase para añadir una nueva funcionalidad, se crea lo que comúnmente se denomina relación EsUn. Es decir, la extensión crea una nueva clase de objeto que “es una” variante de la clase original. La relación EsUn es muy diferente de la relación TieneUn, en la que un objeto utiliza a otro objeto para almacenar estado o realizar tareas (“tiene una” referencia al otro objeto)

Extensión de clases: cuándo y como

  • Ejemplo: Una clase Punto que representa un punto del plano cartesiano. Se puede extender Punto para crear, por ejemplo, una clase Pixel que represente un punto de color en una pantalla. Un Pixel EsUn Punto, es decir, cualquier cosa que sea verdadera referida a un simple Punto será también verdad para un Pixel. La clase Pixel podría añadir mecanismos para representar el color de un pixel, por ejemplo.

Extensión de clases: cuándo y como

  • Por otra parte, un Circulo no es un Punto, aunque se pueda describir mediante un Punto y un radio, un Punto tiene usos que puede no tener un Circulo. Por ejemplo, si tuviésemos un método para situar el centro de un rectángulo en un Punto concreto ¿tendría sentido pasárselo a un Circulo? Un Circulo TieneUn Punto pero NoEsUn Punto, por tanto no debería ser una subclase de Punto

Extensión de clases: cuándo y como

  • El planteamiento de las relaciones EsUn y TieneUn correctas es sutil y potencialmente crítico. Por ejemplo, una forma común y obvia de diseñar una base de datos de empleados utilizando herramientas orientadas a objetos es usar una clase Empleado que tenga las propiedades de todas las personas (como nombre y número de Empleado) y extenderla para obtener clases para algunos tipos particulares de empleados como Director, Ingeniero y Bibliotecario

Extensión de clases: cuándo y como

  • Este diseño falla en situaciones reales, en las que una persona puede actuar desempeñando más de una función. Por ejemplo, un Ingeniero podría estar actuando como Director de un grupo y debería aparecer en sus dos papeles.
  • Un diseño más flexible sería crear una clase Función y extenderla creando clases de funciones como Director. Cambiaríamos así el diseño de la clase Empleado para que tuviese un conjunto de objetos Funcion.

Extensión de clases: cuándo y como

  • De este modo una persona podría estar asociada con un conjunto de funciones cambiantes en la organización. Hemos pasado de decir que un director EsUn Empleado a decir que un Director EsUna Función y que un Empleado puede TenerUna Función de Director, así como otras funciones.
  • Si se realiza la elección inicial incorrecta, los cambios posteriores en el sistema pueden ser complicados de realizar, ya que podrían requerir cambios importantes en el código. Por ejemplo, los métodos del diseño de la primera base de datos de empleados se basarían sin duda en que un objeto Director se puede usar como un Empleado. Esto ya no sería cierto si tuviésemos que cambiar a un diseño basado en funciones, y todo el código original podría ser inservible

Diseño de una clase para ser extendida

  • La clase Atrib es un ejemplo de una clase bien diseñada. Los campos de la clase son private y accesibles únicamente mediante métodos de acceso, lo que evita que sean modificados en forma contraria al contrato de la clase. La clase Atrib presenta una interfaz limpia para sus usuarios y al mismo tiempo se desacopla de otras clases, de forma que permite que su propia implementación cambie en un futuro

Diseño de una clase para ser extendida

  • Dado que AtribColor extiende a Atrib ¿Deberíamos modificar el diseño de Atrib para hacerla más adecuada para su extensión? ¿Deberían haber sido protected los campos nombre y valor, en vez de private, de forma que la subclase pudiera acceder a ellos directamente?

Diseño de una clase para ser extendida

  • Estas decisiones requieren una consideración y meditación cuidadosa sobre sus beneficios y consecuencias. Hacer que los campos de Atrib fuesen protegidos no daría ningún beneficio a la subclase, ya que todas las acciones que se fueran a realizar sobre esos campos se podrían hacer mediante los métodos public que proporciona Atrib. Por otra parte hacer que estos campos fuesen protegidos impediría futuras modificaciones de la implementación de Atrib, ya que las subclases podrían depender de la existencia y el tipo de esos campos, así como del acceso directo a ellos. Por tanto, en este caso, el diseño actual de Atrib es adecuado para su extensión así como para su uso general

Diseño de una clase para ser extendida

  • En la implementación de una cola mediante una lista enlazada, ColaMonoEnlace, los campos cabecera y finali fueron protected. En ese caso había un gran beneficio en prestaciones, al hacer que la subclase pudiese acceder a las celdas de la lista enlazada directamente. No sería práctico implementar la redefinición de añadir en ColaConPrioridades si las únicas herramientas a usar fuesen los métodos añadir y quitar originales.

Diseño de una clase para ser extendida

  • La naturaleza de bajo nivel de la clase ColaMonoEnlace significa también que no nos importa demasiado atarnos a detalles de implementación. Después de todo, es una implementación con una lista enlazada de una cola, y realmente no quedan muchas opciones para cambios. Si hubiésemos escrito una clase de colas más general, que usara una implementación de una lista enlazada, la historia hubiese sido diferente

Diseño de una clase para ser extendida

  • Una clase no final tiene dos interfaces. La interfaz pública es para los programadores que usan nuestra clase. La interfaz protegida es para los programadores que extienden nuestra clase. No es conveniente hacer a la ligera que campos de nuestras clases sean protegidos. Ambas interfaces son contratos reales, y ambas deben ser diseñadas cuidadosamente

Herencia simple frente a herencia múltiple

  • Una clase nueva puede extender sólo a una superclase, modelo conocido como herencia simple. La extensión de una clase significa que la nueva clase no sólo hereda el contrato de su superclase sino también la implementación de su superclase. Algunos lenguajes orientados a objetos emplea herencia múltiple en la que la nueva clase puede tener dos o más superclases

Herencia simple frente a herencia múltiple

  • La herencia múltiple es útil cuando una nueva clase desea combinar múltiples contratos y heredar alguna de las implementaciones de esos contratos, o todas. Pero si hay más de una superclase, surgen problemas cuando el comportamiento de una superclase se hereda de dos formas. Supongamos por un momento el siguiente árbol de tipos:

Herencia simple frente a herencia múltiple

  • W
  • Y
  • X
  • Z

Herencia simple frente a herencia múltiple

  • Esto se conoce comúnmente como herencia en diamante, y no hay nada erróneo en ello. Muchos diseños legítimos muestran esta estructura. Los problemas aparecen cuando la implementación de W almacena algún estado. Si por ejemplo, la clase W tuviese un campo público llamado algo, y tuviésemos una referencia a un objeto de tipo Z llamada zref ¿a qué se referiría zref.algo? Podría referirse a la copia de X de algo, o podría referirse a la copia de Y de algo o X e Y podrían compartir una única copia de algo debido a que Z es realmente sólo un W, incluso aunque Z es a la vez X e Y

Herencia simple frente a herencia múltiple

  • La resolución de estos aspectos no es trivial y complica el diseño y el uso de la jerarquía de clases. Para evitar estos problemas, el lenguaje de programación Java utiliza el modelo de herencia simple de programación orientada a objetos.

Herencia simple frente a herencia múltiple

  • La herencia simple imposibilita la realización de algunos diseños útiles y correctos. Los problemas de la herencia múltiple surgen de la herencia múltiple de la implementación, pero en muchos casos la herencia múltiple se utiliza para heredar algunos contratos abstractos y posiblemente una implementación concreta. Proporcionar un medio de heredar un contrato abstracto sin heredar una implementación, permite tener los beneficios de la herencia múltiple, sin que aparezcan los problemas de la herencia de implementación. La herencia de un contrato abstracto se denomina herencia de interfaz. El lenguaje de programación Java admite este tipo de herencia porque permite declarar un tipo interface

La clase Object

  • Es la raíz de la jerarquía de clases. Todas las clases extienden directa o indirectamente de ella y por tanto una variable de tipo Object puede referirse a cualquier objeto, sea éste la instancia de una clase o un array. Por ejemplo: la clase Atrib puede almacenar un atributo de cualquier tipo por lo que se declara de tipo Object.

La clase Object

  • Una clase así no puede almacenar tipos primitivos (ejemplo: int) pero pueden hacerse objetos que contengan valores de estos tipos si es necesario, usando clases de envoltura (Por ejemplo: Integer)
  • La clase Object define algunos métodos que son heredados por todos los objetos. Ejemplo: public String toString() que devuelve una implementación de cadena de texto del objeto

La clase Object

  • El método toString se invoca implícitamente siempre que se utiliza una referencia a un objeto como un operando del operador + en una expresión de concatenación de cadena de textos. La versión de este método en Object construye una cadena que contiene el nombre de la clase del objeto un carácter @ y una representación hexadecimal del código hash de la instancia

La clase Object

  • public final Class getClass().getName() devuelve el nombre de la clase de un objeto, se determina en tiempo de ejecución
  • public boolean equals (Object obj) Compara el objeto receptor con el referido por obj para ver si son iguales, devolviendo true si tienen el mismo valor y false en caso contrario

La clase Object

  • Si deseamos determinar si 2 referencias se refieren al mismo objeto, podemos compararlas usando == y !=. El método equals pregunta por la igualdad de valores. La implementación por defecto de equals en Object supone que un objeto sólo es igual a sí mismo, comprobando si this==obj

La clase Object

  • El método equals se puede redefinir si se desea proporcionar una noción de igualdad diferente de la implementación por defecto que se proporciona en la clase Object. La opción por defecto es que 2 objetos diferentes no son equal.
  • El término identidad se usa para indicar igualdad de referencias: si 2 referencias son idénticas, la operación == entre ellas devolverá true. El término equivalencia se usa para indicar igualdad de valores (objetos que pueden ser o no idénticos, pero para los que equals devuelve true). Por tanto, puede decirse que en la implementación por defecto de equals la equivalencia es lo mismo que la identidad.

La clase Object

  • Una clase que defina una noción más amplia de igualdad puede tener objetos que no sean idénticos, pero que sean equivalentes, si redefine equals para que devuelva true basándose en los estados de los objetos en vez de sus identidades


La base de datos está protegida por derechos de autor ©absta.info 2016
enviar mensaje

    Página principal