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
- 垃圾回收 启动 (准备回收对象占用的内存)
- 调用 finalize ()方 法 做一些重要的清理工作
- 真正回收 内存。
- 有趣的用法 终结 条件的验证
无论对象时如何创建的 ,垃圾回收器都会负责释放对象所占据的所有内存。所以 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 , 内存 分配以较大的块为单位。 每个块都有相应的 代数(generation count) 来记录 是否存活,通常 块在某处被引用,代数增加
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