《Java编程思想》笔记5.初始化与清理

news/2024/7/7 20:41:13

点击进入我的博客

初始化和清理是编程安全的两个问题。Java延续了C++中的构造器确保初始化,并引入了“垃圾回收器”管理和释放内存。

5.1 用构造方法确保初始化

  • 构造方法和类名相同。原因是尽量减少和成员名称冲突;并且调用构造方法是编译器的责任,名称相同编译器才会知道应该调用哪个方法。
  • 由于构造方法和类名相同,所以构造方法不适应于首字母小写的约定规范。
  • 构造方法有默认的无参构造方法,也可以带参数。
  • 构造方法没有返回值,请注意这跟void不同。

5.2 方法重载

  • 方法重载是指方法名相同,但是参数的个数、类型和顺序不同。
  • 由于构造方法必须和类名相同,即方法名已经确定,但想要用多种方式(参数)创建一个对象,就必须引入方法重载
  • 方法重载不仅适用于构造方法,还适用于其他方法。
  • warningfunc(int i, String str)func(String str, int i)参数顺序不同构成重载,但请尽量避免这种写法。
  • 构成重载深层次的原因:只要这两个方法编译器能区分开来,在调用的时候知道要调用的是哪一个,不会产生混淆,这两个方法就构成重载。
基本类型的重载
  • 对于byteshortintfloatdouble如果找不到对应基本类型方法,则会按照向上转化的路线找匹配的方法
  • 如果是char,如果找不到对应的基本类型方法,直接从int向上找匹配的方法。
    public static void print(char c) {
        System.out.println("char: " + c);
    }

    public static void print(byte b) {
        System.out.println("byte: " + b);
    }

    public static void print(short s) {
        System.out.println("short: " + s);
    }

    public static void print(int i) {
        System.out.println("int: " + i);
    }

    public static void print(long l) {
        System.out.println("long: " + l);
    }

    public static void print(float f) {
        System.out.println("float: " + f);
    }

    public static void print(double d) {
        System.out.println("double: " + d);
    }
返回值不同无法区分两个方法
void f() {};
boolean f() {
    return true;
};
// 只调用f()无法区分是哪个方法

5.3 默认构造器

  • 如果你的类中没有构造器,则编译器会帮你自动创建一个默认构造器。可以通过反编译.class文件来验证这一点。
  • 如果你自己定义了一个构造方法,则编译器不会帮你创建默认构造器。

5.4 this关键字

下述代码中,有两个对象a1a2,按照面向过程的函数形式,在执行func()函数的时候,怎么知道是被a1a2调用呢?为了能用面向对象的语法来编写代码,编译器做了一些幕后工作。它暗自把“所操作的对象”作为第一个参数传递给func()函数,即func(a1)。这是内部的表示形式,我们并不能这样写代码。

public class Test {
    public static void main(String[] args) {
        A a1 = new A();
        A a2 = new A();
        a1.func();
        a2.func();
    }
}

class A {
    void func() {}
}

this关键字只能在方法内部使用,表示对“调用方法的那个对象的引用。”

  • 有人喜欢将this放到每个方法调用和字段引用前,千万不要这么做!
  • 当需要返回当前对象的引用时,可以通过return this;

5.4.1 在构造器中调用构造器

  • 可以通过this(params);来调用其他的构造方法
  • 可以通过this调用一个其他的构造方法,但不能调用两个及以上
  • 通过this调用其他的构造方法必须放到该构造方法的第一行
  • 构造方法不能通过this调用自己
    public Test(int i) {
        System.out.println("Test " + i);
    }

    public Test(String str) {
        System.out.println("Test " + str);
    }

    // (1)
    public Test() {
        this(1);
        // this("imbug");
        System.out.println("Test");
    }

    public static void main(String[] args) {
        Test test = new Test();
    }

5.4.2 static方法

  • static方法中不能使用this关键字
  • static方法中不能调用非静态方法,反之则可以

5.5 清理:终结处理和垃圾回收

  • Java的垃圾回收器(GC)负责回收无用对象占据的内存资源
  • 假定你的对象(不是通过new)获得了一块“特殊”的内存区域,由于GC只知道new分配的内存,所以它不知道如何释放该对象的“特殊”内存区域。为了应付这种情况,Java允许在类中定义一个名为finalize()的方法。

5.5.1 finalize()方法

  1. 一旦GC准备释放对象的存储空间,首先调用该方法;并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。即调用该方法但时候,对象还没有被回收。
  2. finalize()方法不是C++中的析够方法
  3. 在C++中对象一定会被销毁(代码无Bug),但是在Java里的对象并非总是被垃圾回收。
  4. 垃圾回收只与内存相关,也就是说使用GC的唯一原因是为了回收程序不再使用的内存。
  5. 上述讨论了,对象可能会获得一块“特殊”的内存区域,这主要发生在JNI本地方法的情况下,本地方法是在Java中使用非Java代码的方式。非Java代码可能会调用C的malloc()来分配存储空间,而且除了free()方法否则其存储空间将得不到释放,从而造成内存泄漏。此时就可以在finalize()中调用free()方法,清理本地对象。
  6. 不建议用finalize方法完成“非内存资源”的清理工作,但也可以作为确保某些非内存资源(如Socket、文件等)释放的一个补充。
  7. System.gc()与System.runFinalization()方法增加了finalize方法执行的机会,但不保证一定会执行。
  8. 用户可以手动调用对象的finalize方法,但并不影响GC对finalize的行为,即没有卵用~
  9. finalize()执行流程

5.5.2 你必须实施清理

  • Java不允许创建局部对象(即堆栈上的对象),必须使用new创建对象。
  • 无论是“垃圾回收”还是“终结”,都不保证一定会发生。

5.5.3 终结条件

  • 如果某个对象的内存可以被安全释放了,例如对象代表了一个打开的文件,那么回收内存前必须保证文件关闭。这个在finalize()中可以检验文件的状态。
  • System.gc()用于强制进行终结动作。
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        // if(文件未安全关闭)
        System.out.println("error");
    }

    public static void main(String[] args) {
        func();
        System.gc();
    }

    public static void func() {
        Test t1 = new Test();
        Test t2 = new Test();
    }

5.5.4 GC如何工作

更详细内容请看JVM工作原理!!!

  • GC会整理堆内存空间,因此导致new新建对象时的内存分配速度
引用计数
  • 每个对象都含有一个计数器,当引用连接至对象时+1,引用离开作用域或被置为null时-1。GC遍历全部对象,发现计数器为0的时候就会释放其内存。
  • 优点:简单
  • 缺点:慢、循环引用问题、对象应该被回收但引用计数不为零
  • 引用计数只是为了说明GC的工作方式,但实际上似乎没有任何Java虚拟机实现过。
根搜索算法
  • 原理:每个“活”的对象,一定能追溯到其存活在堆栈或静态存储区之中的引用。
  • 方法:从堆栈和静态存储区开始,遍历所有引用;然后追踪它所引用的对象,然后是这些对象包含的所有对象,反复进行直至“根源于堆栈和静态存储区的引用”所形成的网络被全部访问完为止
停止-复制算法
  • 先暂停程序的运行,然后将全部活的对象从当前堆复制到另一个堆,没有复制的都是垃圾;新堆里的对象在内存中时连续的
  • 不属于后台回收模式,因为要暂停程序的运行
  • 把对象从一个堆复制到另一个堆时,所有指向它们的引用都必须要修正。
  • 效率低的原因(1):需要两个分离的堆,因此需要两倍的内存空间
  • 效率低的原因(2):程序稳定后垃圾很少,即需要存活的对象远大于垃圾数量,此时复制到另一个堆非常浪费。
标记-清扫算法
  • 用根搜索算法找到所有存活的对象并标记(此过程不回收),当全部标记工作完成的时候,清理所有没有标记的对象
  • 缺点(1):导致内存空间不连续
  • 缺点(2):也会暂停程序
分代算法
  • JVM中,内存以较大的“块”为单位;如果对象比较大,它会占据单独的块;有了块之后,GC就可以在回收的时候往废弃的块中拷贝对象了
  • 每个块用相应的代数(generation count)来记录是否存活;如果块在某处被引用,其代数会增加;GC会对上次回收动作之后新分配的块进行整理
  • GC会定期进行完整的清理动作,大型对象不会被复制但是其代数会增加;小型对象的那些块则被复制并整理
自适应、分代的、停止复制、标记清扫方式

JVM会进行监视,如果所有对象都很稳定,垃圾回收器的效率降低的话,就切换到标记-清扫模式;同样,JVM会跟踪标记-清扫的效果,要是堆空间出现很多碎片,就会切换回停止-复制模式。

其他附加技术

即使编译器(Just-In-Time JIT):可以把程序全部或部分翻译成机器码来提高运行速度。当需要装载某个类时,编译器会先找到其.class文件,然后将该类的字节码装入内存。此时,有两种方案可供选择:

  • 让即时编译器编译所有代码:这种操作散落在整个程序的声明周期内,累加起来耗时更长;会增加可执行代码的长度,造成页面调度
  • 惰性评估:意思是即时编译器只在必要的时候才编译代码,这样,从不会被执行的代码也许就压根不会被JIT所编译。

5.6 成员初始化

Java尽量保证:所有变量使用前一定会初始化
局部变量:不会自动初始化,而是编译错误
类成员变量:类的每个基本类型数据成员都保证会有初始值;引用类型为null

指定初始化
  1. 定义类成员变量的时候给它赋值——(1)
  2. 通过调用某个方法来提供初值——(2)
  3. 注意:(2)、(3)不能颠倒顺序,因为存在向前引用。
  4. 缺点:这种方式所有成员有相同的属性
public class Test {
    // (1)
    int a = 10;
    // (2)
    int i = f();
    // (3)
    int j = g(i);
    int g(int n) {
        return n;
    }
    int f() {
        return 1;
    }

    public static void main(String[] args) {
        Test t = new Test();
    }
}

5.7 构造器初始化

无法阻止自动初始化的进行,它发生在构造器被调用之前!

5.7.1 初始化顺序

遍历定义的先后顺序决定了初始化的顺序。

5.7.2 静态数据的初始化

  • 静态数据跟非静态数据的默认初值是一致的。
  • 先初始化静态对象,然后初始化非静态对象。
  • 静态初始化只有在必要的时候执行,如创建第一个该类对象或调用静态方法的时候执行。
对象创建过程
  1. 在调用该类的静态方法或者首次new对象(构造器其实也是静态方法)的时候,Java解释器查找类路径定位到该类的.class文件。
  2. 载入该.class文件,静态数据进行初始化,执行静态代码块。
  3. new对象创建对象的时候,首先在堆内存中为此对象分配足够的内存空间。
  4. 把此存储空间清零,即所有非静态基本数据类型置为0,对象类型置为null
  5. 执行非静态数据初始化动作。
  6. 执行构造器。

5.7.3 显式的静态初始化

  • 即静态代码块。
  • 在调用该类的静态方法或者首次new对象的时候执行,即和静态数据初始化相同的条件,但是发生在静态数据初始化之后

5.7.4 非静态实例初始化

  • Java中也有被称为实例初始化的语法,用来初始化每一个对象的非静态变量。
  • 实例初始代码块和成员变量的初始化顺序是按照遍历的先后顺序执行的,但两者执行都在构造方法之前。即如果(1)、(2)位置改变,输出会变成213。
  • 这种语法对于支持“匿名内部类”的初始化是必须的
    // (1)
    {
        System.out.println(1);
    }
    // (2)    
    int i = func();
    int func() {
        System.out.println(2);
        return 2;
    }
    // (3)
    Test() {
        System.out.println(3);
    }

    public static void main(String[] args) {
        new Test(); // output 123
    }

5.8 数组初始化

  • int[] arrint arr[]这两种写法都可以,但更推荐前者。
  • 为了给数组创建相应的内存空间,必须初始化数组的大小;或者初始化的时候直接初始化数组的值(int[] arr = {1, 2, 3}),此时存储空间的分配由编译器负责。
  • 所有数组都有一个固定成员length获知成员数量,但不可以修改这个值。
  • 数组坐标从0开始。
  • 数组中的元素会自动初始化为空值。

5.8.1 可变参数列表

void func(String... args) {}

  • 可变参数列表可以接受不传任何参数,即func()是可行的。
  • 可变列表与自动包装机制可以和谐相处
数组的class
  • 基本数据类型:class、空格、多个(值为数组维数)[、对应数据类型的标识
  • 对象类型:class、空格、多个(值为数组维数)[、大写L、对应数据类型的全路径、;
        System.out.println(new int[0].getClass()); // class [I
        System.out.println(new Integer[0].getClass()); // class [Ljava.lang.Integer;
        System.out.println(new long[0].getClass()); // class [J
        System.out.println(new double[0].getClass()); // class [D
        System.out.println(new int[0][0].getClass()); // class [[I
        System.out.println(new int[0][0][0].getClass()); // class [[[I
        System.out.println(new String[0].getClass()); // class [Ljava.lang.String;
        System.out.println(new String[0][0].getClass()); // class [[Ljava.lang.String;
可变类型引起的重载问题

此段代码编译失败,因为编译器发现有多个方法可以调用。

    public static void main(String[] args) {
        func(1, 'a');
        func('a', 'b');
    }

    static void func(int i, Character... args) {
        System.out.println("first");
    }

    static void func(Character... args) {
        System.out.println("second");
    }

5.9 枚举类型

  • 枚举常量命名规范:全部大写字母用下划线分割
  • 枚举会自动创建toString()方法
  • 会自动创建ordinal()方法,用来表示枚举常量的声明顺序
  • 枚举可以在switch中使用
public class Test {
    public static void main(String[] args) {
        Color green = Color.GREEN;
        Color red = Color.RED;
        System.out.println(green + "   " + green.ordinal());
        System.out.println(red + "   " + red.ordinal());
    }
}

enum Color {
    RED,
    GREEN;
}
反编译后的Color类
  • 在代码中Enum禁止继承
// final class 禁止继承
final class Color extends Enum
{

    public static Color[] values()
    {
        return (Color[])$VALUES.clone();
    }

    public static Color valueOf(String name)
    {
        return (Color)Enum.valueOf(s2/Color, name);
    }
    // 私有构造方法,所以无法用new创建对象
    private Color(String s, int i)
    {
        super(s, i);
    }

    public static final Color RED;
    public static final Color GREEN;
    private static final Color $VALUES[];

    static 
    {
        RED = new Color("RED", 0);
        GREEN = new Color("GREEN", 1);
        $VALUES = (new Color[] {
            RED, GREEN
        });
    }
}

http://www.niftyadmin.cn/n/3366667.html

相关文章

c语言文件I/O 文件读取和写入

C语言文件相关函数主要有以下几种: FILE* fopen(char* filepath, char* mode); 打开文件函数,传入文件地址和打开模式文件打开模式包括"r" 读取,"w" 写入模式,将现有文件清空。如果不存在则创建文件,"a" 附加…

python 多继承

# 为了在py2.x 和py3.x中都能运行,如果没有父类,建议统一继承自object class Father(object):def cook(self):print(Father cook)class Mother(object):def do_housework(self):print(Mother do housework)# 多继承可以让子类的对象,同时具有…

python小练习1.1

#!/usr/bin/env python3 告诉Linux或Mac这是一个可执行文件 # -*- coding: utf-8 -*- 告诉解释器,该文档编码是utf-8print("hello world!") print("你好 世界") print(\n)"""字符与整数编码的转换""" print…

linux是有界函数吗,Linux 中可重入函数与不可重入函数详解

Linux 中可重入函数与不可重入函数详解可重入函数和不可重入函数说起来有点拗口,其实写过多进程(线程)程序的人肯定很快就能明白这两种函数是个神马东西。下面是我对这两个函数的理解:可重入函数可以理解为是能被中断的函数,并且它被中断返回…

Agc019_D Shift and Flip

传送门 题目大意 给定两个长为$n$的$01$串$A,B$,每次操作有三种 将$A$整体向左移动,并将$A_1$放在原来$A_n$的位置上。 将$A$整体向有移动,并将$A_n$放在原来$A_1$的位置上。 选择一个位置$i$使得$B_i1$,将$A_i$变为$1-A_i$。 求将…

Python学习之类

前言:之前写的博客,都是借鉴别的博文或者是环境的配置,没有自己的东西,掌握的很少。以后的博文,我都会尽力加上一些自己的东西。现在开始进入我的python世界吧!为什么要有类:在面向对象编程中&a…

ROSS仿真系统简单教程

安装前的准备 安装git gcc g安装mpich mpich安装教程安装cmake #ROSS 使用cmake编译 下载并解压得文件夹ROSS-master https://github.com/carothersc/ROSS在编译之前,需要安装几个包 sudo apt-get install doxygen sudo apt-get install graphviz否则会出现以…

Linux端的qbittorrent目录,qBittorrent v4.3.0.10便携增强版-BT/磁力下载软件

qBittorrent v4.3.0.10便携增强版-BT/磁力下载软件书法字体2020.11.02qBittorrentqBittorrent是一款免费开源的BitTorrent客户端、磁力链接、BT种子下载利器。qBittorrent不但有windows版本,还有Mac和Linux版本,在全球具有大量的用户,是一款深…