JavaRush /Blog Java /Random-FR /Différence entre les classes abstraites et les interfaces...

Différence entre les classes abstraites et les interfaces

Publié dans le groupe Random-FR
Bonjour! Dans cette conférence, nous expliquerons en quoi les classes abstraites diffèrent des interfaces et examinerons des exemples de classes abstraites communes. Différence entre les classes abstraites et les interfaces - 1Nous avons consacré une conférence séparée aux différences entre une classe abstraite et une interface, car le sujet est très important. La différence entre ces concepts vous sera interrogée dans 90 % des futurs entretiens. Par conséquent, assurez-vous de comprendre ce que vous lisez et si vous ne comprenez pas complètement quelque chose, lisez des sources supplémentaires. Nous savons donc ce qu'est une classe abstraite et ce qu'est une interface. Passons maintenant à leurs différences.
  1. Une interface décrit uniquement le comportement. Il n'a pas de fortune. Mais une classe abstraite a un état : elle décrit les deux.

    Prenons comme exemple une classe abstraite Birdet une interfaceFlyable :

    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;
       }
    }

    Créons une classe d'oiseau Mockingjay(geai moqueur) et héritons deBird :

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

    Comme vous pouvez le voir, nous pouvons facilement accéder à l'état de la classe abstraite - ses variables species(type) et age(âge).

    Mais si nous essayons de faire de même avec l’interface, le tableau sera différent. Nous pouvons essayer d'y ajouter des variables :

    public interface Flyable {
    
       String species = new String();
       int age = 10;
    
       public void fly();
    }
    
    public interface Flyable {
    
       private String species = new String(); // error
       private int age = 10; // also an error
    
       public void fly();
    }

    Nous ne pourrons même pas créer de variables privées dans l'interface. Pourquoi? Parce que le modificateur privé a été créé pour cacher l'implémentation à l'utilisateur. Mais il n’y a pas d’implémentation à l’intérieur de l’interface : il n’y a rien à cacher.

    L'interface décrit uniquement le comportement. Par conséquent, nous ne pourrons pas implémenter de getters et de setters à l'intérieur de l'interface. C'est la nature d'une interface : elle est censée gérer le comportement, pas l'état.

    Java8 a introduit des méthodes d'interface par défaut qui ont une implémentation. Vous les connaissez déjà, nous ne les répéterons donc pas.

  2. Une classe abstraite relie et fédère des classes qui entretiennent une relation très étroite. Dans le même temps, la même interface peut être implémentée par des classes qui n'ont rien en commun.

    Revenons à notre exemple avec les oiseaux.

    Notre classe abstraite Birdest nécessaire pour créer des oiseaux basés sur elle. Seulement des oiseaux et personne d'autre ! Bien sûr, ils seront différents.

    Différence entre les classes abstraites et les interfaces - 2

    Avec l'interface, Flyabletout est différent. Il décrit uniquement le comportement correspondant à son nom – « voler ». La définition de « voler », « capable de voler » inclut de nombreux objets qui ne sont pas liés les uns aux autres.

    Différence entre les classes abstraites et les interfaces - 3

    Ces 4 entités ne sont en aucun cas liées les unes aux autres. Que puis-je dire, ils ne sont pas tous animés. Cependant, ils sont tous Flyablecapables de voler.

    Nous ne pourrions pas les décrire à l’aide d’une classe abstraite. Ils n'ont pas d'état commun ni de champs identiques. Pour caractériser un avion, nous aurons probablement besoin des champs « modèle », « année de fabrication » et « nombre maximum de passagers ». Pour Carlson, il y a des champs pour tous les bonbons qu'il a mangés aujourd'hui et une liste de jeux auxquels il jouera avec le Kid. Pour un moustique... euh-euh... on ne sait même pas... Peut-être « niveau de gêne » ? :)

    L’essentiel est que nous ne pouvons pas les décrire à l’aide d’une classe abstraite. Ils sont trop différents. Mais il existe un comportement commun : ils peuvent voler. L'interface est idéale pour décrire tout ce qui dans le monde peut voler, nager, sauter ou avoir un autre comportement.

  3. Les classes peuvent implémenter autant d’interfaces qu’elles le souhaitent, mais elles ne peuvent hériter que d’une seule classe.

    Nous en avons déjà parlé plus d'une fois. Il n'y a pas d'héritage multiple en Java, mais il existe une implémentation multiple. Ce point découle en partie du précédent : une interface relie de nombreuses classes différentes qui n'ont souvent rien en commun, et une classe abstraite est créée pour un groupe de classes très proches les unes des autres. Il est donc logique que vous ne puissiez hériter que d’une seule de ces classes. Une classe abstraite décrit la relation « est un ».

Interfaces standard InputStream et OutputStream

Nous avons déjà passé en revue les différentes classes responsables du streaming d'entrée et de sortie. Regardons InputStreamet OutputStream. En général, ce ne sont pas des interfaces, mais de véritables classes abstraites. Maintenant que vous savez ce qu'ils sont, il sera donc beaucoup plus facile de travailler avec eux :) InputStream- il s'agit d'une classe abstraite responsable de l'entrée d'octets. Java possède une série de classes qui héritent de InputStream. Chacun d'eux est configuré pour recevoir des données de différentes sources. Puisqu'il InputStreams'agit d'un parent, il propose plusieurs méthodes pour travailler facilement avec les flux de données. Chaque enfant a ces méthodes InputStream:
  • int available()renvoie le nombre d'octets disponibles en lecture ;
  • close()ferme la source d'entrée ;
  • int read()renvoie une représentation entière du prochain octet disponible dans le flux. Si la fin du flux est atteinte, le nombre -1 sera renvoyé ;
  • int read(byte[] buffer)tente de lire des octets dans un tampon, renvoyant le nombre d'octets lus. Lorsqu'il atteint la fin du fichier, il renvoie -1 ;
  • int read(byte[] buffer, int byteOffset, int byteCount)lit une partie d'un bloc d'octets. Utilisé lorsqu'il est possible que le bloc de données n'ait pas été complètement rempli. Lorsqu'il atteint la fin du fichier, renvoie -1 ;
  • long skip(long byteCount)skips byteCount, un octet d'entrée, renvoyant le nombre d'octets ignorés.
Je vous conseille d'étudier la liste complète des méthodes . Il existe en réalité plus d’une douzaine de classes successeurs. Voici quelques exemples:
  1. FileInputStream: le type le plus courant InputStream. Utilisé pour lire les informations d'un fichier ;
  2. StringBufferInputStream: un autre type utile InputStream. Il transforme une chaîne en un flux de données d'entrée InputStream;
  3. BufferedInputStream: flux d’entrée mis en mémoire tampon. Il est le plus souvent utilisé pour améliorer l’efficacité.
Vous souvenez-vous quand nous sommes passés par là BufferedReaderet avons dit que nous n'étions pas obligés de l'utiliser ? Quand on écrit :
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))
... BufferedReaderpas besoin de l'utiliser : InputStreamReaderil fera l'affaire. Mais BufferedReaderil le fait plus efficacement et, en outre, peut lire des données sur des lignes entières plutôt que sur des caractères individuels. Tout BufferedInputStreamest pareil! La classe accumule les données d'entrée dans un tampon spécial sans accéder constamment au périphérique d'entrée. Regardons un exemple :
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 was read" + c);
           }
       } catch(Exception e) {

           e.printStackTrace();

       } finally {

           inputStream.close();
           buffer.close();
       }
   }
}
Dans cet exemple, nous lisons les données d'un fichier situé sur l'ordinateur à l'adresse "D:/Users/UserName/someFile.txt" . Nous créons 2 objets - FileInputStreamet BufferedInputStreamcomme son « wrapper ». Après cela, nous lisons les octets du fichier et les convertissons en caractères. Et ainsi de suite jusqu'à la fin du fichier. Comme vous pouvez le constater, il n'y a rien de compliqué ici. Vous pouvez copier ce code et l'exécuter sur un fichier réel stocké sur votre ordinateur :) Une classe OutputStreamest une classe abstraite qui définit une sortie de flux d'octets. Comme vous l'avez déjà compris, c'est l'antipode de InputStream'a. Il n'est pas responsable de l'endroit où lire les données, mais de l' endroit où les envoyer . Comme InputStream, cette classe abstraite fournit à tous les descendants un groupe de méthodes pour un travail pratique :
  • int close()ferme le flux de sortie ;
  • void flush()efface tous les tampons de sortie ;
  • abstract void write (int oneByte)écrit 1 octet dans le flux de sortie ;
  • void write (byte[] buffer)écrit un tableau d'octets dans le flux de sortie ;
  • void write (byte[] buffer, int offset, int count)écrit une plage de nombres d'octets à partir du tableau, en commençant au décalage de position.
Voici quelques descendants de la classe OutputStream:
  1. DataOutputStream. Flux de sortie qui inclut des méthodes d'écriture de types de données Java standard.

    Une classe très simple pour écrire des types et des chaînes Java primitifs. Vous comprendrez sûrement le code écrit même sans explication :

    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);
    
       }
    }

    Il a des méthodes distinctes pour chaque type - writeDouble(), writeLong(), writeShort()etc.

  2. Classe FileOutputStream . Implémente un mécanisme pour envoyer des données vers un fichier sur le disque. D’ailleurs, nous l’avons déjà utilisé dans l’exemple précédent, l’avez-vous remarqué ? Nous l'avons transmis à l'intérieur du DataOutputStream, qui a fait office de « wrapper ».

  3. BufferedOutputStream. Flux de sortie tamponné. Rien de compliqué non plus, l'essence est la même que dans BufferedInputStream(ou BufferedReader'a). Au lieu de l'enregistrement séquentiel habituel des données, un enregistrement via un tampon de « stockage » spécial est utilisé. En utilisant un tampon, vous pouvez réduire le nombre d'allers-retours vers la destination des données et ainsi améliorer l'efficacité.

    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 will convert this string into an array of bytes and write it to a file
    
           byte[] buffer = text.getBytes();
    
           bufferedStream.write(buffer, 0, buffer.length);
           bufferedStream.close();
       }
    }

    Encore une fois, vous pouvez « jouer » vous-même avec ce code et vérifier comment il fonctionnera sur de vrais fichiers sur votre ordinateur.

Vous pouvez également en savoir plus sur les héritiers dans le matériel « Système InputStreamd'entrée /sortie ». Oh , et nous aurons également une conférence séparée, il y aura donc suffisamment d'informations à leur sujet pour la première connaissance. C'est tout! Nous espérons que vous comprenez bien les différences entre les interfaces et les classes abstraites et que vous êtes prêt à répondre à toute question, même délicate :) OutputStreamFileInputStreamFileOutputStreamBufferedInputStream
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION