615

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

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

我为此争论了一下。然后他让我自己在家试试。

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

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

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

4

16 回答 16

771

在这里,我正在创建我的班级的实例

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

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

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

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

强调我的。

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

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

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

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


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

我的.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

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

于 2012-12-02T16:04:01.307 回答
94

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

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

于 2012-12-02T16:04:53.127 回答
88

= my() {};意味着有一个匿名实现,而不是一个对象的简单实例化,它应该是 : = my()。你永远不能实例化一个抽象类。

于 2012-12-02T16:29:18.867 回答
30

你可以做的只是观察:

  1. 为什么要poly延长my?这是没用的...
  2. 编译的结果是什么?三个文件my.classpoly.classpoly$1.class
  3. 如果我们可以像这样实例化一个抽象类,我们也可以实例化一个接口......奇怪......


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

不,我们不能。我们可以做的是,创建一个匿名类(即第三个文件)并实例化它。


那么超类实例化呢?

抽象超类不是由我们实例化的,而是由 java 实例化的。

编辑:让他测试一下

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

}

输出是:

false
class my$1
class my$2
于 2012-12-06T16:12:15.507 回答
19

您可以简单地回答,只需一行

,你永远不能实例化抽象类

但是,面试官还是不同意,那你可以告诉他/她

你所能做的就是,你可以创建一个匿名类。

并且,根据匿名类,类在同一位置/行声明和实例化

因此,面试官可能有兴趣检查您的信心水平以及您对 OOP 的了解程度。

于 2012-12-07T04:09:10.577 回答
17

技术部分在其他答案中已经很好地涵盖了,主要以:
“他错了,他什么都不知道,请他加入 SO 并全部清除:)”

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

PS:不知道为什么,感觉面试官看了这篇文章。

于 2015-04-07T05:20:32.083 回答
13

抽象类不能被实例化,但它们可以被子类化。看到这个链接

最好的例子是

虽然Calender 类有一个抽象方法 getInstance(),但是当你说Calendar calc=Calendar.getInstance();

calc 将类 GregorianCalendar 的类实例称为“GregorianCalendar extends Calendar

事实上匿名内部类型 允许您创建抽象类的无名子类和它的实例。

于 2012-12-03T09:14:11.727 回答
12

技术解答

抽象类不能被实例化——这是定义和设计的。

来自 JLS,第 8 章。类:

命名类可以被声明为抽象的(第 8.1.1.1 节),如果实现不完整,则必须声明为抽象;这样的类不能被实例化,但可以被子类扩展。

来自 Classes.newInstance() 的 JSE 6 java 文档:

InstantiationException - 如果这个 Class 表示抽象类、接口、数组类、原始类型或 void;或者如果该类没有空构造函数;或者如果实例化由于某种其他原因而失败。

当然,您可以实例化抽象类(包括匿名子类)的具体子类,也可以对抽象类型的对象引用进行类型转换。

不同的角度 - 团队合作和社交智能:

当我们处理复杂的技术和法律规范时,这种技术误解在现实世界中经常发生。

“人际交往能力”在这里可能比“技术技能”更重要。如果竞争性地和积极地试图证明你的论点,那么理论上你可能是正确的,但你也可能在打架/破坏“面子”/制造敌人时造成比其价值更大的伤害。在解决你的分歧时要和解和理解。谁知道-也许您“都对”,但对术语的含义略有不同?

谁知道呢——虽然不太可能,但面试官可能会故意引入一个小冲突/误解,让你陷入一个充满挑战的境地,看看你在情感和社交方面的表现如何。与同事保持亲切和建设性,听从前辈的建议,并在面试后通过电子邮件或电话解决任何挑战/误解。表明你有积极性和注重细节。

于 2013-03-18T03:10:59.217 回答
7

这是一个公认的事实,abstract class不能像每个人都回答的那样实例化

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

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

我的课

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();
    }
}
于 2013-02-14T12:33:26.240 回答
4

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

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

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

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

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

于 2013-11-03T15:39:24.907 回答
4

关于抽象类

  • 无法创建抽象类的对象
  • 可以创建变量(可以表现得像数据类型)
  • 如果子级不能覆盖父级的至少一个抽象方法,则子级也变为抽象
  • 抽象类没有子类是没用的

抽象类的目的是表现得像一个基类。在继承层次结构中,您将看到顶部的抽象类。

于 2013-12-26T10:43:10.427 回答
4

不,您不能实例化抽象类。我们只实例化匿名类。在抽象类中,我们声明抽象方法并仅定义具体方法。

于 2014-01-04T07:24:44.683 回答
3

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

于 2015-11-25T12:31:05.820 回答
2

实例化抽象类是不可能的。你真正能做的是,在抽象类中实现一些常用方法,让其他方法未实现(声明它们是抽象的),并让具体的后代根据他们的需要实现它们。然后你可以创建一个工厂,它返回这个抽象类的一个实例(实际上是他的实现者)。然后在工厂中决定选择哪个实施者。这被称为工厂设计模式:

   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
    }

具体实现者只需要实现声明为抽象的方法,但可以访问抽象类中那些未声明为抽象的类中实现的逻辑:

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

最后工厂看起来像这样:

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

AbstractGridManager 的接收者将调用他的方法并获取逻辑,在具体的下降器中(部分在抽象类方法中)实现,而不知道他得到的具体实现是什么。这也称为控制反转或依赖注入。

于 2014-06-03T16:11:27.513 回答
2

不,我们不能创建抽象类的对象,而是创建抽象类的引用变量。引用变量用于引用派生类的对象(抽象类的子类)

这是说明此概念的示例

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

在这里,我们看到我们无法创建 Figure 类型的对象,但我们可以创建 Figure 类型的引用变量。这里我们创建了一个 Figure 类型的引用变量,Figure 类引用变量用于引用 Rectangle 类和 Triangle 类的对象。

于 2015-04-21T19:06:19.553 回答
0

实际上我们不能直接创建抽象类的对象。我们创建的是抽象调用的引用变量。引用变量用于引用继承抽象类的类的对象,即抽象类的子类。

于 2019-11-24T12:16:23.633 回答