Interfaces

As classes incorporam duas funções interligadas:

  • elas são um mecanismo de reúso de código;
  • elas estabelecem a "interface" dos objetos, ou seja, que atributos e métodos estão disponíveis para serem usados por um cliente externo.

Já vimos anteriormente alguns problemas de usar somente a herança como mecanismo de reúso. O mesmo acontece com a interface. Muitas vezes queremos que objetos compartilhem a mesma interface, sem que usem a herança para isso.

Por essa razão o Java define o mecanismo chamado interface. Ele permite a declaração de um interface que será garantida por um conjunto de classes, sem a necessidade que elas estejam ligadas por herança.

Considere as duas classes a seguir que representam formas geométricas. Ambas oferecem métodos para cálculo do perímetro e área, entretanto, não compartilham código.

Suponha que se deseja padronizar o acesso às interfaces de ambos objetos resolvendo a chamada de métodos de modo polimórfico. É possível se fazer isso sem herança, como será apresentado no próximo bloco.


In [1]:
public class Retangulo {
    private int altura;
    private int largura;

    public Retangulo(int altura, int largura) {
        this.altura = altura;
        this.largura = largura;
    }
    
    public int getAltura() {
        return altura;
    }
    
    public int getLargura() {
        return largura;
    }
    
    public float getPerimetro() {
        return 2 * (altura + largura);
    }
    
    public float getArea() {
        return altura * largura;
    }
}


Out[1]:
com.twosigma.beaker.javash.bkr0a487407.Retangulo

In [2]:
public class Circulo {
    public static float PI = 3.1416f;
    
    private int raio;
    
    public Circulo(int raio) {
        this.raio = raio;
    }
    
    public int getRaio() {
        return raio;
    }
    
    public float getPerimetro() {
        return 2 * Circulo.PI * raio;
    }
    
    public float getArea() {
        return Circulo.PI * raio * raio;
    }
}


Out[2]:
com.twosigma.beaker.javash.bkr0a487407.Circulo

In [3]:
Retangulo rt = new Retangulo(6, 10);
System.out.println("Perímetro do retângulo: " + rt.getPerimetro());
System.out.println("Área do retângulo: " + rt.getArea());

Circulo cc = new Circulo(8);
System.out.println("Perímetro do círculo: " + cc.getPerimetro());
System.out.println("Área do círculo: " + cc.getArea());


Perímetro do retângulo: 32.0
Área do retângulo: 60.0
Perímetro do círculo: 50.2656
Área do círculo: 201.0624
Out[3]:
null

Declarando e Implementando uma interface

Uma interface em Java declara um conjunto de métodos que deverão ser implementados por todas as classes que implementa a interface. A seguinte declaração da interface Geometria:

public interface Geometria {
    public float getPerimetro();
    public float getArea();
}

Indica que todos as classes que a implementarem precisarão implementar getPerimetro() e getArea() com as assinaturas indicadas.

Qualquer classe pode indicar que implementará a interface Geometria com a cláusula implements. Uma vantagem das interfaces sobre a herança (quando a intenção é padronizar a interface) é que uma classe pode implementar várias interfaces.

A seguinte declaração:

Geometria g;

Define uma variável g que é capaz de manter uma referência para qualquer objeto de classe que implementa a interface Geometria. Por essa razão são permitidas as instanciações:

Geometria g = new Retangulo(6, 10);
g = new Circulo(8);

Pode-se chamar qualquer método declarado da interface Geometria e a execução é polimórfica, ou seja, depende da instância.


In [4]:
public interface Geometria {
    public float getPerimetro();
    public float getArea();
}


Out[4]:
com.twosigma.beaker.javash.bkr0a487407.Geometria

In [5]:
public class Retangulo implements Geometria {
    private int altura;
    private int largura;

    public Retangulo(int altura, int largura) {
        this.altura = altura;
        this.largura = largura;
    }
    
    public int getAltura() {
        return altura;
    }
    
    public int getLargura() {
        return largura;
    }
    
    public float getPerimetro() {
        return 2 * (altura + largura);
    }
    
    public float getArea() {
        return altura * largura;
    }
}


Out[5]:
com.twosigma.beaker.javash.bkr0a487407.Retangulo

In [6]:
public class Circulo implements Geometria {
    public static float PI = 3.1416f;
    
    private int raio;
    
    public Circulo(int raio) {
        this.raio = raio;
    }
    
    public int getRaio() {
        return raio;
    }
    
    public float getPerimetro() {
        return 2 * Circulo.PI * raio;
    }
    
    public float getArea() {
        return Circulo.PI * raio * raio;
    }
}


Out[6]:
com.twosigma.beaker.javash.bkr0a487407.Circulo

In [7]:
Geometria g = new Retangulo(6, 10);

System.out.println("Perímetro do retângulo: " + g.getPerimetro());
System.out.println("Área do retângulo: " + g.getArea());

g = new Circulo(8);

System.out.println("Perímetro do círculo: " + g.getPerimetro());
System.out.println("Área do círculo: " + g.getArea());


Perímetro do retângulo: 32.0
Área do retângulo: 60.0
Perímetro do círculo: 50.2656
Área do círculo: 201.0624
Out[7]:
null

Classes Abstratas x Interfaces

As classes abstratas e interfaces têm uma sobreposição de funções. Alguns são levados a acreditar que uma interface é uma classe com todos os métodos abstratos.

Tarefa

Para entender as diferenças, retome a classe abstrata ListStr cuja herdeira você implementou mo notebook de classes abstratas. Se você transformar ListStr na interface IListStr com o intuito de não usar mais classes abstratas, escreva abaixo como ficaria o código da interface e o código modificado das classes. Escreva uma sequência de instruções que usem a interface e as respectivas classes.


In [ ]:

Discussão

Rotinas genéricas que usam métodos abstratos

Por um lado, você notará que não é possível se implementar nenhum método na interface, o que impede de criar abordagem de métodos que usam outros métodos potenciais abstratos, como é o caso do list(). Isso é uma vantagem das classes abstratas que você terá que simular de outro modo.

Múltiplas interfaces

Por outro lado, uma classe pode ter inúmeras interfaces mas só pode ser herdeira de uma classe abstrata, o que limita o poder dessas classes de agir como padronizador de interfaces.

Empréstimo Simples e Composto

Tarefa

Dadas as classes abaixo que representam um empréstimo simples (todas as parcelas iguais com juros fixo) e um empréstimo composto (visto anteriormente), construa uma interface que generalize os tipos diferentes de empréstimo.


In [8]:
public class EmprestimoSimples {
   float s;
   int   n;
   float j;
   int   corrente;
   float p;

   EmprestimoSimples(float s, int n, float j) {
      this.s = s;
      this.n = n;
      this.j = j;
      corrente = 0;
      this.p = s;
   }

   float proximaParcela() {
      corrente++;
      return (corrente <= n) ? p + (p * (j/100)) : 0;
   }
}


Out[8]:
com.twosigma.beaker.javash.bkr0a487407.EmprestimoSimples

In [9]:
public class EmprestimoComposto {
   float s;
   int   n;
   float j;
   int   corrente;
   float p;

   EmprestimoComposto(float s, int n, float j) {
      this.s = s;
      this.n = n;
      this.j = j;
      corrente = 1;
      this.p = s;
   }

   float proximaParcela() {
      float retorno = p;
      corrente++;
      if (corrente <= n)
         p = p + (p * (j/100));
      else
         p = 0;
      return retorno;
   }
}


Out[9]:
com.twosigma.beaker.javash.bkr0a487407.EmprestimoComposto

In [10]:
EmprestimoSimples empS = new EmprestimoSimples(200, 5, 1);
EmprestimoComposto empC = new EmprestimoComposto(200, 5, 1);

int i = 1;
float ps = empS.proximaParcela();
float pc = empC.proximaParcela();
while (ps > 0 || pc > 0) {
   if (ps > 0)
      System.out.println("Emprestimo simples: parcela " + i + " eh " + ps);
   if (pc > 0)
      System.out.println("Emprestimo composto: parcela " + i + " eh " + pc);
   ps = empS.proximaParcela();
   pc = empC.proximaParcela();
   i++;
}


Emprestimo simples: parcela 1 eh 202.0
Emprestimo composto: parcela 1 eh 200.0
Emprestimo simples: parcela 2 eh 202.0
Emprestimo composto: parcela 2 eh 202.0
Emprestimo simples: parcela 3 eh 202.0
Emprestimo composto: parcela 3 eh 204.02
Emprestimo simples: parcela 4 eh 202.0
Emprestimo composto: parcela 4 eh 206.06021
Emprestimo simples: parcela 5 eh 202.0
Emprestimo composto: parcela 5 eh 208.12082
Out[10]:
null

Recebendo Interface como Parâmetro

Considere o novo cenário a seguir em que é definida uma interface para as classes Retangulo e TrianguloRetangulo utilizadas no notebook sobre classes abstratas. Neste caso, não há mecanismo de herança envolvida.

O método sameProportions recebe um objeto que implementa a interface Retangular, ou seja, pode se comparar com qualquer objeto que implementa essa interface:

public boolean sameProportions(Retangular toCompare)

A classe Retangulo implementa duas interfaces:

public class Retangulo implements Geometria, Retangular

In [11]:
public interface Retangular {
   public int getAltura();
   public int getLargura();
   public boolean sameProportions(Retangular toCompare);
}


Out[11]:
com.twosigma.beaker.javash.bkr0a487407.Retangular

In [12]:
public class TrianguloRetangulo implements Retangular {
   private int altura;
   private int largura;
   
   public TrianguloRetangulo(int altura, int largura) {
       this.altura = altura;
       this.largura = largura;
   }

   public int getAltura() {
       return altura;
   }
   
   public int getLargura() {
       return largura;
   }
   
   public float getArea() {
       return getAltura() * getLargura() / 2;
   }
   
   public boolean sameProportions(Retangular toCompare) {
      return (largura / altura == toCompare.getLargura() / toCompare.getAltura());
   }
}


Out[12]:
com.twosigma.beaker.javash.bkr0a487407.TrianguloRetangulo

In [13]:
public class Retangulo implements Geometria, Retangular {
   private int altura;
   private int largura;

   public Retangulo(int altura, int largura) {
       this.altura = altura;
       this.largura = largura;
   }
   
   public int getAltura() {
       return altura;
   }
   
   public int getLargura() {
       return largura;
   }
   
   public float getPerimetro() {
       return 2 * (altura + largura);
   }
   
   public float getArea() {
       return altura * largura;
   }
   
   public boolean sameProportions(Retangular toCompare) {
      return (largura / altura == toCompare.getLargura() / toCompare.getAltura());
   }
}


Out[13]:
com.twosigma.beaker.javash.bkr0a487407.Retangulo

In [14]:
Retangular tr = new TrianguloRetangulo(60, 100);
Retangular rt = new Retangulo(6, 10);

System.out.println("Medidas do triangulo retângulo - altura: " +
   tr.getAltura() + "; largura: " + tr.getLargura());
System.out.println("Medidas do retângulo - altura: " +
   rt.getAltura() + "; largura: " + rt.getLargura());

if (tr.sameProportions(rt))
   System.out.println("Ambos têm as mesmas proporções");
else
   System.out.println("Ambos têm proporções diferentes");


Medidas do triangulo retângulo - altura: 60; largura: 100
Medidas do retângulo - altura: 6; largura: 10
Ambos têm as mesmas proporções
Out[14]:
null

Reunindo Interfaces

Se quisermos reunir interfaces adotadas por Retangulo e TrianguloRetangulo?

Herança de Interfaces

  • Mecanismo de extensão de interface a partir da existente
  • No exemplo a seguir não é possível dissociar Retangular de Geometria

In [15]:
public interface Geometria {
   public float getPerimetro();
   public float getArea();
}


Out[15]:
com.twosigma.beaker.javash.bkr0a487407.Geometria

In [16]:
public interface Retangular extends Geometria {
   public int getAltura();
   public int getLargura();
   public boolean sameProportions(Retangular toCompare);
}


Out[16]:
com.twosigma.beaker.javash.bkr0a487407.Retangular

In [17]:
public class Retangulo implements Retangular {
   private int altura;
   private int largura;

   public Retangulo(int altura, int largura) {
       this.altura = altura;
       this.largura = largura;
   }
   
   public int getAltura() {
       return altura;
   }
   
   public int getLargura() {
       return largura;
   }
   
   public float getPerimetro() {
       return 2 * (altura + largura);
   }
   
   public float getArea() {
       return altura * largura;
   }
   
   public boolean sameProportions(Retangular toCompare) {
      return (largura / altura == toCompare.getLargura() / toCompare.getAltura());
   }
}


Out[17]:
com.twosigma.beaker.javash.bkr0a487407.Retangulo

In [18]:
public class TrianguloRetangulo implements Retangular {
   private int altura;
   private int largura;
   
   public TrianguloRetangulo(int altura, int largura) {
       this.altura = altura;
       this.largura = largura;
   }

   public int getAltura() {
       return altura;
   }
   
   public int getLargura() {
       return largura;
   }
   
   public float getPerimetro() {
      return (float) (altura + largura + Math.sqrt(altura * altura + largura * largura));
   }
  
   public float getArea() {
       return getAltura() * getLargura() / 2;
   }
   
   public boolean sameProportions(Retangular toCompare) {
      return (largura / altura == toCompare.getLargura() / toCompare.getAltura());
   }
}


Out[18]:
com.twosigma.beaker.javash.bkr0a487407.TrianguloRetangulo

In [19]:
Retangular rt = new Retangulo(6, 10);
Retangular tr = new TrianguloRetangulo(60, 100);

System.out.println("Medidas do retângulo - altura: " +
   rt.getAltura() + "; largura: " + rt.getLargura());
System.out.println("Medidas do triangulo retângulo - altura: " +
   tr.getAltura() + "; largura: " + tr.getLargura());
   
System.out.println("Perímetro do retângulo: " + rt.getPerimetro());
System.out.println("Área do retângulo: " + rt.getArea());

System.out.println("Perímetro do triângulo retângulo: " + tr.getPerimetro());
System.out.println("Área do triângulo retângulo: " + tr.getArea());

if (tr.sameProportions(rt))
   System.out.println("Ambos têm as mesmas proporções");
else
   System.out.println("Ambos têm proporções diferentes");


Medidas do retângulo - altura: 6; largura: 10
Medidas do triangulo retângulo - altura: 60; largura: 100
Perímetro do retângulo: 32.0
Área do retângulo: 60.0
Perímetro do triângulo retângulo: 276.61905
Área do triângulo retângulo: 3000.0
Ambos têm as mesmas proporções
Out[19]:
null

Herança Múltipla de Interfaces

Uma interface pode ser herdeira de mais de uma interface. Nesse caso, ela irá requerer a combinação de todos os métodos declarados nas interfaces que ela herda (mais métodos que ela possa acrescentar).


In [20]:
public interface Geometria {
   public float getPerimetro();
   public float getArea();
}


Out[20]:
com.twosigma.beaker.javash.bkr0a487407.Geometria

In [21]:
public interface Retangular {
   public int getAltura();
   public int getLargura();
   public boolean sameProportions(Retangular toCompare);
}


Out[21]:
com.twosigma.beaker.javash.bkr0a487407.Retangular

In [22]:
public interface GeometriaRetangular extends Geometria, Retangular {
}


Out[22]:
com.twosigma.beaker.javash.bkr0a487407.GeometriaRetangular

In [23]:
public class Circulo implements Geometria {
   public static float PI = 3.1416f;
   
   private int raio;
   
   public Circulo(int raio) {
       this.raio = raio;
   }
   
   public int getRaio() {
       return raio;
   }
   
   public float getPerimetro() {
       return 2 * Circulo.PI * raio;
   }
   
   public float getArea() {
       return Circulo.PI * raio * raio;
   }
}


Out[23]:
com.twosigma.beaker.javash.bkr0a487407.Circulo

In [24]:
public class Retangulo implements GeometriaRetangular {
   private int altura;
   private int largura;

   public Retangulo(int altura, int largura) {
       this.altura = altura;
       this.largura = largura;
   }
   
   public int getAltura() {
       return altura;
   }
   
   public int getLargura() {
       return largura;
   }
   
   public float getPerimetro() {
       return 2 * (altura + largura);
   }
   
   public float getArea() {
       return altura * largura;
   }
   
   public boolean sameProportions(Retangular toCompare) {
      return (largura / altura == toCompare.getLargura() / toCompare.getAltura());
   }
}


Out[24]:
com.twosigma.beaker.javash.bkr0a487407.Retangulo

In [25]:
public class TrianguloRetangulo implements Retangular {
   private int altura;
   private int largura;
   
   public TrianguloRetangulo(int altura, int largura) {
       this.altura = altura;
       this.largura = largura;
   }

   public int getAltura() {
       return altura;
   }
   
   public int getLargura() {
       return largura;
   }
   
   public float getArea() {
       return getAltura() * getLargura() / 2;
   }
   
   public boolean sameProportions(Retangular toCompare) {
      return (largura / altura == toCompare.getLargura() / toCompare.getAltura());
   }
}


Out[25]:
com.twosigma.beaker.javash.bkr0a487407.TrianguloRetangulo

In [26]:
GeometriaRetangular rt = new Retangulo(6, 10);

System.out.println("Medidas do retângulo - altura: " +
      rt.getAltura() + "; largura: " + rt.getLargura());
System.out.println("Perímetro do retângulo: " + rt.getPerimetro());
System.out.println("Área do retângulo: " + rt.getArea());

Geometria ci = new Circulo(8);

System.out.println("Perímetro do círculo: " + ci.getPerimetro());
System.out.println("Área do círculo: " + ci.getArea());

Retangular tr = new TrianguloRetangulo(60, 100);

System.out.println("Medidas do triangulo retângulo - altura: " +
   tr.getAltura() + "; largura: " + tr.getLargura());

if (tr.sameProportions(rt))
   System.out.println("Retângulo e triângulo retângulo têm as mesmas proporções");
else
   System.out.println("Retângulo e triângulo retângulo têm proporções diferentes");


Medidas do retângulo - altura: 6; largura: 10
Perímetro do retângulo: 32.0
Área do retângulo: 60.0
Perímetro do círculo: 50.2656
Área do círculo: 201.0624
Medidas do triangulo retângulo - altura: 60; largura: 100
Retângulo e triângulo retângulo têm as mesmas proporções
Out[26]:
null

Tarefa

Classe ConjuntoEmprestimos

Baseado no notebook sobre polimorfismo, construa uma classe denominada ConjuntoEmprestimos que é responsável por controlar um conjunto de empréstimos que podem ser simples ou compostos. Essa classe deve definir pelo menos os seguintes métodos:

  • construtor - recebe como parâmetro o número máximo de empréstimos;
  • adicionaEmprestimo - recebe como parâmetro um empréstimo (simples ou composto) e o armazena (se não ultrapassar o número máximo);
  • proximasParcelas - mostra as próximas parcelas de todos os empréstimos cadastrados (para fins de simplificação, considere que o número da próxima parcela é igual para todos); o método retorna um status de verdadeiro se houve pelo menos um empréstimo com próxima parcela.

Utilize uma interface para generalizar o tratamento de empréstimo. É possível se criar um vetor de interfaces.


In [ ]: