JavaRush /Blog Java /Random-VI /Sự khác biệt giữa các lớp trừu tượng và giao diện

Sự khác biệt giữa các lớp trừu tượng và giao diện

Xuất bản trong nhóm
Xin chào! Trong bài giảng này, chúng ta sẽ nói về việc các lớp trừu tượng khác với các giao diện như thế nào và xem xét các ví dụ về các lớp trừu tượng phổ biến. Sự khác biệt giữa lớp trừu tượng và giao diện - 1Chúng tôi dành một bài giảng riêng về sự khác biệt giữa lớp trừu tượng và giao diện, vì chủ đề này rất quan trọng. Bạn sẽ được hỏi về sự khác biệt giữa các khái niệm này trong 90% các cuộc phỏng vấn trong tương lai. Do đó, hãy chắc chắn hiểu những gì bạn đọc và nếu bạn không hiểu đầy đủ điều gì đó, hãy đọc các nguồn bổ sung. Vì vậy, chúng ta biết lớp trừu tượng là gì và giao diện là gì. Bây giờ chúng ta hãy đi qua sự khác biệt của họ.
  1. Một giao diện chỉ mô tả hành vi. Anh ta không có tài sản. Nhưng một lớp trừu tượng có một trạng thái: nó mô tả cả hai.

    Hãy lấy một lớp trừu tượng Birdvà giao diện làm ví dụ Flyable:

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

    Hãy tạo một lớp chim Mockingjay(mockingjay) và kế thừa từ Bird:

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

    Như bạn có thể thấy, chúng ta có thể dễ dàng truy cập trạng thái của lớp trừu tượng - các biến species(loại) và age(tuổi) của nó.

    Nhưng nếu chúng ta cố gắng làm tương tự với giao diện thì hình ảnh sẽ khác. Chúng ta có thể thử thêm các biến vào nó:

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

    Chúng tôi thậm chí sẽ không thể tạo các biến riêng tư bên trong giao diện. Tại sao? Bởi vì công cụ sửa đổi riêng tư được tạo để ẩn việc triển khai với người dùng. Nhưng không có phần triển khai nào bên trong giao diện: không có gì để ẩn ở đó.

    Giao diện chỉ mô tả hành vi. Theo đó, chúng tôi sẽ không thể triển khai getters và setters bên trong giao diện. Đó là bản chất của giao diện: nó nhằm xử lý hành vi chứ không phải trạng thái.

    Java8 đã giới thiệu các phương thức giao diện mặc định có triển khai. Bạn đã biết về chúng rồi nên chúng tôi sẽ không lặp lại chúng.

  2. Một lớp trừu tượng liên kết và kết hợp các lớp có mối quan hệ rất chặt chẽ. Đồng thời, cùng một giao diện có thể được triển khai bởi các lớp không có điểm chung nào cả.

    Hãy quay lại ví dụ của chúng ta với các loài chim.

    Lớp trừu tượng của chúng tôi Birdlà cần thiết để tạo ra các loài chim dựa trên nó. Chỉ có chim và không có ai khác! Tất nhiên là chúng sẽ khác nhau.

    Sự khác biệt giữa lớp trừu tượng và giao diện - 2

    Với giao diện Flyablemọi thứ đều khác nhau. Nó chỉ mô tả hành vi tương ứng với tên của nó - "bay". Định nghĩa “bay”, “có khả năng bay” bao gồm nhiều vật thể không liên quan tới nhau.

    Sự khác biệt giữa lớp trừu tượng và giao diện - 3

    4 thực thể này không hề liên quan tới nhau. Tôi có thể nói gì đây, không phải tất cả chúng đều có hoạt hình. Tuy nhiên, chúng đều Flyablecó khả năng bay.

    Chúng ta không thể mô tả chúng bằng lớp trừu tượng. Chúng không có trạng thái chung hoặc các trường giống hệt nhau. Để mô tả đặc điểm của một chiếc máy bay, có lẽ chúng ta sẽ cần các trường “model”, “năm sản xuất” và “số lượng hành khách tối đa”. Đối với Carlson, có những cánh đồng dành cho tất cả đồ ngọt mà anh ấy đã ăn hôm nay và một danh sách các trò chơi mà anh ấy sẽ chơi với Kid. Đối với một con muỗi...uh-uh...chúng ta thậm chí còn không biết... Có lẽ là "mức độ khó chịu"? :)

    Điều quan trọng là chúng ta không thể mô tả chúng bằng lớp trừu tượng. Họ quá khác nhau. Nhưng có một hành vi chung: chúng có thể bay. Giao diện này lý tưởng để mô tả mọi thứ trên thế giới có thể bay, bơi, nhảy hoặc có một số hành vi khác.

  3. Các lớp có thể triển khai bao nhiêu giao diện tùy thích nhưng chúng chỉ có thể kế thừa từ một lớp.

    Chúng tôi đã nói về điều này nhiều lần. Không có tính đa kế thừa trong Java, nhưng có nhiều cách triển khai. Điểm này một phần tiếp nối điểm trước: một giao diện kết nối nhiều lớp khác nhau thường không có điểm chung và một lớp trừu tượng được tạo cho một nhóm các lớp rất gần nhau. Vì vậy, điều hợp lý là bạn chỉ có thể kế thừa từ một lớp như vậy. Một lớp trừu tượng mô tả mối quan hệ “là một”.

Giao diện luồng đầu vào và luồng đầu ra tiêu chuẩn

Chúng ta đã học qua các lớp khác nhau chịu trách nhiệm truyền phát đầu vào và đầu ra. Chúng ta hãy nhìn vào InputStreamOutputStream. Nói chung, đây không phải là giao diện mà là các lớp trừu tượng thực sự. Bây giờ bạn đã biết chúng là gì, vì vậy làm việc với chúng sẽ dễ dàng hơn nhiều :) InputStream- đây là một lớp trừu tượng chịu trách nhiệm nhập byte. Java có một loạt các lớp kế thừa từ InputStream. Mỗi người trong số họ được cấu hình để nhận dữ liệu từ các nguồn khác nhau. Vì InputStreamlà cha nên nó cung cấp một số phương thức để làm việc thuận tiện với các luồng dữ liệu. Mỗi đứa trẻ có những phương pháp sau InputStream:
  • int available()trả về số byte có sẵn để đọc;
  • close()đóng nguồn đầu vào;
  • int read()trả về một biểu diễn số nguyên của byte có sẵn tiếp theo trong luồng. Nếu đến cuối luồng, số -1 sẽ được trả về;
  • int read(byte[] buffer)cố gắng đọc byte vào bộ đệm, trả về số byte đã đọc. Khi đến cuối tệp, nó trả về -1;
  • int read(byte[] buffer, int byteOffset, int byteCount)đọc một phần của khối byte. Được sử dụng khi có khả năng khối dữ liệu chưa được lấp đầy hoàn toàn. Khi đến cuối tệp, trả về -1;
  • long skip(long byteCount)bỏ qua byteCount, một byte đầu vào, trả về số byte bị bỏ qua.
Tôi khuyên bạn nên nghiên cứu danh sách đầy đủ các phương pháp . Thực tế có hơn chục lớp kế thừa. Dưới đây là một vài ví dụ:
  1. FileInputStreamInputStream: loại phổ biến nhất Được sử dụng để đọc thông tin từ một tập tin;
  2. StringBufferInputStream: một loại hữu ích khác InputStream. Nó biến một chuỗi thành luồng dữ liệu đầu vào InputStream;
  3. BufferedInputStream: luồng đầu vào được đệm. Nó thường được sử dụng để nâng cao hiệu quả.
Bạn có nhớ khi chúng ta đi ngang qua BufferedReadervà nói rằng chúng ta không cần phải sử dụng nó không? Khi chúng tôi viết:
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))
... BufferedReaderkhông cần sử dụng nó: InputStreamReadernó sẽ thực hiện được công việc. Nhưng BufferedReadernó thực hiện điều đó hiệu quả hơn và hơn nữa, có thể đọc dữ liệu theo toàn bộ dòng thay vì các ký tự riêng lẻ. Tất cả mọi thứ BufferedInputStreamlà như nhau! Lớp tích lũy dữ liệu đầu vào trong một bộ đệm đặc biệt mà không cần truy cập liên tục vào thiết bị đầu vào. Hãy xem một ví dụ:
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();
       }
   }
}
Trong ví dụ này, chúng tôi đang đọc dữ liệu từ một tệp nằm trên máy tính tại địa chỉ "D:/Users/UserName/someFile.txt" . Chúng tôi tạo 2 đối tượng - FileInputStreamBufferedInputStreamlàm “trình bao bọc” của nó. Sau đó, chúng tôi đọc các byte từ tệp và chuyển đổi chúng thành ký tự. Và cứ như vậy cho đến khi hết file. Như bạn có thể thấy, không có gì phức tạp ở đây. Bạn có thể sao chép mã này và chạy nó trên một số tệp thực được lưu trữ trên máy tính của bạn :) Một lớp OutputStreamlà một lớp trừu tượng xác định đầu ra luồng byte. Như bạn đã hiểu, đây là phản âm của InputStream'a. Nó không chịu trách nhiệm về việc đọc dữ liệu từ đâu mà là gửi dữ liệu đó đi đâu . Giống như InputStream, lớp trừu tượng này cung cấp cho tất cả các lớp con một nhóm các phương thức để làm việc thuận tiện:
  • int close()đóng luồng đầu ra;
  • void flush()xóa tất cả bộ đệm đầu ra;
  • abstract void write (int oneByte)ghi 1 byte vào luồng đầu ra;
  • void write (byte[] buffer)ghi một mảng byte vào luồng đầu ra;
  • void write (byte[] buffer, int offset, int count)ghi một phạm vi đếm byte từ mảng, bắt đầu từ vị trí offset.
Dưới đây là một số hậu duệ của lớp OutputStream:
  1. DataOutputStream. Luồng đầu ra bao gồm các phương thức ghi các kiểu dữ liệu Java tiêu chuẩn.

    Một lớp rất đơn giản để viết các kiểu và chuỗi Java nguyên thủy. Chắc chắn bạn sẽ hiểu được đoạn code viết ra ngay cả khi không cần giải thích:

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

    Nó có các phương thức riêng biệt cho từng loại - writeDouble(), writeLong(), writeShort()v.v.

  2. Lớp học FileOutputStream . Thực hiện cơ chế gửi dữ liệu tới một tập tin trên đĩa. Nhân tiện, chúng ta đã sử dụng nó trong ví dụ trước rồi, bạn có để ý không? Chúng tôi đã chuyển nó vào bên trong DataOutputStream, hoạt động như một “trình bao bọc”.

  3. BufferedOutputStream. Luồng đầu ra được đệm. Cũng không có gì phức tạp, bản chất giống như trong BufferedInputStream(hoặc BufferedReader'a). Thay vì ghi dữ liệu tuần tự thông thường, người ta sử dụng việc ghi thông qua bộ đệm “lưu trữ” đặc biệt. Bằng cách sử dụng bộ đệm, bạn có thể giảm số lượng chuyến đi khứ hồi đến đích dữ liệu và từ đó nâng cao hiệu quả.

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

    Một lần nữa, bạn có thể tự mình “chơi” với mã này và kiểm tra xem nó sẽ hoạt động như thế nào trên các tệp thực trên máy tính của bạn.

Bạn cũng có thể đọc về những người thừa kế trong tài liệu “ Hệ thống InputStreamđầu vào / đầu ra ”. Ồ , và chúng ta cũng sẽ có một bài giảng riêng nên có đủ thông tin về họ cho lần đầu làm quen. Đó là tất cả! Chúng tôi hy vọng bạn hiểu rõ về sự khác biệt giữa giao diện và lớp trừu tượng và sẵn sàng trả lời bất kỳ câu hỏi nào, thậm chí là câu hỏi khó :) OutputStreamFileInputStreamFileOutputStreamBufferedInputStream
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION