抽象类的Java构造函数

4
据我所知(如果我错了,请纠正),抽象类是不能被实例化的。你可以给它一个构造函数,但只是不能在该类上调用new。如果在子类中调用super,那么父类的构造函数将会运行(从而创建一个该类的对象?),那么为什么你实际上可以在抽象类的子类中调用super呢?
我相信这与我对构造函数制作对象的误解有关...

1
抽象构造函数可用于在抽象类中初始化变量或执行初始化步骤。 - brso05
4个回答

7
如果在子类中调用super,那么超类的构造函数将会运行(这样创建了该类的一个对象?),但是为什么你可以在抽象类的子类中实际调用super呢?
这个部分是错误的。当您在子类构造函数中调用super时,您只是告诉子类它必须首先执行来自超类(无论是抽象还是不抽象)的初始化代码,然后它将继续执行代码以初始化正在创建的当前类的实例。这并不意味着它会在创建当前实例的过程中创建一个超类实例。

1
那么调用构造函数并不意味着创建一个对象?我现在有点困惑了... - Maxim
1
调用构造函数将固有地调用该对象类型的父树中的所有超级构造函数。创建的唯一对象是您在其上调用“new”的实际类型。 - mnd
@Maxim 调用构造函数确实意味着创建一个对象,但执行 super(...) 并不会调用超类的构造函数来创建超类的对象,而是仅执行超类构造函数中定义的任何行为(无论是否为抽象)。这是在创建当前类的实例时发生的。 - Luiggi Mendoza
通常情况下,如果在使用new关键字时调用构造函数...它确实会调用构造函数,但是在调用super时,它只执行超类构造函数中的代码,而不会创建那个类的新对象。对我来说有点奇怪,因为据我所知,该类的构造函数实际上正在被调用,因此应自动创建一个新对象...我明天必须复习一下,并希望它会更加清晰。非常感谢你们的时间,我真的很感激你们为帮助初学者开始他们的编码之旅所付出的努力。 - Maxim

2

在抽象类中调用构造函数只用于设置特定于该抽象类的属性-否则在抽象类的每个实现中设置这些属性将会很繁琐。该能力可消除样板代码。

在下面的示例中,可以看到如何基于车辆的其他属性计算汽车寿命。在每个Car子类型的实现中执行此操作是过度的。

abstract class Car {
    // Determine how many years a car will last based on other components
    int lifeTimeInYears;

    float price;

    public Car(float price) {
        // Assuming you could calculate the longevity based on price;
        if (price > 50000) {
            lifeTimeInYears = 15;
        }
        else {
            lifeTimeInYears = 10;
        }
    }

    public int getLifeTimeInYears() {
        return lifeTimeInYears;
    }
}

class SportsCar extends Car {

    public SportsCar(float price) {
        super(price);
    }
}

class CommuterCar extends Car {

    public CommuterCar(float price) {
        super(price);
    }
}

public class Test {
    public static void main(String[] args) {
        SportsCar sportsCar = new SportsCar(150000);
        sportsCar.getLifeTimeInYears(); // Value is 15

        CommuterCar commuterCar = new CommuterCar(15000);
        commuterCar.getLifeTimeInYears(); // Value is 10
    }
}

我理解这部分内容,但我一直以为调用构造函数意味着创建一个对象,而由于抽象类是不能创建对象的,所以会有点困惑... - Maxim

1

我会尝试以字节码的方式来解释,看看是否有帮助。

AbstractService.java

public abstract class AbstractService {
    protected int id = 10;
    public abstract void verify();
}

Service.java

public class Service extends AbstractService{
    public static void main(String[] args) {
        Service service = new Service();
        service.verify();
    }

    public void verify() {
        System.out.println("printing id = "+id);
    }
}

如果您查看这些类的生成字节码
public abstract class AbstractService {
    protected int id;

    public AbstractService() {
        /* L4 */
        0 aload_0;                /* this */
        1 invokespecial 1;        /* java.lang.Object() */
        /* L6 */
        4 aload_0;                /* this */
        5 bipush 10;
        7 putfield 2;             /* .id */
        10 return;
    }

    public abstract void verify();
}




public class Service extends com.sample.service.AbstractService {

    public Service() {
        /* L3 */
        0 aload_0;                /* this */
        1 invokespecial 1;        /* com.sample.service.AbstractService() */
        4 return;
    }

    public static void main(java.lang.String[] args) {
        /* L6 */
        0 new 2;
        3 dup;
        4 invokespecial 3;        /* com.sample.service.Service() */
        7 astore_1;               /* service */
        /* L7 */
        8 aload_1;                /* service */
        9 invokevirtual 4;        /* void verify() */
        /* L8 */
        12 return;
    }

    public void verify() {
        /* Skipping this as it's not needed */
    }
}

Service service = new Service();

翻译为:

Service service = new Service();


        0 new 2;
        3 dup;
        4 invokespecial 3;        /* com.sample.service.Service() */

如上所示, 首先执行新的字节码,将创建一个只有实例变量默认值(在本例中id为整数,因此默认为0)但已初始化的新对象,之后是dup,将复制该新对象引用然后调用invokespecial来调用Service(),然后再调用AbstractService(),最终调用Object()。
如果您查看这些方法中的字节码,它们不会创建对象(没有new byte code指令),它们仅仅初始化对象,如将变量id的值设置为10,这是将user-defined values正确初始化的方式。
因此,当您说new SomeClass()时,这不仅仅是调用构造函数,而是创建一个默认状态的对象,然后调用特殊方法称为构造函数(编译器生成的或用户定义的)来初始化该对象。换句话说,构造函数将对象从默认状态(id=0)带入用户定义状态(id=10),以便可以在其上调用方法。
对于初学者来说,这可能太难了,但如果您关注字节码,它就有意义了 :)

1
假设我们定义了抽象类“car”,然后编写一个子类“honda”扩展“car”。为了制造“honda”,您必须首先制造“car”。无论“car”是否抽象,在制作任何子类对象时,您都必须调用super()来首先“制造”超类对象。
请参见我在此处对类似问题的回答:当父类似乎不改变任何属性时,声明方法两次是否有目的?(请注意,这个问题是一个误称,并且实际上是在谈论构造函数)

你说你首先必须制造一辆汽车?但这是我的问题,如果类是抽象的,那么你怎么能制造一辆汽车呢? - Maxim
你的想法有些偏差。让我们用一个不同的类比来解释。为了拥有一个孩子,这个孩子必须有父母。这里的孩子是一个实际的对象(不是抽象的),而父母我们并不关心(它们是抽象的)。父母为孩子提供了很多东西,比如玩具、食物等(提供方法和变量),但我们并不关心或知道他们是谁。我们只知道他们存在,并且在那里。由于每个孩子都有父母,当我们创建孩子时,难道我们不是默认为孩子“创建”了父母吗?即使我们没有说“制造一个父母”? - Aify
即使使用同样的汽车类比,我们也可以这样说:你可以制造任何车辆,或者拥有很多车辆,但它们都不仅仅是“车辆”。你可以指着街上的任何一辆车,它不仅仅是“一辆车”。它可能是“马自达____”,或“本田____”,或“庞蒂亚克____”。仅仅“拥有一辆车”是不可能的。如果你想制造一辆车,你不能这么做,因为你必须知道你正在制造什么(汽车、飞机、船等),但如果你正在制造一辆本田,你知道它是一辆汽车。当有人问你在做什么时,你可以说“我在制造一辆汽车”,即使你正在制造一辆本田。 - Aify

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接