Thinking in java 第5章 初始化与清理 笔记+习题

本文是《Thinking in Java》第5章的笔记,涵盖了初始化、清理、方法重载、this关键字、垃圾回收、构造器、数组初始化、枚举等内容。讲解了Java中如何避免C/C++的内存泄漏问题,以及Java的垃圾回收机制。此外,还讨论了静态与非静态初始化的顺序,枚举类型的使用,以及一系列与构造器和数组相关的实践练习。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Thinking in java 第5章 初始化与清理

学习目录


5.0 序章

1. C中容易忽略初始化和清理,导致内存泄漏;C++中引入了构造器的概念;Java中也采用了构造器,并额外提供了“垃圾回收器”。

 

5.2 方法重载

1. 如果传入数据类型小于方法中声明的形式参数类型,实际数据类型就会被提升。char类型若没有恰好接受char参数的方法,就会被直接提升为int类型。

2. 如果传入数据类型大于方法中声明的形式参数类型,就必须采用强制转换,不然编译器会报错。

 

5.4 this关键字

1. 当需要返回对当前对象的引用时,可以写成return this; 同理也可写成传递参数。

2. static方法就是没有this的方法,在static方法的内部不能调用非静态方法(虽然可以在里面创建一个新对象再调用静态方法,不过没必要直接写一个非静态方法就好),反过来可以。

 

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

1. 垃圾回收只与内存有关。无论是垃圾回收还是终结,都不保证一定会发生。如果JVM并为面临内存耗尽的情形,他是不会浪费时间去执行垃圾回收以恢复内存的。

2. 垃圾回收器的工作方式:

  • 引用计数(仅用作说明):每个对象都含有一个引用计数器,当有引用连接至该对象时,引用计数加1。当引用离开作用域或被设置成null时减1。垃圾收集器会在含有全部对象的列表上遍历,当某个对象引用计数为0时,是放弃占用空间。
  • 停止-复制:暂停程序,将所有存活的对象从当前堆复制到另一个堆,没有被复制的全是垃圾。新堆没有碎片。在处理大量短命的临时对象时很有帮助。如果空间碎片太多也会切换回此方式。
  • 标记-清扫:没有复制工作。相当于对无向图进行遍历,清扫不可达的点。如果所有对象都很稳定,垃圾回收期的效率降低的话,就切换到此方式。

 

5.7 构造器初始化

1. 在类的内部,变量定义的先后顺序决定了初始化的顺序。即使变量定义散布于方法定义之间,它们仍旧会在任何方法(包括构造器)被调用之前得到初始化。

2. 初始化的顺序是先静态对象(如果它们尚未因前面的对象创建过程而被初始化),而后是“非静态”对象。

3. 一旦类中的静态方法/静态域被访问时(main也是),Java解释器必须查找类路径,以定位.class文件然后载入,此时有关静态初始化的所有动作都会执行。因此,静态初始化只在Class对象首次加载的时候进行一次。(例P95-96)

4. 去掉static的初始化为非静态实例初始化,这种语法对于支持“匿名内部类”的初始化时必须的,使得无论调用哪个显示构造器,某些操作都会发生。

 

5.8 数组初始化

1. 可变参数列表让重载过程变得复杂,如f(int... args){}与f(double... args){}同时存在时,调用f()会编译失败。可以在方法中加入一个非可变参数来解决该问题。

 

5.9 枚举类型

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

1. 由于枚举类型的实例是常量,因此按照命名惯例都用大写字母表示

2. ordinals()方法可以返回某个特定的enum常量的声明顺序;staic values()方法会按照声明顺序生成这些常量构成的数组。

3. 可以配合switch使用。

Spiciness degree;
switch(degree) {
    case NOT:
    ...
    case FLAMING:
    default:
}

 


习题:

练习1:创建一个类,它包含一个未初始化的String引用。验证该引用被Java初始化成了null。

public class E5_1 {
    String s;
}

class Main{
    public static void main(String[] args) {
        E5_1 e = new E5_1();
        System.out.println(e.s);
    }
}

/*
null
*/

练习2:创建一个类,它包含一个在定义时就被初始化了的String域,以及另一个通过构造器初始化的String域。这两种方式有何差异?

public class E5_2 {
    String s1;
    String s2 = "111";
}

public class Main {
    public static void main(String[] args) {
        E5_2 e = new E5_2();
        System.out.println("s1 = " + e.s1 + ", s2 = " + e.s2);
        e.s1 = "222";
        System.out.println("s1 = " + e.s1 + ", s2 = " + e.s2);
    }
}

/*
s1 = null, s2 = 111
s1 = 222, s2 = 111
*/

差异:s1先被初始化为null再被初始化为222,初始化了2次;而s2只被初始化了一次。

练习3:创建一个带默认构造器(即无参构造器)的类,在构造器中打印一条消息。为这个类创建一个对象。

public class E5_3 {
    int a;

    public E5_3() {
        System.out.println("一个带默认构造器的类");
    }
}

public class Main {
    public static void main(String[] args) {
        E5_3 e = new E5_3();
    }
}

/*
一个带默认构造器的类
*/

练习4:为前一个练习中的类添加一个重载构造器,令其接受一个字符串参数,并在构造器中把你自己的信息和接受的参数一起打印出来。

public class E5_3 {
    String s;

    public E5_3() {
        System.out.println("一个带默认构造器的类");
    }

    public E5_3(String s) {
        this.s = s;
        System.out.println("一个重载构造器 "+ s);
    }
}

public class Main {
    public static void main(String[] args) {
        E5_3 e = new E5_3("aaa");
    }
}

/*
一个重载构造器 aaa
*/

练习5:创建一个名为Dog的类,它具有重载的bark()方法。此方法应根据不同的基本数据类型进行重载,并根据被调用的版本,打印出不同类型的狗吠(barking)、咆哮(howling)等信息。编写main()来调用所有不同版本的方法。

public class Dog {
    void bark() {
        System.out.println("barking!");
    }

    void bark(String s) {
        System.out.println("howling! "+s);
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.bark();
        dog.bark("tnk");
    }
}

/*
barking!
howling! tnk
*/

练习6:修改前一个练习的程序,让两个重载方法各自接受两个类型的不同的参数,但二者顺序相反。验证其是否工作。

会正常工作。略。

练习7:创建一个没有构造器的类,并在main()中创建其对象,用以验证编译器是否真的自动加入了默认构造器。

意义不明。略。

练习8:编写具有两个方法的类,在第一个方法内调用第二个方法两次:第一次调用时不使用this关键字,第二次调用时使用this关键字——这里只是为了验证它是起作用的,你不应该在实践中使用这种方法。

public class E5_8 {
    void f1() {
        f2();
        this.f2(); // 不推荐
    }
    
    void f2() {
        System.out.println("this is f2");
    }
}

练习9:编写具有2个(重载)构造器的类,并在第一个构造器中通过this调用第二个构造器。

public class E5_9 {
    public E5_9() {
        this("aaa");
    }
    
    public E5_9(String s) {
        System.out.println("using " + s);
    }
}

练习10:编写具有finalize()方法的类,并在方法中打印消息。在main()中为该类创建一个对象。试解释这个程序的行为。

方法已被取消,了解即可。

public class E5_10 {
    boolean checkout=true;
    void checkin(){
        checkout=true;
    }
    protected void finalize(){
        if(checkout) {
            System.out.println("Error:checked out");
            super.finalize();\\报错java.lang.Object中的finalize()已过时
        }
    }
}

public class Main {
    public static void main(String[] args) {
        E5_10 e=new E5_10();
        e.checkin();
        System.gc();
    }
}

练习11:修改前一个练习的程序,让你的finalize()总会被调用。

同上。

练习12:编写名为Tank的类,此类的状态可以是“满的”或“空的”。其终结条件是:对象被清理时必须处于空状态。请编写finalize()以检验终结条件是否成立。在main()中测试Tank可能发生的几种使用方式。

修改一下boolean cheackout的判断即可。略。

练习13:验证前面段落中的语句。

class Cup{
    Cup(int maker){
        System.out.println("Cup("+maker+")");
    }
    void f(int maker){
        System.out.println("f("+maker+")");
    }
}
class Cups{
    static Cup cup1;
    static Cup cup2;
    static {
        cup1=new Cup(1);
        cup2=new Cup(2);
    }
    Cups(){
        System.out.println("Cups()");
    }
}
public class Main {
    public static void main(String[] args) {
        System.out.println("Inside main()");
//        Cups.cup1.f(99);
    }
    static Cups cup1 = new Cups();
    static Cups cup2 = new Cups();
}

/*
Cup(1)
Cup(2)
Cups()
Cups()
Inside main()
*/

练习14:编写一个类,拥有两个静态字符串域,其中一个在定义处初期化,另一个在静态块中初期化。现在加入一个静态方法用于打印出两个字段的值。请证明他们都会在被使用之前完成初始化动作。

public class E5_14 {
    static String a = "aaa";
    static String b;
    static{
        b = "bbb";
    }

    void print() {
        System.out.println("a = " + a);
        System.out.println("b = " + b);
    }
}

public class Main {
    public static void main(String[] args) {
        E5_14 e = new E5_14();
        e.print();
    }
}

/*
a = aaa
b = bbb
*/

练习15:编写一个含有字符串域的类,并采用实例初始化的方法初始化它。

public class E5_15 {
    String s;
    {
        s = "aaa";
        System.out.println("非静态实例初始化");
    }
}

public class Main {
    public static void main(String[] args) {
        E5_15 e = new E5_15();
        System.out.println(e.s);
    }
}

/*
非静态实例初始化
aaa
*/

练习16:创建一个String对象数据(数组?),并为每一个元素赋值一个String,用for循环来打印该数组。

public class E_16 {
    public static void main(String[] args) {
        String[] strings = new String[] {"aaa", "bbb", "ccc"};
        for(String s:strings) {
            System.out.println(s);
        }
    }
}

/*
aaa
bbb
ccc
*/

练习17:创建一个类,它有一个接受一个String参数的构造器。在构造阶段,打印该参数。创建一个该类的引用数组,但是不实际创建对象赋值给该数组。当运行程序时,请注意来自对该构造器的调用中的初始化信息是否打印出来。

public class E5_17 {
    String s;
    public E5_17 (String s) {
        this.s = s;
        System.out.println("this is " + s);
    }
}

public class Main {
    public static void main(String[] args) {
        E5_17[] es;
    }
}

/*
*/

练习18:通过创建对象赋值给引用数组,从而完成前一个练习。

public class E5_17 {
    String s;
    public E5_17 (String s) {
        this.s = s;
        System.out.println("this is " + s);
    }
}

public class Main {
    public static void main(String[] args) {
        E5_17[] es = new E5_17[3];
        es[0] = new E5_17("aaa");
        es[1] = new E5_17("bbb");
        es[2] = new E5_17("ccc");
    }
}

/*
this is aaa
this is bbb
this is ccc
*/

练习19:写一个类,他接受一个可变参数 String数组。 验证你可以向他传递一个用逗号分隔的String 列表, 或者String[]。

public class E5_19 {
    static void print(String... strings) {
        for(String s:strings) {
            System.out.print(s + " ");
        }
        System.out.println();
    }
}

public class Main {
    public static void main(String[] args) {
        String[] strings = new String[] {"aaa", "bbb", "ccc"};
        E5_19.print(strings);
        E5_19.print("ddd","eee","fff");
    }
}

/*
aaa bbb ccc 
ddd eee fff 
*/

练习20:创建一个使用可变参数列表而不是普通main()语法的main().打印所产生的args数组的所有元素,并用不同的数量行参数来测试他。

意义不明。略。

练习21:创建一个enum,它包含纸币中最小面值的6种类型。通过values()循环并打印每一个值及其ordinal()。

public class E5_21 {
    public enum Money{
        YI_YUAN, WU_YUAN, SHI_YUAN, ER_SHI_YUAN, WU_SHI_YUAN
    }

    public static void main(String[] args) {
        for(Money m:Money.values()) {
            System.out.println(m + " : " + m.ordinal());
        }
    }
}

/*
YI_YUAN : 0
WU_YUAN : 1
SHI_YUAN : 2
ER_SHI_YUAN : 3
WU_SHI_YUAN : 4
*/

练习22:在前面的例子中,为enum写一个switch语句,对于每一个case,输出该特定货币的描述。

public class E5_22 {
    public enum Money{
        YI_YUAN, WU_YUAN, SHI_YUAN, ER_SHI_YUAN, WU_SHI_YUAN
    }

    public static void main(String[] args) {
        for(Money m: Money.values()) {
            switch (m) {
                case YI_YUAN:
                    System.out.println("一元人民币");
                    break;
                case WU_YUAN:
                    System.out.println("五元人民币");
                    break;
                case SHI_YUAN:
                    System.out.println("十元人民币");
                    break;
                case ER_SHI_YUAN:
                    System.out.println("二十元人民币");
                    break;
                case WU_SHI_YUAN:
                    System.out.println("五十元人民币");
                    break;
            }
        }
    }
}

/*
一元人民币
五元人民币
十元人民币
二十元人民币
五十元人民币
*/

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值