《Java编程思想》阅读笔记一

时间:2022-11-29 22:27:58

Java编程思想

这是一个通过对《Java编程思想》(Think in java)第四版进行阅读同时对java内容查漏补缺的系列。一些基础的知识不会被罗列出来,这里只会列出一些程序员经常会忽略或者混淆的知识点。

所列知识点全部都是针对自己个人而言,同时也欢迎大家进行补充。


第一章(对象导论)

public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello every one,I'm cpacm");
}
}

这一章是java的整体介绍,让我们先熟悉了java是什么。其具体的内容都会在后面介绍。

面向基本语言的五大特性

  • 万物皆为对象。
  • 程序是对象的集合,它们通过发送消息来告知彼此所要做的。
  • 每个对象都拥有其类型。
  • 某一特定类型的所有对象都可以接收同样的消息。

第二章(一切都是对象)

一、存储

p22

栈(堆栈):存放基本类型变量和对象引用变量。位于RAM区

堆:存放new得到的对象和数组。也位于RAM区

常量存储:存放常量,包括静态变量。

二、基本类型

p26

基本数据类型在没有初始化的时候会获得一个默认值。

基本数据类型 默认值 大小
boolean false 未确定
char null 16bits
byte 0 8bits
short 0 16bits
int 0 32bits
float 0f 32bits
long 0L 64bits
double 0d 64bits

tip1:String不是基本数据类型

tip2:上面的初始默认值并不适用于方法内部变量。其默认初始化值未知。当使用未初始化的变量编译器会返回错误

public class BaseType {
static boolean b;
static char c;
static byte bt;
static short s;
static int i;
static float f;
static long l;
static double d; public static void main(String[] args) {
System.out.println("类变量——"+"boolean:"+b+" char:"+c+" byte:"+bt+" short:"+s+" int:"+i+" float:"+f+" long:"+l+" double:"+d);
} }
//类变量——boolean:false char: byte:0 short:0 int:0 float:0.0 long:0 double:0.0

第三章(操作符)

一、别名现象

p40

当两个变量包含的是同一个引用时,修改其中一个变量的值另一个变量的值也同时改变。记住new出来的对象的=赋值都是只传递引用。

Tip:减少为对象赋值。

class T{
int i;
} public class Assigment { public static void main(String[] args) {
T t1 = new T();
T t2 = new T();
t1.i = 5;
t2.i = 8;
t1 = t2;
t1.i = 10000;
System.out.println(t2.i);
} }
//10000

二、equals方法

p45

在自定义的对象中使用equals方法时需要覆盖此方法,否则默认是比较引用

三、短路

p47

其现象本质为:当已经确定一个逻辑表达式的结果时不会再计算剩余的部分。

public class ShortCircuit {

	public static void main(String[] args) {
// TODO Auto-generated method stub
boolean flag1 = test1()&&test2()||test3()&&test4();
System.out.println("\n");
boolean flag2 = test1()&&test3()||test2()&&test4();
} static boolean test1(){
System.out.println("test1");
return true;
}
static boolean test2(){
System.out.println("test2");
return false;
}
static boolean test3(){
System.out.println("test3");
return true;
}
static boolean test4(){
System.out.println("test4");
return false;
} }
/*
test1
test2
test3
test4 test1
test3
*/

boolean flag2 = test1()&&test3()||test2()&&test4();

若test1为true,test3为true时,因为前面这部分已经确定为true,所以后面部分不会被调用。

四、e

p49

科学与工程领域中,"e"代表自然对数的基数,为2.718。

而在C,C++和java(或者更多的语言)中,"e"代表“10的幂次”

$1.39e-43f = 1.39*10^{-43}$

五、位操作

p49

与,或,异或,非 &,|,^,~

与:所有的位都为1则输出1,否则输出0;

或:只要有一个位是1就输出1;

异或:两个位值相等时输出1;

非:1输出0,0输出1.

位运算不会出现短路现象。

p50

移位操作符:

$<<$:操作数向左移动,低位补0;

$>>$:操作数向右移动,(1)符号为正时,高位补0,(2)符号为负时,高位补1;

$>>>$:java独有操作符,操作数向右移动,高位统一补0。

char,byte,short进行移位操作时先会转成int类型,即32位

public class URShift {

	public static void main(String[] args) {
int i = 1024;
System.out.println(Integer.toBinaryString(i));
i >>= 10;
System.out.println(Integer.toBinaryString(i));
i = -1;
System.out.println(Integer.toBinaryString(i));
i >>>= 10;
System.out.println(Integer.toBinaryString(i));
i <<= 1;
System.out.println(Integer.toBinaryString(i));
short s = -1;
s >>>= 10;//s移位后得到的结果在赋值时会强行转为int,所以移位后的s已经是int型
System.out.println(Integer.toBinaryString(s));
s = -1;
System.out.println(Integer.toBinaryString(s>>>10));
} }
/*
10000000000
1
11111111111111111111111111111111
1111111111111111111111
11111111111111111111110
11111111111111111111111111111111
1111111111111111111111
*/

六、截尾

p55

将float或double转型为整数值时,总是对数字进行截尾,不会进行四舍五入。如果想要得到舍入的结果可以使用Math.round()

public class CastingNumbers {

	public static void main(String[] args) {
double d = 1.7d;
int i = (int)d;
System.out.println(i);
i = (int) Math.round(d);
System.out.println(i);
}
}
//1
//2

第四章(控制执行流程)

一、goto 标签

goto关键词,在java中则是使用标签代替臭名昭著的goto。其实在java中也是最好不要用标签来跳转语句,太伤智商。。

写法

outer:

并放在迭代语句前。

(1)continue 标签

跳出所有循环,并到标签位置的语句,但会重新进入紧接后面的循环里。

(2)break 标签

跳出所有循环,且不再进入紧接后面的循环里。

public class LabeledFor {

	public static void main(String[] args) {
System.out.println("测试continue————————————");
label:
for(int i=0;i<3;i++){
System.out.println("外部for循环"+i);
for(int j=0;j<3;j++){
if(j==1){
continue label;
}
System.out.println("内部循环"+j);
}
}
System.out.println("测试break————————————————");
label2:
for(int m=0;m<3;m++){
System.out.println("外部for循环"+m);
for(int n=0;n<3;n++){
if(n==1){
break label2;
}
System.out.println("内部循环"+n);
}
}
} }
/*
测试continue————————————
外部for循环0
内部循环0
外部for循环1
内部循环0
外部for循环2
内部循环0
测试break————————————————
外部for循环0
内部循环0*/

第五章(初始化与清理)

一、重载

p81

重载主要以传入参数及顺序来区别。不能通过返回值来区别

以基本数据类型传入时:自动包装机制

(1)若传入的数据类型小于方法中声明的形式参数类型,实际数据类型就会提升。

byte->short->int->long->float->double

(2)如果传入的实际参数较大,就得通过类型转换执行窄化转换。

二、垃圾回收机制

p91

标记-清扫(Android中使用这个技术)

从堆栈和存储区出发,遍历所有的引用,进而找出所有存活的对象,并给与标记。遍历完成后,清理所有未被标记的对象。

停止-复制

先暂停程序的运行,然后将所有存活的对象复制到另一个堆,而没有复制的是可回收的内存。

三、初始化顺序

p94

所有的变量都会在任何方法(包括构造器)被调用之前得到初始化。

p95

无论创建多少对象,静态数据都只占用一份存储区域,static不能作用于局部变量。且静态初始化动作只执行一次。

四、可变参数列表。

p102

void method(Object... args){}

调用 method();或method(new Object[]{1,2,3,4});

第七章(复用类)

一、toString()的自动调用

p126

有时候编译器会自动帮你调用toString()方法。

"source"+ source;这时候会自动调用source对象的toString方法。

二、构造函数调用顺序

p130

构造函数总是从基类开始。

class Insert{
Insert(){
System.out.println("Insert");
} } public class Beetle extends Insert{ Beetle(){
System.out.println("Beetle");
} public static void main(String[] args) {
// TODO Auto-generated method stub
Beetle b = new Beetle();
} }
//Insert
//Beetle

三、final

p140

对于基本类型,final使数值恒定不变;而用于对象引用,final使引用恒定不变,但对象本身是可以改变的。

private 属于 final 方法

static final是属于类属性,即能被类调用,不用实例化

final则需要实例化。

四、组合模式

组合模式可以看做是一颗树,每个枝干都可以长出新的枝干,它们的结构都是相同的。

将枝干抽象为一个类,里面包含链接下一个节点的方法,若需要不断的链接下一个节点只需要继承这个方法并实现。

示例:导航菜单

组合模式,将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。

public abstract class Tab {
private String title; public Tab(String title) {
this.title = title;
} protected abstract void add(Tab tab); protected abstract void romove(Tab tab);
} public class CardTab extends Tab{ public CardTab(String title) {
super(title);
// TODO Auto-generated constructor stub
} private List<Tab> tabs; @Override
protected void add(Tab tab) {
// TODO Auto-generated method stub
tabs.add(tab);
} @Override
protected void romove(Tab tab) {
// TODO Auto-generated method stub
tabs.remove(tab);
} } public class TabView {
public static void main(String[] args) {
// TODO Auto-generated method stub
CardTab rootTab = new CardTab("roottab");
CardTab tab1 = new CardTab("tab1");
CardTab tab2 = new CardTab("tab2");
CardTab tab3 = new CardTab("tab3");
rootTab.add(tab1);
rootTab.add(tab2);
rootTab.add(tab3);
CardTab tab4 = new CardTab("tab1-1");
CardTab tab5 = new CardTab("tab1-2");
tab1.add(tab4);
tab1.add(tab5);
}
} /**
* 这样子Tab组成了一个导航列表,这就是一个简单的组合模式.
*/

第八章(多态)

多态是一项让程序员“将改变的事物与未变的事物分离开来”的重要技术。

一、多态缺陷

p156

缺陷1:只有非private方法才可以被覆盖

class Super{
public int field = 0;
public int getField(){return field;};
} class Sub extends Super {
public int field = 1;
public int getField() { return field; }
public int getSuperField() { return super.field; }
} public class FiledAccess {
public static void main(String[] args) {
Super sup = new Sub(); // Upcast
System.out.println("sup.field = " + sup.field +
", sup.getField() = " + sup.getField());
Sub sub = new Sub();
System.out.println("sub.field = " +
sub.field + ", sub.getField() = " +
sub.getField() +
", sub.getSuperField() = " +
sub.getSuperField());
}
} 输出:
sup.field = 0, sup.getField() = 1
sub.field = 1, sub.getField() = 1, sub.getSuperField() = 0

缺陷2:域和静态方法直接在编译时候进行解析,所以多态不会对其产生作用。

class StaticSuper {
public static String staticGet() {
return "Base staticGet()";
}
public String dynamicGet() {
return "Base dynamicGet()";
}
} class StaticSub extends StaticSuper {
public static String staticGet() {
return "Derived staticGet()";
}
public String dynamicGet() {
return "Derived dynamicGet()";
}
} public class StaticPolymorphism {
public static void main(String[] args) {
StaticSuper sup = new StaticSub(); // Upcast
System.out.println(sup.staticGet());
System.out.println(sup.dynamicGet());
}
} /* Output:
Base staticGet()
Derived dynamicGet() */

二、断言

p153

@Override作用:

断言,如果我们使用了这种annotation在一个没有覆盖父类方法的方法时,java编译器将以一个编译错误来警示

三、构造器构造顺序

p158

构造器在多态时的构造顺序:

class Meal {
Meal() {
P.print("Meal()");
}
} class Bread {
Bread() {
P.print("Bread()");
}
} class Cheese {
Cheese() {
P.print("Cheese()");
}
} class Lettuce {
Lettuce() {
P.print("Lettuce()");
}
} class Lunch extends Meal {
Lunch() {
P.print("Lunch()");
}
} class PortableLunch extends Lunch {
PortableLunch() {
P.print("PortableLunch()");
}
} public class Sandwich extends PortableLunch {
private Bread b = new Bread();
private Cheese c = new Cheese();
private Lettuce l = new Lettuce(); public Sandwich() {
P.print("Sandwich()");
} public static void main(String[] args) {
new Sandwich();
} } class P {
public static void print(String s){
System.out.println(s);
}
}
输出:
Meal()
Lunch()
PortableLunch()
Bread()
Cheese()
Lettuce()
Sandwich()

解释说明:

一个继承类实例化的时候必须要确保所使用的成员已经构建完毕,所以必须先调用基类的构造器,所以当实例化Sandwich对象时先调用其基类的构造方法:

Meal()

Lunch()

PortableLunch()

其次对成员变量进行初始化

Bread()

Cheese()

Lettuce()

最后调用构造器

Sandwich()

三、构造器初始化

p163

初始化的过程:

(1)在所有事物发生之前,将分配给对象的存储空间初始化为二进制的零。

(2)调用基类构造器。

(3)按照声明顺序调用成员的初始化方法。

(4)调用导出类(本体)的构造器主体。