[读书笔记]Java编程思想第5章之初始化与清理

Posted Spring-_-Bear

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[读书笔记]Java编程思想第5章之初始化与清理相关的知识,希望对你有一定的参考价值。

  1. Java方法如果实际传入的参数类型小于方法中声明的形式参数类型,实际数据类型就会被提升。char型有所不同,如果无法找到恰好接受char参数的方法,就会把char直接提升为int型。方法接受较小的基本类型作为参数时,如果传入的参数较大,就得通过强制类型转换来执行窄化操作。如果不这样做,编译器就会报错。
  2. 没有默认构造器的话,就没有方法可以调用,就无法创建对象。但是,如果已经定义了一个构造器(无论是否有参数),编译器就不会帮你自动创建默认构造器。
  3. this关键字只能在方法内部使用,表示对“调用方法的那个对象”的引用。只有当需要明确指出当前对象的引用时,才需要使用this关键字。例如,当需要返回当前对象的引用时,就常常在return语句里这样写:
package thinkinjava.charpenter5;

/**
 * @author Spring-_-Bear
 * @version 2021/9/20 11:38
 */
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().increment().print();
    }
}

由于increment()通过this关键字返回了对当前对象的引用,所以很容易在一条语句里对同一对象执行多次操作。

  1. this关键字对于将当前对象传递给其它方法也很有用:
package thinkinjava.charpenter5;

/**
 * @author Spring-_-Bear
 * @version 2021/9/20 11:46
 */

class Person{
    public void eat(Apple apple) {
        Apple peeled = apple.getPeeled();
        System.out.println("Yummy");
    }
}

class Peeler{
    static Apple peel(Apple apple) {
        return apple;
    }
}

class Apple {
    Apple getPeeled(){
        return Peeler.peel(this);
    }
}
public class PassingThis {
    public static void main(String[] args) {
        new Person().eat(new Apple());
    }
}

Apple需要调用Peeler.peel()方法,它是一个外部的工具方法,将执行由于某种原因而必须放在Apple外部的操作(也许是因为该外部方法要应用于许多不同的类,而你却不想重复这些代码)。为了将自身传递给外部方法,Apple必须使用this关键字。

  1. 可以用this在一个构造器中调用另一个构造器。通常写this的时候,都是指“这个对象”或者“当前对象”,而且它本身表示对当前对象的引用。尽管可以用this调用一个构造器,但却不能调用两个。此外,必须将构造器调用置于最开始处,否则编译器会报错。除构造器外,编译器禁止在其他任何方法中调用构造器。
  2. static方法就是没有this的方法。在静态方法内不能调用非静态方法,反过来却可以。
  3. 假定你的对象(并非是使用new)获得了一块“特殊”的内存区域,由于垃圾回收器只知道释放那些经由new分配的内存,所以它并不知道该如何释放该对象的这块“特殊”内存。为了应对这种情况,Java允许在类中定义一个名为finalize()的方法。它的工作原理“假定”是这样的:一旦垃圾回收器准备好释放对象占用的存储的空间,将首先调用其finalize()方法,并且在下一次回收垃圾动作发生时,才会真正回收对象所占的内存。所以要是你打算使用finalize(),就能在垃圾回收时刻做一些重要的清理工作。
  4. Java里的对象并非总是被垃圾回收,C++的对象一定会被销毁(析构),换句话说:1.Java的对象可能不被垃圾回收;2.垃圾回收并不等于“析构”;3.垃圾回收只与内存有关。只要程序没有濒临存储空间用完的那一刻,对象占用的空间就总也得不到释放。如果程序执行退出,并且垃圾回收器一直都没有释放你创建的任何对象的存储空间,则随着程序的退出,那些资源也会全部交换给操作系统。这个策略是恰当的,因为垃圾回收本身也有开销,要是不使用它,那就不用支付这部分开销了。
  5. 使用垃圾回收器的唯一原因是为了回收程序不再使用的内存。所以对于与垃圾回收有关的任何行为来说(尤其是finalize()方法),它们也必须同内存机器回收有关。但这是否意味着要是对象中含有其它对象,finalize()就应该明确释放那些对象呢?不,无论对象是如何创建的,垃圾回收器都会负责释放对象占据的所有内存。这就将对finalize()的需求限制到一种特殊情况,即通过某种创建对象方式以外的方式为对象分配了存储空间。
  6. “本地方法”:本地方法是一种在Java中调用非Java代码的方式。本地方法目前只支持C和C++,但它们可以调用其它语言编写的代码,所以实际上可以调用任何代码。在非Java代码中,也许会调用C的malloc()函数系列来分配存储空间,而且除非调用了free()函数,否则存储空间将得不到释放,从而造成内存泄漏。当然,free()函数是C和C++中的函数,所以需要在finalize()中用本地方法调用它。
  7. Java不允许创建局部对象,必须使用new创建对象。如果Java虚拟机并未面临内存耗尽的情形,它是不会浪费时间去执行垃圾回收以恢复内存的。
  8. finalize()还有一个有趣的用法,它并不依赖于每次都要对finalize()进行调用,这就是对象终结条件的验证。如果对某个对象不再感兴趣,也就是它可以被清理了,这个对象应该处于某种状态,使它占用的内存可以被安全地释放。例如,要是对象代表了一个打开的文件,在对象被回收前程序员应该关闭这个文件。只要对象中存在没有被适当清理的部分,程序就存在很隐晦的缺陷。finalize()可以用来方法这种情况------尽管它并不是总是被调用。如果某次finalize()的动作使得缺陷被发现,那么就可据此找出问题所在。
  9. 以下例子示范了finalize()可能的使用方式:
package thinkinjava.charpenter5;

/**
 * @author Spring-_-Bear
 * @version 2021/9/20 15:51
 */
class Book {
    boolean checkedOut = false;

    Book(boolean checkOut) {
        checkedOut = checkOut;
    }

    void checkIn(){
        checkedOut = false;
    }

    protected void finalize(){
        if (checkedOut) {
            System.out.println("Error: checked out");
        }
        // super.finalize();
    }
}

public class TerminationCondition{
    public static void main(String[] args) {
        Book novel = new Book(true);

        novel.checkIn();

        new Book(true);

        System.gc();
    }
}

本例的终结条件是:所有的Book对象在被当作垃圾回收前都应该被签入(check in)。但在main()方法中,由于程序员的错误,有一本书未被签入。要是没有finalize()来验证终结条件,将很难发现这种缺陷。注意,System.gc()用于强制进行终结动作。即使不这么做,通过反复的程序执行(假设程序将分配大量的存储空间为导致垃圾回收动作的执行),最终也能找出错误的Book对象。你应该总是假设基类版本的finalize()也要做某些重要的事情,因此要用super()来调用它,就像在Book.finalize()中看到的那样,在本例中,它被注释掉了,因为它需要进行异常处理。

  1. 任何“活”的对象,一定能最终追溯到其存活在堆栈或静态存储区之中的引用。Java虚拟机采用一种自适应的垃圾回收技术:1. 停止------复制; 2. 标记------清扫。别名:“自适应的、分代的、停止-复制,标记-清扫式垃圾回收器”。
  2. 在类的内部,变量定义的先后顺序决定了初始化的顺序。即使变量定义散步于方法定义之间,它们仍然会在任何方法(包括构造器)被调用之前得到初始化。
package thinkinjava.charpenter5;

/**
 * @author Spring-_-Bear
 * @version 2021/9/20 16:18
 */
class Window{
    Window(int marker){
        System.out.println("Window(" + marker + ")");
    }
}
public class House {
    Window w1 = new Window(1);

    House(){
        System.out.println("House()");
        w3 = new Window(33);
    }

    Window w2 = new Window(2);

    void f() {
        System.out.println("f()");
    }

    Window w3 = new Window(3);

    public static void main(String[] args) {
        House h = new House();
        h.f();
    }
}
  1. 无论创建多少个对象,静态数据都只占用一份存储区域。static关键字不能应用于局部变量,因此它只能作用于域。
package thinkinjava.charpenter5;

/**
 * @author Spring-_-Bear
 * @version 2021/9/21 7:53
 */

class Bowl {
    Bowl(int marker) {
        System.out.println("Bowl(" + marker + ")");
    }

    void f1(int marker) {
        System.out.println("f1(" + marker + ")");
    }
}

class Table {
    static Bowl bowl1 = new Bowl(1);

    Table() {
        System.out.println("Table()");

        bowl2.f1(1);
    }

    void f2(int marker) {
        System.out.println("f2(" + marker + ")");
    }

    static Bowl bowl2 = new Bowl(2);
}

class Cupboard {
    Bowl bowl3 = new Bowl(3);

    static Bowl bowl4 = new Bowl(4);

    Cupboard() {
        System.out.println("Cupboard()"); 
        bowl4.f1(2);
    }

    void f3(int marker) {
        System.out.println("f3(" + marker + ")");
    }

    static Bowl bowl5 = new Bowl(5);
}

public class StaticInitialization {
    public static void main(String[] args) {
        System.out.println("Creating new Cupboard() in main");
        new Cupboard();
        System.out.println("Creating new Cupboard() in main");
        new Cupboard();

        table.f2(1);
        cupboard.f3(1);
    }

    static Table table = new Table();
    static Cupboard cupboard = new Cupboard();
}

静态初始化只有在必要时刻才会进行,只有在该类的第一个对象被创建或者第一次访问该类的静态数据时,静态变量、对象引用才会被初始化,此后,静态对象不会再被初始化。初始化的顺序先是静态对象,然后才是“非静态对象”。构造器实际上给也是静态方法。

  1. Java允许将多个静态初始化动作组织成一个特殊的“静态子句”(有时也称静态块)
package thinkinjava.charpenter5;

/**
 * @author Spring-_-Bear
 * @version 2021/9/21 18:19
 */
public class Spoon {
    static int i;

    static {
        i = 47;
    }
}
  1. Java数组会自动进行初始化,默认值为Java意义上的0。
package thinkinjava.charpenter5;

import java.util.Arrays;
import java.util.Random;

/**
 * @author Spring-_-Bear
 * @version 2021/9/22 10:11
 */
public class ArrayNew {
    public static void main(String[] args) {
        Random rand = new Random(47);
        int[] a = new int[rand.nextInt(100)];
        System.out.println("length of a = " + a.length);
        System.out.println(Arrays.toString(a));
    }
}
  1. 可以用花括号括起来的列表来初始化对象数组,初始化列表的最后一个逗号都是可选的(这一特性使维护长列表变得更容易)
package thinkinjava.charpenter5;

import java.util.Arrays;

/**
 * @author Spring-_-Bear
 * @version 2021/9/22 10:15
 */
public class ArrayInit {
    public static void main(String[] args) {
        Integer[] a = {
                new Integer(1),
                new Integer(2),
                3,
        };

        Integer[] b = {
                new Integer(1),
                new Integer(2),
                3,
        };

        System.out.println(Arrays.toString(a));
        System.out.println(Arrays.toString(b));
    }
}
  1. 可变参数列表不依赖于自动包装类型,而实际使用的是基本类型。
package thinkinjava.charpenter5;

/**
 * @author Spring-_-Bear
 * @version 2021/9/22 10:48
 */
public class VarargType {
    static void f(Character... args) {
        System.out.print(args.getClass());
        System.out.println(" length " + args.length);
    }

    static void g(int... args) {
        System.out.print(args.getClass());
        System.out.println(" length " + args.length);
    }

    public static void main(String[] args) {
        f('a');
        f();
        g(1);
        g();
        System.out.println("int[]: " + new int[0].getClass());
    }
}

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

package thinkinjava.charpenter5;

/**
 * @author Spring-_-Bear
 * @version 2021/9/22 11:14
 */
class TestDrive
{
    String s;
}
public class Test1 {
    public static void main(String[] args) {
        TestDrive td = new TestDrive();
        System.out.println(td.s);
    }
}

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

package thinkinjava.charpenter5;

/**
 * @author Spring-_-Bear
 * @version 2021/9/22 17:18
 */

class StringTest{
    String s1;
    String s2 = "I am happy!";
    String s3;

    StringTest(String s) {
        this.s3 = s;
    }
}
public class Test2 {
    public static void main(String[] args) {
        StringTest stringTest = new StringTest("Oh,bye!");
        System.out.println(stringTest.s1);
        System.out.println(stringTest.s2);
        System.out.println(stringTest.s3);
    }
}

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

package thinkinjava.charpenter5;

/**
 * @author Spring-_-Bear
 * @version 2021/9/22 17:23
 */
class Constructor {
    Constructor() {
        System.out.println("Hello, welcome to Java programming world!");
    }
}

public class Test3 {
    public static void main(String[] args) {
        Constructor constructor = new Constructor();
    }
}

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

package thinkinjava.charpenter5;

/**
 * @author Spring-_-Bear
 * @version 2021/9/22 17:32
 */
class StringConstructor{
    StringConstructor(){
        System.out.println("Welcome to Java world first time!");
    }

    StringConstructor(String s) {
        System.out.println(s);
    }
}
public class Test4 {
    public static void main(String[] args) {
        StringConstructor stringConstructor1 = new StringConstructor();
        StringConstructor stringConstructor2 = new StringConstructor("Welcome to Java world second time!");
    }
}

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

package thinkinjava.charpenter5;

/**
 * @author Spring-_-Bear
 * @version 2021/9/22 18:25
 */
class Dog {
    void bark() {
        System.out.println("Keep silent!");
    }

    void bark(boolean b) {
        System.out.println("boolean: wang wang");
    }

    void bark(byte by) {
        System.out.println("byte: wang wang");
    }

    void bark(char ch) {
        System.out.println("char: wang wang");
    }

    void bark(short sh) {
        System.out.println("short: wang wang");
    }

    void bark(int i) {
        System.out.以上是关于[读书笔记]Java编程思想第5章之初始化与清理的主要内容,如果未能解决你的问题,请参考以下文章

[读书笔记]Java编程思想第6章之访问权限控制

[读书笔记]Java编程思想第8章之多态

[读书笔记]Java编程思想第3章之操作符

[读书笔记]Java编程思想第9章之接口

[读书笔记]Java编程思想第4章之控制执行流程

[读书笔记]Java编程思想第2章之一切都是对象