《Java编程思想》读书笔记01-初始化与清理

开篇

最近在看Java编程思想,想把Java SE部分重新巩固一下。由于之前看过两遍遍《Java疯狂讲义》,但是看过以后很多东西就忘记了,所以想记录一下学习Java SE的过程与重点。初步计划每章会有一篇博客作为总结,因为前几章比较简单,所以从第五章开始记录笔记。

序言

随着计算机革命的发展,“不安全”的编程方式已逐渐成为编程代价高昂的主因之一。初始化和清理正是设计安全的两个问题。许多错误来源于忘记初始化变量,而没有清理会导致资源用尽。Java采用构造器和垃圾回收器来解决相关问题。

1. 用构造器确保初始化

Java提供构造器,使类的设计者可以确保每个对象都被初始化。
构造器命名有两个问题:
    1. 所取的任何名字都可能与类的某个成员的名字重复
    2. 调用构造器是编译器的责任,所以必须让编译器知道应该调用哪个方法。
解决办法:让构造器采用与类相同的名字。因此“每个方法的首字母小写”的编码风格不适用于构造器。
从概念上将:“初始化”与“创建”是彼此分离的,然而在Java中,两者被捆绑在了一起,不能分离。
构造器没有返回值。

2. 方法重载

所谓方法,就是给某个动作取名字,通过起名字,你可以引用所有的对象和方法。名字起到好有助于系统的理解和修改。但是有时候相同的动作需要传入不同的对象,如果为每一个方法都起一个名字,会很难维护。例如清洗()这个方法,如果穿进去的对象是衬衫,那么执行的操作就是清洗衬衫,如何传进去的对象是汽车,那么执行的操作就是清洗汽车。我们当然可以定义两个方法,清洗汽车()和清洗衬衫(),但是这会很难维护。因此Java提供了方法的重载,使得可以用同一个方法名字,根据传入参数的不同,执行不用的操作。

很显然,Java的构造器是强制方法重载的一个重要原因。因为类名决定构造器名,所以要用多种方式创建一个类的对象时,只能根据传入参数的不同来区分。为了让方法名相同而形参不同的构造器同时存在,就必须用到方法重载。

例:

class Tree {
    int height;

    Tree() {
        prt("Planting a seedling");
        height = 0;
    }

    Tree(int i) {
        prt("Creating new Tree that is " + i + " feet tall");
        height = i;
    }

    void info() {
        prt("Tree is " + height + " feet tall");
    }

    void info(String s) {
        prt(s + ": Tree is " + height + " feet tall");
    }

    static void prt(String s) {
        System.out.println(s);
    }
}

public class Overloading {
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            Tree t = new Tree(i);
            t.info();
            t.info("overloaded method");
        }
        // Overloaded constructor:
        new Tree();
    }
}
如上所示,对象tree有两个构造器,传入不同的参数,会生成不同的tree对象。构造器通过形参不同产生方法重载。

2.1 区分方法重载

通过形参列表来区分重载的方法,每个方法都有独一无二的形参列表。注意:参数顺序的不同也可以区分两个不同的方法。
例:
public class OverloadingOrder {
    static void print(String s, int i) {
        System.out.println("String: " + s + ", int: " + i);
    }

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

    public static void main(String[] args) {
        print("String first", 11);
        print(99, "Int first");
    }
} 
上述代码中print方法的参数都为String与int,但参数顺序不同,所以为两个不同的方法。但请注意,尽量不要这样做,这会使代码难易维护。

2.2 涉及基本类型的重载

基本类型能从“较小”的类型自动提升至“较大”的类型。如果传入的参数类型小于方法中声明的形式参数类型,实际数据类型就会被提升。char类型略有不同,如果无法找到恰好能接受char参数的方法,就会把char直接提升至int类型。
然而如果方法接受较小的类型作为参数。如果传入较大类型的参数,就得通过窄化才能正常调用方法,否则编译器会报错。
Java数据类型自动提升的顺序如下图所示:


图1 Java数据类型自动提升顺序
涉及基本类型重载的例子如下:
public class Test {
    public static void main(String[] args) {
        // 字面量默认作为int处理
        PrimitiveTypeOverload.primitive(5); // 输出结果:int
        byte b = 1;
        PrimitiveTypeOverload.primitive(b); // 输出结果:byte
        // char 在没有确切接收char类型的重载方法的时候,char类型作为int类型处理(65535)
        char c = 'c';
        PrimitiveTypeOverload.primitive(c); // 输出结果:int
        long l = 5L;
        // 传入的参数类型大于声明类型, 需要窄化
        PrimitiveTypeOverload.primitive((char) l); // 输出结果:int
        PrimitiveTypeOverload.primitive((short) l); // 输出结果:short
        PrimitiveTypeOverload.primitive((int) l); // 输出结果:int
    }
}
class PrimitiveTypeOverload {
    public static void primitive(int i) {
        System.out.println("int ");
    }
    public static void primitive(byte b) {
        System.out.println("byte");
    }
    public static void primitive(short s) {
        System.out.println("short");
    }
}

2.3 以返回值区分重载方法

有人会想当然认为,可以通过返回值区分方法。例如如下两个方法:
void f() {}
int f() {}
如果用int x = f(),可以很明确的知道调用的是第二个方法。但如果我不关心方法的返回值,指是想要方法调用的效果,那么如下的例子就不能确定是调用的哪个方法:
f();
结论:根据返回值区分重载方法不可行!

3. 默认构造器

如果你写的类中没有构造器,系统会自动帮你创建一个没有参数的默认构造器。例:
class Bird {
    int i;
}

public class DefaultConstructor {
    public static void main(String[] args) {
        Bird nc = new Bird(); // default!
    }
}
如果你已经定义了一个构造器(无论有无参数),系统都不在帮你自动创建默认构造器。例:
class Bush {
    Bush(int i) {
    }

    Bush(double d) {
    }
}
 new Bird(); //此时会报错,因为没有默认构造器。




4. this关键字

调用一个对象的方法时需要这个对象的名字,即引用。如a,b两个对象,他们都有方法peel()。调用时的代码分别是
a.peel();
b.peel();
面向对象的语法编写代码就是发送消息给对象。编译器在此暗自把“所操作对象的引用”发送给了方法peel()。实际情况应该是这样的:
Banana.peel(a,1);
Banana.peel(b,2);
但不能这样写,编译器无法通过。这说明,调用某个对象的方法一定需要该对象的名字,即引用。但如果要在一个方法的内部获取当前对象的引用时,这种方式就无法完成任务。Java提供了this关键字来代表“调用该方法的当前对象”的引用,注意this只能在方法内部使用。特别:如果在一个方法内部调用另一个方法,不必使用this,直接调用即可。只在必要时使用this,遵循一种一直而直观的编程峰哥能节省时间和金钱。

4.1 在构造器中调用构造器

通常this指当前对象,代表一个当前对象的引用。但如果在this后添加了参数列表,将调用符合这个参数列表的构造器。
注意:
  1. 构造器调用要放在最起始处,否则编译器会报错。
  2. this调用构造器不能调用两次,只能用一次。
  3. 非构造器不能用this调用构造器。
public class Flower {
    private int petalCount = 0;
    private String s = new String("null");


    Flower(int petals) {
        petalCount = petals;
        System.out.println("Constructor w/ int arg only, petalCount= " + petalCount);
    }


    Flower(String ss) {
        System.out.println("Constructor w/ String arg only, s=" + ss);
        s = ss;
    }


    Flower(String s, int petals) {
        this(petals);
        // ! this(s); //  1.不能调用两次this
        this.s = s; // Another use of "this"
        System.out.println("String & int args");
    }


    Flower() {
        this("hi", 47);
        System.out.println("default constructor (no args)");
    }


    void print() {
        // ! this(11); 2.只能在构造器内使用this调用构造器
        System.out.println("petalCount = " + petalCount + " s = " + s);
    }


    public static void main(String[] args) {
        Flower x = new Flower();
        x.print();
    }
}

4.2 static含义

static方法就是没有this的方法,在static方法的内部不能调用非静态方法,反过来却可以。因为可以使用类本身来调用static方法而无需创建类对象,如果static方法调用了非静态方法,而非静态方法需要依赖于对象的实例,可是此时对象还没有创建,因此就找不到该对象的非静态方法,会出错。
当然,如果代码中出现了大量static方法,那一定需要重新考虑自己的设计。

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

Java有一个垃圾回收器,用来监视new创建的所有对象,并辨别那些不会再被引用的对象。使用垃圾回收器的唯一是为了回收不再使用的 内存。垃圾回收的特点:
  1. 对象可能不被垃圾回收。
  2. 垃圾回收不等于“析构”。
  3. 垃圾回收只与内存有关。

将一个对象用完之后就“弃之不顾”是不完全的。Java的垃圾回收机制只与内存相关,负责回收new出来的对象。然而对使用本地方法调用c或c++等语言分配的内存,Java垃圾回收无法起作用。因此出现了finalize()方法,负责执行垃圾回收前的收尾工作。

5.1 finalize()的用途

finalize()工作原理:一旦垃圾回收器准备好释放对象所占用的存储空间,将首先调用其finalize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。Java的垃圾回收器只负责回收 对象所占用的内存,如果用过某种创建对象以外的方式为对象分配了内存,垃圾回收器就毫无作用,这时就需要finalize()方法。例如Java调用C语言的malloc()函数分配了存储空间,此时除非使用free()函数,否则存储空间得不到释放,会放生内存泄漏。这种情况下就需要在finalize()方法中调用free()函数。
当然,不能过多的使用finalize()方法。

5.2 你必须实施清理

Java不允许创建局部对象,必须使用new创建对象。无论是“垃圾回收”还是“终结”,都不一定会发生。如果Java虚拟机没有面临内存耗尽的情况,它是不会浪费时间去执行垃圾回收以恢复内存的。

5.3 终结条件

finalize()可以用于对象的终结条件的验证。例如:
public class Tank {  
    boolean status=false;//false为空,true为满  
      
    Tank(boolean stat){  
        status=stat;  
    }  
      
    void empty(){  
        status=false;  
    }  
      
    protected void finalize(){  
        if(status)  
            System.out.println("error");      
    }  
}  
public class Test {  
    public static void main(String args[]){  
        Tank tk1=new Tank(true);            
        tk1.empty();            
        //Tank tk2=new Tank(true);  
        new Tank(true); //对象一创建就成为垃圾,因为没有引用指向它    
          
        System.gc();  //强制执行终结操作
    }  
}  
输出为error。

本例终止的条件为:Tank()的status值为false,即tank为空。当Tank的status值为true时,会输出error。如果没有finalize()来验证终止条件,很难发现这种缺陷。gc()函数的作用是提醒虚拟机,程序员希望进行一次垃圾回收,但并不保证一定会执行垃圾回收。具体什么时候取决于虚拟机,不同虚拟机有不同对策。

5.4垃圾回收器如何工作

待完成

6. 成员初始化

Java尽力保证所有变量在使用前都能得到初始化。对于局部变量,Java以编译时错误的形势贯彻这种保证, 也就是说局部变量没有默认值。其余默认值如下所示:

boolean        false

char              '/uoooo'(null)

byte              (byte)0

short             (short)0

int                  0

long               0L

float               0.0f

double           0.0d

引用类型默认值为null。

6.1 指定初始化

指定初始化是指在定义类成员变量的时候为其赋值。例:
class Measurement {
    boolean b = true;
    char c = 'x';
    byte B = 47;
    short s = 0xff;
    int i = 999;
    long l = 1;
    float f = 3.14f;
    double d = 3.14159;
}
也可以以同样方法初始化非基本类型的对象。
class Measurement {
    Depth o = new Depth();
    boolean b = true;
}
如果没有初始化d的值就是用它,会出现运行时错误。

7. 构造器初始化 

可以用构造器进行初始化。即在运行时执行某些动作为变量赋初值。但要注意:无法阻止自动初始化的执行, 它将在构造器被调用之前发生。即默认值最先出现。例:
class Counter {
    int i;

    Counter() {
        i = 7;
    }
}
i的值首先是0,然后变成7。对于所有的基本类型和对象引用,包括在定义时已经指定初始值的变量,都是如此。

7.1 初始化顺序

在类的内部,成员变量定义的先后顺序决定了初始化的顺序,先于方法的执行,即使变量定义散布于方法之间,仍旧会在任何方法执行前得到初始化。
class Tag {
    Tag(int marker) {
        System.out.println("Tag(" + marker + ")");
    }
}

class Card {
    Tag t1 = new Tag(1); // Before constructor

    Card() {
        // Indicate we're in the constructor:
        System.out.println("Card()");
        t3 = new Tag(33); // Re-initialize t3
    }

    Tag t2 = new Tag(2); // After constructor

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

    Tag t3 = new Tag(3); // At end
}

public class OrderOfInitialization {
    public static void main(String[] args) {
        Card t = new Card();
        t.f(); // Shows that construction is done
    }
}
其输出结果如下:
Tag(1)
Tag(2)
Tag(3)
Card()
Tag(33)
f()
可以看出,即使tag的定义散布于方法之间,仍然先于任何方法得到了初始化。

7.2 静态数据的初始化

无论创建多少个对象,静态数据都只占用一份存储区域。静态代码只执行一次。static关键字不能用于局部变量。静态初始化只在必要时进行,两个时刻:
  1. 第一次new创建对象时。
  2. 第一次访问静态数据时。
初始化的顺序是:
静态对象----->非静态对象
构造器在初始化完成后执行。例:
class Bowl {
    Bowl(int marker) {
        System.out.println("Bowl(" + marker + ")");
    }

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

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

    Table() {
        System.out.println("Table()");
        b2.f(1);
    }

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

    static Bowl b2 = new Bowl(2);
}

class Cupboard {
    Bowl b3 = new Bowl(3);
    static Bowl b4 = new Bowl(4);

    Cupboard() {
        System.out.println("Cupboard()");
        b4.f(2);
    }

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

    static Bowl b5 = 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();
        t2.f2(1);
        t3.f3(1);
    }

    static Table t2 = new Table();
    static Cupboard t3 = new Cupboard();
}
输出:
Bowl(1)
Bowl(2)
Table()
f(1)
Bowl(4)
Bowl(5)
Bowl(3)
Cupboard()
f(2)
Creating new Cupboard() in main
Bowl(3)
Cupboard()
f(2)
Creating new Cupboard() in main
Bowl(3)
Cupboard()
f(2)
f2(1)
f3(1)



7.3 显示的静态初始化

即为将多个静态初始化语句组成一个特殊的“静态子句”,成为静态代码块。例:
class Spoon {
    static int i;
    static {
        i = 47;
    }
}

7.4 非静态实例初始化

实例初始化用来初始化每一个实例对象的非静态变量。

8. 数组初始化

待完成

9. 枚举类型

待完成


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值