aula-08-interface-e-aula-07-classe-metodo-abstrato

Propaganda
Programação Java
Apêndice Aula 07
Esquecemos algo quando projetamos essa classe? (Classe abstrata)
A estrutura de classes não está ruim. Foi projetada para que os códigos duplicados fossem
mantidos em um nível mínimo e sobrepusemos os métodos que achamos que deveriam ter
implementações específicas da subclasse. Foi construída para ser adequada e flexível a partir
de uma perspectiva polimórfica, já que podemos projetar programas usando a classe Animal e
seus argumentos para que qualquer subtipo de Animal (inclusive aqueles em que não
pensamos no momento em que escrevemos nosso código) possa ser passado e usado no
tempo de execução. Inserimos o protocolo comum a todos os objetos Animal na superclasse e
estamos prontos para começar a criar novos objetos nas subclasses.
Sabemos que podemos dizer:
Wolf aWolf = new Wolf();
E também:
Animal aHippo = new Hippo();
Mas aqui é começa a ficar estranho:
Animal aAnimal = new Animal();
Qual a aparência de um animal?
Quais são os valores das variáveis de instância?
Algumas classes não deviam ser instanciadas!
Faz sentido criar um objeto Wolf, um objeto Hippo ou um objeto Tiger, mas o que exatamente
é um objeto Animal? Que forma ele tem? Que cor, tamanho, quantidade de pernas...
Precisamos de uma classe Animal, devido à herança e ao polimorfismo. Mas queremos que os
programadores instanciem somente as subclasses menos abstratas da classe Animal e não a
própria classe Animal. Queremos objetos Tiger e objetos Lion e não objetos Animal.
Felizmente, há uma maneira simples de impedir que uma casse seja instanciada. Em outras
palavras, de impedir que alguém use "new" com esse tipo. Se marcarmos a classe com
abstract, o compilador impedirá que qualquer código, esteja onde estiver, crie uma instância
desse tipo.
Você ainda poderá usar esse tipo abstrato como um tipo de referência. Na verdade, em grande
parte é por isso que você tem essa classe abstrata (para usá-la como um argumento ou tipo de
retorno polimórfico ou para criar uma matriz polimórfica).
Quando você estiver projetando sua estrutura de herança de classe, terá que decidir que
classes serão abstratas e quais serão concretas. As classes concretas são aquelas específicas o
suficiente para ser instanciadas. Uma classe ser concreta significa apenas que não há
problemas em se criar objetos desse tipo.
Criar uma classe abstrata é fácil: insira a palavra-chave abstract antes da declaração da classe:
abstract public class Canine extends Animal {
public void roam() {}
}
O compilador não permitirá que você instancie uma classe abstrata
Uma classe ser abstrata significa que ninguém poderá criar uma nova instância dessa classe.
Você ainda poderá usar essa classe abstrata como um tipo de referência na declaração, para
fins de polimorfismo, mas não terá que se preocupar com o fato de alguém criar objetos desse
tipo. O compilador garante isso.
abstract public class Canine extends Animal {
public void roam() {}
}
public class MakeCanine {
public void go() {
Canine c = new Canine();
c.roam();
}
}
Uma classe abstrata praticamente (uma classe abstrata pode ter membros estáticos, veremos
mais a frente no curso) não tem utilidade, a menos que seja estendida. Quem se encarregará
do trabalho no tempo de execução serão instâncias da subclasse da classe abstrata.
Abstrato versus concreto
Uma classe que não é abstrata é chamada de concreta. Na árvore de herança de Animal, se
tornarmos Animal, Canine e Feline abstratas, isso deixará Hippo, Wolf, Dog, Tiger, Lion e Cat
como as subclasses concretas.
Examine a APl Java e você encontrará várias classes abstratas, principalmente na biblioteca de
GUIs. Qual a aparência de um componente de GUI? Component é a superclasse das classes
relacionadas às GUls para coisas como botões, áreas de texto, barras de rolagem, caixas de
diálogo e o que mais pudermos imaginar. Você não criará a instância de um objeto Component
genérico e o inserirá na tela, criará um objeto JButton. Em outras palavras, você instanciará
somente uma subclasse concreta de Component, mas nunca o próprio objeto Component.
Métodos abstratos
Além das classes, você também pode marcar os métodos como abstratos. Uma classe abstrata
significa que ela deve ser estendida; um método abstrato significa que ele deve ser
sobreposto. Você pode chegar à conclusão de que alguns (ou todos) comportamentos de uma
classe abstrata não terão sentido, a menos que sejam implementados por uma subclasse mais
específica. Em outras palavras, não é possível pensar na implementação de um método
genérico que pudesse ser útil para as subclasses. Qual seria a aparência de um método eat()
genérico?
Um método abstrato não tem corpo!
Como você já decidiu que nenhum código faria sentido no método abstrato, não inserirá um
corpo no método. Portanto, não haverá chaves: simplesmente termine a declaração com um
ponto-e-vírgula:
abstract public void eat();
Se você declarar um método como abstrato, também deve marcar a classe como abstrata.
Não é possível ter um método abstrato em uma classe não-abstrata
Mesmo se você inserir apenas um método abstrato em uma classe terá que torná-la abstrata.
Mas é possível combinar métodos abstratos e não abstratos na classe abstrata.
Qual é a vantagem em se ter um método abstrato? Pensei que a finalidade de uma classe
abstrata seria termos um código comum que pudesse ser herdado pelas subclasses
As implementações de métodos herdáveis (em outras palavras, métodos com corpos reais) são
algo bom de inserir em uma superclasse. Quando isso faz sentido. E em uma classe abstrata,
geralmente não faz sentido, porque você não conseguirá criar um código genérico que seja útil
para as subclasses. A vantagem de um método abstrato é que ainda que você não tenha
inserido nenhum código de método real, terá definido parte do protocolo para um grupo de
subtipos (subclasses).
E isso é bom por causa do polimorfismo! Lembre-se, o que queremos é a possibilidade de usar
um tipo da superclasse (geralmente abstrato) como argumento, tipo de retorno ou tipo de
matriz de um método. Dessa forma, você poderá adicionar novos subtipos (como uma nova
subclasse de Animal) ao seu programa sem ter que reescrever (ou adicionar) novos métodos
para lidar com esses novos tipos. Imagine o que você teria que alterar na classe Vet, se ela não
usasse Animal como o tipo de argumento de seus métodos. Seria preciso ter um método
separado para cada subclasse de Animal! Um método que usasse um objeto Lion, um que
usasse um objeto Wolf, um que usasse... Percebeu? Portanto, com um método abstrato, você
está dizendo, "todos os subtipos desse tipo têm esse método" para se beneficiar do
polimorfismo.
Você deve implementar todos os métodos abstratos
Implementar um método abstrato é como sobrepor um método
Os métodos abstratos não têm um corpo; eles existem somente por causa do polimorfismo.
Isso significa que a primeira classe concreta da árvore de herança deve implementar todos os
métodos abstratos. No entanto, você pode retardar o processo sendo o máximo possível
abstrato. Se tanto Animal quanto Canine forem abstratas, por exemplo, e as duas tiverem
métodos abstratos, a classe Canine não terá que implementar os métodos abstratos de
Animal. Mas, assim que chegarmos à primeira subclasse concreta, como Dog, essa subclasse
terá que implementar todos os métodos abstratos tanto de Animal quanto de Canine.
Mas lembre-se de que uma classe abstrata pode ter tanto métodos abstratos quanto nãoabstratos, portanto Canine, por exemplo, poderia implementar um método abstrato de
Animal, para que Dog não tivesse que fazê-lo. Mas se Canine não fizer nada com os métodos
abstratos de Animal, Dog terá que implementar todos eles.
Quando dizemos "você deve implementar o método abstrato", isso significa que deve fornecer
um corpo. Significa que você deve criar um método não-abstrato em sua classe com a mesma
assinatura (nome e argumentos) e um tipo de retorno que seja compatível com o declarado
para o método abstrato. O que você vai inserir nesse método é problema seu. O Java só
verificará se o método está lá, em sua subclasse concreta.
Todas as classes em Java estendem a classe Object
A classe Object é a mãe de todas as classes; é a superclasse de tudo.
Mesmo sem você se beneficiar do polimorfismo, ainda terá que criar uma classe com métodos
que usem e retornem seu tipo polimórfico. Sem uma superclasse comum para tudo em Java,
não haveria uma maneira de os desenvolvedores criarem classes com métodos que pudessem
usar os seus tipos personalizados. Os tipos que eles não conheciam quando criaram a classe
ArrayList.
Portanto, você esteve criando subclasses da classe Object desde o início sem nem mesmo
saber disso. Toda classe que você criar estenderá Object, sem que seja preciso declará-lo.
Mas você pode considerar isso como se tivesse criado uma classe dessa forma:
public class Dog extends Object {}
Porém espere um minuto, Dog já estende algo, Canine. Sem problemas. O compilador fará
Canine estender Object. Mas Canine estende Animal. Sem problemas, o compilador fará com
que Animal estenda Object.
Qualquer classe que não estender explicitamente outra classe estenderá implicitamente
Object
Então, já que Dog estende Canine, não estenderá diretamente Object (embora o estenda
indiretamente) e o mesmo é verdade para Canine, mas Animal estende diretamente Object.
Mas o que é essa ultrasupermegaclasse Object?
Se você fosse a Java, que comportamento iria querer que todo objeto tivesse? Vejamos... Que
tal um método que lhe permita descobrir se um objeto é igual a outro? E um que possa lhe
informar o tipo de classe real desse objeto? Talvez um método que forneça um código de
hashing para o objeto, para que você possa usá-lo em tabelas de hashing. Um método que
exiba uma mensagem na forma de string para esse objeto.
A classe Object tem realmente métodos para essas quatro coisas. Isso não é tudo, mas esses
são os métodos que nos importam realmente.
1. equals(Object o): Informa se dois objetos são considerados 'iguais'.
Dog d = new Dog();
Cat c = new Cat();
if (d.equals(c)) {
System.out.println("true");
} else {
System.out.println("false");
}
2. getClass(): Retorna a classe em que o objeto foi instanciado.
Cat c = new Cat();
System.out.println(c.getClass());
3. hashCode(): Exibe o código de hashing do objeto (por enquanto, considere-o como uma
identificação exclusiva).
Cat c = new Cat();
System.out.println(c.hashCode());
4. toString(): Exibe uma mensagem na forma de string com o nome da classe e alguns outros
números que raramente interessam.
Cat c = new Cat();
System.out.println(c.toString());
Se você não tiver certeza se é um determinado objeto é instância de uma classe, poderá usar o
operador instanceof para verificar. Porque, se estiver errado quando fizer uma conversão, por
exemplo, verá uma exceção ClassCastException no tempo de execução e produzirá uma
interrupção inesperada.
Dog d = new Dog();
if (d instanceof Dog)
System.out.println("d é uma instância de Dog");
E se você tiver que alterar o contrato?
Certo, suponhamos que você fosse um objeto Dog. Sua classe Dog não é o único contrato que
define quem você é. Lembre-se de que você herdará os métodos acessíveis (o que geralmente
significa públicos) de todas as suas superclasses.
É verdade que sua classe Dog define um contrato.
Mas não todo o seu contrato.
Tudo que existir na classe Canine fará parte de seu contrato.
Tudo que existir na classe Animal fará parte de seu contrato.
Tudo que existir na classe Object fará parte de seu contrato.
De acordo com o teste É-UM, você é todas essas coisas: Canino, Animal e Objeto.
Mas e se a pessoa que projetou sua classe tivesse em mente o programa de simulação de
animais e agora quisesse usar você (a classe Dog) em um tutorial da feira de ciências sobre
objetos Animal.
Tudo bem, talvez você possa ser reutilizado para isso.
Entretanto, e se posteriormente ela quiser usá-lo no programa de um Pet Shop? Você não tem
nenhum comportamento de animal doméstico (Pet). Um objeto Pet precisa de métodos como
serAmigavel() e brincar().
Bem, agora suponhamos que você fosse o programador da classe Dog. Sem problemas, certo?
Apenas adicione mais alguns métodos a ela. Você não estará prejudicando o código das outras
pessoas adicionando métodos, já que não está mexendo nos métodos existentes que outro
código pode estar chamando em objetos Dog.
Consegue identificar alguma desvantagem nessa abordagem? (Adicionar métodos de Pet à
classe Dog?)
Na verdade precisamos de:
- Uma maneira de termos comportamentos de animal doméstico apenas em classes Pet.
- Uma maneira de garantir que todas as classes Pet tenham os mesmos métodos definidos
(mesmo nome, mesmos argumentos, mesmos tipos de retorno, nenhum método faltando,
etc).
- Uma maneira de nos beneficiarmos do polimorfismo para que todos os objetos Pet possam
ter seus métodos chamados, sem que tenhamos que usar argumentos, tipos de retorno e
matrizes específicos de cada classe Pet.
Precisamos de DUAS superclasses no topo da árvore (Pet e Animal)
- Devemos criar uma nova superclasse abstrata chamada Pet e fornecermos para ela todos os
métodos de Pet.
- Dog estende tanto Pet quanto Animal (por meio de Canine).
- Cat estende tanto Pet quanto Animal (por meio de Feline), portanto usa os métodos das duas
classes.
- Os animais que não são domésticos não herdam nada de Pet.
Há apenas um problema na abordagem de duas "superclasses": chama-se "herança
múltipla" e pode ser algo realmente perigoso
A herança múltipla apresenta um problema conhecido como "Losango Mortal" (em inglês,
Deadly Diamond of Death, conhecido pelo seu acrônimo, DDD):
- Tanto CDBurner quanto DVDBurner herdam de DigitalRecorder e as duas classes sobrepõem
o método burn(). Ambas herdam a variável de instância 'i'.
- Imagine se a variável de instância 'i' for usada por CDBurner e DVDBurner, com valores
diferentes. O que aconteceria se ComboDrive precisasse usar os dois valores de 'i'?
- Problema da herança múltipla. Que método burn() será executado quando você chamar
burn() em ComboDrive?
Em Java, não é possível herança múltipla, devido ao DDD
- Uma linguagem que permita o DDD pode levar a algumas complexidades desagradáveis,
porque você precisará de regras especiais para lidar com as possíveis ambiguidades.
- Regras adicionais significarão mais trabalho na precaução contra esses "casos especiais".
- Java foi projetada para ser simples, com regras consistentes que não travem em alguns
cenários. Portanto, (diferente da C++) ela o protegerá de ter que pensar no DDD.
Mas isso nos traz de volta ao problema original... Como manipularemos a questão Animal/Pet?
A interface vem nos socorrer (Interface)
Java lhe fornecerá uma solução. Uma interface. Não a interface gráfica (GUI).
Uma interface Java resolverá seu problema de herança múltipla fornecendo muitos dos
benefícios polimórficos desse tipo de herança sem a ameaça do DDD.
A maneira como as interfaces se livram do DDD é surpreendentemente simples: elas tornam
todos os métodos abstratos. Dessa forma, a subclasse terá que implementar os métodos
(lembre-se de que os métodos abstratos devem ser implementados pela primeira subclasse
concreta), portanto, no tempo de execução, a JVM não ficará confusa com relação a qual das
duas versões herdadas deve chamar.
- Uma interface Java é como uma classe 100% abstrata.
- Todos os métodos de uma interface são abstratos, portanto, qualquer classe que FOR-UM
animal doméstico DEVE implementar (isto é, sobrepor) os métodos de Pet.
Para definir uma interface:
public interface Pet { }
Para implementar uma interface:
public class Dog extends Canine implements Pet { }
- Observe que, quando você implementar uma interface, ainda poderá estender uma classe.
Criando e implementando a interface de Pet
public interface Pet {
abstract public void beFriendly();
abstract public void play();
}
- Os métodos da interface são implicitamente abstratos e públicos, portanto, digitar 'abstract'
e 'public' é opcional (na verdade, não é considerado um 'estilo adequado' digitar as palavras,
mas foi digitado aqui apenas para reforçar).
public class Dog extends Canine implements Pet {
public void makeNoise() { }
public void eat() { }
public void beFriendly() { }
public void play() { }
}
As interfaces não fornecem realmente a herança múltipla, porque você não pode inserir
qualquer código de implementação nelas. Se todos os métodos são abstratos, o que uma
interface vai proporcionar realmente?
Polimorfismo! As interfaces são a última palavra em flexibilidade, porque, se você usá-las em
vez das subclasses concretas (ou até mesmo tipos da superclasse abstrata) como argumentos e
tipos de retorno, poderá passar qualquer coisa que implemente essa interface. E pense bem,
com uma interface, a classe não tem que ser proveniente de apenas uma árvore de herança.
Uma classe pode estender uma classe e implementar uma interface. Mas outra classe pode
implementar a mesma interface, mesmo vindo de uma árvore de herança completamente
diferente! Portanto, você poderá tratar um objeto pelo tipo de classe da qual foi instanciado.
Na verdade, se você escrever um código que use interfaces, não terá nem mesmo que fornecer
uma superclasse a ser estendida. Poderá apenas fornecer a interface e dizer "veja, não me
importo com o tipo de estrutura de herança de classes que lhe deu origem, apenas
implemente essa interface e poderá continuar".
O fato de você não poder inserir código de implementação deixa de ser um problema para a
maioria dos bons projetistas, porque grande parte dos métodos da interface não faria sentido
se implementada de uma maneira genérica. Em outras palavras, a maioria dos métodos da
interface teria que ser sobreposta mesmo se os métodos não fossem forçados a ser abstratos.
Classes de árvore de herança diferentes podem implementar a mesma interface
A classe RoboDog não é proveniente da
árvore de herança de Animal, mas mesmo
assim pode fazer parte de Pet!
- Quando você usar uma classe como um tipo polimórfico (como em uma matriz de tipo
Animal ou um método que use um argumento de tipo Canine), os objetos que poderá inserir
nesse tipo devem ser provenientes da mesma árvore de herança. Mas não de qualquer local
da árvore de herança; os objetos devem ser provenientes de uma classe que seja uma
subclasse do tipo polimórfico. Um argumento de tipo Canine pode aceitar um objeto Wolf e
um objeto Dog, mas não Cat ou Hippo.
- Mas quando você usar uma interface como um tipo polimórfico (como em uma matriz de
objetos Pet), os objetos poderão ser provenientes de qualquer local da árvore de herança. O
único requisito é que os objetos sejam provenientes de uma classe que implemente a
interface.
- Você quer, por exemplo, que um objeto possa salvar seu estado em um arquivo? Em Java,
implemente a interface Serializable. Precisa que os objetos executem seus métodos em um
segmento separado? Implemente Runnable. Lembre-se de que classes de qualquer local da
árvore de herança podem ter que implementar essas interfaces. Quase qualquer classe pode
ter que ser salva ou executada.
O melhor é que uma classe pode implementar várias interfaces
Um objeto Dog É-UM tipo Canine, É-UM tipo Animal e É-UM tipo Object, tudo através da
herança. Mas Dog É-UM tipo Pet através da implementação da interface e esse objeto também
pode implementar outras interfaces. Você poderia escrever:
public class Dog extends Canine implements Pet, Saveable, Paintable { }
Como saber se você deve criar uma classe, uma subclasse, uma classe abstrata ou uma
interface?
1. Crie uma classe que não estenda nada (a não ser Object) quando sua nova classe não passar
no teste É-UM com nenhum outro tipo.
2. Crie uma subclasse (em outras palavras, estenda uma classe) somente quando tiver que
criar uma versão mais específica de uma classe e precisar sobrepor ou adicionar novos
comportamentos.
3. Use uma classe abstrata quando quiser definir um modelo para um grupo de subclasses e
tiver pelo menos algum código de implementação que todas as subclasses possam usar. Torne
a classe abstrata quando quiser garantir que ninguém possa criar objetos desse tipo.
4. Use uma interface quando quiser definir uma função que outras classes possam
desempenhar, independentemente de onde essas classes estejam na árvore de herança.
- Quando você não quiser que uma classe seja instanciada (em outras palavras, não quiser
que ninguém crie um novo objeto desse tipo de classe) marque-a com a palavra-chave
abstract.
- Uma classe abstrata pode ter tanto métodos abstratos quanto não-abstratos.
- Se uma classe tiver ao menos um método abstrato, ela deve ser marcada como abstrata.
- O método abstrato não tem corpo e a declaração termina com ponto-e-vírgula (não há
chaves).
- Todos os métodos abstratos devem ser implementados na primeira subclasse concreta da
árvore de herança.
- Toda classe em Java é uma subclasse direta ou indireta da classe Object.
- A herança múltipla não é permitida em Java, por causa dos problemas associados ao
"Losango Mortal". Isso significa que você só poderá estender uma classe (isto é, só poderá ter
uma superclasse imediata).
- Uma interface é como uma classe abstrata 100% pura. Ela só define métodos abstratos.
- Crie uma interface usando a palavra-chave interface em vez da palavra class.
- Implemente uma interface usando a palavra-chave implements. Exemplo: Dog implements
Pet.
- Sua classe poderá implementar várias interfaces.
- Uma classe que implementar uma interface deve implementar todos os métodos dessa
interface, já que eles serão implicitamente públicos e abstratos.
- Para chamar um método com a versão da superclasse a partir de uma subclasse que o tenha
sobreposto, use a palavra-chave super. Exemplo: super.runReport();
Download