变量以及方法调用的生存空间在stack里,对象的生存空间在heap里,而变量也有很多种类,其实这里说的变量都在stack里是不对的,比如实例变量,假如对一个类进行实例化,就会在heap上分配存储空间给对象,但是要分配多少空间,取决于该对象所有的实例变量,因此实例变量生存空间是在对象所在的heap空间上
局部变量:stack上,本身又被称为栈变量
实例变量:heap上,上面已经说了
方法:stack上,每当调用一个方法,该方法就会放到栈顶,将调用自己的方法压栈
这里栈顶上的方法,是目前正在执行的方法,会一直等待在这里直到执行完毕了,执行完之后,栈顶块就被释放掉,往下走
class Number { public void one() { System.out.println("one"); } public void two() { one(); } public void three() { two(); } } public class Main { public static void main(String[] args) { Number n = new Number(); n.three(); } }
这里有三个方法,three调用two(),two调用one(),整个过程也比较简单
1:首先调用thee(),此时three()就放到栈顶
2:接着执行three()发现调用了two(),那么two()就放到栈顶,把three()压在下面
3:然后执行two()发现调用了one(),那么one()放到栈顶,下面分别是two()和three()
4:one()执行输出,执行完之后释放掉栈顶块,two()成栈顶了,执行完,继续到three(),最终执行完退出
而对于构造函数来说,当创建了一个新的对象,继承下来的所有父类的构造函数也都会执行,包括抽象类,虽然抽象类无法进行new实例化操作,但作为父类,也有构造函数,并且构造函数也会在具体子类创建出实例的时候执行
也就是说,在执行构造函数的时候,首先会去执行父类们的构造函数,一直到Object这个为止
class People { public People() { System.out.println("People"); } } class Man extends People { public Man() { System.out.println("Man"); } } public class Main { public static void main(String[] args) { Man m = new Man(); } }
这里输出的结果,先执行People类后执行Man类的构造函数输出,具体流程
1:new Man()调用Man的构造函数,此时Man()放到栈顶
2:Man()调用父类People的构造函数,此时People()放到栈顶,把Man()压在下面
3:People()调用父类Object的构造函数,此时Object()放到栈顶,把People()和Man()依次压在下面
4:Object()执行完毕,栈顶块释放,接着执行People()输出,进而释放掉,最后执行Man()输出,结束
可见第一个构造函数被调用的类,也是最后一个完成构造函数执行的,每个子类的构造函数立即会调用父类的构造函数,向上一直到Object,然后原路返回,并且如果是子类里调用父类的构造函数,其实在每个构造函数里的第一条语句,都是super(),假如你没显式添加,编译器会加上这种调用,这就是为什么先调用父类构造函数的原因,也就是说,上面一段和下面一段等价
class People { public People() { super(); System.out.println("People"); } } class Man extends People { public Man() { super(); System.out.println("Man"); } } public class Main { public static void main(String[] args) { Man m = new Man(); } }
假如将super()不放在第一条语句
class Man extends People { public Man() { System.out.println("Man"); super(); } }
编译器是无法通过的
javac Main.java Main.java:11: 错误: 对super的调用必须是构造器中的第一个语句 super(); ^ 1 个错误
这里编译器默认给加的仅仅是无参数的,比如给父类构造函数加一个参数
class People { public People(String s) { System.out.println(s); } } class Man extends People { public Man() { System.out.println("Man"); } } public class Main { public static void main(String[] args) { Man m = new Man(); } }
编译就无法通过
javac Main.java Main.java:8: 错误: 无法将类 People中的构造器 People应用到给定类型; public Man() { ^ 需要: String 找到: 没有参数 原因: 实际参数列表和形式参数列表长度不同 1 个错误
这时候就可以通过super()来引用父类,并将值穿进去
class People { public People(String s) { System.out.println(s); } } class Man extends People { public Man() { super("Pepole"); System.out.println("Man"); } } public class Main { public static void main(String[] args) { Man m = new Man(); } }
最后来一个带参数父类构造函数的场景:
如果父类构造函数有参数,并且是一个抽象类无法实例化传值进去,也无法直接被继承
abstract class People { private String name; public String getName() { return name; } public People(String theName) { name = theName; } } class Man extends People { public Man(String name) { super(name); } } public class Main { public static void main(String[] args) { Man m = new Man("LiHui"); System.out.println(m.getName()); } }
对于People类,有一个私有的实例变量name,还有一个获取实例变量name值的方法getName(),虽然name是私有,但是getName()可以被子类继承,那么只要通过super(name)向父类构造函数传入数据,将name的值传进去,进而保存在私有实例变量name里,最后通过调用可继承的成员方法getName()就行了