Qual a principal diferença entre interfaces e classes abstratas, quando utilizar as classes abstratas?
Interfaces Vs. Classes Abstratas
12 Respostas
A pattern mais comum pro uso de classes abstratas é implementar um ‘esqueleto’ da implementação de uma interface, o package java.util tem alguns exemplos.
Eu uso classes abstratas no lugar de interfaces por alguns motivos:
-Preciso de maior acoplamento, normalmente via metodos protected.
-Performance, chamar 1 metodo virtual de uma classe é mais rápido que o de uma interface.
-Evitar a promiscuidade promovida por interfaces, qualquer classe pode implementar uma inteface, o ‘contrato’ de herança é muito mais estreito que o de uma interface.
Uso interfaces para comunicar capacidade e comportamento de um objeto, já classe abstrata para identidade, ‘kind of’.
Olá,
Naturalmente existem situações onde devemos usar classes abstratas e outras situações onde devemos usar interfaces, como em casos onde a arvore de heranças de uma classe já está complicada e não devemos complicá-la ainda mais, e o assunto é obviamente controverso.
Mas foi dito que o uso de classes abstratas pode ser escolhido por motivo de performance, dai resolvi conferir pois achei as citações muito interessantes e estamos sempre correndo atrás da danada da performance.
Fiz um programa teste e conclui que, neste programa, o uso de interface foi um pouco mais rápido ( muito pouco, mas foi ).
Segue o codigo :
abstract class Abs{
abstract public String get();
}
interface I{
String get();
}
public class Simple extends Abs implements I{
public String get(){
return “get in Simple”;
}
public static void main( String [] argv )
{
long intTime , absTime;
Simple app = new Simple();
I i = app;
Abs a = app;
long start = System.currentTimeMillis();
for( int j = 0 ; j < 200000 ; j++ )
{
System.out.println( i.get() );
<a href="//i.get">//i.get</a>();
}
intTime = System.currentTimeMillis()-start;
start = System.currentTimeMillis();
for( int j = 0 ; j < 200000 ; j++ )
{
System.out.println( a.get() );
//a.get();
}
absTime = System.currentTimeMillis()-start;
System.out.println("tempo gasto usando abstract="+absTime);
System.out.println("tempo gasto usando interface="+intTime);
}
}
este programa rodando em um Pentium II 933mhz
produziu a seguinte saida :
tempo gasto usando abstract=26057
tempo gasto usando interface=25587
se vc retirar a chamada a System.out.println para imprimir o retorno do
metodo e desmarcar as duas linhas que estão marcadas no metodo main
a tempo oscila entre
tempo gasto usando abstract=0
tempo gasto usando interface=10
e
tempo gasto usando abstract=10
tempo gasto usando interface=0
mas no teste usando a impressão na tela, usando interface é sempre mais rápido.
Se vc disse que usar abstract é mais rápido é por que vc testou de outra forma, sendo assim, gostaria de saber como foi que vc chegou a essa conclusão.
Claudio Gualberto.
Claudio, seu teste possui duas falhas ao meu ver:
-Voce extende a classe abstrata e implementa a interface no mesmo objeto, ai fica dificil saber oque realmente vai acontecer.
-Seu teste faz IO durante o processo de teste, oque pode causar resultados imprevisiveis devido a buferização e outros fatores.
Resolvi fazer um teste que não tenha esses problemas:
abstract class Abs
{
public abstract void teste();
}
interface Int
{
void teste();
}
class ClassA extends Abs
{
public void teste()
{
}
}
class ClassB implements Int
{
public void teste()
{
}
}
public class test
{
public static void main(String[] tst)
{
//primeiro vamos forçar o warmup da JVM;
Abs a = new ClassA();
Int b = new ClassB();
for(int i = 0; i < 1000; ++i)
{
System.currentTimeMillis();
a.teste();
b.teste();
}
//agora sim testamos
long tempo1, tempo2;
tempo2 = System.currentTimeMillis();
for(int i = 0; i < 100000000; ++i)
{
b.teste(); b.teste(); b.teste(); b.teste(); b.teste();
b.teste(); b.teste(); b.teste(); b.teste(); b.teste();
}
tempo2 = System.currentTimeMillis() - tempo2;
tempo1 = System.currentTimeMillis();
for(int i = 0; i < 100000000; ++i)
{
a.teste(); a.teste(); a.teste(); a.teste(); a.teste();
a.teste(); a.teste(); a.teste(); a.teste(); a.teste();
}
tempo1 = System.currentTimeMillis() - tempo1;
System.out.println("via classe abstrata levou: "+tempo1);
System.out.println("via interface levou: "+tempo2);
}
}
Pra compilar eu usei: javac -g:none
E os resultados são muito diferentes dos seus:
Mesmo trocando a ordem do teste os resultados não se alteram.
Os resultados indicam um custo 50x maior para interfaces, porem eu acredito que em um ambiente real isso seja bem menor pq acretido que a JVM tenha otimizado a chama virtual nesse caso.
Posso fazer algumas sugestões que eu tirei de um cara que montou um relógio pra fazer testes??
quando vcs forem testar, façam um teste de pior caso sempre. Por exemplo, um método que não faz nada vai sendo otimizado pelo JIT até a remoção completa. Isso, para a subclasse, vale mais do que para a interface, pq o HotSpot faz “agressive inlining”. Por isso, seu método sempre tem que fazer alguma coisa.
Fora isso, não testem um depois o outro. Testem assim:
teste 1
teste 2
teste 1
teste 2
teste 2
teste 1
Só dá pra dizer que deu tudo certo quando os três testes favorecerem o mesmo candidato. Isso porque na primeira rodada, o JIT está “frio”, com o tempo ele vai otimizando o código (de máquina) já gerado. Quanto mais vezes vc roda um método, mais otimizado ele fica, na esperança de que os métodos cruciais do programa sejam otimizados ao máximo.
A terceira rodada não é realmente necessária, a menos que vc dependa em algum nível do GC. Pq o GC pode distorcer os cálculos a favor do primeiro em alguns casos e do segundo em outros.
Por causa do HotSpot mesmo, a JVM arranca, quando dá, o overhead do lookup de métodos e bota seu conteúdo lá. Por isso, quando vc declara uma variável final, o JIT transforma o primeiro código no segundo (abaixo):
// suponhamos que sua instância tenha um método assim:
public int getX() {
return x; // x é final
}
// em algum lugar, alguém tem um laço assim:
while (i++ < s.getX()) {
... // faz alguma coisa
}
// esse laço vira
while (i++ < s.x) {
... // faz alguma coisa
}
É mais ou menos a mesma coisa que acontece com os métodos. Quando a árvore de classes permite, ele troca o método pelo conteúdo dele. Se o método for final, ele faz isso com certeza. Se não, ele faz “agressivamente”, e marca aquela classe para, se em algum momento for carregada uma subclasse dela, ele precisa tirar o inline.
por enquanto é só…
Dei uma adaptada no teste fazendo a chamada aos métodos
em ordem randômica, e no meu teste as interfaces foram
mais rápidas:
via classe abstrata levou: 21125
via interface levou: 19890
import java.util.*;
abstract class Abs
{
public abstract void teste();
}
interface Int
{
void teste();
}
class ClassA1 extends Abs
{
public void teste()
{ int a=1;
}
}
class ClassA2 extends Abs
{
public void teste()
{
double a=90.0;
}
}
class ClassB1 implements Int
{
public void teste()
{ int a=1;
}
}
class ClassB2 implements Int
{
public void teste()
{ double a=90.0;
}
}
public class Simple
{
public static void main(String[] tst)
{
Random rand = new Random();
int x;
long tempo1, tempo2;
//primeiro vamos forçar o warmup da JVM;
Abs a1 = new ClassA1();
Abs a2 = new ClassA2();
Int b1 = new ClassB1();
Int b2 = new ClassB2();
// testa primeiro as classes abstratas
tempo1 = System.currentTimeMillis();
for(int i = 0; i < 100000000; ++i)
{
// sorteia o x de 0 a 5
x= rand.nextInt(5);
// conforme o X faz uma sequencia de
// chamada de metodo diferente
switch (x)
{
case 0:
a1.teste();
a2.teste();
a1.teste();
a2.teste();
a1.teste();
a2.teste();
break;
case 1:
a1.teste();
a2.teste();
a2.teste();
a2.teste();
a1.teste();
a1.teste();
break;
case 2:
a2.teste();
a2.teste();
a1.teste();
a2.teste();
a1.teste();
a1.teste();
break;
case 3:
a2.teste();
a2.teste();
a2.teste();
a1.teste();
a1.teste();
a1.teste();
break;
case 4:
a2.teste();
a1.teste();
a1.teste();
a2.teste();
a2.teste();
a1.teste();
break;
}
}
tempo1 = System.currentTimeMillis() - tempo1;
// testa as interfaces
tempo2 = System.currentTimeMillis();
for(int i = 0; i < 100000000; ++i)
{
// sorteia o x de 0 a 5
x= rand.nextInt(5);
// conforme o X faz uma sequencia de
// chamada de metodo diferente
switch (x)
{
case 0:
b1.teste();
b2.teste();
b1.teste();
b2.teste();
b1.teste();
b2.teste();
break;
case 1:
b1.teste();
b2.teste();
b2.teste();
b2.teste();
b1.teste();
b1.teste();
break;
case 2:
b2.teste();
b2.teste();
b1.teste();
b2.teste();
b1.teste();
b1.teste();
break;
case 3:
b2.teste();
b2.teste();
b2.teste();
b1.teste();
b1.teste();
b1.teste();
break;
case 4:
b2.teste();
b1.teste();
b1.teste();
b2.teste();
b2.teste();
b1.teste();
break;
}
}
tempo2 = System.currentTimeMillis() - tempo2;
System.out.println("via classe abstrata levou: "+tempo1);
System.out.println("via interface levou: "+tempo2);
}
}
Foi bom, mas não foi bem o que eu pensei que vc ia fazer.
Eu sugeri:
Classe Abstrata
Interface
Abstrata
Interface
Interface
Abstrata
Tipo, deveriam ser 6 loops, e não dois. Seu teste ia ser perfeito pra comparar int vs double…
Mas mesmo assim, seus métodos não fazem nada. Inicalizar um tipo primitivo não faz nada.
eu pensava em algo do tipo i++, entende?
Qq hora eu faço um teste pra ver… mas eu acho que vai dar abstratas mesmo…
[]s
Resolvi fazer um teste onde a HotSpot não teria oportunidade de fazer inlining ou desvirtualização de chamadas, pelo menos eu acho.
Usando 4 classes diferentes que implementam via classe abstrata e 4 via interface, sendo 1 de cada retornada aleatoriamente.
E acabei constatando que a diferença de performance é minima, pouco mais de 5% a favor da implementação com classe abstrata.
E mesmo usando varias interfaces a performance não degrada.
Resumindo, aquele mito que interfaces são muito mais lentas que classes abstratas é balela, tomar 1 decisão de design baseado nesse fato é locura.
O código de testes que usei foi o seguinte:
abstract class Abs
{
protected int i;
public abstract void teste();
}
interface Int
{
void teste();
}
interface Int2
{
void teste2();
}
interface Int3
{
void teste3();
}
interface Int4
{
void teste4();
}
class ClassA1 extends Abs
{
public void teste()
{ ++i;
}
}
class ClassA2 extends Abs
{
public void teste()
{ ++i;
}
}
class ClassA3 extends Abs
{
public void teste()
{ ++i;
}
}
class ClassA4 extends Abs
{
public void teste()
{ ++i;
}
}
class ClassB1 implements Int4, Int2, Int3, Int
{ protected int i;
public void teste()
{ ++i;
}
public void teste2()
{ ++i;
}
public void teste3()
{ ++i;
}
public void teste4()
{ ++i;
}
}
class ClassB2 implements Int4, Int2, Int
{ protected int i;
public void teste()
{ ++i;
}
public void teste2()
{ ++i;
}
public void teste3()
{ ++i;
}
public void teste4()
{ ++i;
}
}
class ClassB3 implements Int4, Int
{ protected int i;
public void teste()
{ ++i;
}
public void teste2()
{ ++i;
}
public void teste3()
{ ++i;
}
public void teste4()
{ ++i;
}
}
class ClassB4 implements Int, Int3
{ protected int i;
public void teste()
{ ++i;
}
public void teste2()
{ ++i;
}
public void teste3()
{ ++i;
}
public void teste4()
{ ++i;
}
}
public class test
{
static java.util.Random rand = new java.util.Random();
static Abs sorteiaA()
{
switch(rand.nextInt(4)) {
case 0: return new ClassA1();
case 1: return new ClassA2();
case 2: return new ClassA3();
case 3: return new ClassA4();
}
return null;
}
static Int sorteiaB()
{
switch(rand.nextInt(4)) {
case 0: return new ClassB1();
case 1: return new ClassB2();
case 2: return new ClassB3();
case 3: return new ClassB4();
}
return null;
}
public static void main(String[] tst)
{
Abs a = sorteiaA();
Int b = sorteiaB();
//primeiro vamos forçar o warmup da JVM;
for(int i = 0; i < 1000; ++i)
{
System.currentTimeMillis();
a.teste();
b.teste();
}
//agora sim testamos
long tempo1, tempo2;
tempo1 = System.currentTimeMillis();
for(int i = 0; i < 10000000; ++i)
{
a.teste(); a.teste(); a.teste(); a.teste(); a.teste();
a.teste(); a.teste(); a.teste(); a.teste(); a.teste();
}
tempo1 = System.currentTimeMillis() - tempo1;
tempo2 = System.currentTimeMillis();
for(int i = 0; i < 10000000; ++i)
{
b.teste(); b.teste(); b.teste(); b.teste(); b.teste();
b.teste(); b.teste(); b.teste(); b.teste(); b.teste();
}
tempo2 = System.currentTimeMillis() - tempo2;
System.out.println("via classe abstrata levou: "+tempo1);
System.out.println("via interface levou: "+tempo2);
}
}
O loco!! (tm Faustao)
Eu acredito, Louds!! Imagina um programa de verdade, onde tem interfaces e classe misturadas, possivelmente mais de um classloader…
Se não me engano, isso é meio recente, na jdk1.3 eles já tinham melhorado um pouco, e na 1.4 ficou redondo.
A forma como as interfaces são alocadas e como são encontrados os métodos de uma instância já não é aquele esquema copiado do smalltalk como era antes, eles otimizaram um monte de coisas.
Mas a JVM da sun é uma das mais lerdas em qualquer benchmark que vc faça. Talvez outras JVMs tenham resultados diferentes (eu chutaria que a JVM da blackdown deve ter discrepâncias maiores), vai saber?
[]s!!
Esses dias estamavamos discutindo interfaces:
http://www.guj.com.br/forum/viewtopic.php?p=14145&highlight=#14145
Abraco,
A VM da blackdown é basicamente a da sun com algumas modifcações.
Hehehehe… ainda bem! 
Desculpa, mas foi engraçado… hehehe!!
Eu entendi o que vc quis dizer!!
Mas a parte que é machine-dependant da blackdown no linuc roda bem melhor que a da Sun… ou pelo menos rodava, até a 1.3…
[]s
Hehehehe… ainda bem! 
Desculpa, mas foi engraçado… hehehe!!
Eu entendi o que vc quis dizer!!
Mas a parte que é machine-dependant da blackdown no linuc roda bem melhor que a da Sun… ou pelo menos rodava, até a 1.3…
[]s
Vc quer dizer a parte dependente do SO ne? Nessa parte a blackdown funciona mesmo 1 pouco melhor, já que a sun ainda pensa que linux e solaris são a mesma coisa.