堆和栈

变量以及方法调用的生存空间在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()就行了

发表评论