CodeGym /Blogue Java /Random-PT /A diferença entre classes abstratas e interfaces
John Squirrels
Nível 41
San Francisco

A diferença entre classes abstratas e interfaces

Publicado no grupo Random-PT
Oi! Nesta lição, falaremos sobre como as classes abstratas diferem das interfaces e consideraremos alguns exemplos com classes abstratas comuns. A diferença entre classes abstratas e interfaces - 1Dedicamos uma lição separada às diferenças entre uma classe abstrata e uma interface, porque esse tópico é muito importante. Você será questionado sobre a diferença entre esses conceitos em 90% das futuras entrevistas. Isso significa que você deve ter certeza de descobrir o que está lendo. E se você não entender algo completamente, leia as fontes adicionais. Então, sabemos o que é uma classe abstrata e o que é uma interface. Agora vamos examinar suas diferenças.
  1. Uma interface apenas descreve o comportamento. Não tem estado. Mas uma classe abstrata inclui estado: ela descreve ambos.

    Por exemplo, pegue a Birdclasse abstrata e a CanFlyinterface:

    
    public abstract class Bird {
       private String species;
       private int age;
    
       public abstract void fly();
    
       public String getSpecies() {
           return species;
       }
    
       public void setSpecies(String species) {
           this.species = species;
       }
    
       public int getAge() {
           return age;
       }
    
       public void setAge(int age) {
           this.age = age;
       }
    }
    

    Vamos criar uma MockingJayclasse bird e torná-la herdada Bird:

    
    public class MockingJay extends Bird {
    
       @Override
       public void fly() {
           System.out.println("Fly, bird!");
       }
    
       public static void main(String[] args) {
    
           MockingJay someBird = new MockingJay();
           someBird.setAge(19);
           System.out.println(someBird.getAge());
       }
    }
    

    Como você pode ver, podemos acessar facilmente o estado da classe abstrata — suas variáveis species​​e .age

    Mas se tentarmos fazer o mesmo com uma interface, a imagem é diferente. Podemos tentar adicionar variáveis ​​a ele:

    
    public interface CanFly {
    
       String species = new String();
       int age = 10;
    
       public void fly();
    }
    
    public interface CanFly {
    
       private String species = new String(); // Error
       private int age = 10; // Another error
    
       public void fly();
    }
    

    Não podemos nem declarar variáveis ​​privadas dentro de uma interface. Por que? Porque o modificador privado foi criado para ocultar a implementação do usuário. E uma interface não tem implementação dentro dela: não há nada a esconder.

    Uma interface apenas descreve o comportamento. Da mesma forma, não podemos implementar getters e setters dentro de uma interface. Essa é a natureza das interfaces: elas são necessárias para trabalhar com comportamento, não com estado.

    O Java 8 introduziu métodos padrão para interfaces que possuem uma implementação. Você já sabe sobre eles, então não vamos nos repetir.

  2. Uma classe abstrata conecta e une classes que estão intimamente relacionadas. Ao mesmo tempo, uma única interface pode ser implementada por classes que não têm absolutamente nada em comum.

    Voltemos ao nosso exemplo com pássaros.

    Nossa Birdclasse abstrata é necessária para criar pássaros baseados nessa classe. Apenas pássaros e nada mais! Claro, haverá diferentes tipos de pássaros.

    A diferença entre classes abstratas e interfaces - 2

    Com a CanFlyinterface, cada um se dá à sua maneira. Descreve apenas o comportamento (voar) associado ao seu nome. Muitas coisas não relacionadas 'podem voar'.

    A diferença entre classes abstratas e interfaces - 3

    Estas 4 entidades não estão relacionadas entre si. Nem todos estão vivos. No entanto, todos eles CanFly.

    Não poderíamos descrevê-los usando uma classe abstrata. Eles não compartilham o mesmo estado ou campos idênticos. Para definir uma aeronave, provavelmente precisaríamos de campos para o modelo, ano de produção e número máximo de passageiros. Para Carlson, precisaríamos de campos para todos os doces que comeu hoje e uma lista das brincadeiras que fará com seu irmãozinho. Para um mosquito, ...uh... eu nem sei... Talvez, um 'nível de aborrecimento'? :)

    A questão é que não podemos usar uma classe abstrata para descrevê-los. Eles são muito diferentes. Mas eles têm comportamento compartilhado: eles podem voar. Uma interface é perfeita para descrever tudo no mundo que pode voar, nadar, pular ou exibir algum outro comportamento.

  3. As classes podem implementar quantas interfaces você quiser, mas só podem herdar uma classe.

    Já mencionamos isso mais de uma vez. Java não possui herança múltipla de classes, mas suporta herança múltipla de interfaces. Este ponto decorre em parte do anterior: uma interface conecta muitas classes diferentes que geralmente não têm mais nada em comum, enquanto uma classe abstrata é criada para um grupo de classes muito próximas. Portanto, faz sentido que você só possa herdar uma dessas classes. Uma classe abstrata descreve um relacionamento 'é-um'.

Interfaces padrão: InputStream e OutputStream

Já examinamos várias classes responsáveis ​​pelos fluxos de entrada e saída. Vamos considerar InputStreame OutputStream. Em geral, essas não são interfaces, mas sim classes abstratas totalmente genuínas. Agora você sabe o que isso significa, então será muito mais fácil trabalhar com eles :) InputStreamé uma classe abstrata responsável pela entrada de bytes. Java tem várias classes que herdam InputStream. Cada um deles é projetado para receber dados de diferentes fontes. Por InputStreamser o pai, ele fornece vários métodos que facilitam o trabalho com fluxos de dados. Cada descendente de InputStreamtem estes métodos:
  • int available()retorna o número de bytes disponíveis para leitura;
  • close()fecha o fluxo de entrada;
  • int read()retorna uma representação inteira do próximo byte disponível no fluxo. Se o fim do fluxo for atingido, -1 será retornado;
  • int read(byte[] buffer)tenta ler bytes no buffer e retorna o número de bytes lidos. Quando chega ao final do arquivo, retorna -1;
  • int read(byte[] buffer, int byteOffset, int byteCount)escreve parte de um bloco de bytes. É usado quando a matriz de bytes pode não ter sido totalmente preenchida. Quando chega ao final do arquivo, retorna -1;
  • long skip(long byteCount)ignora byteCount bytes no fluxo de entrada e retorna o número de bytes ignorados.
Eu recomendo que você estude a lista completa de métodos . Na verdade, existem mais de dez classes filhas. Por exemplo, aqui estão alguns:
  1. FileInputStream: o tipo mais comum de InputStream. É usado para ler informações de um arquivo;
  2. StringBufferInputStream: Outro tipo útil de arquivo InputStream. Ele converte uma string em um InputStream;
  3. BufferedInputStream: Um fluxo de entrada armazenado em buffer. É usado com mais frequência para aumentar o desempenho.
Lembra quando falamos BufferedReadere dissemos que você não precisa usá-lo? Quando escrevemos:

BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))
…você não precisa usar BufferedReader: An InputStreamReaderpode fazer o trabalho. Mas BufferedReadermelhora o desempenho e também pode ler linhas inteiras de dados em vez de caracteres individuais. A mesma coisa se aplica a BufferedInputStream! A classe acumula dados de entrada em um buffer especial sem acessar constantemente o dispositivo de entrada. Vamos considerar um exemplo:

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.InputStream;

public class BufferedInputExample {

   public static void main(String[] args) throws Exception {
       InputStream inputStream = null;
       BufferedInputStream buffer = null;

       try {

           inputStream = new FileInputStream("D:/Users/UserName/someFile.txt");

           buffer = new BufferedInputStream(inputStream);

           while(buffer.available()>0) {

               char c = (char)buffer.read();

                System.out.println("Character read: " + c);
           }
       } catch(Exception e) {

           e.printStackTrace();

       } finally {

           inputStream.close();
           buffer.close();
       }
   }
}
Neste exemplo, lemos os dados de um arquivo localizado em um computador em ' D:/Users/UserName/someFile.txt '. Criamos 2 objetos - a FileInputStreame a BufferedInputStreamque o 'envolve'. Em seguida, lemos os bytes do arquivo e os convertemos em caracteres. E fazemos isso até o final do arquivo. Como você pode ver, não há nada complicado aqui. Você pode copiar este código e executá-lo em um arquivo real em seu computador :) A OutputStreamclasse é uma classe abstrata que representa um fluxo de saída de bytes. Como você já sabe, isso é o oposto de um arquivo InputStream. Ele não é responsável por ler dados de algum lugar, mas sim por enviar dados para algum lugar . Assim como InputStream, essa classe abstrata fornece a todos os seus descendentes um conjunto de métodos convenientes:
  • void close()fecha o fluxo de saída;
  • void flush()limpa todos os buffers de saída;
  • abstract void write(int oneByte)grava 1 byte no fluxo de saída;
  • void write(byte[] buffer)grava uma matriz de bytes no fluxo de saída;
  • void write(byte[] buffer, int offset, int count)grava um intervalo de contagem de bytes de uma matriz, começando na posição de deslocamento.
Aqui estão alguns dos descendentes da OutputStreamclasse:
  1. DataOutputStream. Um fluxo de saída que inclui métodos para gravar tipos de dados Java padrão.

    Uma classe muito simples para escrever tipos de dados e strings Java primitivos. Você provavelmente entenderá o seguinte código mesmo sem uma explicação:

    
    import java.io.*;
    
    public class DataOutputStreamExample {
    
       public static void main(String[] args) throws IOException {
    
           DataOutputStream dos = new DataOutputStream(new FileOutputStream("testFile.txt"));
    
           dos.writeUTF("SomeString");
           dos.writeInt(22);
           dos.writeDouble(1.21323);
           dos.writeBoolean(true);
    
       }
    }
    

    Possui métodos separados para cada tipo — writeDouble(), writeLong(), writeShort()e assim por diante.


  2. FileOutputStream. Essa classe implementa um mecanismo para enviar dados para um arquivo no disco. A propósito, já o usamos no último exemplo. Você notou? Passamos para DataOutputStream, que atuou como um 'wrapper'.

  3. BufferedOutputStream. Um fluxo de saída em buffer. Também não há nada complicado aqui. Seu propósito é análogo a BufferedInputStream(ou BufferedReader). Em vez da leitura sequencial usual de dados, ele grava os dados usando um buffer especial 'cumulativo'. O buffer permite reduzir o número de vezes que o coletor de dados é acessado, aumentando assim o desempenho.

    
    import java.io.*;
    
    public class DataOutputStreamExample {
    
         public static void main(String[] args) throws IOException {
    
               FileOutputStream outputStream = new FileOutputStream("D:/Users/Username/someFile.txt");
               BufferedOutputStream bufferedStream = new BufferedOutputStream(outputStream);
    
               String text = "I love Java!"; // We'll convert this string to a byte array and write it to a file
    
               byte[] buffer = text.getBytes();
    
               bufferedStream.write(buffer, 0, buffer.length);
         }
    }
    

    Novamente, você mesmo pode brincar com esse código e verificar se ele funcionará em arquivos reais em seu computador.

Teremos uma lição separada sobre FileInputStream, FileOutputStreame BuffreredInputStream, então isso é informação suficiente para um primeiro contato. É isso! Esperamos que você entenda as diferenças entre interfaces e classes abstratas e esteja pronto para responder a qualquer pergunta, até mesmo perguntas capciosas :)
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION