Baixe o app para aproveitar ainda mais
Prévia do material em texto
1 FACULDADE DE COMPUTAÇÃO E INFORMÁTICA BACHARELADOS EM CIÊNCIA DA COMPUTAÇÃO E SISTEMAS DE INFORMAÇÃO E TECNOLOGIA EM ANÁLISE E DESENVOLVIMENTO DE SISTEMAS Linguagem de Programação I – SEMANA 08 TEORIA: HERANÇA Nossos objetivos nesta aula são: Conhecer o conceito de herança e implementá-lo em Java Conhecer e utilizar apontador super Conhecer e utilizar o operador instanceof Conhecer e implementar polimorfismo por inclusão Conhecer e implementar polimorfismo por coesão ou type casting Conhecer as classes empacotadoras ou wrappers. Continuar a praticar com desenvolvimento orientado a testes (TDD-Test Driven Development) A referência para esta aula é o Capítulo 10 (Herança) do nosso livro- texto: Horstmann, C. Conceitos de Computação com Java. 5.ed. Porto Alegre: Bookman, 2009. Não deixem de ler este capítulo antes e após a aula de hoje! HERANÇA Herança é um tipo especial de relacionamento do tipo generalização-especialização, onde uma subclasse (mais especializada) herda parte(ou o todo) de uma superclasse (mais genérica). Um dos objetivos principais da herança é fazer reuso de um código já implementado nas superclasses e adicionais os detalhes necessários para que a subclasse que esteja estendendo a superclasse seja mais especializada. O conceito de herança é uma das palavras-chave que caracterizam o paradigma de programação orientada a objetos (POO), juntamente com os mecanismos de abstração de tipos abstratos de dados. Assim, o conceito de herança é comum a diversas linguagens de programação orientadas a objetos. 2 Para entendermos a necessidade de herança, vamos relembrar como faríamos com a nossa classe BankAccount, caso desejássemos incluir um requisito de tipo de conta: public class BankAccount { private int accountNumber; // número da conta private String password ; // senha da conta private String owner; // proprietário da conta private double balance; // saldo da conta private String type; // tipo da conta public BankAccount(int accountNumber, String password, String owner, double balance, String type) { this.accountNumber=accountNumber; this.password=password; this.owner=owner; this.balance=balance; this.type=type; } public void deposit(double amount) { // Deposita valor na conta bancária balance += amount; } public void withDraw(double amount) { // Retira valor da conta bancária balance -= amount; } public double getBalance() { // Consulta o saldo da conta bancária return balance; } public int getAccountNumber(){ // Consulta o número da conta bancária return accountNumber; } public String getPassword(){ // Consulta a senha return password; } public String getOwner(){ // Consulta o proprietário da conta return owner; } public String getType(){ // Consulta o tipo da conta return type; } } 3 Para criarmos uma conta do tipo corrente e outra do tipo poupança, fazemos as seguintes instanciações: BankAccount c = new BankAccount(....,”CONTA CORRENTE”); BankAccount p = new BankAccount(...,”POUPANÇA”); Queremos, agora, incluir um detalhe específico de cálculo de juros quando a conta for do tipo poupança. Uma primeira tentativa para se fazer isto é mostrado abaixo: public class BankAccount { private int accountNumber; // número da conta private String password ; // senha da conta private String owner; // proprietário da conta private double balance; // saldo da conta private String type; // tipo da conta private double interestRate; // taxa de juros na forma de porcentagem public BankAccount(int accountNumber, String password, String owner, double balance, String type, double interestRate) { ... this.interestRate=interestRate; } public void deposit(double amount) {...} public void withDraw(double amount) {...} public double getBalance() { if (type.equals(“CONTA CORRENTE”)) return balance; else return balance*(1+interestRate/100.0); } public int getAccountNumber(){...} public String getPassword(){...} public String getOwner(){...} public String getType(){...} public double getInterestRate(){ // Devolve a taxa de juros return interestRate; } } Embora esta implementação funcione, ela exibe um problema muito sério: perda de coesão. Na mesma classe, estão representados os interesses tanto de uma conta corrente, quanto de uma poupança. 4 Para refatorar a implementação anterior utilizando o mecanismo de herança, vamos inicialmente concentrar na classe BankAccount somente os detalhes do correntista e saldo e retirar o tipo da conta, conforme mostrado abaixo: public class BankAccount { protected int accountNumber; // número da conta protected String password ; // senha da conta protected String owner; // proprietário da conta protected double balance; // saldo da conta public BankAccount(int accountNumber, String password, String owner, double balance){ ... } public void deposit(double amount) {...} public void withDraw(double amount) {...} public double getBalance() { return balance; } public int getAccountNumber(){...} public String getPassword(){...} public String getOwner(){...} } Tendo como base a classe BankAccount (superclasse), vamos utilizar o mecanismo de herança para construir a classe SavingsAccount (subclasse) para representar uma conta de poupança, conforme mostrado abaixo: public class SavingsAccount extends BankAccount { private double interestRate; // taxa de juros public SavingsAccount(int accountNumber, String password, String owner, double balance, double interestRate){ super(accountNumber, password,owner,balance); this.interestRate=interestRate; } public double savingsBalance(){ // Devolve o saldo da poupança return getBalance()*(1+interestRate); } } 5 A classe SavingsAccount estende a classe BankAccount através da palavra reservada extends. Devido a uma alternativa de projeto da Linguagem Java, só podemos estender uma classe de cada vez (herança simples). Outras linguagens OO como, por exemplo C++, permitem herança múltipla, ou seja, estender mais de uma classe ao mesmo tempo. Em Java, toda classe que não estende especificamente uma outra é uma subclasse da classe Object. Por exemplo, a classe BankAccount estende a classe Object. A classe Object tem alguns métodos que fazem sentido para todos os objetos, como os métodos toString e equals, que você pode utilizar para obter uma string que descreve o estado de um objeto. Ao realizar a extensão, tudo o que não for privado em BankAccount poderá ser acessado de SavingsAccount. Além disto, SavingsAccount acrescenta dois novos detalhes: o um atributo específico para representar a taxa de juros (interestRate) o um método específico para calcular o saldo da poupança (savingsBalance) As subclasses não têmacesso aos campos privados da superclasse, a não ser que façamos a alteração no modificador de acesso. 6 MODIFICADOR LIMITE DE VISIBILIDADE private Este é o nível de encapsulamento mais restritivo. A visibilidade das declarações limita-se à classe onde está definido o atributo/método. protected A visibilidade das declarações limita-se à própria classe e às classes herdeiras dela. “sem especificação de modificador” (package) A visibilidade das declarações limita-se à própria classe e às classes do mesmo package, mas não às classes herdeiras. Classes herdeiras não precisam ser do mesmo package. public Estas declarações são sempre acessíveis. No construtor da classe SavingsAccount, estamos reutilizando o construtor da classe BankAccount, acessado com o apontador super. Podemos utilizar de três maneiras: o super(....) invoca o construtor da superclasse o super.id acessa o atributo da superclasse identificado por id o super.m(...) acessa o método da superclasse identificado por m Quando não houver confusão entre o nome de identificadores da superclasse e da subclasse, o operador super pode ser suprimido. O mesmo ocorre no caso de acesso a métodos. Na classe do método savingsBalance(), estamos acessando o método getBalance() sem usar super, uma vez que não há confusão de nomes entre SavingsAccount e BankAccount. Para se instanciar uma conta corrente e uma poupança com a refatoração da herança, fazemos: BankAccount c = new BankAccount(...); SavingsAccount p = new SavingsAccount(...); 7 POLIMORFISMO O polimorfismo é a habilidade de uma ou mais classes responder a uma mesma mensagem de forma diferente. A Taxomia de Cardelli e Wegner que classifica o polimorfismo em categorias e subcategorias: Vimos o polimorfismo Ad-Hoc de sobrecarga: permite que um nome de método seja usado mais do que uma vez com diferentes quantidades ou tipos de parâmetros. A seleção do método adequado é feita com base em comparação dos tipos ou quantidade dos parâmetros Mesmo nome de método, parâmetros diferentes Vimos também o polimorfismo universal de inclusão: A sobrescrita (override) é a implementação de métodos em subclasses que possuem o mesmo nome e assinatura de métodos de sua superclasse, com o objetivo de anular o comportamento que o método apresentava na superclasse ou acrescentar novas instruções. Mesmo nome de método, mesmos parâmetros Vimos o polimorfismo paramétrico, Genéricos em Java, em que um único método é codificado, e ele trabalhará uniformemente num intervalo de tipos. Um exemplo: ArrayList <String> list = new ArrayList<String>(); Dizemos que a classe ArrayList é uma classe genérica e os colchetes angulares em torno do tipo String informam que String é o parâmetro de tipo. Você pode substituir String por qualquer outra classe e obter um tipo diferente de lista. Veremos o polimorfismo de coerção e o polimorfismo de inclusão. Porém, antes vamos formalizar uma classe empacotadora. 8 Classes Empacotadoras Números não são objetos em Java, então você não pode inserir diretamente na lista de Array. Ou seja, não pode formar ArrayList <double>. Porém, antes você deve transformá-lo em objeto por meio das classes empacotadoras (wrappers). Cada objeto de classe empacotadora contém um valor do tipo primitivo correspondente. Por exemplo, um objeto da classe Double contém um valor do tipo double. A conversão entre tipos primitivos e classes empacotadoras correspondentes é automática. Esse processo é chamado de auto-empacotamento ou auto- boxing. Por exemplo, se atribuir um número a um objeto Double, o número é automaticamente “colocado em uma caixa [box]”, a saber, um objeto empacotador. Double d = 29.95; // auto-empacotamento; ↔ Double d = new Double(29.95); Inversamente objetos de classes empacotadoras são automaticamente “desempacotados” para tipos primitivos. double x = d; // auto-desempacotamento ↔ double x = d.doubleValue(); Portanto, listas de arrays de números são simples e diretas. Basta lembrar-se de utilizar o tipo empacotador quando você declarar a lista de arrays e então usar o auto-empacotamento. ArrayList <Double> data = new ArrayList <Double> (); data.add (29.95); double x = data.get(0); 9 POLIMORFISMO AD-HOC POR COERÇÃO (TYPE CASTING) É o meio de contornar a rigidez de tipos monomórficos. A linguagem dá suporte à coerção através de um mapeamento interno entre tipos. O tipo que está sendo mapeado é chamado coargido. Usamos a coerção para converter um tipo em que outro tipo diferente. Exemplo: double balance = “a lot”; //Erro: tipos incompatíveis int dollars = 2; double balance = dollars; //Ok: legal armazenar um inteiro em um double double balance; int dollars = balance; //Erro: não podemos atribuir um ponto flutuante a um inteiro Temos que converter o valor de ponto-flutuante para inteiro usando uma coerção. int dollars = (int) balance; //Casting Temos que usar coerção em Java, pois a conversão perde informação. Temos que confirmar que concordamos com essa perda de informação. Uma coerção sempre tem a forma: (nomeDoTipo) expressão EXERCÍCIO TUTORIADO 1. Vamos supor que você deseje um valor truncado em dólares, deixando de lado a parte fracionária. Como proceder? 2. Usar simplesmente uma coerção para converter um ponto-flutuante para inteiro não é sempre uma boa ideia. Vamos supor que você deseje um valor aproximado em dólares ao invés de deixar de lado a parte fracionária, como o trecho de código a seguir. double price = 44.95; int dollars = (int) price; Como você pode melhorar para promover o arredondamento? 10 A operação de cast de objetos é semelhante à operação de cast de tipos primitivos, sendo que aqui deseja-se uma conversão dentro de uma hierarquia. O type casting aplicado em objetos tem as seguintes características: o É possível fazer cast de classes desde que estejam em uma mesma hierarquia; o Não podemos fazer cast entre classes "irmãs", tal como entre Funcionario e Cliente; o O cast não representa uma mudança estrutural do objeto; Existem dois tipos de type casting, são eles: o Cast up: conversão para classes localizadas nos níveis acima da hierarquia; o Cast down: conversão para classes localizadas nos níveis inferiores da hierarquia. Cast Up: o Com base na hierarquia de classes, podemos concluir que: o Todo Cliente é uma Pessoa e toda Pessoa é um Object; o Portanto, podemos realizar a operação de cast up, visualizando um objeto da classe Cliente como Pessoa ou Object, mas o objeto não perderá definitivamente suas características de Cliente o O cast up pode ser explícito ou automático As atribuições permitidas entre variáveis de superclasse e de subclasses são: o Superclasse Superclasse o Subclasse Subclasse o Superclasse Subclasse EXERCÍCIO TUTORIADO O que ocorre em cada linha do trecho de código Cliente c = new Cliente(); ___________________________________________________ Pessoa p = (Pessoa) c; _____________________________________________________ Pessoa p2 = c; _____________________________________________________ Pessoa p3 = new Cliente();___________________________________________________ Object o = (Object) c; _____________________________________________________ Object o = c; _____________________________________________________11 Cast Down: o A operação de cast down é oposta à operação de cast up, isto é, ao invés de generalizarmos um objeto vamos especializá-lo; o Se um objeto é criado como Cliente, e sofre um cast up para Pessoa, é possível fazer o cast down para voltar a visualizá-lo como Cliente. Exemplo: o Lembrando que: Todo Cliente é uma Pessoa, mas nem toda Pessoa é um Cliente; o Se um objeto é criado e declarado como Pessoa, não é possível fazer o cast down para transformá-lo em Cliente; o Quando for possível realizar a operação de cast down, deverá ser feita sempre de forma explícita. EXERCÍCIO TUTORIADO Diga se é válida ou não cada atribuição. Justifique sua resposta. Cliente c = new Cliente(); ________________________________________________ Pessoa p = c; ________________________________________________ Cliente c2 = (Cliente)p; _______________________________________________ Pessoa p2 = new Pessoa(); ________________________________________________ Cliente c3 = (Cliente)p2; _______________________________________________ Na conversão de tipos primitivos, perdemos informação e dizemos ao compilador que concordamos. Na conversão de tipos de instância, corremos o risco de causar uma exceção e dizemos ao compilador que concordamos em correr esse risco. Para ter a segurança de que a coerção terá sucesso, é melhor testá-la antes de colocá-la em prática. O operador instanceof testa se um determinado objeto é instância de uma determinada classe. Ele retornará true se o objeto for uma instância da classe (ou de suas subclasses) e false, caso contrário. Retomando a nossa classe BankAccount e SavingsAccount, outra possibilidade de instanciação de poupança é da seguinte forma: BankAccount p = new SavingsAccounts(...); //Casting up Porém, se invocarmos o método savingsBalance() diretamente de p, receberemos um erro de compilação, pois savingsAccount() não está definido em BankAccount. Para contornar isto, podemos verificar dinamicamente qual o tipo está armazenado em p e realizar um casting para invocar o método corretamente: 12 if (p instanceof SavingsAccount) System.out.println(((SavingsAccount) p).savingsBalance()); POLIMORFISMO POR INCLUSÃO Na implementação da nossa classe SavingsAccount, utilizamos um método para consultar o saldo com nome savingsBalance, conforme mostrado abaixo: public class SavingsAccount extends BankAccount { private double interestRate; // taxa de juros public BankAccount(int accountNumber, String password, String owner, double balance, double interestRate){ super(accountNumber, password,owner,balance); this.interestRate=interestRate; } public double savingsBalance(){ // Devolve o saldo da poupança return getBalance()*(1+interestRate); } } Porém, se quisermos manter o mesmo nome getBalance() utilizado na superclasse BankAccount, podemos utilizar o recurso de polimorfismo por inclusão: definimos métodos com a mesma assinatura tanto na superclasse quanto na subclasse. Abaixo, temos a refatoração da nossa implementação acima para explorar o polimorfismo por inclusão: public class SavingsAccount extends BankAccount { private double interestRate; // taxa de juros public BankAccount(int accountNumber, String password, String owner, double balance, double interestRate){ super(accountNumber, password,owner,balance); this.interestRate=interestRate; } public double getBalance(){ // Devolve o saldo da poupança return super.getBalance()*(1+interestRate); } } Neste exemplo, observe que foi fundamental o uso do apontador super para diferenciar entre o getBalance() da classe SavingsAccount e aquele da classe BankAccount. 13 EXERCÍCIOS TUTORIADO Tendo como base a classe BankAccount, implemente uma nova classe LawAccount via herança para representar contas vinculadas a processos jurídicos. Numa conta deste tipo, precisamos armazenar o motivo da conta vinculada como um texto (por exemplo, “Custos advogatícios da União”), a data da criação da conta e data em que a conta poderá ser liberada. Além disto, contas vinculadas possuem uma taxa de administração que deve ser descontada do saldo no momento do seu resgate. Faça uso do mecanismo de polimorfismo por inclusão. Sugestão: para datas, utilize a classe Date do pacote java.util. 14 15 ATIVIDADE DE LABORATÓRIO Na semana passada, implementamos uma coleção de objetos do tipo BankAccount através da classe Bank, fazendo a leitura a partir de um arquivo. Nesta atividade, vamos expandir esta implementação permitindo que a classe Bank armazene tanto as contas normais (BankAccount) quanto poupanças (SavingsAccount). Para isto, utilizando a metodologia TDD: 1. Refatore a classe Bank para que consiga representar, na mesma coleção, tanto objetos do tipo BankAccount quanto SavingsAccount. 2. Refatore o construtor de Bank para monte a estrutura da coleção de contas utilizado, agora, o seguinte formato de arquivo: Número de contas Número da Conta 1#Senha da Conta 1#Proprietário da Conta 1#saldo da Conta1 Número da Conta 2#Senha da Conta 2#Proprietário da Conta 2#saldo da Conta2#taxa juros ... Número da Conta N#Senha da Conta N#Proprietário da Conta N#saldo da ContaN Se a linha de uma conta contiver quatro parâmetros, ela deve ser instanciada como um objeto do tipo BankAccount. Caso a linha tenha cinco parâmetros, ela deve ser instanciada como um objeto do tipo SavingsAccount. 3. Refatore o método sort(...) para que ordene esta nova coleção em ordem crescente de saldo. O saldo de uma conta de poupança deve se considerado com o saldo normal + juros. 4. Implemente um método ArrayList<BankAccount> accounts() que devolva somente os objetos do tipo BankAccount armazenados na coleção de Bank. 5. Implemente um método ArrayList<SavingsAccount> SavingsAccounts() que devolva somente os objetos do tipo SavingsAccount armazenados na coleção de Bank. Junto com o projeto, entregue a classe principal com os testes de sua implementação. 16 EXERCÍCIOS EXTRA-CLASSE 1. Implemente, na classe Bank, um método changeAccount(...) para substituir os dados de uma conta atual (oldAccount), caso exista, pelos dados de uma nova conta (newAccount): public void changeAccount(BankAccount oldAccount, BankAccount newAccount){...} Seu método deve receber tanto contas normais quanto poupanças. 2. Refatore, na classe Bank, o método de busca linear do método find(...) para o método de busca binária. Seu método deve receber tanto contas normais quanto poupanças. Sugestão: utilize o método sort() implementado no laboratório. 3. Implemente, na classe Bank, um método importAccounts(...), que recebe uma instância da classe Bank e importa todas as contas desta instância. Casa haja conflito de números de conta, as duas contas com números iguais deverão ser mantidas. public void importAccounts(Bank b){...} Seu método deve permitir a importação tanto de contas normais quanto poupanças. 4. Implemente, na classe Bank, uma versão polimórfica do método sort() para, ao invés de ordenar a própria coleção de contas, devolver uma cópia ordenada deste coleção, mantendo a coleção inicialna sua forma original: public void sort(ArrayList<BankAccount> ord){…} Seu método deve permitir a ordenação tanto de contas normais quanto poupanças. 5. Seja n um inteiro e x um número de ponto flutuante. Explique a diferença entre: n = (int) x; e n = (int) Math.round(x); 6. Seja n um inteiro e x um número de ponto flutuante. Explique a diferença entre: n = (int) (x + 0.5); e n = (int) Math.round(x); Para quais valores de x eles dão o mesmo resultado? Para quais valores de x eles dão resultados diferentes? 17 7. Quais são os valores das expressões a seguir? Em cada linha, suponha que: double x = 2.5; double y = -1.5; int m = 18; int n = 4; String s = "Hello"; String t = "World"; a. x + n * y - (x + n) * y b. m / n + m % n c. 5 * x - n / 5 d. Math.sqrt(Math.sqrt(n)) e. (int) Math.round(x) f. (int) Math.round(x) + (int) Math.round(y) g. s + t h. s + n i. 1 - (1 - (1 - (1 - (1 - n)))) j. s.substring(1, 3) k. s.length() + t.length() 8. Quais destas condições retornam true? Verifique na documentação Java os padrões de herança. a. Rectangle r = new Rectangle(5, 10, 20, 30); b. if (r instanceof Rectangle) . . . c. if (r instanceof Point) . . . d. if (r instanceof Rectangle2D.Double) . . . e. if (r instanceof RectangularShape) . . . f. if (r instanceof Object) . . . g. if (r instanceof Shape) . . . 9. O que há de errado com o laço a seguir? double[] data = new double[10]; for (int i = 1; i <= 10; i++) data[i] = i * i; Explique duas maneiras de corrigir o erro. 10. Implemente os métodos toString, equals e clone para todas as subclasses da classe BankAccount. Escreva testes de unidade que verificam se os seus métodos funcionam corretamente. Certifique-se de testar que uma conta bancária contém objetos provenientes de uma combinação de classes de BankAccount.
Compartilhar