Thinking in JAVA 内容简述
第一章 对象引论
这章主要介绍的是面向对象的思想,理解抽象、接口、继承等概念及其具体实现等等,自我感觉完全理解有些难度,需要反复阅读、理解、领悟,这里就不详细介绍了。在以后的学习中,这些知识是会在里面融会贯通。
第二章 一切都是对象
在Java程序中,万事万物皆对象,即使是Java程序本身,也是一个对象。
一、用引用(reference)操纵对象
C语言中是使用指针来操纵对象的,但是Java呢?用的是reference来操纵对象,我习惯把他叫做句柄,句柄和对象的关系就像是电视遥控器和电视机的关系一样,既然是这样,那么遥控器和电视机都是可以独立出现的
例如:String name;
这个Java语句只会产生句柄(遥控器),并不产生实际的对象(电视机),句柄并没有连接到实际的对象上。这样写代码是不推荐的,一个没有指向对象的句柄在调用的时候编译器会报错,应当使用String name="jyl";,或者使用String name =new String("jyl");这里new的意思是产生一个String类型的对象,并且这个String叫做jyl。这2个方法都可以实现对象的创建。
二、必须由你创建所有对象
1、储存到什么地方
a、寄存器 这里是核心、最快的存储空间,由编译器根据需求进行分配,不能直接操纵;
b、堆栈(stack )位于RAM中,速度仅次于寄存器,效率高,因为这里要求被存储的数据有具体大小和存活时间限制,使用弹性小,这里一般存储的是我们的对象句柄,而对象是不存在这里的;
c、堆(heap)这里是通用的存储空间,也位于RAM中,他比stack好在编译器不需要知道实际在heap中存储数据的大小,也不知道这个空间需要分配多长时间,弹性好,所以用来存储对象,但是速度要比stack慢很多;
d、静态存储(static storage)这里是存放被声明为static的特定成员,也在RAM里,java对象本身是不会分配在这里的;
e、常量储存(constant storage)这里存放的是程序中的常量,常量值不会改变,最安全,可以选择将其放在ROM中;
f、非RAM存储(non-ram storage)流或者是持久化对象。
2、基本数据类型
boolean(Boolean)、char(16 Character)、byte(8 Byte)、short(16 Short)、int(32 Integer)、long(64Long)、float(32 Float)、double(64 Double)、void(Void)
(当byte类型参与运算时将会自动提升到int类型,这里要注意类型转换)
一共九种基本数据类型,这些数据是不用new 来创建的,直接int i=0,不使用heap空间,他是被放在stack中的,速度快!但是假如你要是想用heap来存储基本数据类型的话,就要使用该类型的外覆类来实现了,例如Integer i= new Integer("0");
注意:String不是基本数据类型,它是对象!从它的定义方法就可以知道了!
3、数组(array)
Java中的数组使用和定义上要比C和C++更安全,当你在定义数组的时候,其实产生的是一个储存对象句柄的数组,而每一个句柄所指向的值会被设定为null,一旦Java看到这个null就知道这个引用还没有指向某一个对象。在使用任何引用前必须为其指定一个对象,否则运行时将会报错!
三、永远不需要销毁对象
1、作用域
Java中基本类型的作用域是由一对大括号决定的,在作用域内定义的变量,只可用于作用域结束之前
注意:
{
int i=1;{
int i=100;//!这样的定义在Java中是不允许的!编译器会认为i已经被定义过了
}
}
2、对象作用域
对象拥有的寿命和基本类型是不一样的,当你使用new来产生一个对象的时候,即使离开了大括号,该对象还是存在的
{
String name =new Sting("baby");
}
句柄name会在大括号的生存空间之外消失的,但是他所指向的string对象却还在继续占用着内存,但是大家会认为大量的无用对象会占用大量的内存,Java中是怎么解决的呢?他使用的是垃圾回收机制,垃圾回收器会在特定的时间检查使用new创建的对象,假如这些对象已经没有句柄指向他们,那么他就回把无用的对象清理掉。
四、创建新的数据类型:类
如果一切都是对象,那么是什么来决定某一类对象的外观与行为呢?Java中使用class关键字来自己定义一个类
例如:class ATypeName{ //class body goes here}这样你就定义一个ATypeName类,当然这个类没有任何属性
1、域(数据成员)和方法(field & method)
一个类中,存在2种成员,一个是数据成员。它可以是任何类型的对象(必须使用构造器进行初始化),也可以是基本数据类型(不是引用);另外一个是方法。
域不能在对象间共享。
2、基本成员默认值
基本数据类型在声明的时候系统会自动的给它赋予一个默认的初始值。
boolean(false)、char(null)、byte(0)、short(0)、int(0)、long(0L)、float(0f)、double(0d)
注意:当变量作为一个类的成员使用时,Java才确保给定其默认值。但该初始化方法并不适用于“局部”变量(即并非是某个类的属性,如:方法里面定义变量)。
五、方法(method)、参数(argument)、返回值(return value)
方法的基本组成包括:名字、参数、返回值、方法体。 例如
returnType methodName(//argument list){
//method body
}
其中,名称 methodName()、参数argument list、返回类型returnType、方法体{//method body},对于一个方法而言,名称+参数的组合必须是唯一的,参数也可以是空的。methodName的类型一定要和返回值的类型“兼容”。
Java中的方法只能作为类的一部分来创建。方法只有通过对象才能被调用,且这个对象必须能执行这个方法调用。
在向一个方法传递一个对象的时候,其实传递的是该对象的句柄(基本数据类型除外),而传递的对象类型一定要和方法中接受参数的参数类型相同。当你不需要方法给你返回什么东西的时候,你可以把该方法的返回类型设置为void,而此时方法中的return就是用来离开方法的,不需要等到他执行完毕,如果方法的返回类型不为void的时候,你可以使用return返回一个和返回类型一样的值。
六、构建一个Java程序
1、名字可视性
Java采用了与Internet域名相似的指定符来避免名字冲突的问题。
2、运用其它构件
Java使用import关键字来导入一个包,也就是一个类库。
3、static关键字
使用static关键字主要用来解决两个问题。一个是,你只想为某特定数据分配一份存储空间,而不去考虑究竟要创建多少对象,还是甚至根本就不创建任何对象。另一个是,即使没有创建对象,也可以调用这个方法。
创建多个对象引用static变量时,在内存中其实只有一份存储空间,他们指向的是同一个地方。
4、你的第一个Java程序
每个程序的开头必须声明import语句,以便引入在文件代码中用到的所有额外类。
java.lang是默认导入到每个Java文件中的。
类的名字必须和文件名相同,且类中必须包含一个main()方法,同时main()方法的参数是一个字符串数组(虽然方法中可能并未用到)。
第三章 控制程序流
一、使用Java操作符
1、优先级
使用括号避免在一些优先级上不必要的麻烦。
2、赋值
在为“对象”赋值的时候,我们真正操作的是对对象的引用。所以“将一个对象赋值给另外一个对象”,实际上是“将一个引用复制到另外一个地方”,同时那个被赋值的对象会由“垃圾回收器”自动清除。修改任意一个对象的同时会修改另外一个对象。
3、关系操作符
==和!=比较是对象的引用,所以对于不同对象的引用,虽然数值相等,但结果还是不等。
4、逻辑运算符
逻辑与、逻辑或、逻辑非在Java中不可将一个非布尔值当做布尔值在逻辑表达式中使用。
Java不允许我们将一个数字作为布尔值使用(C、C++、PHP可以)。
“短路”现象可以获得潜在的性能提升。
5、流程控制
break和continue:
a、一般的continue会退回最内层循环的开头,继续执行;
b、带标签的continue会到达标签的位置,并重新进入紧接着那个标签后面的循环;
c、一般的break会中断并跳出当前循环;
d、带标签的break会中断并跳出当前标签所指的循环。
switch(整数选择因子),注意这里必须是int或char那样的整数值和字符值,break是可选的。
注意:将一个float或double值转换成整数值后,总是将小数位置“砍掉”。
Math.random()会产生一个double值,输出中包括了0.0,即范围为[0,1)。
第四章 初始化与清除
在创建对象时(new),将会为对象分配存储空间,并调用相应的构造器。构造器本身没有返回值。
一、方法重载
1、区分重载方法
最佳选择是通过参数类型列表来进行区分。
2、涉及基本类型的重载
基本类型可以从一个“较小”类型自动提升到一个“较大类型”,反之则报错。
基本类型顺序参考:char byte int short long float double
3、以返回值区分重载方法
由于我们有时不关心方法的返回值,因此通过返回值来区分重载方法是不被推荐的。
4、缺省构造器
当我们写的类中没有构造器,系统会自动帮我们创建一个缺省构造器,因此是不会报错的;但如果我们写的类中已经有了构造器,但我们不进行正确初始化,系统则会报错。
如:
class Hat{
Hat(int i){}
Hat(float f){}
}
new Hat();
5、this关键字
this关键字只能在方法内部使用,表示对“调用方法的那个对象”的引用
a、在构造器中调用构造器
Java中可以在构造器中使用this(args1,args2)调用一个其他构造器,但不能用相同的方法调用两个构造器。此外,我们必须将构造器调用置于最起始处,否则编译器将会报错。
this关键字还可以用来区分参数名和数据成员名相同的情况。
除构造器外,Java禁止在其他任何方法中调用构造器。
b、static的含义
静态方法就是没有this的方法。在静态方法的内部不可以调用非静态方法,反过来可以。
6、清除(cleanup):终结(finalization)和垃圾回收(garbage collection)
Java垃圾回收器只释放经由new分配的内存,而不会释放一些不是由new分配的“特殊”内存。
a、finalize用途何在?
对象可能不被回收;垃圾回收并不等于“析构”;垃圾回收只与内存有关。
b、你必须执行清除
由于finalize并不等同于“析构”,所以你必须执行清除。
c、终结条件
finalize的价值在于可以用来发现对象的内存未被释放的情况,尽管他并不总是会被调用。
7、成员初始化
Java尽力保证:所有变量在使用前都能得到恰当的初始化。对于定义于方法内部的局部变量,Java以编译时刻错误的形式来保证贯彻这种保证。
a、指定初始化
在定义类成员变量的地方为其赋值。
也可以用同样的方法初始化非基本类型的对象。(如:Depth d = new Depth();)
甚至可以通过调用某个方法来提供初始值。(方法里的参数必须是已经初始化了的)
b、构造器初始化
我们无法屏蔽自动初始化的进行,他将在构造器调用之前发生。
c、初始化顺序
在类的内部,变量定义的先后顺序决定了初始化的顺序。即使变量定义散布于方法定义之间,他们仍旧会在任何方法(包括构造器)调用之前得到初始化。
d、静态数据的初始化
静态初始化只有在必要的时刻才会进行。如果不创建对象,或者不引用对象的静态变量,那么静态的数据永远也不会被创建。只有第一个对象被创建或者第一次访问对象静态数据的时候,它们才会被初始化。此后,静态数据不会再次被初始化。
初始化的顺序是先“静态”,后“非静态”。
对象的创建过程(以Dog为例):
I、当首次创建类型为Dog的对象时(构造器可看成静态方法),或者Dog类的静态方法/静态域首次被访问时,Java解释器必须查找类路径,以定位Dog.class文件。
II、然后载入Dog.class(这将创建一个class对象),有关静态初始化的动作都会执行。因此,静态初始化只在class对象首次加载的时候进行一次。
III、当你用new Dog()创建对象的时候,首先将在堆上为Dog对象分配足够的存储空间。
IV、这块存储空间会被清零,这就将Dog中的所有基本类型数据设置成默认值,而引用则设置成了null。
V、执行所有出现于域定义处的初始化动作。
VI、执行构造器。
e、明确进行的静态初始化
Java允许你将多个静态初始化动作组织成一个特殊的“静态子句”。
f、非静态实例初始化
与静态初始化字句相同,只是少了static关键字而已。
8、数组初始化
数组只是相同类型的,用同一标识符名称封装在一起的一个对象序列或基本类型数据序列。
两种定义方式:int[] a;//推荐 int a[];
编译器不允许指定数组的大小,但可以new关键字规定数组的个数,如:int[] a = new int[3];
Java中可以将一个数组赋值给另外一个数组,其实真正做的就是复制了一个引用,所以改变其中任意一个的值,两个的值都会改变。
Java数组中的下标值是不允许超过其范围的。
如果数组里的元素不是基本类型,那么你必须使用new。
初始化对象数组有两种形式:
Integer[] a1 = {new Integer(1),new Integer(2),new Integer(3),}; //最后一个逗号是可选的
Integer[] a2 = new Integer[]{new Integer(1),new Integer(2),new Integer(3),};
Java数组里的缺省行为只会打印类的名字 + @ + 对象的地址
a、多维数组
int[][] a1 = {{1,2,3,},{4,5,6,},};
int[][][] a2 = new int[2][2][4];
int[][][] a3 = new int[rand.nextInt(7)][][];
Integer[][] a4 = {{new Integer(1),new Integer(2),},{new Integer(3),new Integer(4),},{new Integer(5),new Integer(6),}};
Integer[][] a5;a5 = new Integer[3][];
第五章 隐藏具体实现
如何将变动的事物与保持不变的事物相互隔离?
解决“程序库的代码的变动不会对客户端程序代码产生任何影响”这个问题,因此Java提供了访问权限修饰符(public、protected、包访问权限(无关键字)、private)供程序开发人员向客户端程序员指明哪些是可用的,哪些是不可用的。
如何将构件捆绑到一个内聚的程序库单元中?
一、包(package):程序库单元
我们之所以要导入包,就是要提供一个管理名字空间的机制。所有类成员的名称都是彼此隔离的。
编写一个java源代码文件,此文件通常被称为编译单元。每个编译单元必须有一个后缀名.java,而在编译单元之中则可以有一个public类,该类的名称必须与文件的名称相同(包括大小写,不含后缀名)。每个编译单元只能有一个public类(或者没有),否则编译器就不会接受。如果在该编译单元之中还有额外的类的话,那么在包之外的世界是无法看见这些类的,这是因为他们不是public类,而且他们主要是被用于为主public类提供支持。
一个Java可运行程序是一组可以打包并压缩为一个Java文档文件(JAR,用Java的jar文档生成器)的.class文件,Java解释器(interpreter)负责对这些文件的查找、装载和解释。
程序库实际上是一组类文件。其中每个文件都有一个public类,因此每个文件都是一个构件。如果你希望这些构件(每一个都有它们自己分离开的.java和.class文件)从属于同一个群组,就可以使用关键字package。且该语句必须是文件中除注释以外的第一句程序代码。
Java包的命名规则全部使用小写字母,包括中间的字也是如此。
两种方式引入其它包内的类:
//直接用包名链接
mypackage.MyClass m = new mypackage.MyClass();
//导入一个包
import mypackage.*;
MyClass m = MyClass();
1、创建独一无二的包名
根据域名倒序规则,每一个包名对应域名上相应的区段,所以Java可以保证每一个包名都是独一无二的。
Java解释器的运行过程:首先,找出环境变量classpath通过操作系统,从根目录开始,解释器获取包的名称并将每个句点替换成反斜杠已从classpath跟中产生一个路径名称。得到的路径会与classpath中的各个不同的项相连接,解释器就在这些目录中查找与你所要创建的类相关的名称的.class文件。
a、冲突
如果将两个含有相同名称的程序库以‘*’形式同时导入,且这两个程序库中都包含有某一个相同的类,这样在导入的时候不会有什么问题,但一旦需要创建该类的对象时就会产生冲突,解决方式是在类名称前面加上相应的包名。
2、定制工具库
为了避免一些常用方法的重复输入可以根据自己的情况定制工具库。
为了方便调用,这里建议定义成静态方法,直接通过类名进行调用即可。
3、用imports改变行为
Java中没有类似C的“条件编译”功能,因此在调试的时候可以通过修改被引入的package的方法来实现。
4、对使用package的忠告
创建的包必须位于其名称所指定的目录之中,而且该目录必须是在以classpath开始的目录中可以查询到的。
5、Java访问权限修饰词(access specifier)
a、包访问权限
默认的访问权限,没有任何关键字,处于某个编译单元之中的所有类彼此之间都是自动可访问的。
取得对某成员的访问权的唯一途径是:
使该成员称为public。
不加访问权限修饰符并将其放置于同一个包内。
通过继承而来的类可以与访问public成员一样地访问protected成员。且两个类都处于同一个包内。
提供访问器和编译器方法,以读取和改变数值。
b、public:接口访问权限
使用关键字public,表示紧跟着的成员声明对每个人都是可用的。
I、缺省包(default package)
当两个文件处于一个相同的目录并且没有给自己设定任何包名称时,Java将这样的文件自动看作是隶属 于该目录的缺省包之中,于是它们为该目录中所有其他的文件都提供了包访问权限。
c、private:你不可以去碰!
关键字private的意思是,除了包含该成员的类之外,其他任何类都是无法访问这个成员的。
d、protected:继承访问权
关键字protected处理的是一个被称为继承的概念,我们可以获取一个现有类——我们将其作为基类引用,然后将新成员添加该现有类中,而不必触及这个现有类。
6、接口(Interface)和实现(implementation)
封装:把数据和方法包装进类中,与具体实现的隐藏(访问权限的控制)结合到一起。
出于两个很重要的原因,访问权限控制将权限的边界划在了数据类型的内部。
第一个原因:要设定客户端程序员可以使用和不可以使用的界限。
第二个原因:将接口和具体实现进行分离。
7、类的访问权限
a、每个编译单元都只能有一个public类(每个编译单元都有一个单一的公共接口,用public类来表现)。
b、public类的名称必须完全与含有该编译单元的文件名相匹配,包括大小写。
c、编译单元内完全不带public类也是可能的。
第六章 复用类
一、组合语法
每一个非基本类型的对象都有一个toString()方法,而且当编译器需要一个string而你却只有一个对象时,该方法便会被调用。
二、继承语法
super关键字指代的是当前类所继承的基类。
1、初始化基类
构建过程是从基类“向外”扩散的,所以基类在导出类构造器可以访问它之前,就已经完成了初始化。
2、带参数的构造器
如果基类的构造器里含有参数,则必须用关键字super显示地编写调用基类构造器的语句,并且配以适当的参数列表。
3、结合使用组合和继承
编译器强制你去初始化基类,并且要求你要在构造器起始处就要这么做,但是它并不监督你必须将乘员对象也初始化,因此在这一点是自己必须时刻注意。
I、确保正确清除
II、名称屏蔽
4、组合与继承之间选择
组合与继承都允许你在新的类中设置子对象,组合是显示地这样做的,而继承则是隐式的。
组合技术通常用于你想要在新类中使用现有类的功能而非它的接口的情形。
“is-a(是一个)”的关系是用继承来表达的,而“has-a(有一个)”的关系则是用组合来表达的。
I、受保护的(protected)
5、增量开发
6、向上转型
7、关键字final
I、final数据
它可以是一个永不改变的“编译期常量”;它可以是一个在运行期被初始化的值,而你不希望它被改变。
一个即时static又是final的域只占有一份不能改变的存储空间。
对于原始类型,final使数值恒定不变,而用于对象引用,final使引用恒定不变。
定义为public,可以被用于包之外;定义为static来强调只有一份;定义为final来说明它是一个常量。
带有恒定初始值的final static原始类型全用大写字母命名,并且字与字之间用下划线来隔开。
II、空白final
空白final是指被声明为final但又为给定初值的数据成员。无论什么情况,编译器都确保空白final在使用前必须被初始化。
III、final参数
Java允许在参数列表中以声明的方式将参数指明为final。这意味着你无法在方法中更改参数引用所指向的对象。
IV、final方法
原因:把方法锁定,以预防任何继承类修改它的意义;同意编译器将针对该方法的所有调用都转为内嵌调用,可以提高效率。
V、final和private
类中所有的private方法都被隐含是final的。由于无法取用private方法,所以你也无法重载之。你可以对private方法增加final修饰符,但这并不能给该方法增加任何额外的意义。
VI、final类
当将某个类的整体定义为final时,也就声明了你不打算继承给类,而且也不允许别人这样做。
浙公网安备 33010602011771号