《Head First Java》(点击查看详情)
1、写在前面的话
这本书的知识点说实话感觉有点散乱,但是贵在其将文字转换成了生动和更容易接受的图片,大量的比喻让人感受到了知识点的有趣之处,让学习不再那么枯燥无奈。
掌握了Java的一些基础之后,现在回过头来再看这本书,依然觉得收获颇丰,于是便把那些让人印象深刻的地方记录下来,总结、思考和分享,出于精力和效率,这里就不再细致地去罗列和整理每个知识点了(作为强迫症不得不说真的有过这样的想法,可是想着未来还有好多书要看,每本都流水一样地写,我得把自己弄疯掉)。
2、类与对象
类和对象到底有什么区别?类,其实就是用来创建对象的模型。女娲造人,只需要知道是有胳膊有腿的人类就好了(类),至于捏出来的是帅哥还是美女,高大还是矮小,那又是另一个概念了(对象)。
3、认识变量
Java中的变量有两种类型:primitive主数据类型(即我们常说的基本数据类型)和引用(引用数据类型)。不论是哪种数据类型,在Java面前都要求严格遵守它的管理规则,不允许类型之间的胡乱转换,比如是绝不会允许把长颈鹿类型变量放进兔子类型变量中去。接下来,我们就来分别认识这两种数据类型,和他们之间的一些变换规则。
3.1 主数据类型(基本数据类型)
变量就像杯子,不同的数据类型,往往代表了它们杯子不同的大小容量。这个大概是我目前认识到的最有意思又贴切的比喻了,因为这生动地让我明白了类型之间的转换意义。
我们都知道,基本数据类型之间的运算是存在类型转换的,特别是自动转换,其实在运算时有以下规则(由低到高转换):
- 所有的byte、short、char型的值将被提升为int型;
- 如果有一个操作数是long型,计算结果是long型;
- 如果有一个操作数是float型,计算结果是float型;
- 如果有一个操作数是double型,计算结果是double型。
顺便来看道题吧:
//下列代码片段中,存在编辑错误的语句是?
byte b1=1,b2=2,b3,b6,b8;
final byte b4=4,b5=6,b7;
b3=(b1+b2); /*语句1*/
b6=b4+b5; /*语句2*/
b8=(b1+b4); /*语句3*/
b7=(b2+b5); /*语句4*/
System.out.println(b3+b6);
8
1
//下列代码片段中,存在编辑错误的语句是?
2
byte b1=1,b2=2,b3,b6,b8;
3
final byte b4=4,b5=6,b7;
4
b3=(b1+b2); /*语句1*/
5
b6=b4+b5; /*语句2*/
6
b8=(b1+b4); /*语句3*/
7
b7=(b2+b5); /*语句4*/
8
System.out.println(b3+b6);
答案是语句1、3、4(更多请戳链接)
就像书中所说的一样,编译器是为了确保变量能存下所保存的值。就像你小杯的茶叶和大杯的茶叶倒进另外一个杯子里,那么为了避免溢出来,自然这另一个杯子要选择大的。
3.2 对象引用(引用数据类型)
Dog dog = new Dog();
1
1
Dog dog = new Dog();
如上,我们都知道基本数据类型的变量,而实际上,是没有对象变量这种说法的,只有引用到对象的变量。也就是说,这里的dog,并不是对象的容器,而是类似指向对象的指针,或者说是地址(如果你还不明白,你可以想象真正的对象是一个宾馆房间,而这里的变量就是对应的房间号门卡)
之前我们比喻说,基本数据类型是大小不同的杯子,而这里,实际上是没有超巨大的被子来装载对象的,对象只会存在于可回收垃圾的堆上。
虽然没有杯子可以承载直接的对象,但是还是可以用来放“遥控器”的,你要记住的是,对象本身并没有放进变量中,而是放入了可以远程控制对象的遥控器!
所以,当我们使用数组,它自身就是非基本数据类型,而当里面存入的是我们口中的其他“对象”时,它实际上是这样的:
引用指向了一个对象,这个对象中装满了引用们,这些引用们再最终会指向真正的对象。
好的,那么说到这里,既然我们只是存放了引用,那么那些真正的对象,假如没有对它的引用,会怎么样呢?
事实上,对于没有引用的对象,最终会被标记为垃圾,因为没有引用意味着我们不再会去使用它了,所以在一定时候它就会被Java虚拟机回收清理掉。
3.3 实例变量与局部变量
需要注意的是:
- 实例变量有默认值(不论你赋值与否):e.g. booleans --> false; references --> null; integers --> 0
- 局部变量没有默认值,如果在初始化之前就想使用,编译器就会报错
4、方法的使用
Java中的方法参数是通过值传递的,也就是说是通过拷贝传递。
值得注意的是,我们之前提到的所谓”对象变量“实际是引用,是遥控器,所以如果传入参数是对象,实际上也就是拷贝了引用,所以对于对象引用作为参数来说,往往我们在方法中的操作会改变真正对象的值,而基础数据类型则不会。
看看下面这个题目,最终会输出什么?
public class Test {
//属性
int a = 11;
char[] ch = {'n', 'b'};
//方法
public void change(int a,char ch[]) {
a = 99;
ch[0] = 's';
}
//测试
public static void main(String args[]) {
Test test = new Test();
test.change(test.a, test.ch);
System.out.println("test.a = " + test.a);
System.out.println("test.ch = " + test.ch[0] + test.ch[1]);
}
}
对于基本数据类型,拷贝过去对原来的属性没有影响,最终还是11;
对于引用数据类型,传递的是复制的遥控器,在方法中远程更改了真正的对象,所以输出不是nb,而是sb
x
1
public class Test {
2
//属性
3
int a = 11;
4
char[] ch = {'n', 'b'};
5
6
//方法
7
public void change(int a,char ch[]) {
8
a = 99;
9
ch[0] = 's';
10
}
11
12
//测试
13
public static void main(String args[]) {
14
Test test = new Test();
15
test.change(test.a, test.ch);
16
System.out.println("test.a = " + test.a);
17
System.out.println("test.ch = " + test.ch[0] + test.ch[1]);
18
}
19
}
20
21
对于基本数据类型,拷贝过去对原来的属性没有影响,最终还是11;
22
对于引用数据类型,传递的是复制的遥控器,在方法中远程更改了真正的对象,所以输出不是nb,而是sb
5、数据封装
为了更好地隐藏我们的数据,我们往往要在实例变量的修饰符使用private,这意味着外界的其他类无法使用例如dog.size = 9;的方法进行控制和篡改。我们需要建立setter,强制要求所有方法通过setter来设定变量而不是直接存取,这样我们就可以进行一些条件限制,比如身高我们在setter中可以限制其不能为负数,但是如果是public int height,那就难以控制了。
但是众所周知,我们常见的private类型的变量,实际上在getter和setter中并没有设定什么特殊的动作,就是说,感觉像是多此一举,只是把变量传给了值而已。可是,它的真正的好处在于,万一某天你发现这个变量需要检查,你只需要更改一下getter或setter,并不影响其他的代码。想象一下你使用public的方式伺候了一大堆变量,突然因为需求变更要求该变量必须在一定的范围内,你岂不是要修改成吨的代码?可怕。