Think in Java第四版 读书笔记2
习题答案 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相关推荐
- Think in Java第四版 读书笔记10 第16章 数组
Think in Java第四版 读书笔记10 第16章 数组 数组和容器很像 但他们有一些差别 16.1 数组为什么特殊 数组与容器的区别主要在效率和存储类型 效率:数组是简单的线性序列 使得数组的 ...
- Think in Java第四版 读书笔记9第15章 泛型
Think in Java第四版 读书笔记9第15章 泛型 泛型:适用于很多很多的类型 与其他语言相比 Java的泛型可能有许多局限 但是它还是有很多优点的. 本章介绍java泛型的局限和优势以及ja ...
- Think in Java第四版 读书笔记7第13章 字符串
本章内容 1.string的基本使用 2.string拼接符 + 3.Object方法toString 4.String的常用方法 5.String的格式化输出 6.正则表达式 13.1 不可变字符串 ...
- Think in Java第四版 读书笔记6第12章 异常处理
12.1 概念 异常可以将"在正常时候执行的代码"和"发生错误时的代码"相分离,达到结构清晰的目的. a.受检查异常checkedException 编译器强制 ...
- Think in Java第四版 读书笔记1
第一章对象导论(Java的几个重要部分) 访问控制的目的: 1.权限控制 2.类创建者修改某些实现而不会影响类使用者 代码复用的方式: 1.继承 2.组合(composition UML中实心菱形+实 ...
- Think In Java第四版读书笔记
02-一切都是对象 将一切都"看作"对象,操纵的标识符实际是指向一个对象的"句柄". 可将这一情形想象成用遥控板(句柄)操纵电视机(对象). String s; ...
- Think in Java第四版 读书笔记8第14章 类型信息(RTTI与反射)
Java如何在运行时识别对象和类的信息? 1.RTTI(Run-time type information) 它假定我们在编译时已经知道了所有类型 2.反射 它允许我们在运行时发现和使用类的信息 14 ...
- Think in Java第四版 读书笔记5第11章
第十一章 持有对象(主要讲容器类) 概要 通常程序中我们需要一些容器来存储对象或者对象的引用 在java中承担这一责任的是数组和容器类 数组VS容器类 数组存在一个缺陷:长度固定不够灵活 而容器类则没 ...
- Think in Java第四版 读书笔记4第九章第十章
第九章 抽象类与接口 9.1抽象类和抽象接口 抽象类可以有具体实现的方法(并不是所有方法都是abstract的)(比如这样 abstract void test3();) 子类继承抽象类要么要实现(覆 ...
最新文章
- 装饰器模式(Decorator)
- java 智能家居管理系统_智能家居系统手机客户端应用源码
- qt 中的 quit() close()与 exit()
- 算法 --- 希尔排序、归并排序、快速排序的js实现
- js-----Date==字符串
- 信息学奥赛一本通C++语言——1007:计算(a+b)×c的值
- spring问题-使用tomcat容器,通过url映射寻找view的时候,会报错
- 云服务器与传统服务器的优劣对比_相比于传统服务器,云服务器的优势在哪
- ai人工智能的数据服务_数据科学和人工智能如何改变超市购物
- SketchUp教程:BIG事务所的建筑竞赛分析图表现(附笔刷+处理稿)
- 【人月神话】浅谈人月神话0.2什么是“人月”,为什么是“神话”?
- wget 整站下载
- Spring boot 更改启动LOGO,佛祖保佑,永无BUG或神兽保佑
- 重磅消息!微信Windows电脑版本,终于支持刷朋友圈啦!
- 三菱伺服电机编码器故障判断方法
- PIL:处理图像的好模块
- 计算机与音乐制作专业就业前景,计算机音乐制作专业就业形势不错
- mysql 下一年_mysql时间增加一年
- 隐形墨水笔上亚马逊要做什么检测?
- HTML+CSS系列学习:重生之我要精通编程语言修仙
热门文章
- T研究:国内云BPM市场规模尚小,预计2018年仅为3.29亿元
- 数据结构--hashtable(散列表)
- ZJOI2008 树的统计 树链剖分学习
- CodeIgniter辅助函数
- C#在类型实例化时都干了什么:从一道笔试题说开去
- DolphinDB配置
- android10手机众筹,最小Android 10手机?屏幕仅3英寸的Jelly 2开始众筹
- linux自动输入sudo密码_用大写字母输入 Linux 命令,实现以 sudo 用户权限运行
- C/C++排序算法(5)归并排序
- 数据挖掘的好书_唐宇迪:入门数据挖掘,我最推荐这本书