Thinking In Java
目录:
第一章——简介
第二章——一切皆对象
1. 标识符
2. 引用所指的对象必须由本人创建
3. 基本类型
4. Static
* Tips
5. JavaDoc
* Tips
第三章——操作符
1. +号
2. =号
* Tips
3. ++和–
4. ==和equals
5. 逻辑操作符
6. 直接常量
7. 指数表示
8. 按位操作符
9. 三元操作符
10.类型转换
第四章——控制执行流程
1. for语句中的逗号操作符
2.For Each语句
3.无条件分支语句(Return、Break、Continue、Goto)
4.Switch
第五章 初始化与清理
1.构造器来确保初始化
2.方法重载
3.this关键字
4.清理:finalize()和gc()
5.垃圾回收工作机制
6.成员初始化
第一章——简介
Java编程思想 第四版 Bruce Eckel著 陈昊鹏译
源代码资源位于 www.MindView.net
第二章——一切皆对象
1. 标识符
- 常见的String s语句只是创建了一个引用,并不是对象。
- 可以把引用当做遥控器,对象当做电视机。遥控器保证与电视机的连接并控制电视机。但是我们实际操作的是遥控器(引用),再由遥控器控制电视机。
2. 引用所指的对象必须由本人创建
-
如常用的String s = “abc”;就相当于String s = new String(“abc”);前半句创建引用,后半句创建对象,=将引用指向了对象。
在Java中,一个引用可以指向0个或者1个对象,一个对象可以有0个或者1个或者多个引用。如在上面的代码中加入s2=s;就相当于String对象”abc”增加了一个引用s2。
-
熟悉了上面的说明,做下这个测试吧,s1~6的相等关系是(内存等,非值等)?
String s1 = "abc";
String s2 = s1;
String s3 = "abc";
String s4 = new String("abc");
String s5 = s4;
String s6 = new String("abc");
s1 = s2 = s3 != s4
s4 = s5 != s6
-
计算机的数据存储从快到慢分为寄存器、堆栈、堆、常量存储、非RAM存储。引用位于堆栈中,而对象位于堆中。
3. 基本类型
- Java中基本类型在不同架构的机器上占用相同的存储空间(Java良好的移植性)。
基本类型 | 大小 | 最小值 | 最大值 | 包装器类型 |
---|---|---|---|---|
boolean | - | - | - | Boolean |
char | 16 bits | Unicode 0 | Unicode 2^16 -1 | Character |
byte | 8 bits | -128 | +127 | Byte |
short | 16 bits | -2^15 | +2^15 -1 | Short |
int | 32 bits | -2^31 | +2^31 -1 | Integer |
long | 64 bits | -2^63 | +2^63 -1 | Long |
float | 32 bits | IEEE754 | IEEE754 | Float |
double | 64 bits | IEEE754 | IEEE754 | Double |
void | - | Void |
-
转换关系:
byte也叫字节,简称B。1B = 8 bit。bit也叫比特,是一个二进制单位。1KB = 1024B。
-
高精度的整型BigInteger和浮点型BigDecimal不属于基本类型,详情参考JDK。
4. Static
- static 修饰的成员变量和函数,对该类和该类所有的实例都只有一份存储空间(可以说是类和类的实例共享的)。调用方法可以是实例名.或者类名.
* Tips
- 函数的返回类型若为void,则return方法只是用来退出函数。
- java.lang是默认导入到每一个Java文件中的。
5. JavaDoc
-
Java解决了程序员同时需要维护文档和代码两份无关联文件的繁琐。Java可以将代码中的注释生成为HTML格式。
Java终端采用命令 javadoc xxx.java即可。对于中文doc采用utf-8编码,即 javadoc xxx.java -encoding UTF-8 -charset UTF-8
-
Java中的注释有两种( /*java*/ 和 //java ),但是javadoc可以导出的是下列方式的注释:
/** This is a annotation */
/**
* This is an annotation
* @author Aran
* @param args
*/
-
注释要有注释的规范,java的注释主要有类注释、域注释和方法注释,分别出现在类、域(变量定义)和方法定义之前。此外还有一些约定俗成的标签,如@param,@author等等,在此不做赘述。
-
eclipse也可以对整个project导出doc,并且可以自己编写doclets自定义javadoc的导出规则。这些自行参考网上文献,再次不做赘述。
* Tips
- 组成类的所有单词首字母大写(不需要连接线),变量函数的首个单词首字母小写,其它大写。
第三章——操作符
1. +号
- +号除了传统的加,+还意味着”字符串连接”,如果必要,它还要执行”字符串转换”。当编译器观察到一个String后紧跟一个+后又紧跟一个非String类型的元素时,就会尝试将这个非String类型的元素转换为String型。
2. =号
- 赋值操作分类两种类型。分别是对基本数据类型的赋值和对针对对象的赋值。前者直接将一个地方的内容复制到了另一个地方;后者只是引用之间的复制,引用所指的依然是原本的对象。
* Tips
- 一个java文件中可以有多个类,但是只能有一个和java文件同名的public类。其它类直接定义为class 类名{}
- java中生成random数据须使用java.util.Random类,并为初始化函数提供种子。(不提供则采用当前时间作为种子)对实例调用.nextInt/Float/Long/Double()获取随机数字。(注意int型的下限为0)
3. ++和–
- 对于前缀运算符会先执行运算,在生成值;对于后缀运算符会先生成值再执行运算。在这里尤其注意java 中的中间缓存变量机制带来的一些bug,参考本Wiz中的《Java中的中间缓存变量机制》。
4. ==和equals
- equals主要用来比较内容等,而==是比较对象的引用。类似于Python当中的==和is。
5. 逻辑操作符
- 即 与&& 或|| 非! 和C和C++一样java的逻辑运算中也会出现短路现象:在前部分的运算得到表达式结果后不再计算后面的表达式。但是和C和C++不一样的是,
- Java中的或与非操作只可应用于布尔值,不可用于Int型。而且Int型和布尔型也没有任何的转换关系,如if(0)的做法在Java中是错误的。 但是在该是用String型的时候是用了布尔型,布尔值会自动转换成为适当的文本类型。
6. 直接常量
- java允许在基本类型的数值后面加上L(表示Long)、F(表示Float)、D(表示Double)来明确指定一个数的数据类型。如float i = 0.0F;
7. 指数表示
- 采用底数e代表10来表示指数。如1.39*10^-43这样的数采用1.39E-43F来表示。(后面的f见上文直接变量)
8. 按位操作符
- 与& 或| 非~ 异或^
- 操作对象都是单个比特,即二进制位。对于布尔值,不能执行非的操作。且不会短路操作。
- 异或,就是相异为真,相同为假。
ps. 移位操作符在本书p49页,使用较少,在此不做赘述。
9. 三元操作符
- boolean-exp ? value0 : value1
- 如果boolean-exp为真,则输出value0;为假则输出value1。
10.类型转换
-
java中的类型转换分为两种:
- 窄化转换,即将能容纳更多数据信息的类型转换为能容纳较少数据信息的类型。
- 扩展转换,即将能容纳较少数据信息的类型转换为能容纳较多数据信息的类型。
-
窄化转换,一般而言需要显时表示。从long型、float型、double型转换为int,从int型转换为char、short、byte都是窄化。需要 (新类型)value 来表示。
-
扩展转换一般默认进行,为了规范,建议显时标注。
-
窄化转换的截尾和舍入:
默认的窄化转换,即从float转int是直接执行截尾,把小数点后面数字直接去除。
如果想要四舍五入,则需要使用java.lang.Math中的round()方法。
-
扩展转换的提升:
一个计算式中同时出现好几种类型,则会把计算结果提升为最大类型。
第四章——控制执行流程
包含if-else、while、do-while、for、for each、return、break和switch语句。
1. for语句中的逗号操作符
-
不同于逗号分隔符(常用于分割函数参数),这里的逗号操作符for语句的控制表达式中的初始化和步进控制部分。
-
如示例:
for (int i = 1, j = i + 10; i < 5 && j < 30; i++, j = j * 2) {
System.out.println("i = "+i+" j = "+j);
}
2.For Each语句
ForEach语句是一种更加简洁的for语句,常用于数组、容器和Iterable对象。
ForEach语法可以不必创建int变量去对由访问项构成的序列进行计数,ForEach将自动产生每一项。
语法的默认表达式为:for(类型 x : 数组/容器){ x… }
-
以给float数组赋值和显示为例:
Random rand = new Random();
float[] f = new float[10];
for (float x : f)
{
x = rand.nextFloat();
System.out.println(x);
}
-
以给String对象迭代显示为例:
for(char x : "Happy Spring Festival".toCharArray())
{
System.out.print(x+".");
}
这里使用了String类中的toCharArray()函数,把String类转换成char数组。
3.无条件分支语句(Return、Break、Continue、Goto)
-
Return
return会导致当前方法退出并返回那个值。如果一个方法声明并非返回void,那么必须确保每一条代码路径都返回一个值。
-
Break和Continue
前者强行退出循环,不执行循环中剩余的语句。后者则只是停止执行当前的迭代,退回循环起始处并开始下一次迭代。
-
goto
尽管java中保留了goto的功能,但并没有goto关键字。采用break、continue+标签的方法,可以实现goto的功能。
标签是后面跟有冒号的标识符,类似:
label1:
先上结论:
- 一般的continue会退回最内层循环的开头(顶部),并继续执行。
- 带有标签的continue会到达标签的位置,并重新进入紧接在那个标签后面的循环。
- 一般的break会中断并跳出当前循环。
- 带有标签的break会中断并跳出标签所指的循环。
java 中的标签后面必须紧跟循环语句,否则会被忽略掉从而在continue和break语句中无法识别。
通过代码来加深理解(参考P71-72页代码):
public static void main(String[] args) {
int i = 0;
outer:
for (; true;) {
System.out.println("Enter outer-for");
inner:
for (; i < 10; i++) {
System.out.println("Enter inner-for");
System.out.println("i = " + i);
if (i == 1) {
System.out.println("continue");
continue;
}
if (i == 2) {
System.out.println("break");
i++;// 否则i无法自增
break;
}
if (i == 3) {
System.out.println("continue inner");
continue inner;
}
if(i == 4){
System.out.println("break inner");
i++;// 否则i无法自增
break inner;
}
if(i == 5){
System.out.println("continue outer");
i++;// 否则i无法自增
continue outer;
}
if(i == 6){
System.out.println("break outer");
break outer;//直接退出
}
}
}
}
出现的结果是:
Enter outer-for
Enter inner-for
i = 0
Enter inner-for
i = 1
continue
Enter inner-for
i = 2
break
Enter outer-for
Enter inner-for
i = 3
continue inner
Enter inner-for
i = 4
break inner
Enter outer-for
Enter inner-for
i = 5
continue outer
Enter outer-for
Enter inner-for
i = 6
break outer
xxxxxxxxxx1 1
值得注意的是,break和continue outer的操作会导致内层循环不执行++,这个要理清楚。
4.Switch
标准的switch范式如下:
switch(integral-selector){
case integral-value1 : statement; break;
case integral-value2 : statement; break;
default: statement; break;
}
使用switch语句需要注意一下几点:
- 语句中integral-selector(整数选择因子)是一个能够产生整数的表达式。switch会个将表达式的结果与每个integral-value对比,若相符则执行对应语句。若没有相符的,则执行default语句。
- 整数选择因子必须是int或者char型的整数值。
- 进入相应的case后,若没有break,则顺序执行下面的语句。default也是这样!
下面的例子(由P74-75,4.8改编)判断a-z哪些是元音哪些是辅音:
public static void main(String[] args) {
for (int i = 0; i < 26; i++) {
System.out.print((char)('a' + i) + ":");
switch ('a' + i) {
case 'a':
case 'e':
case 'i':
case 'o':
case 'u':
System.out.println("vowel");
break;
case 'y':
case 'w':
System.out.println("sometimes vowel");
break;
default:
System.out.println("consonant");
}
}
}
利用了case下没有break就依次顺序执行的原理。
第五章 初始化与清理
1.构造器来确保初始化
-
Java中构造器采用和类相同的名字。
-
构造器不会返回任何东西(没有返回值)。
-
java对没有自定义构造器的类提供默认构造器。如果自定义了任何一个构造器,则该类不再具有默认构造器。
2.方法重载
-
Java通过每个相同函数名的独一无二的参数类型列表来区分不同的重载方法。(参数顺序的不同也可以)
-
不同的参数数据类型可以作为重载的方法,而数据类型又存在类型转换的问题。方法重载时的类型专函遵循以下规则:
- 如果传入的数据类型窄于方法中声明的形式参数类型,那么实际的数据类型就会被提升。如,info(float a){}函数,也可以接受int型的参数,此时int型的参数自动提升为float型。同理,char类型可以以自动提升为int类型。
- 如果传入的数据类型宽于方法中声明的形式参数类型,那么就报错了。需要程序员进行显示显式类型转换。
- 返回值无法区分函数重载。
3.this关键字
-
this关键字只能在方法内部使用,表示对”调用方法的那个对象”的引用。如果要在方法内部调用同一个类的另一个方法,则没有必要使用this。
-
在类中函数通常通过返回this来返回当前对象的引用。
-
this关键字还可以当做构造器来使用。但是在一个方法中只能指代一个构造器。见下面代码:
public class My0504 {
My0504(int i) {
System.out.println(i);
}
My0504(String s) {
System.out.println(s);
}
My0504(int i, String s){
this(i);//正确,相当于调用My0504(int i)
this(s);//错误,此时this已经指代My0504(int i)
}
}
-
static方法中不能使用this,函数内还是函数参数都不行。事实上,static方法中也只能直接调用static方法。
4.清理:finalize()和gc()
- java允许在类中定义一个名为finalize()的方法,它的工作原理”假定”是:一旦JVM中的垃圾回收器准备好释放对象占用的存储空间,则其会首先调用该对象的finalize()方法。并在下一次垃圾回收动作发生时,真正的回收对象占用的空间。
- 理解finalize需要理解一下三点:
- 对象可能不被垃圾回收。
- 垃圾回收不等于”析构”。
- 垃圾回收只与内存有关。
- system.gc()只是告诉垃圾收集器打算进行垃圾收集,而垃圾收集器进不进行收集是不确定的。
5.垃圾回收工作机制
- 详见该书89-91页。和wiz笔记的”Java 垃圾回收”。
6.成员初始化
- 基本类型未初始化是否有初值?在函数内定义的未初始化的基本类型,没有初值。在类内定义的未初始化的基本类型,拥有初值。分别是
类型 | 未初始化时的默认值 |
---|---|
boolean | false |
char | (char值为0,显示为空白) |
byte | 0 |
short | 0 |
int | 0 |
long | 0 |
float | 0.0 |
double | 0.0 |
引用 | null |
-
-
上述情况说明,在类创建的过程,无论开发者有没有指定,类内的成员变量都会进行初始化。如果用户指定初始化,那么需要注意初始化的顺序问题:
public class My0506 {
int i = f();//正确
int k = j;//错误,向前引用
int f(){
return 11;
g();//正确
}
void g(){
System.out.println("hello");
}
int j =11;
}
可以看到,函数可以不分顺序的引用,但是成员变量的引用要注意定义的顺序。
-
上面讨论了默认初始化、指定初始化,下面讨论构造器初始化。
public class Counter{
int i;
Counter(){
i = 7;
}
}
在上述类中i首先被初始化为0,再被置为7。
构造器初始化要特别注意初始化顺序的问题。简而言之,就是类会优先执行默认初始化或者指定初始化,然后再执行构造器初始化。定义的先后顺序并不影响该初始化顺序。
-