习题答案 http://greggordon.org/java/tij4/solutions.htm

第四章 控制流程(本节很简单,很多内容掠过)

if else
if else if
while
do - while
for
增强for循环 for( : ) (此中循环可以用于任何实现Iterable的对象)
比如for(int x : numbers)
亦可以通过自定义一个range方法返回特定的数组,比如范围限定,步长等等来控制增强for循环
与普通for循环相比增强for循环简洁易懂,所以尽量多用增强for循环
return
break continue
while(true)等价于for(;;)都可以用来执行无限循环
java的goto:goto名声似乎不怎么好,在Java中也有类似功能的东西:标签,但是,实际几年开发中我一次也没有看到有人使用这种方式编程,这里就贴下书的截图,当作课外知识了解一下吧


switch case(书中的理论还不是很新其实在jdk1.7已经支持使用string作为分支判断了,但是这样做也有一定的性能缺陷,见仁见智吧)
书中的斐波那契的练习题:

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 = 9;System.out.println("First " + k + " Fibonacci number(s): ");for(int i = 0; i < k; i++)System.out.println(f.fib(i));}
}

这应该是书里最早的递归函数了吧
吸血鬼数字的练习题还是挺有意思的,如果对数字游戏比较感兴趣可以看一下。

第五章 初始化与清理

初始化对象–构造函数

(命名与类名相同 必须要让编译器知道调用的具体是哪个构造函数–参数的重要性)
User user = new User();
内部操作:为user实例分配空间并调用构造函数,即user实例被初始化了
每个类都会默认有一个无参构造函数,但是如果创建了其他构造函数,则没有了默认无参构造函数
例子

/* Create a class with a String field that is initialized at the point of
* definition, and another one that is initialized by the constructor. What is
* the difference between the two approaches.
*/class Tester2 {String s1;String s2 = "hello";String s3;Tester2() { s3 = "good-bye"; }
}public class ConstructorTest2 {public static void main(String[] args) {Tester2 t = new Tester2();System.out.println("t.s1: " + t.s1);System.out.println("t.s2: " + t.s2);System.out.println("t.s3: " + t.s3);}
}

s2和s3的初始化时机不同,s2的创建与初始化是早于构造函数的,如果你想每次初始化都赋值同一个值,可以使用s2的赋值方式,但是如果你有多个构造函数,初始化时又想给域赋予不同的初始值,那么还是采用s3的赋值方式,因为构造函数中的赋值会覆盖之前的赋值。

方法重载

方法名相同 参数不同的方法就是方法的重载了。重点在于参数不同上,何为参数不同?1.参数类型 2.参数个数3.参数顺序都相同则是参数相同了,反之则不同。
1.关于顺序 但是使用参数顺序来进行重载不利于方法的识别度,代码维护不太方便
2.关于类型 类型也不行 为什么呢 因为java基本数据的类型提升
在第三章“类型提升和强制转换”一节中 我们提到,Java的基本类型存在类型提升的例子,比如

 static void getInt(int x){System.out.println(x);}public static void main(String[] args) {char a = 'a';getInt(a);byte b = 12;getInt(b);}

像上面这样的char和byte就自动进行了类型提升变成了int类型,所以虽然方法定义规定参数是int类型,但实际使用是如果参数不是int类型也可能不会报错,这可能与预期不符
不过排除这个例外,用参数类型来区分方法的重载还是可靠的。
3.关于个数
参数个数不同,编译是一定可以区分重载方法的。
4.返回值类型不能区分重载方法
比如
void f(){}
int f(){return 0;}
如果你不执行int x = f();而是之间调用了f();编译器是不知道你实际想调用的是哪个方法的

默认构造器

如果没有写构造方法,Java会自动创建一个无参默认构造方法,但是如果写了,则默认构造方法就不存在了

this 关键字

this表示调用当前方法的那个对象的引用

在构造器中调用构造器

可以使用this在构造器中调用构造器,避免代码重复,此时调用必须放在第一行否则报错。

static关键字

static就是没有this的方法,可以理解为找不到调用static方法的“当前对象”,静态方法中无法调用非静态方法(有特殊例子,但感觉没有考虑的必要,喜欢钻研的可以考虑一下,不过有些钻牛角尖了)反过来却可以,静态方法设计出来就是为了能不用创建实例,只用类名就可以调用的方法。static的另一个名字叫类方法,也可以帮助理解

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

为什么垃圾回收不是必然执行

Java垃圾回收器不会主动回收不是使用new创建的对象(比如通过C C++和JNI来创建的对象),finalize方法专门回收这类特殊对象。
Java没有析构函数,Java中对象可能不会被回收,垃圾回收不等于析构。
垃圾回收之所以不是必然执行,是因为可能系统内存始终够用,没有濒临存储空间用完,并且可能持续到程序运行完毕,随着程序退出,所有内存才会归还给系统。因为垃圾回收也是需要开销的,如果不使用他,就可以省下这笔开销了。
所以即使在finalize方法回收内存,但是垃圾回收一直没有执行,内存也就一直没有释放

finalize的正真用途

它不是用来回收普通对象的方法,而是用来回收使用native方法(C C++)创建的对象,这类对象往往使用C malloc来分配内存
对比Java的垃圾回收与C++的析构函数和对象回收

finalize的另外的用途

public class Book {private boolean checkedOut = false;public Book(boolean checkedOut) {this.checkedOut = checkedOut;}void checkedIn(){this.checkedOut = false;}@Overrideprotected void finalize() throws Throwable {if (checkedOut) {System.out.println("errorxx");super.finalize();}}public static void main(String [] args){new Book(true);System.gc();}}

这里finalize方法应该是检测回对象收前的一些错误,应该类似于Effective Java中的安全网,为了在对象清理前必须满足某种状态。但我还是不太理解这种做法的作用,难道是提示程序中可能存在的错误?

垃圾回收器如何工作

C++类似于一个院子,所有对象类似于院子里的东西
几种垃圾回收原理的比较
Java类似于一个传送带,所有对象类似于传送带上的货物,但是这个比喻有些不恰当,这涉及页面调度和资源访问
**引用计数:**对对象进行计数,没有一个引用指向对象,计数+1,当对象计数为0时表示对象可以回收。但是如果存在循环引用,则对象永远无法回收。(个人觉得此处使用离散数学的图的概念可以解决这个问题,当存在不可达的子图时,子图可以被回收,书中所述的“更快的模式”可能就是利用了图的概念的垃圾回收)
停止-复制(stop-and-copy)
先暂停程序的运行,将存活的对象copy到新堆,此时他们相互紧挨着,copy完毕后,可以想象有一个地址映射表,将所有旧地址转换成新地址。(旧数据所在的堆空间此时可以完全回收了)
此种方案有两个问题,1 空间消耗double,2 copy如果发生在没有多少垃圾时就得不偿失了
解决1:按需从堆中分配几块较大内存,copy只发生在这些大块内存之间
解决2:加入垃圾检查机制,如果没有产生多少垃圾,则切换模式,进入标记-清扫(mark-and-clean)模式
标记-清扫
从堆栈和静态存储出发,遍历引用,找出并标记所有存活引用。全部标记完毕后,清理没有被标记的对象。清理完毕后,堆空间不连续。将空间变成连续空间的工作就交给垃圾回收器了。
所以Java垃圾回收是“自适应的”,如果虚拟机检测到垃圾比较少就切换到标记-清扫模式,当发现堆空间出现许多碎片,页面调度效率降低时进入停止-复制模式。

加载机制

我们知道java文件需要先编译成class文件 让后转换为字节码装入内存。这个过程可以分为两种机制
1.一次性装载,即一次性编译所有代码
2.即时装载,即运行时只加载需要的代码

成员初始化

Java变量像C一样遵循先定义后使用的原则
类的字段可以不进行初始化,它们有默认值
方法体中的变量则必须进行初始化,否则报错,提示需要进行初始化

构造器初始化

类字段初始化

类字段存在默认初始化且顺序优先于构造器,无法阻止类的字段变量的初始化。

静态数据初始化

static数据只占用一份存储区域,static关键字不能应用于局部变量
例子

public class Bowl {public Bowl(int marker) {System.out.println("Bowl()"+marker);}void f1(int marker){System.out.println("f1()"+marker);}
}
public class Table {static Bowl bowl1 = new Bowl(1);static Bowl bowl2 = new Bowl(2);public Table() {System.out.println("Table()");bowl2.f1(1);}void f2(int marker){System.out.println("f2()"+marker);}}
public class Cupboard {Bowl bowl3 = new Bowl(3);static Bowl bowl4 = new Bowl(4);static Bowl bowl5 = new Bowl(5);public Cupboard() {System.out.println("Cupbooard()");bowl4.f1(2);}void f3(int marker){System.out.println("f3()"+marker);}}
public class StaticInitialization {static Table table = new Table();static Cupboard cupbooard = new Cupboard();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);cupbooard.f3(1);}}

输出

Bowl()1
Bowl()2
Table()
f1()1
Bowl()4
Bowl()5
Bowl()3
Cupbooard()
f1()2
Creating new Cupboard in main
Bowl()3
Cupbooard()
f1()2
Creating new Cupboard in main
Bowl()3
Cupbooard()
f1()2
f2()1
f3()1

分析:
当使用到相关类时,静态变量最先初始化(没有使用到 则其静态变量不会初始化)且只初始化一次,接着初始化非静态类的字段,然后构造方法调用。
顺序:
class文件加载
静态变量初始化(仅仅一次)
分配内存空间
非静态的类的字段初始化
构造函数被调用

静态代码块

此类方法经常用于进行变量初始化
结合例子更易理解

public class Mug {public Mug(int marker) {System.out.println("Mug()"+marker);}void f(int marker){System.out.println("f()"+marker);}
}
public class Mugs {Mug mug1;Mug mug2;{mug1 = new Mug(1);mug2 = new Mug(2);System.out.println("Mug1 and Mug2 initialized");}public Mugs() {System.out.println("Mugs()");}public Mugs(int i) {System.out.println("Mugs(int)"+i);}public static void main(String[] args) {System.out.println("inside main");new Mugs();System.out.println("new Mugs() completed");new Mugs(1);System.out.println("new Mugs(1) completed");}}

输出

inside main
Mug()1
Mug()2
Mug1 and Mug2 initialized
Mugs()
new Mugs() completed
Mug()1
Mug()2
Mug1 and Mug2 initialized
Mugs(int)1
new Mugs(1) completed

注意和静态变量初始化的例子进行对比执行顺序和执行次数是重点
静态代码块只要类的构造函数调用一次就会执行一次

数组初始化

初始化可以有如下几种形式

 int a1[] = { 1, 2, 3, 4 };Integer[] a2 = { new Integer(1), new Integer(2), 3 };Integer[] a3 = new Integer[] { new Integer(1), new Integer(2), 3 };

可变参数

用法很简单便利,但是要注意写法吗,错误的写法可能导致空指针异常,同时在需要注重性能时,要慎用,参见EJ第42条 慎用可变参数

枚举类型简介

相对于int string常量,枚举更安全,代码的可读性更高

第六章 访问权限控制

访问修饰符的意义

1.内部重构,不影响外部。(sdk代码重构不影响客户端代码)
2.让客户端只需要关注他需要关注的部分。

命名冲突的解决方案

1.使用package
2.使用类名时进行导包
导包有两种方式
1.import例如 import java.util.ArrayList;
2.写出全称 例如 java.util.ArrayList arrayList;

Java的类的查找

Java解释器会读取classPatch的内容 将import的后面的东西翻译成路径(比如com.test会翻译成类似com/test) 然后就能读取到文件

代码简化

书中提到一种简化方法调用的一种手段比如将System.out.println封装一下可以直接写print
例如

package com.test;public class Print {public static void print(Object o){System.out.println(o);}
}

使用

package test;import com.test.Print;public class MyTest {public static void main(String[] args) {Print.print("a");}}

但是个人认为这样对原生方法进行二次封装,使得共同认识降低,也就是一个没有参与过此项目的人来看代码,需要追踪代码才能知道是对原生方法进行了二次封装,这种方法不多还行,多了的话,熟悉起来比较困难且如果不同项目命名不同,感觉弊大于利。

通过切换import改变行为

比如将包分为debug和非debug版本
例子

package debug;public class Debug {public static void debug(String s) {System.out.println(s);}
}
package debugoff;public class Debug {public static void debug(String s) {}
}
package test;//import debug.Debug;
import debugoff.Debug;public class Test {public static void main(String[] args) throws Exception {Debug.debug("this is debug log");}}

通过切换不同包的Debug类可以实现Debug和正式版之间的切换

访问权限的获取


访问权限这块比较简单,略过

一种习惯

书中建议方法按照访问权限由大到小进行排序也就是public protected 默认权限 private

类访问权限

一个类至多有一个public类 但是也可以没有public类(public可以不写 此时该类为包内可用)

Think in Java第四版 读书笔记2相关推荐

  1. Think in Java第四版 读书笔记10 第16章 数组

    Think in Java第四版 读书笔记10 第16章 数组 数组和容器很像 但他们有一些差别 16.1 数组为什么特殊 数组与容器的区别主要在效率和存储类型 效率:数组是简单的线性序列 使得数组的 ...

  2. Think in Java第四版 读书笔记9第15章 泛型

    Think in Java第四版 读书笔记9第15章 泛型 泛型:适用于很多很多的类型 与其他语言相比 Java的泛型可能有许多局限 但是它还是有很多优点的. 本章介绍java泛型的局限和优势以及ja ...

  3. Think in Java第四版 读书笔记7第13章 字符串

    本章内容 1.string的基本使用 2.string拼接符 + 3.Object方法toString 4.String的常用方法 5.String的格式化输出 6.正则表达式 13.1 不可变字符串 ...

  4. Think in Java第四版 读书笔记6第12章 异常处理

    12.1 概念 异常可以将"在正常时候执行的代码"和"发生错误时的代码"相分离,达到结构清晰的目的. a.受检查异常checkedException 编译器强制 ...

  5. Think in Java第四版 读书笔记1

    第一章对象导论(Java的几个重要部分) 访问控制的目的: 1.权限控制 2.类创建者修改某些实现而不会影响类使用者 代码复用的方式: 1.继承 2.组合(composition UML中实心菱形+实 ...

  6. Think In Java第四版读书笔记

    02-一切都是对象 将一切都"看作"对象,操纵的标识符实际是指向一个对象的"句柄". 可将这一情形想象成用遥控板(句柄)操纵电视机(对象). String s; ...

  7. Think in Java第四版 读书笔记8第14章 类型信息(RTTI与反射)

    Java如何在运行时识别对象和类的信息? 1.RTTI(Run-time type information) 它假定我们在编译时已经知道了所有类型 2.反射 它允许我们在运行时发现和使用类的信息 14 ...

  8. Think in Java第四版 读书笔记5第11章

    第十一章 持有对象(主要讲容器类) 概要 通常程序中我们需要一些容器来存储对象或者对象的引用 在java中承担这一责任的是数组和容器类 数组VS容器类 数组存在一个缺陷:长度固定不够灵活 而容器类则没 ...

  9. Think in Java第四版 读书笔记4第九章第十章

    第九章 抽象类与接口 9.1抽象类和抽象接口 抽象类可以有具体实现的方法(并不是所有方法都是abstract的)(比如这样 abstract void test3();) 子类继承抽象类要么要实现(覆 ...

最新文章

  1. 装饰器模式(Decorator)
  2. java 智能家居管理系统_智能家居系统手机客户端应用源码
  3. qt 中的 quit() close()与 exit()
  4. 算法 --- 希尔排序、归并排序、快速排序的js实现
  5. js-----Date==字符串
  6. 信息学奥赛一本通C++语言——1007:计算(a+b)×c的值
  7. spring问题-使用tomcat容器,通过url映射寻找view的时候,会报错
  8. 云服务器与传统服务器的优劣对比_相比于传统服务器,云服务器的优势在哪
  9. ai人工智能的数据服务_数据科学和人工智能如何改变超市购物
  10. SketchUp教程:BIG事务所的建筑竞赛分析图表现(附笔刷+处理稿)
  11. 【人月神话】浅谈人月神话0.2什么是“人月”,为什么是“神话”?
  12. wget 整站下载
  13. Spring boot 更改启动LOGO,佛祖保佑,永无BUG或神兽保佑
  14. 重磅消息!微信Windows电脑版本,终于支持刷朋友圈啦!
  15. 三菱伺服电机编码器故障判断方法
  16. PIL:处理图像的好模块
  17. 计算机与音乐制作专业就业前景,计算机音乐制作专业就业形势不错
  18. mysql 下一年_mysql时间增加一年
  19. 隐形墨水笔上亚马逊要做什么检测?
  20. HTML+CSS系列学习:重生之我要精通编程语言修仙

热门文章

  1. T研究:国内云BPM市场规模尚小,预计2018年仅为3.29亿元
  2. 数据结构--hashtable(散列表)
  3. ZJOI2008 树的统计 树链剖分学习
  4. CodeIgniter辅助函数
  5. C#在类型实例化时都干了什么:从一道笔试题说开去
  6. DolphinDB配置
  7. android10手机众筹,最小Android 10手机?屏幕仅3英寸的Jelly 2开始众筹
  8. linux自动输入sudo密码_用大写字母输入 Linux 命令,实现以 sudo 用户权限运行
  9. C/C++排序算法(5)归并排序
  10. 数据挖掘的好书_唐宇迪:入门数据挖掘,我最推荐这本书