Chúng ta có thể khởi tạo một lớp trừu tượng?


573

Trong một cuộc phỏng vấn của tôi, tôi đã được hỏi "Nếu chúng ta có thể khởi tạo một lớp trừu tượng?"

Câu trả lời của tôi là "Không, chúng tôi không thể". Nhưng, người phỏng vấn nói với tôi "Sai, chúng ta có thể."

Tôi đã tranh luận một chút về điều này. Sau đó, anh ấy nói với tôi để thử điều này ở nhà.

abstract class my {
    public void mymethod() {
        System.out.print("Abstract");
    }
}

class poly {
    public static void main(String a[]) {
        my m = new my() {};
        m.mymethod();
    }
}

Ở đây, tôi đang tạo cá thể của lớp và phương thức gọi của lớp trừu tượng. Bất cứ ai có thể vui lòng giải thích điều này cho tôi? Tôi đã thực sự sai trong cuộc phỏng vấn của tôi?


2
Mặc dù liên quan chỉ hơi, người ta có lẽ có thể nhanh chóng một lớp trừu tượng trong C ++: nếu bạn lấy được một lớp học không trừu tượng Btừ một một trừu tượng A, trong phần thi của Bví dụ, trong đó bao gồm chạy Aconstructor 's, loại thời gian chạy của đối tượng thực sự A. Chỉ tạm thời tuy nhiên.
Vlad

8
@jWeavers: Ví dụ anh ấy đưa ra là hoàn toàn sai. Bạn nên hỏi "việc sử dụng lớp trừu tượng" từ anh ta là gì. Nếu bạn đang mở rộng nó, thì tại sao bạn lại tạo một thể hiện của lớp mở rộng? Nó là một đối tượng hoàn toàn mới, nơi bạn kết thúc không có dữ liệu ..
Lemon Juice

3
Hoặc có thể là người phỏng vấn muốn kiểm tra xem bạn tự tin như thế nào về tuyên bố của mình so với những gì anh ấy đề xuất!
Sid

5
Anh nói dối em. Bạn đánh rơi quả bóng khi bạn không thể chỉ ra đó không phải là mã này làm gì và giải thích các lớp con ẩn danh là gì. Có lẽ anh ấy đã biết điều đó và muốn xem nếu bạn biết.
candied_orange

2
Đây không phải là một chương trình đố vui, nhưng một cuộc phỏng vấn việc làm, phải không? Vậy điều gì sẽ xảy ra nếu Java, hoặc C ++, cho phép khởi tạo các lớp trừu tượng? Bạn sẽ không làm điều đó, bởi vì đó không phải là một điều thông minh để làm. Trong Objective-C, các lớp trừu tượng chỉ trừu tượng theo quy ước và khởi tạo chúng là một lỗi.
gnasher729

Câu trả lời:


722

Ở đây, tôi đang tạo cá thể của lớp tôi

Không, bạn không tạo ra thể hiện của lớp trừu tượng của bạn ở đây. Thay vào đó, bạn đang tạo một thể hiện của một lớp con ẩn danh của lớp trừu tượng của bạn. Và sau đó, bạn đang gọi phương thức trên tham chiếu lớp trừu tượng của bạn trỏ đến đối tượng lớp con .

Hành vi này được liệt kê rõ ràng trong JLS - Mục # 15.9.1 : -

Nếu biểu thức tạo cá thể lớp kết thúc trong một thân lớp, thì lớp được khởi tạo là một lớp ẩn danh. Sau đó:

  • Nếu T biểu thị một lớp, thì một lớp con trực tiếp ẩn danh của lớp có tên T được khai báo. Đó là lỗi thời gian biên dịch nếu lớp được ký hiệu là T là lớp cuối cùng.
  • Nếu T biểu thị một giao diện, thì một lớp con trực tiếp ẩn danh của Object thực hiện giao diện có tên T được khai báo.
  • Trong cả hai trường hợp, phần thân của lớp con là ClassBody được đưa ra trong biểu thức tạo cá thể lớp.
  • Lớp được khởi tạo là lớp con ẩn danh.

Nhấn mạnh mỏ.

Ngoài ra, trong JLS - Mục # 12.5 , bạn có thể đọc về Quá trình tạo đối tượng . Tôi sẽ trích dẫn một tuyên bố từ đây: -

Bất cứ khi nào một cá thể lớp mới được tạo, không gian bộ nhớ được phân bổ cho nó có chỗ cho tất cả các biến thể hiện được khai báo trong loại lớp và tất cả các biến thể hiện được khai báo trong mỗi siêu lớp của loại lớp, bao gồm tất cả các biến thể hiện có thể bị ẩn.

Ngay trước khi kết quả tham chiếu đến đối tượng vừa tạo được trả về, hàm tạo được chỉ định được xử lý để khởi tạo đối tượng mới bằng thủ tục sau:

Bạn có thể đọc về thủ tục đầy đủ trên liên kết tôi cung cấp.


Để thực tế thấy rằng lớp được khởi tạo là một lớp con ẩn danh , bạn chỉ cần biên dịch cả hai lớp của bạn. Giả sử bạn đặt các lớp đó vào hai tệp khác nhau:

My.java:

abstract class My {
    public void myMethod() {
        System.out.print("Abstract");
    }
}

Poly.java:

class Poly extends My {
    public static void main(String a[]) {
        My m = new My() {};
        m.myMethod();
    }
}

Bây giờ, biên dịch cả hai tệp nguồn của bạn:

javac My.java Poly.java

Bây giờ trong thư mục nơi bạn biên dịch mã nguồn, bạn sẽ thấy các tệp lớp sau:

My.class
Poly$1.class  // Class file corresponding to anonymous subclass
Poly.class

Xem lớp đó - Poly$1.class. Đó là tệp lớp được tạo bởi trình biên dịch tương ứng với lớp con ẩn danh mà bạn đã khởi tạo bằng mã dưới đây:

new My() {};

Vì vậy, rõ ràng là có một lớp khác được khởi tạo. Chỉ có điều, lớp đó chỉ được đặt tên sau khi biên dịch bởi trình biên dịch.

Nói chung, tất cả các lớp con ẩn danh trong lớp của bạn sẽ được đặt tên theo kiểu này:

Poly$1.class, Poly$2.class, Poly$3.class, ... so on

Những con số đó biểu thị thứ tự mà các lớp ẩn danh đó xuất hiện trong lớp kèm theo.


172
@coders. Câu trả lời chính xác là: - Bạn không thể khởi tạo lớp trừu tượng của mình, tuy nhiên bạn có thể khởi tạo một lớp con cụ thể của lớp trừu tượng.
Rohit Jain

16
Trong một dòng bạn có thể nói: - Bạn không bao giờ có thể khởi tạo một lớp trừu tượng. Đó là mục đích của một lớp trừu tượng.
Rahul Tripathi

66
Có vẻ như người phỏng vấn được đầu tư nhiều hơn vào câu trả lời của anh ta hơn là của bạn ...
Neil T.

7
Theo một nhận xét khác (có tham chiếu JLS ), "Một đối tượng được cho là một thể hiện của lớp và của tất cả các siêu lớp của lớp" - do đó, thực tế chúng ta không tạo ra một thể hiện của lớp trừu tượng ở đây sao? tức là bắt đầu lớp trừu tượng?
arshajii

6
@ARS Tôi muốn nói rằng có một sự khác biệt giữa là một instance ofinstantiating. Bạn chỉ khởi tạo một lớp, trong khi đối tượng bạn tạo có thể là một thể hiện của nhiều lớp do kế thừa.
Simon Forsberg

89

Ở trên khởi tạo một lớp bên trong ẩn danh là một lớp con của mylớp trừu tượng. Nó không hoàn toàn tương đương với việc khởi tạo lớp trừu tượng. OTOH, mỗi thể hiện của lớp con là một thể hiện của tất cả các siêu lớp và giao diện của nó, vì vậy hầu hết các lớp trừu tượng thực sự được khởi tạo bằng cách khởi tạo một trong các lớp con cụ thể của chúng.

Nếu người phỏng vấn chỉ nói "sai!" không cần giải thích, và đưa ra ví dụ này, như một ví dụ độc đáo, tôi nghĩ rằng anh ta không biết mình đang nói về cái gì.


10
Nói đúng ra , siêu lớp trừu tượng không được khởi tạo. Hàm tạo của nó được gọi để khởi tạo các biến thể hiện.
Nhận thức

4
Đúng vậy: subclassInstance instanceof SuperClasssẽ trả về true, vì vậy đối tượng là một thể hiện của siêu lớp, điều đó có nghĩa là siêu lớp đã được đặt sẵn. Nhưng đó chỉ là nitpicking ngữ nghĩa.
JB Nizet

5
Có thể là ngữ nghĩa thực sự. Java định nghĩa việc khởi tạo theo cách tạo các đối tượng thông qua từ khóa mới (điều mà bạn không thể làm với một lớp trừu tượng). Nhưng tất nhiên, lớp con cụ thể sẽ báo cáo chính xác rằng đó là một thể hiện của mọi thành viên trong hệ thống phân cấp cha của nó.
Nhận thức

11
đoạn 4.12.6 của JLS nói: "Một đối tượng được cho là một thể hiện của lớp và của tất cả các siêu lớp của lớp.".
JB Nizet

85

= my() {};có nghĩa là có một triển khai ẩn danh, không phải là khởi tạo đơn giản một đối tượng, mà lẽ ra phải là : = my(). Bạn không bao giờ có thể khởi tạo một lớp trừu tượng.


30

Chỉ cần quan sát bạn có thể thực hiện:

  1. Tại sao polykéo dài my? Điều này là vô ích ...
  2. Kết quả của việc biên soạn là gì? Ba tập tin: my.class, poly.classpoly$1.class
  3. Nếu chúng ta có thể khởi tạo một lớp trừu tượng như thế, chúng ta có thể khởi tạo một giao diện quá ... kỳ lạ ...


Chúng ta có thể khởi tạo một lớp trừu tượng?

Không, chúng tôi không thể. Những gì chúng ta có thể làm là, tạo một lớp ẩn danh (đó là tệp thứ ba) và khởi tạo nó.


Điều gì về một khởi tạo siêu lớp?

Lớp siêu trừu tượng không phải do chúng tôi khởi tạo mà là java.

EDIT: Yêu cầu anh ta kiểm tra điều này

public static final void main(final String[] args) {
    final my m1 = new my() {
    };
    final my m2 = new my() {
    };
    System.out.println(m1 == m2);

    System.out.println(m1.getClass().toString());
    System.out.println(m2.getClass().toString());

}

đầu ra là:

false
class my$1
class my$2

1 để quan sát 3: ví dụ, chúng ta có thể làm Serializable s = new Serializable() {};(đó là khá vô dụng) và nếu gắn thẻ vào mã của bạn sẽ cung cấp cho class my$3(hoặc bất kỳ kèm theo lớp và số)
Khôi phục Monica - notmaynard

18

Bạn chỉ có thể trả lời, chỉ trong một dòng

Không , bạn không bao giờ có thể ví dụ Lớp trừu tượng

Nhưng, người phỏng vấn vẫn không đồng ý, sau đó bạn có thể nói với anh ấy / cô ấy

tất cả những gì bạn có thể làm là, bạn có thể tạo một Lớp ẩn danh.

Và, theo lớp Ẩn danh, lớp đã khai báo và khởi tạo tại cùng một vị trí / dòng

Vì vậy, có thể, người phỏng vấn sẽ quan tâm để kiểm tra mức độ tự tin của bạn và mức độ bạn biết về OOP.


17

Phần kỹ thuật đã được đề cập đầy đủ trong các câu trả lời khác, và nó chủ yếu kết thúc bằng:
"Anh ấy sai, anh ấy không biết gì, yêu cầu anh ấy tham gia SO và xóa tất cả :)"

Tôi muốn giải quyết một thực tế (đã được đề cập trong các câu trả lời khác) rằng đây có thể là một câu hỏi căng thẳng và là một công cụ quan trọng để nhiều người phỏng vấn biết thêm về bạn và cách bạn phản ứng với các tình huống khó khăn và bất thường. Bằng cách cung cấp cho bạn mã không chính xác, anh ta có thể muốn xem nếu bạn tranh luận lại. Để biết liệu bạn có tự tin để đứng lên chống lại đàn anh của mình trong những tình huống tương tự như thế này không.

Tái bút: Tôi không biết tại sao nhưng tôi có cảm giác rằng người phỏng vấn đã đọc bài đăng này.


13

Các lớp trừu tượng không thể được khởi tạo, nhưng chúng có thể được phân lớp. Xem liên kết này

Ví dụ tốt nhất là

Mặc dù lớp Calender có một phương thức trừu tượng getInstance () , nhưng khi bạn nóiCalendar calc=Calendar.getInstance();

calc đang đề cập đến thể hiện của lớp GregorianCalWiki là "GregorianCalWiki mở rộng Lịch "

Infact annonymous loại bên trong cho phép bạn tạo một lớp con không có tên của lớp trừu tượng và một ví dụ về điều này.


11

Trả lời kỹ thuật

Các lớp trừu tượng không thể được khởi tạo - đây là theo định nghĩa và thiết kế.

Từ JLS, Chương 8. Các lớp học:

Một lớp được đặt tên có thể được khai báo là trừu tượng (§8.1.1.1) và phải được khai báo là trừu tượng nếu nó được thực hiện không đầy đủ; một lớp như vậy không thể được khởi tạo, nhưng có thể được mở rộng bởi các lớp con.

Từ tài liệu java JSE 6 cho Classes.newInstance ():

InstantiationException - nếu Lớp này đại diện cho một lớp trừu tượng, một giao diện, một lớp mảng, một kiểu nguyên thủy hoặc void; hoặc nếu lớp không có hàm tạo rỗng; hoặc nếu việc khởi tạo thất bại vì một số lý do khác.

Tất nhiên, bạn có thể khởi tạo một lớp con cụ thể của một lớp trừu tượng (bao gồm cả một lớp con ẩn danh) và cũng có thể thực hiện một kiểu chữ của một tham chiếu đối tượng đến một kiểu trừu tượng.

Một góc nhìn khác về điều này - Teamplay & Social Intelligence:

Loại hiểu lầm kỹ thuật này xảy ra thường xuyên trong thế giới thực khi chúng ta đối phó với các công nghệ phức tạp và thông số kỹ thuật hợp pháp.

"Kỹ năng con người" có thể quan trọng hơn ở đây so với "Kỹ năng kỹ thuật". Nếu cạnh tranh và quyết liệt cố gắng chứng minh khía cạnh tranh luận của bạn, thì bạn có thể đúng về mặt lý thuyết, nhưng bạn cũng có thể gây thiệt hại nhiều hơn khi có một "khuôn mặt" chiến đấu / gây tổn hại / tạo ra kẻ thù hơn giá trị của nó. Hãy hòa giải và hiểu biết trong việc giải quyết sự khác biệt của bạn. Ai biết được - có thể bạn "cả hai đều đúng" nhưng làm việc có nghĩa hơi khác nhau cho các thuật ngữ ??

Ai biết được - mặc dù không có khả năng, có thể người phỏng vấn đã cố tình đưa ra một mâu thuẫn / hiểu lầm nhỏ để đưa bạn vào một tình huống đầy thách thức và xem cách bạn hành xử theo cảm xúc và xã hội. Hãy duyên dáng và xây dựng với các đồng nghiệp, làm theo lời khuyên từ người cao niên và theo dõi sau cuộc phỏng vấn để giải quyết mọi thách thức / hiểu lầm - qua email hoặc gọi điện thoại. Cho thấy bạn có động lực và định hướng chi tiết.


7

Đó là một thực tế cũng được thành lập mà abstract classcó thể không được khởi tạo như tất cả mọi người đã trả lời.

Khi chương trình định nghĩa lớp nặc danh, trình biên dịch thực sự tạo ra một lớp mới với tên khác nhau (có mô hình EnclosedClassName$nở đâu nlà số lớp ẩn danh)

Vì vậy, nếu bạn dịch ngược lớp Java này, bạn sẽ tìm thấy mã như dưới đây:

lớp học của tôi

abstract class my { 
    public void mymethod() 
    { 
        System.out.print("Abstract"); 
    }
} 

poly $ 1. class (lớp được tạo của "lớp ẩn danh")

class poly$1 extends my 
{
} 

ploly.cass

public class poly extends my
{
    public static void main(String[] a)
    {
        my m = new poly.1(); // instance of poly.1 class NOT the abstract my class

        m.mymethod();
    }
}

4

Không, bạn không thể khởi tạo một lớp trừu tượng. Chúng tôi chỉ khởi tạo lớp ẩn danh. Trong lớp trừu tượng, chúng tôi khai báo các phương thức trừu tượng và chỉ định nghĩa các phương thức cụ thể.


4

Về các lớp học trừu tượng

  • Không thể tạo đối tượng của một lớp trừu tượng
  • Có thể tạo các biến (có thể hoạt động như kiểu dữ liệu)
  • Nếu một đứa trẻ không thể ghi đè ít nhất một phương thức trừu tượng của cha mẹ, thì đứa trẻ cũng trở nên trừu tượng
  • Các lớp trừu tượng là vô dụng nếu không có các lớp con

Mục đích của một lớp trừu tượng là hành xử như một cơ sở. Trong hệ thống phân cấp thừa kế, bạn sẽ thấy các lớp trừu tượng hướng lên trên cùng.


3

Bạn có thể nói:
chúng ta không thể khởi tạo một lớp trừu tượng, nhưng chúng ta có thể sử dụng newtừ khóa để tạo một thể hiện của lớp ẩn danh bằng cách chỉ thêm {}phần thân thực hiện vào cuối lớp trừu tượng.


3

Mở rộng một lớp học không có nghĩa là bạn đang bắt đầu lớp học. Trên thực tế, trong trường hợp của bạn, bạn đang tạo một thể hiện của lớp con.

Tôi khá chắc chắn rằng các lớp trừu tượng không cho phép bắt đầu. Vì vậy, tôi sẽ nói không: bạn không thể khởi tạo một lớp trừu tượng. Nhưng, bạn có thể mở rộng nó / kế thừa nó.

Bạn không thể trực tiếp khởi tạo một lớp trừu tượng. Nhưng điều đó không có nghĩa là bạn không thể lấy một thể hiện của lớp (không phải là một thể hiện của lớp trừu tượng ban đầu) một cách gián tiếp. Ý tôi là bạn không thể khởi tạo lớp trừu tượng gốc, nhưng bạn có thể:

  1. Tạo một lớp trống
  2. Kế thừa nó từ lớp trừu tượng
  3. Khởi tạo lớp học

Vì vậy, bạn có quyền truy cập vào tất cả các phương thức và thuộc tính trong một lớp trừu tượng thông qua thể hiện của lớp dẫn xuất.


2

Không thể khởi tạo một lớp trừu tượng. Những gì bạn thực sự có thể làm, đã thực hiện một số phương thức phổ biến trong một lớp trừu tượng và để cho những người khác không thực hiện (tuyên bố chúng trừu tượng) và để cho trình giải mã cụ thể thực hiện chúng tùy theo nhu cầu của họ. Sau đó, bạn có thể tạo một nhà máy, trả về một thể hiện của lớp trừu tượng này (thực ra là người triển khai của anh ta). Trong nhà máy sau đó bạn quyết định, lựa chọn người thực hiện. Đây được gọi là một mẫu thiết kế nhà máy:

   public abstract class AbstractGridManager {
        private LifecicleAlgorithmIntrface lifecicleAlgorithm;
        // ... more private fields

        //Method implemented in concrete Manager implementors 
        abstract public Grid initGrid();

        //Methods common to all implementors
        public Grid calculateNextLifecicle(Grid grid){
            return this.getLifecicleAlgorithm().calculateNextLifecicle(grid);
        }

        public LifecicleAlgorithmIntrface getLifecicleAlgorithm() {
            return lifecicleAlgorithm;
        }
        public void setLifecicleAlgorithm(LifecicleAlgorithmIntrface lifecicleAlgorithm) {
            this.lifecicleAlgorithm = lifecicleAlgorithm;
        }
        // ... more common logic and getters-setters pairs
    }

Người triển khai cụ thể chỉ cần thực hiện các phương thức được khai báo là trừu tượng, nhưng sẽ có quyền truy cập vào logic được triển khai trong các lớp đó trong một lớp trừu tượng, không được khai báo là trừu tượng:

public class FileInputGridManager extends AbstractGridManager {

private String filePath;

//Method implemented in concrete Manager implementors 
abstract public Grid initGrid();

public class FileInputGridManager extends AbstractGridManager {

    private String filePath;

    //Method implemented in concrete Manager implementors 
    abstract public Grid initGrid();

    public Grid initGrid(String filePath) {
        List<Cell> cells = new ArrayList<>();
        char[] chars;
        File file = new File(filePath); // for example foo.txt
        // ... more logic
        return grid;
    }
}

Cuối cùng, nhà máy trông giống như thế này:

public class GridManagerFactory {
    public static AbstractGridManager getGridManager(LifecicleAlgorithmIntrface lifecicleAlgorithm, String... args){
        AbstractGridManager manager = null;

        // input from the command line
        if(args.length == 2){
            CommandLineGridManager clManager = new CommandLineGridManager();
            clManager.setWidth(Integer.parseInt(args[0]));
            clManager.setHeight(Integer.parseInt(args[1]));
            // possibly more configuration logic
            ...
            manager = clManager;
        } 
        // input from the file
        else if(args.length == 1){
            FileInputGridManager fiManager = new FileInputGridManager();
            fiManager.setFilePath(args[0]);
            // possibly more method calls from abstract class
            ...
            manager = fiManager ;
        }
        //... more possible concrete implementors
        else{
            manager = new CommandLineGridManager();
        }
        manager.setLifecicleAlgorithm(lifecicleAlgorithm);
        return manager;
    }
}

Người nhận của AbstractGridManager sẽ gọi các phương thức trên anh ta và nhận logic, được triển khai trong phần giải mã cụ thể (và một phần trong các phương thức lớp trừu tượng) mà không biết triển khai cụ thể mà anh ta có là gì. Điều này còn được gọi là đảo ngược kiểm soát hoặc tiêm phụ thuộc.


2

Không, chúng ta không thể tạo đối tượng của lớp trừu tượng, nhưng tạo biến tham chiếu của lớp trừu tượng. Biến tham chiếu được sử dụng để chỉ các đối tượng của các lớp dẫn xuất (Các lớp con của lớp Trừu tượng)

Dưới đây là ví dụ minh họa khái niệm này

abstract class Figure { 

    double dim1; 

    double dim2; 

    Figure(double a, double b) { 

        dim1 = a; 

        dim2 = b; 

    } 

    // area is now an abstract method 

    abstract double area(); 

    }


    class Rectangle extends Figure { 
        Rectangle(double a, double b) { 
        super(a, b); 
    } 
    // override area for rectangle 
    double area() { 
        System.out.println("Inside Area for Rectangle."); 
        return dim1 * dim2; 
    } 
}

class Triangle extends Figure { 
    Triangle(double a, double b) { 
        super(a, b); 
    } 
    // override area for right triangle 
    double area() { 
        System.out.println("Inside Area for Triangle."); 
        return dim1 * dim2 / 2; 
    } 
}

class AbstractAreas { 
    public static void main(String args[]) { 
        // Figure f = new Figure(10, 10); // illegal now 
        Rectangle r = new Rectangle(9, 5); 
        Triangle t = new Triangle(10, 8); 
        Figure figref; // this is OK, no object is created 
        figref = r; 
        System.out.println("Area is " + figref.area()); 
        figref = t; 
        System.out.println("Area is " + figref.area()); 
    } 
}

Ở đây chúng ta thấy rằng chúng ta không thể tạo đối tượng của kiểu Hình nhưng chúng ta có thể tạo một biến tham chiếu của kiểu Hình. Ở đây chúng ta đã tạo một biến tham chiếu có kiểu tham chiếu Hình và biến tham chiếu Lớp được sử dụng để tham chiếu đến các đối tượng của Hình chữ nhật và Tam giác lớp.


0

Thật ra chúng ta không thể tạo một đối tượng của một lớp trừu tượng trực tiếp. Những gì chúng ta tạo là một biến tham chiếu của một cuộc gọi trừu tượng. Biến tham chiếu được sử dụng để Tham chiếu đến đối tượng của lớp kế thừa lớp Trừu tượng tức là lớp con của lớp trừu tượng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.