【问题标题】:Can we instantiate an abstract class?我们可以实例化一个抽象类吗?
【发布时间】:2020-11-21 07:45:04
【问题描述】:

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

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

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

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

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

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

【问题讨论】:

  • 虽然关系不大,但也许可以在 C++ 中实例化一个抽象类:如果从抽象类 B 派生非抽象类 B在构造部分B 实例,包括运行A 的构造函数,对象的运行时类型实际上是A。不过只是暂时的。
  • @jWeavers:他给出的例子是完全错误的。你应该问他“那么抽象类有什么用”。如果要扩展它,那么为什么要创建扩展类的实例?这是一个全新的对象,你最终没有数据..
  • 或者可能是面试官想检查你对他提出的陈述的信心!
  • 他骗了你。当你没有指出这不是这段代码所做的并解释什么是匿名子类时,你就丢了球。他可能已经知道了,想看看你是否知道。
  • 这不是智力竞赛节目,而是工作面试,对吧?那么如果 Java 或 C++ 允许实例化抽象类呢?你不会这样做,因为这不是一件聪明的事情。在 Objective-C 中,抽象类只是约定俗成的抽象类,实例化它们是一个 bug。

标签: java oop class object abstract


【解决方案1】:

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

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

JLS - Section # 15.9.1 中明确列出了这种行为:-

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

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

强调我的。

另外,在JLS - Section # 12.5,您可以阅读对象创建过程。我将在这里引用一个声明:-

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

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

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


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

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

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

【讨论】:

  • @coders。确切的答案是: - 你不能实例化你的抽象类,但是你可以实例化你抽象类的一个具体子类。
  • 在一行中你可以说:- 你永远不能实例化一个抽象类。这就是抽象类的目的。
  • 听起来面试官对他的回答比对你的回答更投入......
  • 根据another comment(带有JLS reference),“一个对象被认为是它的类和它的类的所有超类的一个实例”——因此,我们实际上不是在技术上在这里创建抽象类的实例?即实例化抽象类?
  • @A.R.S.我想说成为instance ofinstantiating 是有区别的。您只实例化一个类,而您创建的对象由于继承关系可以是多个类的实例。
【解决方案2】:

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

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

【讨论】:

  • 严格来说,抽象超类没有被实例化。调用它的构造函数来初始化实例变量。
  • 是的:subclassInstance instanceof SuperClass 将返回 true,因此该对象是超类的实例,这意味着超类已被实例化。但这只是语义上的吹毛求疵。
  • 确实是语义。 Java 通过 new 关键字创建对象来定义 instantiation(你不能用抽象类来做到这一点)。但是,具体的子类当然会正确地报告它是其父层次结构中每个成员的实例。
  • paragraph 4.12.6 of the JLS 说:“一个对象被称为它的类和它的类的所有超类的一个实例。”。
【解决方案3】:

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

【讨论】:

    【解决方案4】:

    你可以做的只是观察:

    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
    

    【讨论】:

    • +1 用于观察 3:例如,我们可以做 Serializable s = new Serializable() {};(这是非常没用的),如果标记到您的代码上会给出 class my$3(或任何封闭类和数字)
    【解决方案5】:

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

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

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

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

    并且,根据匿名类,类在同一个地方/行声明和实例化

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

    【讨论】:

      【解决方案6】:

      技术部分已经在其他答案中得到了很好的介绍,主要结束于:
      “他错了,他什么都不知道,请他加入 SO 并清除一切:)”

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

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

      【讨论】:

        【解决方案7】:

        抽象类不能被实例化,但它们可以被子类化。 See This Link

        最好的例子是

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

        calc 将类 GregorianCalendar 的类实例称为“GregorianCalendar 扩展日历

        Infact 匿名内部类型 允许您创建抽象类的无名子类及其实例。

        【讨论】:

          【解决方案8】:

          技术解答

          抽象类不能被实例化 - 这是根据定义和设计。

          来自 JLS,第 8 章。类:

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

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

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

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

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

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

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

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

          【讨论】:

            【解决方案9】:

            众所周知,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();
                }
            }
            

            【讨论】:

              【解决方案10】:

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

              【讨论】:

                【解决方案11】:

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

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

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

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

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

                【讨论】:

                  【解决方案12】:

                  关于抽象类

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

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

                  【讨论】:

                    【解决方案13】:

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

                    【讨论】:

                      【解决方案14】:

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

                         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 的接收者会调用他的方法并获取逻辑,在具体的后代中(部分在抽象类方法中)实现,而不知道他得到的具体实现是什么。这也称为控制反转或依赖注入。

                      【讨论】:

                        【解决方案15】:

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

                        这里是说明这个概念的例子

                        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类的对象。

                        【讨论】:

                          【解决方案16】:

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

                          【讨论】:

                            猜你喜欢
                            • 2011-06-02
                            • 2021-08-02
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 2010-09-21
                            相关资源
                            最近更新 更多