在我的一次采访中,有人问我“我们是否可以实例化一个抽象类?”
我的回答是“不,我们不能”。但是,面试官告诉我“错了,我们可以。”
我为此争论了一下。然后他让我自己在家试试。
abstract class my {
public void mymethod() {
System.out.print("Abstract");
}
}
class poly {
public static void main(String a[]) {
my m = new my() {};
m.mymethod();
}
}
在这里,我正在创建我的类的实例并调用抽象类的方法。谁能给我解释一下?我面试的时候真的错了吗?
A
派生一个非抽象类 B
,在 B
的构造部分实例,其中包含运行 A
的构造函数,对象的运行时类型实际上是 A
。然而只是暂时的。
在这里,我正在创建我的班级的实例
不,您不是在这里创建抽象类的实例。相反,您正在创建抽象类的匿名子类的实例。然后您在指向子类对象的抽象类引用上调用该方法。
此行为在 JLS - Section # 15.9.1 中明确列出:-
如果类实例创建表达式以类体结尾,则被实例化的类是匿名类。那么:如果 T 表示一个类,则声明一个由 T 命名的类的匿名直接子类。如果 T 表示的类是最终类,则这是编译时错误。如果 T 表示一个接口,则声明一个实现由 T 命名的接口的 Object 的匿名直接子类。在任何一种情况下,子类的主体都是类实例创建表达式中给出的 ClassBody。被实例化的类是匿名子类。
强调我的。
此外,在 JLS - Section # 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
这些数字表示这些匿名类出现在封闭类中的顺序。
以上实例化了一个匿名内部类,它是 my
抽象类的子类。它并不严格等同于实例化抽象类本身。 OTOH,每个子类实例都是其所有超类和接口的实例,因此大多数抽象类确实是通过实例化它们的具体子类之一来实例化的。
如果面试官只是说“错了!”没有解释,并举了这个例子,作为一个独特的反例,我认为他不知道他在说什么,虽然。
subclassInstance instanceof SuperClass
将返回 true,因此该对象是超类的实例,这意味着超类已被实例化。但这只是语义上的吹毛求疵。
= my() {};
表示存在匿名实现,而不是对象的简单实例化,它应该是:= my()
。你永远不能实例化一个抽象类。
你可以做的只是观察:
为什么 poly 扩展我的?这没用……编译的结果是什么?三个文件:my.class、poly.class 和 poly$1.class 如果我们可以像这样实例化一个抽象类,我们也可以实例化一个接口……奇怪……
我们可以实例化一个抽象类吗?
不,我们不能。我们可以做的是,创建一个匿名类(即第三个文件)并实例化它。
那么超类实例化呢?
抽象超类不是由我们实例化的,而是由 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
Serializable s = new Serializable() {};
(这是非常没用的),如果标记到你的代码会给出 class my$3
(或任何封闭类和数字)
您可以简单地回答,只需一行
不,你永远不能实例化抽象类
但是,面试官还是不同意,那你可以告诉他/她
你所能做的就是,你可以创建一个匿名类。
并且,根据匿名类,类在同一位置/行声明和实例化
因此,面试官可能有兴趣检查您的信心水平以及您对 OOP 的了解程度。
技术部分在其他答案中已经很好地涵盖了,主要以:“他错了,他什么都不知道,请他加入 SO 并全部清除:)”
我想说明一个事实(在其他答案中已经提到),这可能是一个stress-question,并且是许多面试官更多地了解您以及您如何应对困难和不寻常的情况的重要工具。通过给你不正确的代码,他可能想看看你是否反驳。要知道您是否有信心在类似的情况下与您的前辈抗衡。
PS:不知道为什么,感觉面试官看了这篇文章。
抽象类不能被实例化,但它们可以被子类化。 See This Link
最好的例子是
虽然 Calender 类有一个抽象方法 getInstance(),但是当你说 Calendar calc=Calendar.getInstance();
calc 将类 GregorianCalendar 的类实例称为“GregorianCalendar extends Calendar”
事实上匿名内部类型允许您创建抽象类的无名子类和它的实例。
技术解答
抽象类不能被实例化——这是定义和设计的。
来自 JLS,第 8 章。类:
命名类可以被声明为抽象的(第 8.1.1.1 节),如果实现不完整,则必须声明为抽象;这样的类不能被实例化,但可以被子类扩展。
来自 Classes.newInstance() 的 JSE 6 java 文档:
InstantiationException - 如果这个 Class 表示抽象类、接口、数组类、原始类型或 void;或者如果该类没有空构造函数;或者如果实例化由于某种其他原因而失败。
当然,您可以实例化抽象类(包括匿名子类)的具体子类,也可以对抽象类型的对象引用进行类型转换。
不同的角度 - 团队合作和社交智能:
当我们处理复杂的技术和法律规范时,这种技术误解在现实世界中经常发生。
“人际交往能力”在这里可能比“技术技能”更重要。如果竞争性地和积极地试图证明你的论点,那么理论上你可能是正确的,但你也可能在打架/破坏“面子”/制造敌人时造成比其价值更大的伤害。在解决你的分歧时要和解和理解。谁知道-也许您“都对”,但对术语的含义略有不同?
谁知道呢——虽然不太可能,但面试官可能会故意引入一个小冲突/误解,让你陷入一个充满挑战的境地,看看你在情感和社交方面的表现如何。与同事保持亲切和建设性,听从前辈的建议,并在面试后通过电子邮件或电话解决任何挑战/误解。表明你有积极性和注重细节。
众所周知,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();
}
}
关于抽象类
无法创建抽象类的对象
可以创建变量(可以表现得像数据类型)
如果子级不能覆盖父级的至少一个抽象方法,则子级也变为抽象
抽象类没有子类是没用的
抽象类的目的是表现得像一个基类。在继承层次结构中,您将看到顶部的抽象类。
不,您不能实例化抽象类。我们只实例化匿名类。在抽象类中,我们声明抽象方法并仅定义具体方法。
扩展一个类并不意味着您正在实例化该类。实际上,在您的情况下,您正在创建子类的实例。
我很确定抽象类不允许启动。所以,我会说不:你不能实例化一个抽象类。但是,您可以扩展它/继承它。
您不能直接实例化抽象类。但这并不意味着您不能间接获得类的实例(实际上不是原始抽象类的实例)。我的意思是你不能实例化原始抽象类,但你可以:
创建一个空类从抽象类继承它实例化派生类
因此,您可以通过派生类实例访问抽象类中的所有方法和属性。
你可以说:
我们不能实例化一个抽象类,但是我们可以使用 new
关键字创建一个匿名类实例,只需在最后添加 {}
作为实现主体的抽象类。
实例化抽象类是不可能的。你真正能做的是,在抽象类中实现一些常用方法,让其他方法未实现(声明它们是抽象的),并让具体的后代根据他们的需要实现它们。然后你可以创建一个工厂,它返回这个抽象类的一个实例(实际上是他的实现者)。然后在工厂中决定选择哪个实施者。这被称为工厂设计模式:
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 的接收者将调用他的方法并获取逻辑,在具体的下降器中(部分在抽象类方法中)实现,而不知道他得到的具体实现是什么。这也称为控制反转或依赖注入。
不,我们不能创建抽象类的对象,而是创建抽象类的引用变量。引用变量用于引用派生类的对象(抽象类的子类)
这是说明此概念的示例
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 类的对象。
实际上我们不能直接创建抽象类的对象。我们创建的是抽象调用的引用变量。引用变量用于引用继承抽象类的类的对象,即抽象类的子类。
不定期副业成功案例分享
instance of
和instantiating
之间是有区别的。您只实例化一个类,而您创建的该对象由于继承而可以是多个类的实例。