笔记序言
作为一个学习Java的人怎么能不看一遍Java的Bible呢?在这之前刷过《Head First Java》,但始终觉得不够全面,而且因为是‘刷’,就没有留下任何笔记。于是趁当当搞活动马上买了一本《Thinking in Java》回来,开卷之余,顺手就写下这个笔记。翻开目录一共二十二章,暂时打算每五章发一篇笔记,因为视情况可能会跳过图形化界面的章节。
第一章 对象导论
-
访问权限关键字(access specifier) : public, private, protected
饰词 本类 同一个包的类 子类 其他类 public √ √ √ √ protected √ √ √ × default √ √ × × private √ × × × -
单根继承结构
Java中所有的类最终都继承自单一的基类(Object)。 -
对象的创建和生命周期
Java采用动态内存分配方式,通过new关键字来构建对象的动态实例(基本类型是特例),所有的对象都在堆 (Heap)中分配空间。当“垃圾回收器”的机制发现对象不再被使用时,会销毁对象,自动释放对象占用的内存。
第二章 一切都是对象
-
内存分配
(1) 寄存器:最快的存储区,位于处理器内部,根据需求进行分配,在Java中不能直接控制。
(2) 堆栈:位于通用RAM(随机访问存储器)中,堆栈指针下移分配内存,上移释放内存。存放基本类型的变量数据和对象的引用,但对象本身不存放在栈中,而是存放在堆(new 出来的对象)或者常量池中(字符串常量对象存放在常量池中)。
(3) 堆:一种通用的内存池(也位于RAM区),用于存放所有的Java对象。
(4) 常量储存:常量值通常直接放在程序代码的内部,常量池存放字符串常量和基本类型常量(public static final)。
(5) 非RAM存储:储存于硬盘等永久存储空间。基本例子:流对象&持久化对象。 -
基本类型
Java每种基本类型所占存储空间的大小具有不变性,所有数值类型都有正负号,没有无符号类型。基本类型 大小 最小值 最大值 包装器类型 默认值 boolean - - - Boolean false char 16-bit Unicode 0 Unicode 2^16-1 Character ‘\u0000’(null) byte 8bits -128 +127 Byte (byte)0 short 16bits -215 +215-1 Short (short)0 int 32bits -231 +231-1 Integer 0 long 64bits -263 +263-1 Long 0L float 32bits IEEE754 IEEE754 Float 0.0f double 64bits IEEE754 IEEE754 Double 0.0d void - - - Void 高精度计算类:BigInteger & BigDecimal
-
数组
Java数组对象自动初始化为null,范围检查保证了数组无法越界访问。 -
作用域
Java的作用域由花括号的位置决定。 -
对象的作用域
Java对象可存活于作用域之外。在Java中我们只需要创建对象,一旦不再需要,垃圾回收器会让它们自行消失。 -
类
class ATypeName { /* Class body goes here */}
类中可设置字段(数据成员)和方法(成员函数) -
方法、参数、返回值
方法基本组成部分:名称在、参数、返回值、方法体。
方法的参数列表指定要传递给方法什么样的信息。 -
static关键字
当一个事物被声明为static时,就意味着这个域或方法不会与包含他它的那个类的任何对象实例关联在一起。静态数据成员可以通过对象名来定位它,也可以直接通过类名访问。静态方法雷同,自行类比。class StaticTest { static int i = 48; public static void main(String[] args) { StaticTest st = new StaticTest(); /*两种访问方式*/ st.i; //这里eclipse会建议使用下面那种静态方式访问 StaticTest.i; } }
-
第一个Java程序
java.lang默认导入到每个Java文件中,其所有类都可以被直接使用。
类的名字和文件名必须相同。 -
编码风格
“驼峰风格”
第三章 操作符
-
操作符“+”
"+"在System.out.println()中意味着字符串拼接,当“+”后面紧接着一个非String类型的元素时,就会尝试把这个非String类型的元素转换为String。 -
别名问题
class Tank { int level; } public class Assignment { public static void main(String[] args) { Tank t1 = new Tank(); Tank t2 = new Tank(); t1.level = 9; t2.level = 47; System.out.println("1: t1.level:" + t1.level + ", t2.level:" + t2.level); t1 = t2; //这里已经绑在一起了,两个引用指向同一个对象,老t1已经被回收 System.out.println("1: t1.level:" + t1.level + ", t2.level:" + t2.level); t1.level = 27; System.out.println("1: t1.level:" + t1.level + ", t2.level:" + t2.level); } } /*Output: 1: t1.level:9, t2.level:47 1: t1.level:47, t2.level:47 1: t1.level:27, t2.level:27 */
这里我们期望t1和t2是独立的,因此我们可以如此解决:
t1.level = t2.level;
-
对象的等价性
==
和!=
比较的是对象的引用。equals()方法用于比较对象的实际内容是否相同,但equals()方法不适用于基本类型,基本类型直接使用==
和!=
。
这里值得注意的是,equals()默认行为是比较引用,即默认是==
。大部分的java类已经实现了equals()方法的重写,使其比较对象的实际内容。但如果自己写的新类没有进行重写,则equals()方法还是比较的是对象的引用。 -
直接常量
直接常量的后缀字符标志了它的类型。
大写(或小写)的L,代表long(一般不用小写l因为容易和数字1混淆)。
大写(或小写)的F,代表float。
大写(或小写)的D,代表double。
0x(或0X)前缀 + 0-9或小写(或大写)的a-f来表示十六进制。
通过使用Integer、Long类的静态方法 toBinaryString() 输出二进制形式的结果。 -
指数计数法
e代表10的幂次,例如1.39e-43表示的是1.39×10-43 -
按位操作符
与(&),或(|),非(~),异或(^)
运算并赋值:&=,|=,^= -
移位操作符
<<
左移位操作符(低位补0)
>>
“无符号”右移位操作符(若符号为正,高位插入0;若符号为负,高位插入1)
>>>
“有符号”右移位操作符(无论正负,都在高位插入0)
只能对int型数据进行操作,如果对char,byte或者short类型的数值进行移位处理,那么在移位之前,他们会被转换为int类型,并且得到的结果也是一个int类型的值。 -
三元操作符
boolean - exp ? value0 : value1
第四章 控制执行流程
-
if-else语句
if(Boolean-exp) statement else statement
布尔表达式必须产生一个布尔结果,statement指用分号结尾的简单语句或复合语句(封闭花括号内的一组简单语句)
-
迭代语句
while语句while(Boolean-exp) statement
do-while语句(区别于while,它至少会执行一次)
do statement while(Boolean-exp)
for语句
for(initialization; Boolean-exp; step) statement
for语句的初始化表达式(initialization),布尔表达式(Boolean-exp),步进(step)都可以为空。
-
逗号操作符
只在for语句中起作用,在初始化,和步进控制部分使用,判断部分不能用,只能是一个布尔表达式,使用例子如下:public class CommaOperator { public static void main(String[] args) { for(int i = 1, j = i + 10; i < 5; i++, j = i * 2) { System.out.println("i = " + i + " j = " + j); } } } /*Output: i = 1 j = 11 i = 2 j = 4 i = 3 j = 6 i = 4 j = 8 */
-
Foreach语法
Java SE5引入的一种更简洁的for语法用于数组和容器,即foreach语法。任何返回一个数组的方法都可以使用foreach。for(Type ATypeName : TheSameTypeArray) statement
-
无条件分支关键字
Java中有多个关键词表示无条件分支,它们只是表示这个分支无需任何测试即可发生,包括return、break、continue以及一种与其他语言中的goto类似的跳转到标号语句的方式。 -
无穷循环
while(true)
和for(;;)
-
万恶的goto
Java中没有goto语句,但goto认识Java的一个保留字。Java中通过continue和break关键字使用与goto相同的机制:标签。
标签是后面跟有冒号的标识符,如label1:
。==在Java中需要使用标签的唯一理由是:因为有循环嵌套存在,而且想从多层嵌套中break或continue。例如:lable1: outer-iteration { inner-iteration { //... break; // (1) //... continue; // (2) //... continue label1; // (3) //... break label1; // (4) } }
在(1)中,break中断内部迭代,回到外部迭代。
在(2)中,continue使执行点移回内部迭代的起始处。
在(3)中,continue label1同时中断内部迭代以及外部迭代,直接转到label1处,随后。它实际上是继续迭代过程,但却从外部迭代开始。
在(4)中,break label1也会中断所有迭代,并回到label1处,但并不重新进入迭代,即它实际是完全中止了两个迭代。规则:
(1) 一般的continue会退回最内层循环的开头(顶部),并继续执行。
(2) 带标签的continue会到达标签的位置,并重新进入紧接在那个标签后面的循环。
(3) 一般的break会中断并跳出当前循环。
(4) 带标签的break会中断并跳出标签所指的循环。 -
switch语句
switch(integral-selector) { case integral-value1 : statement; break; case integral-value2 : statement; break; case integral-value3 : statement; break; case integral-value4 : statement; break; case integral-value5 : statement; break; //... default: statement; }
其中integral-selector(整数选择因子)是一个能够产生整数值的表达式。能用于switch判断的类型有:byte、short、int、char(JDK1.6),还有枚举类型,但是在JDK1.7后添加了对String类型的判断,这里注意并不支持long类型。
功能相同的case是可以合并的:switch(integral-selector) { case integral-value1 : case integral-value2 : statement; break; //... default: statement; }
第五章 初始化与清理
-
构造器
(1)构造器采用和类完全相同的名称("每个方法首字母小写"编码风格并不适用于构造器)。
(2)不接受任何参数的构造器叫做默认构造器,有形参的构造器可以在初始化对象时提供实参。
(3)在创建对象时,会为对象分配储存空间,并调用相应的构造器。
(4)构造器没有返回值。 -
方法重载
方法名相同但参数类型列表不同,每个重载的方法都必须有一个独一无二的参数类型列表。参数顺序不同也能区分重载方法,但一般不要这样做。 -
涉及基本类型的重载
当传入较小类型时,基本类型能从一个较小的类型自动提升至较大的类型。因此当找不到最合适的方法时,会自动向上转型到最接近的类型(byte-short-int-long-float-double)。char略有不同,如果找不到接受char参数的方法,会直接把char提升到int。
当传入较大类型时,需要我们手动进行窄化处理,否则会报错。 -
关于默认构造器
当我们没有手动创建一个构造器时,编译器会自动生成一个默认构造器(无参构造器)。当我们定义了一个构造器(无论有无参数),编译器不会帮我们创建默认构造器。 -
this关键字
this关键字只能在方法内部使用,表示对“调用方法的那个对象”的引用。this的用法和引用没有什么不同,但如果在方法内部调用同一类中的另一个方法就不必使用this,直接调用即可。
this可以实现在一个构造器中调用另一个构造器。注意的地方有三点:
①尽管可以用this调用一个构造器,但却不能调用两个。
②必须将构造器调用至于最起始处,否则编译器会报错。
③除构造器之外,编译器禁止在其他任何方法中调用构造器。 -
static的含义
在第二章的第8条笔记提到过static关键字。可以在没有创建任何对象的前提下,仅仅通过类本身来调用static方法。 这正是static方法的主要用途。 -
清理:终结处理和垃圾回收
Java垃圾回收的三个注意点:
①对象可能不被垃圾回收
②垃圾回收不等于析构
③垃圾回收只跟内存有关
使用垃圾回收器的唯一原因:回收程序不再使用的内存。
无论是“垃圾回收”还是“终结”,都不保证一定会发生。如果Java虚拟机(JVM)并没有面临内存耗尽的情形,它是不会浪费时间去执行垃圾回收以恢复内存的。一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用其finalize()方法, 并且在下一次垃圾回收动作发生时,才会真正的回收对象占用的内存。一般不建议使用finalize()方法。
Java虚拟机将采用一种自适应的垃圾回收技术:模式 简述 优点 缺点 适用情形 停止-复制(stop-and-copy) 需要先将程序暂定,然后将有效的全部复制到另外一片内存中,没有复制的就是需要清除的。 新堆是有序的,一个一个的紧凑排列的。 当没有很多需要回收的对象时,也要进行复制。 适用于需要清除的对象比较多的情况。 标记-清扫(mark-and-sweep) 遍历所有的引用,当引用为存活状态的时候,就进行标记,这个过程不会回收任何的对象,只有当全部标记完成之后才进行释放。 当需要回收的对象比较少的时候效率会比较高。 剩下的堆空间不是连续的,垃圾回收如果希望是连续空间的话,就要重新整理剩下的对象。 适用于产生很少垃圾甚至不会产生垃圾的情景。 -
成员初始化
类的每个基本类型数据成员保证都会有一个初始值。详见第二章第2条笔记。 -
构造器初始化
初始化顺序:在类的内部,变量定义的先后顺序决定了初始化的顺序。
静态数据的初始化:无论创建多少个对象,静态数据都只占用一份存储区域。先初始化静态对象,然后再初始化非静态对象。静态初始化只会进行一次。
初始化块:初始化块优先于构造器进行初始化。
总结:(静态变量、静态初始化块) > (变量、初始化块) > 构造器 -
数组初始化
有两种方式可以定义数组int[] a1;
和int a1[];
。一般建议使用第一种。
使用花括号来初始化数组int a1 = {1, 2, 3, 4, 5};
,初始化数组int[] a2;
,并且令a2 = a1
,这里a1和a2是相同数组的两个别名,通过a2进行的修改a1也能看到。这里实际做的只是复制了一个引用。
数组元素中的基本数据类型值会自动初始化成空值(对于数字和字符,就是0;对于布尔型,是false)。
如果创建的是一个引用数组,需要先创建新的对象,并把对象赋值给引用,初始化才算结束。如果忘记了创建对象,并且试图使用数组中的空引用,就会在运行时产生异常。也可以用花括号括起来的列表来初始化对象数组,有两种方式:public class ArrayInit { public static void main(String[] args) { Integer[] a = { new Integer(1), new Integer(2), 3, //Autoboxing }; Integer[] b = new Integer[]{ new Integer(1), new Integer(2), 3, //Autoboxing }; System.out.println(Arrays.toString(a)); System.out.println(Arrays.toString(b)); } } /*Output: [1, 2, 3] [1, 2, 3] */ /*这里是书上的代码,new Integer(num)这种构造器方法在jdk1.9里已经被废弃,不过还能用。*/
初始化列表的最后一个逗号都是可选的(这一特性使维护长列表变得更容易)。
-
可变参数列表
创建以Object数组为参数的方法来实现:class A {} public class VarArgs { static void printArray(Object[] args) { System.out.println(); for(Object obj : args) { System.out.print(obj + " "); } } public static void main(String[] args) { printArray(new Object[]{new Integer(47), new Float(3.14), new Double(11.11)}); printArray(new Object[]{"One", "Two", "Three"}); printArray(new Object[]{new A(), new A(), new A()}); } } /*Output: 47 3.14 11.11 One Two Three com.A@4c3e4790 com.A@38cccef com.A@5679c6c6 */
以上代码是在Java SE5之前的实现方法,Java SE5加入了新特性来实现这个功能:
public class NewVarArgs { static void printArray(Object... args) { System.out.println(); for(Object obj : args) { System.out.print(obj + " "); } } public static void main(String[] args) { // Can take individual elements: printArray(new Integer(47), new Float(3.14), new Double(11.11)); printArray(47.3, 3.14F, 11.11); printArray("one", "two", "three"); printArray(new A(), new A(), new A()); // Or an array: printArray((Object[])new Integer[]{1, 2, 3, 4}); printArray(); //Empty list is OK } } /*Output: 47 3.14 11.11 47.3 3.14 11.11 one two three com.A@4c3e4790 com.A@38cccef com.A@5679c6c6 1 2 3 4 */
最后一行表明0个参数传递给可变参数列表也可以,这很有用,因为这说明参数可有可无。Object之外类型的可变参数列表,如String…(这里所有的可变参数都必须是String对象)。在可变参数列表中可以使用任何类型的参数,包括基本类型。
使用可变参数列表会让重载变得复杂,如果一个方法带有非可变参数和可变参数列表,则在重载这个方法时也要必须要有非可变参数。 -
枚举类型
Java SE5添加的一个小特性——enum关键字。举例创建一个具有5个具名值名为Spiciness的枚举类型://: Spiciness.java public enum Spiciness { NOT, MILD, MEDIUM, HOT, FLAMING }
由于枚举类型的实例是常量,因此按照命名规则惯例它们都用大写字母表示(如果在一个名字中有多个单词,用下划线将它们隔开)。为了使用enum,需要创建一个该类型的引用,并将其赋值给某个实例:
//: SimpleEnumUse.java public class SimpleEnumUse { public static void main(String[] args) { Spiciness howHot = Spiciness.MEDIUM; System.out.println(howHot); } } /*Output: MEDIUM */
上面代码编译器自动添加了toString()方法,以便显示某个enum实例的名字。另外,编译器还会自动创建 ordinal() 方法,用来表示某个特定enum常量的声明顺序,以及 static values() 方法,用来按照enum常量的声明顺序产生由这些常量值构成的数组。举例如下:
//: EnumOrder.java public class EnumOrder { public static void main(String[] args) { for(Spiciness s : Spiciness.values()) { System.out.println(s + ".ordinal " + s.ordinal()); } } } /*Output: NOT.ordinal 0 MILD.ordinal 1 MEDIUM.ordinal 2 HOT.ordinal 3 FLAMING.ordinal 4 */
可以看出其实enum就是一个类。enum有一个实用特性,它可以在switch语句内使用:
//: Burrito.java public class Burrito { Spiciness degree; public Burrito(Spiciness degree) {this.degree = degree;} public void describe() { System.out.print("This burrito is "); switch(degree) { case NOT: System.out.println("not spicy at all."); break; case MILD: case MEDIUM: System.out.println("a little hot."); break; case HOT: case FLAMING: default: System.out.println("maybe too hot."); } } public static void main(String[] args) { Burrito plain = new Burrito(Spiciness.NOT), greenChile = new Burrito(Spiciness.MEDIUM), jalapeno = new Burrito(Spiciness.HOT); plain.describe(); greenChile.describe(); jalapeno.describe(); } } /*Output: This burrito is not spicy at all. This burrito is a little hot. This burrito is maybe too hot. */