Java 之路 (五) -- 初始化和清理(构造器与初始化、方法重载、this、垃圾回收器、枚举类型)

学习内容:

  • 构造器
  • 方法重载
  • this 关键字
  • 垃圾回收器的清理
  • 初始化问题
  • 枚举类型

这一章内容有一点点多,需要注意的地方也很多。下面就开始我的表演了。


1. 构造器

(1) 概念:

  • 一个创建对象时被自动调用的特殊方法

(2) 作用:

  • 通过构造器,创建对象,并确保对象得到初始化。

(3) 命名:

  • 构造器的名称必须与类名相同。

(4) 特殊:

  • 构造器是一种特殊类型的方法,它没有返回值。但是!它与返回值为空(void)不同。
    • 对于空返回值,方法本身不会自动返回什么,但是可以选择让它返回别的东西
    • 对于构造器,不会返回任何东西。new 表达式返回了对新建对象的引用,但是构造器本身没有返回任何值
  • 不接受任何参数的构造器称为 默认构造器 / 无参构造器
  • 如果类中没有构造器,那么编译器会自动创建 默认构造器;反之,如果已经定义了一个构造器(无论是否有参数),编译器都不会再自动创建 默认构造器。

2. 方法重载

(1) 原因

  • 每个方法要有独一无二的标识符
  • 构造器强制重载方法名:为了让方法名相同而形式参数不同的构造器同时存在。

(2) 重载规则:

  • 具有相同的的方法名
  • 必须有一个独一无二的参数类型列表(包括参数类型,以及参数类型对应的顺序)

(3) 需要注意,涉及基本类型的重载

  • 常数值会被当作 int 值处理
  • 如果传入实参的数据类型 小于 方法中声明的形参的数据类型,那么会将 实参的数据类型提升
    • 特殊的,对于 char 而言,如果没有恰好接收 char 参顺的方法,那么会把 char 提升至 int
  • 如果传入实参的数据类型 大于 方法中声明的形参的数据类型,那么会将 实参的数据类型进行窄化转换

3. this 关键字

(1) 作用

  • 通过 this 关键字,可以在方法的内部获得当前对象的引用。
  • this 只能在方法内部使用,表示对 “调用方法的那个对象” 的引用

(2) 用途 1 - 需要明确指出当前对象的引用

  1. 比如需要返回这个引用

    public class Leaf{
        int i=0;
        Leaf increment(){
            i++;
            return this;
        }
        void print(){
            System.out.println("i = " + i);
        }
        public static void main(String[] args){
            Leaf x = new Leaf();
            x.increment().increment().print();
        } 
    }
    
    //结果为 i = 3
    
    //分析
    //因为 increment()方法中返回了 对象的引用,所以才可以连缀多个 increment() 方法。
    
  2. 比如将当前对象传递给其他方法

    class person{
        public void eat(Apple apple){
            Apple peeled = apple.getPeeled();
            Ssytem.out.println("Yummy");
        }
    }
        
    class Peeler{
        static Apple peel(Apple apple){
            // ... remove peel
            return apple; // Peeled
        }
    }
    class Apple{
        Apple getPeeled(){
            return Peeler.peel(this);
        }
    }
    public class PassingThis{
        public static void main(String[] args){
            new Person().eat(new Apple());
        }
    }
    
    //输出为 Yummy
    
    //分析
    //Apple 需要调用 Peeler.peel() 方法,为了将自身传递给这个外部方法, Apple 必须使用 this 关键字
    
  3. 比如初始化成员变量时,避免参数重名造成混淆

    public class Person{
        String name;
        public Person(String name){
            this.name = name;
        }
    }
    
    //this.name 指的是 Person 类的 name 这个成员变量
    //name 指的是 接收的 String 参数 name
    

(3) 用途 2 - 在构造器中调用构造器

  • 通过 this,可以在一个构造器中调用另一个构造器,避免重复代码

  • 一般来说,单独的 this 关键字指的是 “当前对象”,表示引用;如果为 this 添加函数列表,这就产生了对符合此参数列表的某个构造器的明确调用。

    public class Person{
        String name;
        int age;
        public Person(String name){
            this.name = name;
        }
        public Person(String name,int age){
            this(name);
            this.age = age;
            System.out.println("name : " + name + "; age :" + age);
        }
    }
    
    //如果此时调用 
    Person person = new Person("whadlive",21);
    //那么输出的结果是 name : whdalive; age : 21
    
    //原因
    //首先 new Person("whdalive,21) 调用了 Person(String name,int age) 这个构造器
    //然后内部又通过 this(name) 调用了 Person(String name)。
    

(4) 关于 static 的问题;

  • static 方法就是没有 this 的方法 -- 因为 static 属于 类,而非对象,自然不存在引用,即没有 this
  • static 方法内部不能调用非静态方法;反过来是可以的。

4. 清理:终结处理和垃圾回收

写在最前面,很重要:

  1. 对象可能不被垃圾回收
  2. 垃圾回收并不等于"析构"
  3. 垃圾回收只与内存有关

(1) Java 中的垃圾回收器负责回收无用对象占据的内存资源

  • 特殊情况:

    假定对象(并非使用 new) 获得了一块特殊的内存区域(比如在 Java 中使用 C 并且通过 malloc 分配空间),而 垃圾回收器只能释放由 new 分配的内存,所以此时这块特殊的内存区域无法释放。

    应对方法:Java 中定义了 finalize() 方法

    • 当垃圾回收器准备好释放对象占用的存储空间,首先会调用 finalize() 方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。也就是说,我们可以通过 finalize() 方法做一些重要的清理工作。(比如在 finalize() 方法中去调用 C 语言的 free() )
  • 坑点

    • 垃圾回收(垃圾回收有关的任何行为) 不能保证一定会发生

      我们无法控制垃圾回收的时机,前面第 3 点提到了,垃圾回收只与内存有关,如果 jvm 并未面临内存耗尽,它是不会浪费时间执行垃圾回收以恢复内存的。因此我们不能将 finalize() 作为通用的清理方法,我们需要创建其他的一些方法去进行清理。

    • 关于 System.gc()

      首要记住一点:System.gc() 不能保证执行垃圾回收,原因还是由于 垃圾回收只和内存有关。

      这个方法的作用只是提醒 JVM:开发者希望进行一次垃圾回收,但是否执行垃圾回收全看 虚拟机的脸色。

(3)终结条件

  • 对象处于某种状态,使它使用的内存可以被安全的释放

(4) 垃圾回收器如何工作?(需要好好消化)

首先提个问题:在堆上分配内存代价很高,但是由于垃圾回收器的存在,在java中,在堆中分配内存的速度甚至可以与其他语言在栈上的速度向媲美. 为什么?

因为java的垃圾回收器一方面会释放空间,一方面会进行内存碎片整理. 所以java创建对象的时候,在堆上分配内存只需要将堆指针移动一下,就像在栈上那样。

  • 垃圾回收机制 - 引用计数法(并非 Java 使用)

    每个对象都有一个引用计数器,如果有一个引用变量连接到该对象时,则该对象的引用计数器加 1;当引用离开作用域或者被置为 null 的时候,引用计时器减 1 。如果引用计数器为 0,则判定该对象失活。(经常会被立即清理)。但是如果出现循环引用的时候,单纯靠引用计数器就不行了.。

  • Java 采用的垃圾回收机制的思想:

    所有活的对象不管是被引用了多少层,一定可以追溯到存活在堆栈或者静态存储区之中的引用。对于发现的每个引用,追踪它引用的对象,寻找此对象包含的所有引用,反复进行,直到 ”根源于堆栈和静态存储区的引用“所形成的网络全部被访问为止。这样就找到了所有”活“的对象。

  • Java 采用的 自适应 的垃圾回收技术。

    在上面思想的基础下,关于如何处理找到的存活对象,取决于不同的 jvm 实现。

    有一种做法为 停止-复制

    • 简单来说就是 先暂停程序,但后将所有存活的对象复制到另外一个堆中,没有被复制的全是垃圾。当对象被复制到新的堆中时,紧凑排列。 当对象从一个堆被复制到另外一个堆之后,指向它的引用就必须被修正,静态存储区和栈上的引用可以直接被修正.,但可能还有其他指向这些对象的引用,会在之后的遍历中被找到并修正。
    • 这种方式效率低,存在两个问题:
      • 开销变大,增加了一个堆,在两个分离的堆之间来回操作
      • 复制的问题,程序稳定之后,只有少量垃圾,全部将内存复制一遍很浪费。
    • 解决方法:
      • 针对 开销大的问题:按需从堆中分配几块较大的内存,复制动作发生在这些大块内存之间 。
      • 针对 复制的问题:jvm 进行检查,没有新垃圾产生的话,转换到另一种工作模式 标记-清扫,这也是为什么说 java 是 自适应 的垃圾回收。

    关于 标记-清扫

    • 思路:同样是从堆栈和静态存储区出发,遍历所有引用,进而找出所有存活的对象。每当找到一个存活对象,就给它一个比奥及,这个过程中不会回收任何对象。当全部标记工作完成的时候,才开始清理动作。清理过程中,没有标记的对象被释放,并不进行复制。这样,剩下的堆空间是不连续的,如果需要连续空间,则需要重新整理剩下对象。
    • 同样的,也需要在程序暂停的时候才能进行。
  • 进一步解释 自适应

    • 前置知识:内存分配以较大的 块 为单位,如果对象较大就会占用单独的块。

    • 细节:停止-复制 严格来说要先把所有存活对象从旧堆复制到新堆,然后才能释放旧对象,这将导致大量内存复制行为。 在分配 块 之后,垃圾回收器可以往废弃的 块 中拷贝对象,每个 块 有相应的 代数generation count 来记录它是否存活。通常如果块在某处被引用,代数 会增加;垃圾回收器将对上次回收动作之后的新分配的 块 进行整理。

      同时,垃圾回收器会定期进行完整的整理动作--大型对象不会被复制(只是增加 代数),内含小型对象的那些 块 则被复制并整理。

      个人理解,这种做法就是避免复制大块内存,只复制一些小的对象。

      Java 虚拟机会进行监视,如果所有对象都很稳定,垃圾回收器效率降低,则切换到 标记-清扫 模式。同样如果 标记-清扫 模式的效率降低的话,就切换回 停止-复制 模式。


5. 初始化

(1) 类的成员变量 & 局部变量:

  • 对于类的成员变量:
    • 如果是基本数据类型:未初始化,则会默认设置初值(具体的值见 Java 之路 (二) -- 一切都是对象
    • 如果是对象引用:未初始化,则会默认设置为 null
  • 局部变量未初始化就使用,会报错。

(2) 初始化的顺序 (重点)

  1. 此处直接引入 对象的创建过程,加入有个名为 Dog 的类:
    1. 当首次创建 Dog 的对象时,或者 Dog 类的静态方法/静态域首次被访问时,Java 解释器查找类路径,定位 Dog.class 文件
    2. 然后载入 Dog.class(这会创建一个 Class 对象),执行有关静态初始化的所有动作。因此,静态初始化只在 Class 对象首次加载的时候进行一次
    3. 当用 new Dog() 创建对象的时候,首先在堆上为 Dog 对象分配存储空间
    4. 这块存储空间会被清零,也就自动的将 Dog 对象的所有成员变量设置成了默认值。
    5. 执行所有出现于成员变量定义处的初始化动作
    6. 执行构造器。(涉及到 第7章继承时 比较麻烦,之后会详细分析)
  2. 补充:
    1. 非静态成员变量的定义顺序决定了初始化的顺序。
    2. static 不会改变成员变量未初始化的默认值

(3) 关于数组的初始化

  • 关于数组

    //对于基本数据类型:
    //
    //此时只定义了一个数组,同时拥有的只是对数组的引用
    int[] a1;
    int a1[];
    
    
    //两种初始化形式
    //1.先创建,后分别对数组元素初始化
    
    int[] a1 = new int[space];//此时定义的同时,在数据里创建了 固定个数的元素,一旦个数固定,不能修改,此时 数组中的元素全部初始化为 默认值(由类型决定,此处为 int 的默认值 0)
    a1[0] = 1;a1[2]=2;...
        
    //2.也可以通过如下方式,创建的同时进行初始化
    int[] a1 = {1,2,3,4,5};
    
    
    //对于非基本类型的数组
    //假定有一个 Person 类
    
    //两种形式
    //1.先创建,后分别对数组元素初始化
    Person[] people = new People[space];//此时创建的是一个引用数组,该数组中的元素都是 Person 类型的空引用。
    //需要对 元素进行初始化之后才可以使用,否则会发生异常
    people[0] = new People();
    
    //2.创建同时初始化
    People[] people = {new People(),new People()}
    
    
  • 需要强调一个知识点:可变参数列表

    • 应用于参数个数或类型未知的场合。

      public class Main {
          public void printf(String... args) {
              for (String s : args) {
                  System.out.println(s);
              }
          }   
      }
      
    • 语法: "类型" + "..." + "空格" + "参数名称"

    • 指定参数时,实际上编译器会帮我们填充数组,这样我们获取的仍旧是一个数组。


6. 枚举

本章只涉及一些 枚举 的概念,具体在 Java 中的特性在原书 第 19 章,留待日后整理。

(1) 枚举,即 enum,在 Java SE5 中加入。

(2) enum 可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用。这时一种非常有用的功能。

(3) enum 是一个类,我们只需要把他用作一种创建数据类型的方式,然后直接将所得到的类型拿来使用即可。

(4) 简单示例:

public enum Spiciness {
    NOT, MILD, MEDIUM, HOT, FLAMING
}

//通过以下调用,即可获得 MEDIUM 这个值。
Spiciness sp = Spiciness.MEDIUM;

(5) 问题

  • 在 Effective java 中,认为 枚举 代替常量是一个非常安全的方法。
  • 但是学 Android 的过程中,发现 Google 官方不建议使用 枚举。
    • 原因是因为 占内存
    • 因为 反编译之后,会发现 枚举对象的变量 全部会以 static final 形式存在。(由网上的分析文章得来,并未亲自实践过)

总结

这一章算是真正接触到 Java 这门语言了(也许吧),虽然都很基础,但也是属于必须掌握的知识。

另外强调关于 垃圾回收的部分,这一章只讲了理论性的东西,然而现在回头看,只了解这些是不够的。毕竟出门动辄都是从源码层问 垃圾回收是怎么实现得,hhh,累觉不爱,所以还是要再深入了解。

不多BB了,期待下一章吧。

共勉。

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

推荐阅读更多精彩内容