七海的java学习笔记(九)

时间:2023-02-25 14:22:22

第九章 构造器与垃圾收集器

        堆与栈

                在Java中,程序员会在乎内存中的两种区域:对象的生存空间堆(heap)和方法调用及变量的生存空间栈(stack)。当java虚拟机启动时,它会从底层的操作系统取得一块内存,并以此区段来执行Java程序。至于有多少内存,以及你是否能够调整它都要看java虚拟机与平台的版本而定。但通常你对这些事情无法加以控制。如果程序设计得不错的话,你或许也不太需要在乎。
                    我们已经知道所有的对象都存活于可垃圾回收的堆上,但我们还没看过变量的生存空间。而变量存在于哪一个空间要看它是哪一种变量而定。这里说的“哪一种”不是指它的类型,而是实例变量或局部变量。后者这种区域变量又被称为栈变量,该名称已经说明了它所存在的区域。

                    实例变量是被声明在类而不是方法里面。它们代表每个独立对象的“字段”(每个实例都能有不同的值)。实例变量存在于所属的对象中。

public class Duck{
    int size; //定义在类中的实例变量,每个Duck对象都会有独立的size
}

                    局部变量和方法的参数都是被声明在方法中。它们是暂时的,且生命周期只限于方法被放在栈上的这段期间(也就是方法调用至执行完毕为止)。

public void foo(int x){  //方法接收的参数也是局部变量
      int i = x+3;
      boolean b = true;
      //定义在方法内部的局部变量
}

                    

                    方法会被堆在一起。当你调用一个方法时,该方法会放在调用栈的栈顶。实际被堆上栈的是堆栈块,它带有方法的状态,包括执行到哪一行程序以及所有的局部变量的值。

                    栈顶上的方法是目前正在执行的方法。方法会一直待在这里直到执行完毕为止,如果foo()方法调用bar()方法,则bar()方法会放在foo()方法的上面。

                    有关对象局部变量:要记得非primitive的变量只是保存对象的引用而已,而不是对象本身。你已经知道对象位于堆内存中。不论对象是否声明或创建,如果局部变量是个对该对象的引用,只有变量本身会放在栈上。

                    对象本身只会存在于堆上。

                    如果局部变量生存在栈上,那么实例变量呢?实例变量存在于堆内存的对象中。


        构造函数

                你可以帮类编写构造函数,但如果你没有写,编译器会偷偷帮你写一个。下面的就是编译器写出来的:
public Duck(){  //Duck是类名
  
}
                写构造函数时要注意:1. 函数名要与类名相同
                                                  2. 不需要定义返回值类型
                                                  3. 不可以写return语句
                在我们创建对象时,其实就调用了构造函数:
Duck d = new Duck(); 这里的 Duck()就是在调用Duck类的构造函数
                构造函数的一项关键特征就是它会在对象能够被赋值给引用之前就执行。这代表你可以有机会在对象被使用之前介入。比如这样:
class Duck{
      public Duck(){
         System.out.println("Quack");
      }
}
public class UseADuck{
      public static void main(String[]args){
         Duck d = new Duck();
      }
}  //创建对象时会在屏幕上打印Quack,构造函数让我们有机会可以介入new的过程
                 大部分人都是使用构造函数来初始化对象的状态,也就是说设置给对象的实例变量赋值:
public class Duck{
    int size;
    public Duck(int s){ //构造函数在这里要接收一个int型的参数
         size = s;
    }
}
public class UseADuck{
     public static void main(String[] args){
         Duck d = new Duck(43);//需要传值给Duck的构造函数
     }
}
                当你在类中自定义了构造函数后,浏览器就不会帮你写空参数的构造函数了。
                但是一般来说还是要自己写一个空参数的构造函数。
                构造方法也是可以重载的。

        构造方法与继承

                在创建新对象时,所有继承下来的构造函数都会执行。这代表每个父类都有一个构造函数(因为每个类至少都会有一个构造函数),且每个构造函数都会在子类对象创建时期执行。
                构造函数在执行的时候,第一件事就是去执行它的父类的构造函数,这会连锁反应到Object这个类为止。每个子类的构造函数默认第一行都有一条隐式语句super();
                super():惠访问父类中空参数的构造方法。而且子类中所有的构造方法默认第一行都是super();。
                当父类的构造方法有参数时,我们也可以手动输入super();语句,只要在括号里传入对应的参数即可。
                

        从某个构造函数调用重载版的另一个构造函数

                如果有某个重载版的构造函数除了不能处理不同类型的参数之外,可以处理所有的工作,那要怎么办?只要用this()语句来调用即可,调用带参数的重载版的构造方法时只要在括号内输入对应的参数即可。
                this()只能用在构造函数中,且它必须是第一行语句。这样会和super()起冲突,所以每个构造函数可以选择调用super()和this(),但不可以同时调用。

        

        对象的生命周期

                对象的生命周期完全要看引用到它的"引用"。如果引用还活着,则对象也会继续活在堆上。如果引用死了,则对象就会跟着消失。
                如果对象生命周期要看引用变量的生命周期而定,那变量到底会活多久?
                这又要看它是局部变量或实例变量而定。
                1. 局部变量只会存活在声明该变量的方法中。只要变量的堆栈块还存在于堆栈上,局部变量就算活着。也就是说,活到方法执行完毕位置
                2. 实例变量的寿命与对象相同。如果对象还活着,则实例变量也会是活的。

                只要有活着的引用,对象也就会活着。如果某个对象的引用已经不在它的范围中,但此引用还是活着的,则此对象就会继续活在堆上。如果对对象的唯一引用死了,对象就会从堆中被踢开。引用变量会跟堆栈块一起解散,因此被踢开的对象也就正式的声明出局。

                有三种方法可以释放对象的引用:

//1. 引用永久性的离开它的范围
void go(){
     Life z = new Life();
}
//2. 引用被赋值到其他对象上
Life z = new Life();
z = new Life();
//3. 直接将引用设定为null
Life z = new Life();
z = null;