在我的一次面试中,有人问我:“我们是否可以实例化一个抽象类?”

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

我对此进行了一些争论。然后他让我自己在家试试。

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

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

在这里,我正在创建我的类的实例和调用抽象类的方法。有人能给我解释一下吗?我的面试真的错了吗?


当前回答

抽象类不能像每个人回答的那样被实例化,这是一个公认的事实。

当程序定义匿名类时,编译器实际上创建了一个具有不同名称的新类(具有EnclosedClassName$n模式,其中n是匿名类号)

所以如果你反编译这个Java类,你会发现如下代码:

my.class

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

Poly $1.class(匿名类的生成类)

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

其他回答

你可以说:我们不能实例化一个抽象类,但我们可以使用new关键字创建一个匿名类实例,只需在抽象类的末尾添加{}作为实现体。

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

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

这种行为在JLS -章节# 15.9.1中明确列出:-

If the class instance creation expression ends in a class body, then the class being instantiated is an anonymous class. Then: If T denotes a class, then an anonymous direct subclass of the class named by T is declared. It is a compile-time error if the class denoted by T is a final class. If T denotes an interface, then an anonymous direct subclass of Object that implements the interface named by T is declared. In either case, the body of the subclass is the ClassBody given in the class instance creation expression. The class being instantiated is the anonymous subclass.

我特别强调。

此外,在JLS -第12.5节中,您可以阅读到关于对象创建过程的内容。我在这里引用其中的一段话:-

每当创建一个新的类实例时,就分配内存空间 为该类中声明的所有实例变量留出空间 类的每个超类中声明的所有实例变量 类类型,包括可能隐藏的所有实例变量。 控件返回对新创建对象的引用之前 结果,则处理指定的构造函数以初始化new 对象,使用以下过程:

你可以在我提供的链接上阅读完整的程序。


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

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

现在,编译两个源文件:

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

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

扩展类并不意味着实例化类。实际上,在您的示例中,您正在创建子类的一个实例。

我非常确定抽象类不允许初始化。所以,我会说不:你不能实例化一个抽象类。但是,您可以扩展/继承它。

你不能直接实例化一个抽象类。但这并不意味着您不能间接获得类的实例(实际上不是原始抽象类的实例)。我的意思是你不能实例化原始的抽象类,但是你可以:

创建一个空类 从抽象类继承 实例化派生类

因此,您可以通过派生类实例访问抽象类中的所有方法和属性。

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

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

技术部分在其他答案中已经很好地涵盖了,它主要以: “他错了,他什么都不懂,让他加入SO,把一切都弄清楚:)”

我想说明一个事实(在其他回答中已经提到过),这可能是一个压力问题,也是许多面试官更多了解你以及你如何应对困难和不寻常情况的重要工具。通过给你错误的密码,他可能想看看你是否会反驳。要知道在类似的情况下,你是否有信心站起来对抗你的前辈。

附注:我不知道为什么,但我有一种感觉,面试官已经读了这篇文章。