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

一年又一年,字节跳动 Lark(飞书) 研发团队又双叒叕开始招新生啦!
【内推码】:GTPUVBA
【内推链接】:https://job.toutiao.com/s/JRupWVj
【招生对象】:20年9月后~21年8月前 毕业的同学
【报名时间】:6.16-7.16(提前批简历投递只有一个月抓住机会哦!)
【画重点】:提前批和正式秋招不矛盾!面试成功,提前锁定Offer;若有失利,额外获得一次面试机会,正式秋招开启后还可再次投递。

点击进入我的博客

初始化和清理是编程安全的两个问题。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
        });
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,718评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,683评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,207评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,755评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,862评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,050评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,136评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,882评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,330评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,651评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,789评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,477评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,135评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,864评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,099评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,598评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,697评论 2 351

推荐阅读更多精彩内容