我们可以实例化一个抽象类吗?

2022-08-31 04:13:23

在我的一次面试中,我被问到“如果我们能实例化一个抽象类吗?

我的回答是“不。我们不能”。但是,面试官告诉我:“错了,我们可以。

我对此争论了一下。然后他告诉我自己在家里试试这个。

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

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

在这里,我正在创建我的类的实例并调用抽象类的方法。任何人都可以向我解释一下吗?我在面试时真的错了吗?


答案 1

在这里,我正在创建我的类的实例

不,您不是在此处创建抽象类的实例。相反,您正在创建抽象类的匿名子类的实例。然后,在指向子类对象抽象类引用上调用该方法。

此行为在 JLS - 第 15.9.1 节中明确列出: -

如果类实例创建表达式以类体结束,则要实例化的类是匿名类。然后:

  • 如果 T 表示一个类,则声明由 T 命名的类的匿名直接子类。如果 T 表示的类是最终类,则为编译时错误。
  • 如果 T 表示接口,则声明 Object 的匿名直接子类,该子类实现由 T 命名的接口。
  • 在任一情况下,子类的主体都是类实例创建表达式中给出的 ClassBody。
  • 正在实例化的类是匿名子类。

强调我的。

此外,在 JLS - 第 12.5 节中,您可以阅读有关对象创建过程的信息。我在这里引用一句话: -

每当创建新的类实例时,都会为其分配内存空间,并为类类型中声明的所有实例变量和在类类型的每个超类中声明的所有实例变量(包括可能隐藏的所有实例变量)留出空间。

在将对新创建对象的引用作为结果返回之前,将使用以下过程处理指示的构造函数以初始化新对象:

您可以在我提供的链接上阅读有关完整过程的信息。


要实际看到要实例化的类是匿名子类,您只需要编译这两个类即可。假设您将这些类放在两个不同的文件中:

我的.java:

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

聚.java:

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

现在,编译两个源文件:

javac My.java Poly.java

现在,在编译源代码的目录中,您将看到以下类文件:

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

请参阅该类 - 。它是由编译器创建的类文件,对应于您使用以下代码实例化的匿名子类:Poly$1.class

new My() {};

因此,很明显,正在实例化不同的类。只是,只有在编译器编译后,才会为该类指定一个名称。

通常,类中的所有匿名子类都将按以下方式命名:

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

这些数字表示这些匿名类在封闭类中的出现顺序。


答案 2

上面实例化一个匿名内部类,它是抽象类的子类。它并不严格等同于实例化抽象类本身。OTOH,每个子类实例都是其所有超类和接口的实例,因此大多数抽象类确实是通过实例化其具体子类之一来实例化的。my

如果面试官只是说“错了!”而没有解释,并举了这个例子,作为一个独特的反例,我认为他不知道他在说什么。