Baixe o app para aproveitar ainda mais
Prévia do material em texto
Aula 06: POLIMORFISMO Objetivos Recordando Na última aula vimos o conceito de herança que resumidamente poderíamos dizer que: A herança é um importante recurso da programação orientada a objeto que permite derivar uma nova classe mais especializada a partir de outra mais genérica existente. A classe derivada é chamada de subclasse, enquanto a classe original é dita superclasse. A subclasse assume (herda) os atributos (variáveis de instância) da superclasse e pode adicionar novos atributos exclusivamente seus. Quanto aos métodos, a subclasse pode utilizar (herdar) ou alterar os existentes na superclasse, bem como pode criar novos para seu uso. Os construtores não são herdados. Membros da subclasse Ao escrever métodos na subclasse pode-se: 1. sobrescrever (ou sobrepor) métodos da superclasse. Um método da subclasse sobrescreve um da superclasse se ele tem a mesma assinatura (mesmo nome e mesmos tipos de parâmetros) do método da superclasse. Deve ter, inclusive, o mesmo tipo de retorno. Sempre que aplicado a um objeto do tipo da subclasse, o método da subclasse será chamado. 2. herdar métodos da superclasse. Todo método da superclasse que não é sobrescrito na subclasse é herdado por esta. O método da superclasse pode ser aplicado a um objeto da subclasse. 3. definir novos métodos para a subclasse que não aparecem na superclasse. Esses métodos só poderão ser aplicados a objetos da subclasse. Compreender tipos estáticos e dinâmicos Definir o conceito de polimorfismo de método Apresentar classes e métodos abstratos Esclarecer o uso do operador this Apresentar classes e métodos final Para os atributos, não se pode sobrescrever nenhum atributo da superclasse. Pode-se apenas: 1. herdar os atributos da superclasse. Todos os atributos da superclasse são automaticamente herdados pela subclasse. Todo objeto da subclasse terá cada um desses atributos. 2. definir novos atributos. Só objetos da subclasse terão esses atributos. Chamando métodos da subclasse Quando um objeto da subclasse chama um método, temos as seguintes situações: 1. Se for um método da subclasse que sobrescreve um método da superclasse, o da subclasse é acionado (ele esconde o da superclasse). 2. Se o método chamado só existe na superclasse, ele será acionado (a subclasse herda métodos da superclasse que ela não sobrescreve). 3. Se o método é exclusivo da subclasse, ele será acionado. 4. Se o método chamado não existe na subclasse, nem na superclasse, nem na superclasse da superclasse, e em nenhuma superclasse hierarquia acima, então acontece um erro. Construtores da subclasse Os construtores da superclasse não são herdados pela subclasse, logo, esta deverá ter seus próprios construtores para inicializar seus atributos. Todavia, ao instanciar um objeto da subclasse, devemos inicializar aqueles atributos que ela está herdando, e o fazemos chamando algum construtor da superclasse, usando a chamada super(...). Obs. Se o construtor da subclasse não chama um da superclasse, então o construtor padrão da superclasse é chamado automaticamente. Se a superclasse não tem um construtor padrão, dá erro de compilação. Polimorfismo De um modo geral, polimorfismo é a realização de uma tarefa de formas diferentes (poli – muitas; morphos – formas). Uma utilização importante do polimorfismo em Java ocorre nas hierarquias de herança, quando dois objetos, sendo um da superclasse e outro da subclasse, respondem diferentemente à mesma chamada de um método. Antes de vermos um exemplo, vamos estudar a conversão de objetos numa hierarquia de herança. Conversão de um tipo para outro mais genérico (Upcasting) Observe a seguinte hierarquia de herança. A instrução Pai p; declara p como uma referência da superclasse Pai – isto define a vocação de p: referenciar qualquer objeto do tipo Pai. Atente, agora, para a seguinte regras: Regra 1: Em Java, podemos atribuir um objeto da subclasse a uma referência de sua superclasse. (Esta operação é chamada upcasting, de up type casting). Esta regra é razoável, pois todo objeto da subclasse é um objeto da sua superclasse. Então, podemos atribuir a p um objeto Filha: Pai p; p = new Filha(); Agora p, uma referência de superclasse, está apontando um objeto de subclasse. A vocação de p não foi contrariada, pois o objeto Filha é também um objeto Pai. Observe, a seguir, p acionando métodos: p.m1(); //chama m1( ) de Filha, porque ela sobrescreveu o m1 de Pai p.m2(); //chama m2( ) de Pai, herdado por Filha Porém, cuidado com a chamada seguinte: p.m3(); //ERRO de compilação Este último comando foi uma tentativa de acessar, através de p (referência de superclasse) um membro exclusivo da subclasse, o que contraria a vocação de p, expressa na regra 2: Regra 2. Uma referência de superclasse só reconhece membros disponíveis na superclasse, mesmo que esteja apontando para um objeto de subclasse. A figura ao lado tenta mostrar, de outra forma, a relação entre Pai e Filha. A Filha herda do Pai todo o seu conhecimento (atributos) e comportamentos (métodos), mas pode acrescentar conhecimento e comportamentos novos exclusivamente seus, aos quais o Pai não tem acesso. A parte escura da figura mostra o que o Pai sabe. Resumindo, o upcasting é a conversão de um objeto de tipo mais específico para um tipo mais genérico, feita implicitamente através de atribuição, mas a partir da conversão só os membros do tipo mais genérico podem ser acessados sem artifícios. Para o nosso exemplo, resta a questão: como acessar o método m3? A resposta está no downcasting. Conversão de um tipo para outro mais específico (Downcasting) Regra 3 Em Java, a atribuição de um objeto de superclasse a uma referência de subclasse, sem uma coerção explícita, não é permitida. Ex: Filha f = p; // ERRO de compilação Isto parece, também, razoável, pois f tem vocação de referenciar e saber coisas que não existem no objeto Pai (o atributo y e o método m3). Ou ainda, a relação É UM tem mão única, da subclasse para a superclasse, portanto, um objeto Pai não é um objeto Filha. Há casos, todavia, em que podemos "forçar a barra" através de coerção, se soubermos que o objeto atualmente com referência de superclasse é, na realidade, um objeto da subclasse para a qual estamos convertendo. Assim, poderíamos chamar o método m3. Filha f = (Filha) p; //nome da classe destino entre parênteses f.m3(); Esta realidade, citada na frase anterior, é algo verificado por Java apenas em tempo de execução. Se o objeto for do tipo da subclasse, a coerção será válida, mas se não for, ocorrerá uma ClassCastException. Para proteger nosso código dessa incerteza, convém usar o operador especial instanceof com a seguinte sintaxe: variável objeto instanceof Classe Este operador retorna true se a variável objeto é do tipo da Classe, false em caso contrário. Assim, nossa coerção anterior ficaria mais segura se codificada assim: if (p instanceof Filha){ Filha f = (Filha) p; f.m3(); } Observações: 1. downcasting vem de down type casting. 2. O downcast só será válido se, em tempo de execução, o objeto tiver um relacionamento É UM com o tipo dado entre parênteses – ou seja, (C)ref só vale se ref É UM C. Animal a; Macaco m; Girafa g; m = new Macaco(); a = m; g = (Girafa) m; //Erro em tempo de compilação g = (Girafa) a; //Erro em tempo de execução Uma aplicação prática do polimorfismo O exemplo seguinte, embora simples, mostra, na classe TestaAlteta,a presença do polimorfismo, ao permitir instanciar, através de uma mesma referência de superclasse, tanto objetos desta como de sua subclasse. Numa academia, os atletas são categorizados pelo peso, conforme tabela da esquerda. Os lutadores, porém, têm categorias definidas pela da direita. Mas, é obvio, todo lutador é um atleta. Temos, então, a seguinte hierarquia de herança : A codificação correspondente em Java: --------------------------------------------------------------- Classe Atleta --------------------------------------------------------------- public class Atleta{ private int peso; public Atleta(int p){ peso = p; } public String defineCategoria(){ String cat; if (peso <= 50) cat = "Infantil"; else if (peso <= 65) cat = "Juvenil"; else cat = "Adulto"; return cat; } public int getPeso(){ return peso; } } --------------------------------------------------------------- Classe Lutador --------------------------------------------------------------- public class Lutador extends Atleta{ public Lutador(int p){ super(p); } public String defineCategoria(){ String cat; if (getPeso() <= 54) cat = "Pluma"; else if (getPeso() <= 60) cat = "Leve"; else if (getPeso() <=75) cat = "Meio-leve"; else cat = "Pesado"; return cat; } } --------------------------------------------------------------- Classe Principal --------------------------------------------------------------- public class TestaAtleta{ public static void main(String args[]) { Teclado t = new Teclado(); int n = t.leInt("Quantidade de atletas: "); int r, p; Atleta a; //referência da superclasse for (int i = 0; i < n ; i++){ p = t.leInt("Peso: "); r = (int)(Math.random() * 10); // sorteio para instanciar um if (r % 2 == 0) // atleta ou um lutador a = new Atleta(p); else a = new Lutador(p); //aqui ocorre upcasting System.out.println("Categoria: " + a.defineCategoria()); } } } Veja que a referência de superclasse a é usada para instanciar ora um objeto da superclasse, ora um da subclasse. E a chamada do método defineCategoria, na última linha, aciona o código correspondente à forma que o objeto tomou dentro do if: ou a forma de um atleta, ou a forma de um lutador. O tipo real do objeto é que determina o método a ser chamado. Sugerimos que o aluno execute no computador o código acima e digite sempre o mesmo peso para diversas instanciações. Observações: a. Se a academia do exemplo vier a criar no futuro outra modalidade de atleta, por exemplo, nadador, com outras regras para a definição de sua categoria, bastará estender a classe Atleta, como foi feito para o lutador, sem afetar as classes já prontas. Chama-se esta capacidade de extensibilidade, que é uma das vantagens do polimorfismo. b. O polimorfismo como foi apresentado aqui é, para a maioria das pessoas, o verdadeiro polimorfismo. Só existe em hierarquias de herança ou interface. Todavia, a sobrecarga é também um tipo de polimorfismo. Alguns autores destacam os dois tipos, caso em que chama-se o que estudamos de polimorfismo tipo sobreposição (ligação tardia, dinâmica ou dynamic binding) e o outro de polimorfismo tipo sobrecarga (ligação precoce, estática ou early binding). A diferença principal é que na sobrecarga quem escolhe o método a ser acionado é o compilador e no polimorfismo com ligação dinâmica quem decide é a JVM. Não confundir sobrecarga com sobrescrita Um método sobrecarrega outro se ambos têm o mesmo nome, mas parâmetros de tipos ou quantidades diferentes. Os dois métodos podem estar na mesma classe ou um na superclasse e outro na subclasse. A sobrecarga se aplica também a construtores de uma mesma classe. Um método de uma subclasse sobrescreve um da sua superclasse se tem exatamente a mesma assinatura (nome, parâmetros e tipo de retorno) do método da superclasse (tem apenas uma outra implementação). Obviamente, não pode haver sobrescrita de métodos numa mesma classe ou sobrescrita de construtores. Classes e métodos abstratos Na aula passada, criamos duas classes utilizando o conceito de herança e colocamos na superclasse o que era comum às duas classes. Entretanto, cabe observar que era previsto haver empregados (objetos, portanto) de ambas as classes (EmpComissionado e EmpFixoComissionado). Existem situações em que classes possuem um conjunto de características comuns que, entretanto, não é suficiente para que com estes seja possível definir objetos reais. Esse conjunto de características comuns é então separado em uma superclasse destinada apenas a definir o que é comum às subclasses dela derivadas, sem que se tenha a intenção de efetivamente instanciar objetos dessa classe. Tal classe é conhecida como Classe Abstrata. Verificando este assunto a partir de um exemplo simples. Ex. Uma empresa contrata empregados através de uma das duas modalidades de pagamento: mensalistas ou horistas. O empregado mensalista recebe um salário básico mensal fixo mais as horas extras que ele faz no mês. O valor de uma hora-extra corresponde a 1/160 do salário básico mensal. O empregado horista simplesmente recebe pelas horas trabalhadas no mês, não existindo o recurso de hora extra. Percebe-se, facilmente, a presença da seguinte hierarquia: Todavia, nenhum empregado jamais será instanciado na superclasse, uma vez que ele deve ser obrigatoriamente enquadrado numa das duas categorias: mensalista ou horista. A classe empregado, neste caso, é dita uma classe abstrata. Uma classe abstrata é aquela que não pode ser instanciada. É uma classe que existe para ser herdada obrigatoriamente. Constitui-se num "molde" a partir do qual subclasses serão construídas. Em Java, indica-se com o modificador abstract antes da palavra class. public abstract class Empregado{ private String nome; .......... Uma classe que realmente instancia objetos é dita uma classe concreta. É o caso das nossas classes Horista e Mensalista. Uma superclasse abstrata é extremamente genérica, especificando apenas características comuns a qualquer subclasse possível. Em nosso exemplo, todo empregado tem um nome. Uma classe abstrata possui, em geral, um ou mais métodos abstratos. Um método abstrato é um método composto apenas de assinatura e sem implementação (sem bloco de código). A implementação deverá ser feita obrigatoriamente pelas subclasses concretas. Indica-se método pelo modificador abstract antes do tipo de retorno. public abstract double calculaLiquidoMes(int horas); Este método abstrato na classe Empregado obriga sua sobrescrita em subclasses concretas. Assim, cada categoria específica de empregado irá implementar de forma diferente o cálculo do valor líquido que o empregado irá receber ao final do mês. Observações: • Construtores não podem ser abstratos • Uma classe que contém um ou mais métodos abstratos deve ser declarada como abstrata, mesmo que tenha métodos não abstratos • Cada subclasse concreta de uma superclasse abstrata deve implementar os métodos abstratos da superclasse • Se uma classe estende outra que tem método abstrato e não o implementa, então ela também é abstrata e deve ser declarada como tal Para o projeto da aula anterior poderíamos ter criado a estrutura ao lado, onde Empregado fosse uma classe reservada para conter as características comuns a todos os empregados. Essa classe, entretanto, não possuiria todos os dados necessários para definir um empregado real, o que implica em quenão haverá nenhum objeto real dessa classe, caracterizando-a como uma classe abstrata. Essa classe deve conter apenas atributos que sejam comuns a todos os empregados, como nome, sobrenome e CPF. Nas classes derivadas é que aparecerão atributos relacionados à forma de contratação e que influenciam a remuneração dos empregados, tais como total de vendas, bônus, horas trabalhadas, salário base, etc. Nesse contexto, para que possamos utilizar corretamente o polimorfismo de método no cálculo do salário, devemos definir um método abstrato salário() na superclasse Empregado. Tal método não deve possuir efetivamente nenhuma implementação, pois não é sequer possível calcular o salário nessa classe. O código efetivo de implementação do método salário() deve ser deixado a cargo das subclasses, que possuem as informações necessárias para realizar o cálculo do salário. Esse método da superclasse Empregado é chamado de Método Abstrato e sua sobrescrição nas subclasses permite o Polimorfismo de Método. Na aula passada, quando implementamos diferentes versões para o método construtor de uma classe, fizemos uma Sobrecarga de Método. De fato podemos fazer isto com qualquer método da classe. Como visto no caso do construtor, o que diferencia essas implementações e permite à linguagem determinar qual implementação efetivamente chamar é a quantidade de argumentos definidos em cada uma e a quantidade de parâmetros passados na chamada do referido método. No exemplo de hoje, que será uma adaptação do exemplo da aula passada, faremos uma sobrecarga do método salario na classe EmpAssalariado, de forma a permitir dois cálculos diferentes para o salário, um com um abono (passado como parâmetro) e outro sem abono. Operador this Novamente recordando a aula passada, quando criamos os métodos parametrizados, declaramos os argumentos com nomes “parecidos” com os atributos da classe. Fazendo dessa forma, ficou claro que o argumento é uma variável e o atributo é outra, como, por exemplo, em: public EmpComissionado (String n, String sn, String cic, double vendas,double comissao){ nome = n; sobrenome = sn; cpf = cic; setTotalVendas (vendas); setTaxaComissao (comissao); } No construtor acima estamos lidando com dez variáveis (cinco argumentos do método e cinco atributos da classe). Esta forma, entretanto, não favorece a compreensão, na medida que alguns nomes de argumentos são bastante esclarecedores do seu conteúdo e criar um sinônimo pode ser indesejável. Porem, razões sintáticas nos fizeram utilizar os sinônimos, pois se o argumento sn, por exemplo, fosse chamado sobrenome, teríamos o comando de atribuição: sobrenome = sobrenome; Isto não seria corretamente compreendido pelo compilador. Neste caso estamos diante de uma sobrecarga de nome e o compilador resolve isso escolhendo a referência mais próxima (a que foi definida por último). Portanto, a variável sobrenome referenciada nesse comando não seria o atributo da classe e sim o argumento do método (o último definido). Soluciona-se isso usando o operador this, que faz referência ao objeto atual (objeto corrente ao tempo da execução). Assim o trecho de programa anterior poderia ser mais elegantemente definido assim: public EmpComissionado (String nome, String sobrenome, String cpf, double totalVendas,double taxaComissao){ this.nome = nome; this.sobrenome = sobrenome; this.cpf = cpf; setTotalVendas (totalVendas); setTaxaComissao (taxaComissao); } O mesmo se aplica aos demais métodos, como, por exemplo, aos métodos set dos atributos das classes. Classes e métodos final Podem ocorrer situações em que não seja desejável permitir que eventuais subclasses sobrescrevam métodos de uma superclasse. Nestes casos devemos impedir a redefinição do método através do uso de uma palavra reservada na declaração do mesmo. As declarações de métodos precedidas pela palavra reservada final não podem ser sobrescritas em subclasses. Os métodos declarados como private são implicitamente final porque é impossível sobrescrevê-los em uma subclasse. Como as implementações dos métodos final não podem mudar, todas as subclasses utilizam a mesma implementação do método, e as chamadas aos métodos final são resolvidas em tempo de compilação, o que é conhecido como vinculação estática. Uma classe declarada como final não pode ser superclasse, ou seja, nenhuma classe pode herdar de uma classe declarada como final. Tornar uma classe final pode ser necessário, por exemplo, para atender a requisitos de segurança, impedindo que programadores criem subclasses que poderiam “driblar” restrições de segurança. Já atributos declarados como final se comportam como constantes e devem ser iniciados na declaração ou no construtor da classe, não podendo ser reescritos posteriormente. Exemplos: private final int fator = 2; // atributo final protected final void abrir(); // metodo final public final class conta { // classe final ... } Revendo a aplicação Para consolidar os conceitos de classe abstrata, método abstrato, sobrecarga de método, polimorfismo de método, operador this e operador final, vamos alterar a aplicação que fizemos na aula passada para o código que se segue. Primeiramente, vamos adicionar a classe abstrata Empregado, que possui apenas três atributos (nome, sobrenome e cpf). Essa classe impede que classes derivadas alterem os métodos set desses atributos (uso do final) e usa sobrecarga de nomes para receber argumentos no construtor com os mesmos nomes dos atributos (uso do this): //Classe Empregado public abstract class Empregado extends Object { protected String nome; protected String sobrenome; protected String cpf; public Empregado (// Construtor com 3 argumentos String nome, String sobrenome, String cpf){ this.nome = nome; // uso do operador this this.sobrenome = sobrenome; this.cpf = cpf; } public final void setNome (String nome){//usa final this.nome = nome; } public String getNome() { return nome; } public final void setSobrenome (String sobrenome){ this.sobrenome = sobrenome; } public String getSobrenome() { return sobrenome; } public final void setCpf (String cpf){ this.cpf = cpf; } public String getCpf() { return cpf; } public abstract double salario(); // Método abstrato } // fim da classe Empregado A seguir vamos reescrever a classe EmpComissionado para herdar da classe abstrata Empregado, fazendo as adaptações necessárias no construtor. //Classe Empregado Comissionado public class EmpComissionado extends Empregado { protected double totalVendas; protected double taxaComissao; protected double bonus; public EmpComissionado (String nome, String sobrenome, String cpf, double totalVendas, double taxaComissao, double bonus){ // chama construtor da classe abstrata super (nome, sobrenome, cpf); setTotalVendas (totalVendas); setTaxaComissao (taxaComissao); setBonus(bonus); } public void setTotalVendas (double totalVendas){ this.totalVendas = (totalVendas < 0.0) ? 0.0 : totalVendas; } public double getTotalVendas () { return totalVendas; } public void setTaxaComissao (double taxaComissao){ this.taxaComissao = (taxaComissao < 0.0) ? 0.0 : taxaComissao; } public double getTaxaComissao () { return taxaComissao; } publicvoid setBonus (double bonus){ this.bonus = (bonus < 0.0) ? 0.0 : bonus; } public double getBonus (){ return bonus; } public double salario(){ // Calcula o salário return (taxaComissao * totalVendas + getBonus() * 2 * taxaComissao * totalVendas); } public String toString () { return String.format ( "%s%s %s\n%s%s\n%s%.2f\n%s%.2f\n%s%.2f", "Identificacao: ", nome, sobrenome, "CPF: ", cpf, "Total de vendas: ", totalVendas, "Taxa de comissão: ", taxaComissao, "Salario total: ", salario() ); } } // fim da classe Empregado Comissionado A classe EmpFixoComissionado continua herdando da classe EmpComissionado. Só sofreu alteração nos métodos toString() e salario() que agora contemplam o proposto no desafio da aula passada: //Classe Empregado com salário base mais Comissão public class EmpFixoComissionado extends EmpComissionado { private double salarioBase; // atributo exclusivo // Construtor com 7 argumentos public EmpFixoComissionado ( String nome, String sobrenome, String cpf, double totalVendas, double taxaComissao, double salarioBase, double bonus){ // chama construtor da superclasse EmpComissionado super (nome, sobrenome, cpf, totalVendas, taxaComissao,bonus); // inicia atributo próprio da classe setSalarioBase (salarioBase); } public void setSalarioBase (double salarioBase){ this.salarioBase = (salarioBase < 0.0) ? 0.0 : salarioBase; } public double getSalarioBase() { return salarioBase; } public double salario(){ // Calcula o salário return (salarioBase + taxaComissao * totalVendas + getBonus() * taxaComissao * totalVendas); } public String toString () { return String.format ("%s%s %s\n%s%s\n%s%.2f \n%s%.2f\n%s%.2f\n%s%.2f", "Identificacao: ", nome, sobrenome, "CPF: ", cpf, "Total de vendas: ", totalVendas, "Taxa de comissão: ", taxaComissao, "Salario base: ", salarioBase, "Salario total: ", salario() ); } } // fim da classe Empregado Fixo Comissionado A nova classe EmpAssalariado herda diretamente da classe abstrata: //Classe Empregado Assalariado public class EmpAssalariado extends Empregado { private double salarioMensal; // atributo exclusivo // Construtor com 4 argumentos public EmpAssalariado (String nome, String sobrenome, String cpf, double salarioMensal){ // chama construtor da classe abstrata super (nome, sobrenome, cpf); setSalarioMensal(salarioMensal); } public void setSalarioMensal (double salarioMensal){ this.salarioMensal = salarioMensal; } public double getSalarioMensal(){ return salarioMensal; } public double salario(){ // Salário sem abono return (getSalarioMensal()); } public double salario(double abono){ //sal. c/ abono return (getSalarioMensal() + abono); } public String toString () { return String.format ( "%s%s %s\n%s%s\n%s%.2f\n%s%.2f\n", "Identificacao: ", nome, sobrenome, "CPF: ", cpf, "Salario total: ", salario(), "Salario total com abono: ", salario(100.0)); } } // fim da classe Empregado Assalariado Finalmente, o código de teste (classe Principal) fica como se segue (agora só está imprimindo chamando o método toString das classes): public class Principal{ public static void main ( String argv[]) { EmpFixoComissionado empregadoFC = new EmpFixoComissionado ("Roberto", "Silveira", "123456789-10", 10000, 0.02, 300, 0.01); EmpComissionado empregadoC = new EmpComissionado ("Francisco", "Oliveira", "987654321-00", 20000, 0.02, 0.01); EmpAssalariado empregadoA = new EmpAssalariado ("Joaquim", "Alberto", "192837465-99", 1000); System.out.printf ("\n%s: \n\n%s\n\n", "Informacoes do empregado comissionado:", empregadoC.toString() ); System.out.printf ("\n%s: \n\n%s\n\n", "Informacoes do empregado fixo comissionado:", empregadoFC.toString() ); System.out.printf ("\n%s: \n\n%s\n\n", "Informacoes do empregado assalariado:", empregadoA.toString() ); } // fim do método main } // fim da classe Principal Compile e execute o código acima. Desafios 1. Altere o código da classe de teste (Principal) para que o empregado assalariado (objeto empregadoA) seja declarado como da classe estática Empregado mas continue sendo instanciado como da classe EmpAssalariado. Execute e veja que funciona perfeitamente o polimorfismo de métodos. 2. Agora tente alterar também a classe de instanciação do objeto empregadoA para Empregado. Compile e explique a ocorrência de erro. 3. Atendendo ao diagrama de classes da página dois desta aula, implemente também a classe EmpHorista. Crie os métodos e atributos que julgar necessários para que os objetos desta classe tenham um comportamento semelhante aos objetos das demais classes do projeto. Altere a classe Principal para que esta instancie e teste também um objeto empregadoHorista, imprimindo suas informações, inclusive seu salário, que deve ser calculado com base em um valor constante (use um atributo final) representativo do valor da hora trabalhada, multiplicado pelo número de horas trabalhadas (um atributo exclusivo desta classe). 4. Na classe de teste crie um empregado de cada classe declarada, inicie o objeto com os valores adequados de parâmetros e imprima um relatório com as informações de cada empregado a partir de chamadas aos métodos toString de cada classe. 5. Altere o exemplo criado no exercício 4 da aula 3, que envolvia a criação de uma super classe FiguraGeometrica e de classes filhas Quadrado e Circulo. A alteração deve ser no sentido de que a superclasse deve passar a ser abstrata e os métodos para cálculo de área e perímetro devem passar a ser abstratos também (ao invés de implementar na superclasse um método inútil que retorna sempre zero, como foi feito na aula passada). Essa alteração força naturalmente a sobrescrição dos métodos de cálculo nas classes derivadas, de forma a realizarem as contas corretamente, de acordo com o tipo de figura da classe derivada. Após testar, introduza mais uma alteração, que consiste em criar mais duas classes, Quadrilatero (que deve ser subclasse de FiguraGeometrica e superclasse de Quadrado) e Retangulo (que também deve ser subclasse de Quadrilatero). A classe Quadrilatero deve possuir um atributo constante numLados que deve ser iniciado com o valor 4. Altere adequadamente os atributos e os métodos de cálculo das classes. Instancie um objeto de cada figura na classe de teste, criando-os com as referências estáticas e dinâmicas iguais e imprimindo todos os atributos e cálculos dos objetos. Por fim, altere a criação dos objetos na classe de teste, de forma que a referência estática passe a ser FiguraGeometrica. Isto é parecido com o que foi feito no desafio 1 desta aula, entretanto, observe que é gerado um erro de compilação ao ser chamado qualquer método get referente a um atributo específico das subclasses (tal como getRaio()), por exemplo. Explique esse erro.
Compartilhar