Think in Java 第四 五 章

Think in Java 第四章

控制执行流程

测试while

public class whileTest {
    static boolean condition(){
        boolean result = Math.random() < 0.99;
        System.out.println(result + ",");
        return result;
    }

    public static void main(String[] args) {
        while (condition())
            System.out.println("Inside 'while'");
        System.out.println("Exited 'while'");
    }
}
===============================================
    true,
Inside 'while'
true,
Inside 'while'
true,
Inside 'while'
true,
Inside 'while'
false,
Exited 'while'

Math.random 随机生产 0 ---1 的数字

do - while 至少执行一次

for循环常用于计数任务

public class whileTest {
    public static void main(String[] args) {
        Random rand1 = new Random();
        Random rand2 = new Random();
        for(int i = 0; i < 5; i++) {
            int x = rand1.nextInt();
            int y = rand2.nextInt();
            if(x < y) System.out.println(x + " < " + y);
            else if(x > y) System.out.println(x + " > " + y);
            else System.out.println(x + " = " + y);
        }
        Random rand3 = new Random();
        Random rand4 = new Random();
        for(int i = 0; i < 5; i++) {
            int x = rand3.nextInt(10);
            int y = rand4.nextInt(10);
            if(x < y) System.out.println(x + " < " + y);
            else if(x > y) System.out.println(x + " > " + y);
            else System.out.println(x + " = " + y);
        }
    }

}
====================================================

-1636716208 < -1366578013
-630773338 > -1420580444
342336183 < 784053746
1142887002 > 325688001
-1102830792 > -1683393166
8 > 4
3 > 0
7 < 8
0 < 3
1 < 6

forseach

不必创建 int 变量 去对由其访问项构成的序列 进行计数

for (int i : range(10))
print(i+"");
===============
0 1 2 3 4 5 6 7 8 9

练习8

public class SwitchTest {
	public static void main(String[] args) {
		for(int i = 0; i < 11; i++) {	
			switch(i) {
				case 0: print("zero"); break;
				case 1: print("isa"); break;
				case 2: print("dalawa"); break;
				case 3: print("tatlo"); break;
				case 4: print("apat"); break;
				case 5: print("lima"); break;
				case 6: print("anim"); break;
				case 7: print("pito"); break;
				case 8: print("walo"); break;
				case 9: print("siyam"); break;
				default: print("default");
			}
		}
	}	
}
======================================================
zero
isa
dalawa
isa
dalawa
dalawa
tatlo
apat
lima
anim
pito
walo
siyam

练习9

public class Fibonacci {
	int fib(int n) {
		if(n < 2) return 1;
		return (fib(n - 2) + fib(n - 1));
	}
	public static void main(String[] args) {
		Fibonacci f = new Fibonacci();
		int k = Integer.parseInt(args[0]);
		System.out.println("First " + k + " Fibonacci number(s): ");
		for(int i = 0; i < k; i++)
			System.out.println(f.fib(i));
	}	
}
==================================================
1
1
2
3

第五章

初始化与清理

构造器 和 垃圾回收机制

5.1 用构造器 确保初始化

​ 可以假想为每一个类都定义了 initialize() 方法。通过构造器,确保每个类都被初始化了。创建对象时,如果其类具有构造器,Java就会在用户操作之前自动调用相应的构造器,从而保证了初始化。

构造器 与 类 同名, 方便计算机自动找到,而且不与自定义的属性或者方法重名。

构造器

  • 名称与类名完全相同,方法名的首字母 小写,不适用于构造器
  • 没有参数的构造器,叫无参构造 或者 默认构造
  • 有形式参数的构造器叫有参构造,以便指向如何创建对象(Tree 类有一个构造器,接受整形变量来表示数的高度,创建Tree对象的时候,就形成了一个有高度的Tree)
  • 有助于 减少错误,代码容易阅读

练习1

lass Tester {
	String s;
}

public class ConstructorTest {
	public static void main(String[] args) {
		Tester t = new Tester();
		System.out.println(t.s);
	}
}
=============================================
null

练习2

class Tester2{
    String s1;
    String s2 = "hello";
    String s3 = "initialization by self";
    Tester2(){
        s3 = "constructor initialization";
    }
}
public class ConstructorTest {
    public static void main(String[] args) {
        Tester2 t2 = new Tester2();
        System.out.println("t2.s1="+ t2.s1);
        System.out.println("t2.s2="+ t2.s2);
        System.out.println("t2.s3="+ t2.s3);
    }
}
=================================================
t2.s1=null
t2.s2=hello
t2.s3=constructor initnizetion

构造器初始化优先级高

5.2 方法重载

名字

  • 创建一个对象时,给对对象分配的存储空间取了一个名字

  • 方法则是给某个动作取个名字

  • 通过名字引用所有的对象和方法。

  • 每一个方法都要有唯一的名称

    ​ 假设你要创建一个类,既可以用标准方法初始化,也可以从文件里读取信息来初始化,这就需要两个构造器:一个默认构造器,一个取字符串作为形式参数----该字符串表示初始化方法对象所需要的文件名称。急类名。为了让方法名相同而形式参数不同的方法同时存在,必须用到方法的重载

5.2.1 区分方法的重载

区分方法的重载 其实很简单,

  • 每个重载的方法都必须有第一无二的参数列表。

  • 参数的顺序也可以把他们区分开来

5.3 默认构造方法

没有形式参数,创建一个“默认对象”。如果你写的类中没有构造器,则编译器会自动帮你创建一个默认构造方法。

练习3

class Kabayo{
    Kabayo(){
        System.out.println(" is a Constructor");
    }
}

public class DefaultConstructorTest {
    public static void main(String[] args) {
        Kabayo k = new Kabayo();
    }
}
======================================
     is a Constructor

5.4 This 关键字

如果 只有 一个 Peel 方法,创建了一个对象 a,一个对象 b.

a.peel(1);

b.peel(2);

它是被a调用了 还是 b呢?

编译器 暗自中 把 所操作对象的引用 作为 参数传递给 peel() 内部表示形式

peel(a,1);

peel(b,1);

在方法内部获得对当前对象的引用, 当前对象 : 调用这个方法的对象。 this

this 值调用方法的那个对象的引用 a b

  • 当你 在 方法中 调用 同一个类的其他方法 直接调用就好
  • 当你在方法中使用了 this 会应用到同一类的其他方法
  • 当需要返回当前对象的引用时,常常用 return 返回this
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();
    }
}
==========================================================
3

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

  • this 关键字 对于将当前方法传递给其他方法也很有用

练习8

public class Doc {
    public static void main(String[] args) {
        new Doc1().intubate();
    }
}
class Doc1{
    void intubate() {
        System.out.println("prepare patient");
        laryngoscopy();
        this.laryngoscopy();
    }
    void laryngoscopy() {
        System.out.println("use laryngoscope");
    }

}
===================================================
prepare patient
use laryngoscope
use laryngoscope

5.4.1 在构造器中调用构造器

一个类中写了多个构造器,有时可能想在一个构造器中调用另一个构造器,避免代码重复 可以用this

  • This 表示 这个对象 或者 当前对象,而它本身代表当前地下的引用。

  • this 添加了参数列表,对符合此参数列表的某个构造器的明确调用

    • 只能调用一个 不能调用两个
    • 编译器 禁止在其他方法里面 调用 构造器

5.4.2 static的含义

static 就是没有 this的 方法

  • 在 static 方法中 不能调用非静态方法,反过来可以。
  • 在没有创建任何对象的前提下,仅仅通过类本身来调用static方法。
  • 全局方法。

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

  • 垃圾 回收 只知道释放哪些经由 new分配的内存

  • 为了释放 那些 不由 new 分配的内存,允许在 类中定义 finalize()方法。

    finalize

    1. 垃圾回收 启动 (准备回收对象占用的内存)
    2. 调用 finalize ()方 法 做一些重要的清理工作
    3. 真正回收 内存。
    4. 有趣的用法 终结 条件的验证

无论对象时如何创建的 ,垃圾回收器都会负责释放对象所占据的所有内存。所以 finalize之所以要用 ,是因为在分配内存时候可能用到了 本地方法 的情况 本地方法目前只支持 c 和 c++

也许调用了 c 的 malloc()函数 系列 来分配存储空间。而且除非用 free()函数 来释放 空间 否则 得不到释放

如果JVM 虚拟机 并未 面临内存耗尽的情况 是不会浪费实际去执行 垃圾回收 回复内存的

练习10

class WebBank{
    boolean loggedIn = false;
    WebBank(boolean logStatus){
        loggedIn = logStatus;
    }
    void logIn(){
        loggedIn = true;
    }
    void logOut(){
        loggedIn = false;
    }
    protected void finalize(){
        if (loggedIn)
            System.out.println("Error : still logged in");
    }
}
public class TerminationConditionEx {
    public static void main(String[] args) {
        WebBank bank2 = new WebBank(true);  // 定义出来不用 垃圾回收自动清理
        bank1.logOut();
        new WebBank(true);  //  一直new 一个 对象 ,一直得不到清理。调用 fianlize方法 检查 验证终结条件
        System.gc();

    }
}

==========================================================
Error : still logged in

练习11

class Webbank {
    boolean loggedIn = false;
    Webbank(boolean logStatus) {
        loggedIn = logStatus;
    }
    void logOut() {
        loggedIn = false;
    }
    protected void finalize() {
        if(loggedIn)
            System.out.println("Error: still logged in");
        // Normally, you'll also call the base-class version:
        // super.finalize();
    }
}
public class BankTest {
    public static void main(String[] args) {
        Webbank bank1 = new Webbank(true);
        Webbank bank2 = new Webbank(true);//垃圾回收自动清除
        new Webbank(true);  // 添加未被处理对象 
        // Proper cleanup: log out of bank1 before going home:
        bank1.logOut(); // bank1 对象 被清理
        // Forget to logout of bank2 and unnamed new bank
        // Attempts to finalize any missed banks:
        System.out.println("Try 1: "); 
        System.runFinalization(); 
        System.out.println("Try 2: ");
        Runtime.getRuntime().runFinalization();
        System.out.println("Try 3: ");
        System.gc();
        System.out.println("Try 4: ");
        // using deprecated since 1.1 method:
        System.runFinalizersOnExit(true);
    }
}

=======================================================================
    

练习12

class Tank{
    int howFull = 0;
    Tank(){this(0);}
    Tank(int fullness){
        howFull = fullness;
    }
    void sayHowFull(){
        if(howFull == 0) System.out.println("Tank is empty");
        else System.out.println("Tank filling status =" + howFull);
    }
    void empy(){
        howFull = 0;
    }
    protected void finalize(){
        if(howFull == 0)
            System.out.println("Error:Tank is empty");
    }

}

public class TankTest {
    public static void main(String[] args) {
        Tank tank1 = new Tank();
        Tank tank2 = new Tank(3);
        Tank tank3 = new Tank(5);

        tank2.empy();

        new Tank(6);
        System.out.println("Check tanks:");
        System.out.println("tank1:");
        tank1.sayHowFull();
        System.out.println("tank2:");
        tank2.sayHowFull();
        System.out.println("tank3:");
        tank3.sayHowFull();
        System.out.println("first forced gc():");
        System.gc();
        System.out.println("try deprecated runFinalizersOnExit(true):");
        System.runFinalizersOnExit(true);
        System.out.println("last force gc():");
        System.gc();
    }
}
Check tanks:
tank1:
Tank is empty
tank2:
Tank is empty
tank3:
Tank filling status =5
first forced gc():
try deprecated runFinalizersOnExit(true):
last force gc():
Error:Tank is empty
Error:Tank is empty

5.5.4 垃圾回收是如何工作

垃圾回收对与提高对象的创建速度有明显的效果。 存储空间的释放竟然会影响存储空间的分配

Java中 堆的实现 像一个传送带,每分配一个内存,它就前进一格。 对象在存储空间的分配速度非常快。Java的堆指针只是简单的移动到尚未分配的区域。但不是单纯的像传送带一样,垃圾回收的介入,在分配内存的同时,也释放前面已经分配好的内存,堆指针可以指向释放的区域,使得对象紧凑排列。

  • 引用计数法。常用来说明垃圾收集的工作方式,但似乎从未被应用于任何一种JVM中
  • 对 任何 “活”,一定能最终追溯到其存活在堆栈或者静态存储区之中的引用。

如何找到活着的对象(不同的Java虚拟机 实现不同)

  • 停止--复制 (不属于后台回收模式)停止程序 将所有存活的对象从当前堆复制到另一个堆,没有完全复制的都是垃圾。当对象复制到新堆的时候是 一个挨着一个
    • 引用必须被修正 (位于 堆 或 静态存储区的引用可以被直接修改)
    • 必须有两个堆 JVM 从堆中按需要 分配几个大堆 复制动作发生在大堆之间
    • 当产生少了垃圾 或者 没有垃圾的时候,一味的复制 会浪费很大的效率
      • 为了避免: JVM 会进行检查:没有新垃圾产生或者少量,切换到另一种模式(标记 -- 清除)
  • 标记--清除 思路也是 : 从堆栈 或者 静态存储区域出发,遍历所有引用,进而找出存活的对象。
    • 步骤
      • 每当找到一个活得对象,就会给对象一个标记,这个过程不会回收任何对象
      • 标记工作全部完成后,清理没有被标记的对象。空间是不连续的 ,重新整理剩下的对象。
  • 自适应
    • JVM , 内存 分配以较大的块为单位。 每个块都有相应的 代数(generation count) 来记录 是否存活,通常 块在某处被引用,代数增加
      • 大型对象单独占一个块。 不会被 复制 只是 代数增加
      • 小型对象 会被 复制 并整合
    • JVM 会 监视
      • 所有的 块 都稳定,但是垃圾回收效率 很低 切换到 标记 -- 清楚 方式
      • 堆中空间出现 很多碎片, 切换到 停止--复制

JVM 有 许多 附加技术 用来 提升速度

  • 类加载器 有关 的
    • Just in time 即时 把 程序 全部 或者 部分 翻译成 本地机器码(JVM 工作),程序运行速度会得到提高。

例子

​ 当 加载 一个 类库时候(通常时该类创建第一个对象),编译器会找到 .class文件,然后将该类的 字节码 装入内存。

两种方案:

  • 即时编译器 编译所有代码
    • 降低程序速度
  • 惰性评估 (lazy evaluation)
    • 即时 编译器 只在需要的时候 编译代码 不会被执行的代码 压根不会 被编译。
    • 代码每次 被执行的时候都会做一些 优化 执行的次数 越多 速度越快。

成员初始化

Java 尽力 保证: 所有的 成员变量在使用前都能得到恰当的初始化。对于方法的局部变量,Java以编译时的错误来贯彻这种保证

  • 基本数据 类型 成员 保证都会有一个初始值

5.6.1 指定初始化

5.7 构造器 初始化

可以用 构造 来初始化。 无法阻止 自动 初始化 的 进行,他将在构造器被调用之前发生

5 .7.1 初始化 顺序

变量 定义的先后顺序 决定了 初始化的先后顺序。即使变量定义散步于方法定义之间,他们仍然会在任何方法(包括构造器)被调用之前得到初始化。

5.7.2 静态数据的初始化

​ 无论建立多少个对象,静态数据都只占一份 存储区域。static 不能应用与局部变量,因此它只能作用 于

  • 静态的基本域 没有进行初始化 , 获得基本类型的标准初值
  • 一个对象的引用,默认值就是null
  • 静态初始化 只有在 必要时刻才会 进行,如果 在类中定义了静态 或者类中的方法定义了静态,那么在没有调用这个类(生成类的对象) 或者方位类的静态成员 那么 不会被初始化,如果 调用了这个类 (创建 类 对象 或者 类的精要方法 静态域 首次被访问)
    • Java解释器必须查找到类路径定位到 类.class文件。
    • 然后载入 class 有关静态初始化的所有操作都会被执行,(因此,静态初始化只在 class对象首次加载的时候进行一次)
    • 当new一个对象的时候,首先会在堆上为这个 对象 分配足够的存储空间
    • 保证这个 空间事清零的 自动的将 类中的所有基本类型 都设置成默认值,数字 字符 布尔是 0 引用为null
    • 执行所有字段定义处的初始化动作
    • 执行构造器。
  • 静态变量 被 初始化 过后 再 重复调用 不会 被出被再次初始化

5.7.3 显式的静态初始化

Java 允许 将多个静态初始化 组织 成 一个 “静态块”


这段代码仅执行一次 :当首次生成 这个类的 一个对象时,或者首次访问 属于那个类的静态数据 成员时

练习 14

class Go {
    static String s1 = "run";
    static String s2, s3;
    static {
        s2 = "drive car";
        s3 = "fly plane";
        System.out.println("s2 & s3 initialized");
    }
    static void how() {
        System.out.println(s1 + " or " + s2 + " or " + s3);
    }
    Go() {
        System.out.println("Go()"); }
}

public class ExplicitStaticEx {
    public static void main(String[] args) {
    	System.out.println(g1.s1);
        System.out.println("Inside main()");
        Go.how();
        System.out.println("Go.s1: " + Go.s1);
    }
    static Go g1 = new Go();
    static Go g2 = new Go();
}
================================================
s2 & s3 initialized
Go()
Go()
run
Inside main()
run or drive car or fly plane
Go.s1: run

5.7.4 非静态实例化

Java 中也有 实例初始化 的类似语法, 用来初始化每个对象的非静态变量。

练习 15

class Test {
    String s;
    {
        s = "Initializing string in Tester";
        System.out.println(s);
    }
    Test() {
        System.out.println("Tester()");
    }
}

public class InstanceClauseTest {
    public static void main(String[] args) {
        new Test();
    }
}
====================================================
Initializing string in Tester
Tester()

5.8 数组初始化

int[] a

定义的时候 初始化

int[] a = new int[rand.nextInt(20)]

a[i] = rand.nextInt(500);

也可以用花括号括起来 的 列表 来 初始化 对象数组。 有两种形式

Integer[] a = {

	new Integer(1),

	new Integer(2),

	3,  // autoboxing自动封装

}
Integer[] b = new Integer[]{

	new Integer(1),
	new Integer(2),
	3, //autoboxing
}

创建一个引用数组, 创建一个对象 并把对象赋值给引用 才算初始化进程结束

练习16

public class StringArrays {

        public static void main(String[] args) {
            String[] s = { "one", "two", "three", };
            for(int i = 0; i < s.length; i++)
                System.out.println("s[" + i + "] = " + s[i]);
        }
    }

练习17

class InitTest {
	InitTest(String s) {
		System.out.println("InitTest()");
		System.out.println(s);
	}
}

public class InitTest17 {
	public static void main(String[] args) {
		InitTest[] it = new InitTest[10];
	}
}

什么都没打印


练习18

class InitTest {
    InitTest(String s) {
        System.out.println("InitTest()");
        System.out.println(s);
    }
}

public class InitTest18 {
    public static void main(String[] args) {
        InitTest[] it = new InitTest[5];
        for(int i = 0; i < it.length; i++)
            it[i] = new InitTest(Integer.toString(i));
    }
}
===========================================================
InitTest()
0
InitTest()
1
InitTest()
2
InitTest()
3
InitTest()
4

5.8.1 可变参数列表

可以 应用于 参数个数 或者 类型 未知 的 场合。

由于所有的类都直接或者间接继承object类,所以可以 创建 以 object 数组 为参数的方法。

**有了 可变参数 编译器 就再也不哟个 显示的编写 数组 语法了,当你 指定参数时,编译器 实际上会为你填充参数 如果你有了 一组事物 你可以把他们当作列表传递 而如果你已经有了一个数组 该方法可以把它们当作 可变参数列表传递 **

当有 可选的 尾随(trailing) 参数 这一方法 就会很好用

可变参数 列表为 数组的情况 如果没有 元素 数组尺寸为 0

可变参数 列表 可以 于 自动包装机制 和谐相处

可变参数列表 使 重载 变得 复杂了

每种情况下,编译器都会自动包装机制来匹配重载方法,然后匹配最明确的方法

在不使用 参数调用 f() 时 编译器 就不知道 调用那个方法了

可以 增加一个非 可变参数来 解决


练习19

public class InitTest19 {
    static void showStrings(String... args) {
        for(String s : args)
            System.out.print(s + " ");
        System.out.println();
    }
    public static void main(String[] args) {
        showStrings("one", "TWO", "three", "four");
        showStrings(new String[]{"1", "2", "3", "4"});
    }
}
===========================================================
one TWO three four 
1 2 3 4   

练习 20

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

5.9 枚举类型

enum 关键字

enum 可以和 switch 组合

练习 21

public class EnumEx21 {
    public enum Bills {
        ONE, FIVE, TEN, TWENTY, FIFTY, HUNDRED
    }
    public static void main(String[] args) {
        for(Bills b : Bills.values())
            System.out.println(b + ", ordinal " + b.ordinal());
    }
}
===================================================================
ONE, ordinal 0
FIVE, ordinal 1
TEN, ordinal 2
TWENTY, ordinal 3
FIFTY, ordinal 4
HUNDRED, ordinal 5

练习 21

enum Bills {
    ONE, FIVE, TEN, TWENTY, FIFTY, HUNDRED
}
public class Wallet {
    Bills b;
    public static void main(String[] args) {
        for(Bills b : Bills.values()) {
            System.out.print("Worth: ");
            switch(b) {
                case ONE: System.out.println("$1"); break;
                case FIVE: System.out.println("$5"); break;
                case TEN: System.out.println("$10"); break;
                case TWENTY: System.out.println("$20"); break;
                case FIFTY: System.out.println("$50"); break;
                case HUNDRED: System.out.println("$100"); break;
                default: break;
            }
        }
    }
}
=====================================================================
Worth: $1
Worth: $5
Worth: $10
Worth: $20
Worth: $50
Worth: $100
posted @ 2021-01-27 17:54  AronJudge  阅读(76)  评论(0编辑  收藏  举报