据我所知(如果我错了,请纠正我)一个抽象的类无法实例化。你可以给它一个构造函数,但是不能在该类上调用new。如果在子类中调用super,超类构造函数将运行(从而创建该类的对象?)那么为什么你实际上可以在抽象类的子类中调用super? 我确定这与我对构造对象的误解有关...
答案 0 :(得分:6)
如果在子类中调用super,超类构造函数将运行(从而创建该类的对象??)那么为什么你可以在抽象类的子类中精确地调用super?
这部分是错误的。当您在子类构造函数中调用super
时,您只是告诉子类它首先必须从超类(抽象与否)执行初始化代码,然后它将继续执行代码到初始化正在创建的类的当前实例。这并不意味着它将在创建当前实例的过程中创建超类的实例。
答案 1 :(得分: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
}
}
答案 2 :(得分:1)
让我们说例如我们定义抽象类" car"。然后我们编写一个子类," honda"延伸" car"。为了制造一辆" honda",你必须先制作一辆"汽车"。无论是否" car"是抽象的,为了制作任何子类对象,你必须调用super()to" make"首先是超类对象。
在这里查看我对类似问题的回答:What is (is there?) a purpose behind declaring a method twice when parent class appears to not change any properties?(请注意,这个问题用词不当,实际上是在讨论构造函数)
答案 3 :(得分: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();
已翻译为
0 new 2;
3 dup;
4 invokespecial 3; /* com.sample.service.Service() */
如上所示,首先执行新的字节代码,它将创建一个新的Object,只有实例变量的默认值(在这种情况下,id是整数,所以默认为0)但是初始化,然后是dup,它将创建一个副本这个新的对象引用,然后invokespecial将调用Service(),它将依次调用AbstractService(),然后调用Object()
如果查看这些方法中的字节代码,他们不会创建对象(没有新的字节代码指令),他们只是初始化对象,比如设置变量id = 10的值,这是对用户的正确初始化 - 定义的值。
所以当你说新的SomeClass()时,它不只是调用你的构造函数,而是创建一个具有默认状态的对象,然后调用名为constructor的特殊方法(编译器生成一个或者用户定义的一个)来初始化该对象。换句话说,构造函数将对象从默认状态(id = 0)带到用户定义的状态(id = 10),以便对象可以使用,即在其上调用方法。
这在初学者级别太多了,但是如果你注意字节代码就会有意义:)