您的位置:首页 > 编程语言 > Java开发

《Thinking in Java》读书笔记(二)

2005-09-21 06:43 302 查看
第二章 万事万物皆对象
一. Reference是操控对象的钥匙。
Java中的Reference可以大致理解为C++中的Reference,但是注意Java中都是pass by value,而C++的Reference则并非如此。
【注】在C++中Reference != Pointer,主要区别如下:
(1)看上去就不一样,Pointer用->,Reference用.;
(2)Reference在声明时就必须被初始化,而Pointer不用。
(3)Reference必须指向一个对象,不能指向NULL,而Pointer就可以为NULL。
因为这个特性,使用Reference之前不需要检测它是否为空,所以某些时候使用Reference的代码效率会比使用Pointer更高,而且更加安全。但Java中,Reference是可以被设为NULL的。
(4)Reference一旦声明并指向一个对象后,就不能改变了(不可以重新赋值),而Pointer就可以随时改变它要指向哪个对象。所以引用有点像常量Pointer,就是Pointer本身是常量,不能更改,而不是说它所指的数据不能更改。

二. 所有对象必须由你建立:使用new。
1. 对象存储在哪里?
(1)Registers(寄存器):在CPU内部,是最快的存储场所,但程序员无法直接控制。
(2)Stack(栈):位于一般的RAM(随机访问内存),速度仅次于Registers。存放基本类型的数据和对象的reference,但对象本身不存放在stack中,而是存放在Heap中。
(3)Heap(堆):也位于RAM,存放用new产生的数据。他比stack好在编译器不需要知道实际在heap中存储数据的大小,也不知道这个空间需要分配多长时间,弹性好,但分配空间的速度比Stack慢很多。
(4)Static storage(静态存储空间):位于RAM,存放在对象中用static定义的静态成员,它是“程序执行期间”一直存在的数据,Java对象本身是不会被分配在这里的。
(5)Constant storage(常量存储空间):存放常量。
(6)NON-RAM存储空间:硬盘等永久存储空间,实现对象的持久化存储。
2. 特例:基本类型(primitive types)
基本类型数据存放在Stack中,存放的是数据。而产生对象时,只把对象的reference存放在stack中,用于指向某个对象,对象本身存放在Heap中。
这便使Java混入了非OO的语言性质,基本数据类型的数据是不用new 来创建的,直接int i=0,不使用heap空间,而放在stack中的,是为了速度快!但是假如你要是想用heap来存储基本数据类型的话,就要使用该类型的外覆类型来实现了 例如Integer i= new Integer("0");
【注意】String不是基本数据类型,她是对象!
Java 中对于 String 操作符"+"的处理很糟糕。性能不好,一般推荐用 StringBuffer 代替。

Java还提供了两个用于高精度计算的classes:BigInteger和BigDecimal,它们不是基本类型,只能在堆里创建。
它们能提供任意精度的整数和定点数(小数位固定的数),但它们的高精度是以速度为代价的。
3. Java中的数组(array)
当你产生某个存储对象的数组时,真正产生的其实是存储reference的数组。这个数组建立后,其中的每一个reference都会被自动设为null,表示“不指向任何对象”。

三. 你不需要摧毁对象
1. 基本类型的生存范围(Scoping)
java中基本类型的生存范围是由一对大括号决定的,在生存空间内定义的变量,只能用于生存空间之内。
2. 对象的生存范围
对象拥有的寿命和基本类型是不一样的,当你使用new来产生一个对象的时候,即使离开了大括号,该对象还是存在的。例如:
{
String s =new Sting("a string");
  }
s这个Reference会在生存范围之外消失,但是他所指向的string对象却还在继续占用着内存,Java中使用垃圾回收器(garbage collector)来解决内存泄漏问题,垃圾回收器会在特定的时间检查使用new创建的对象,假如这些对象已经没有Reference指向他们时,便释放这些对象所占用的内存。

四. 建立新的数据类型:用Class
注意养成一个好习惯,在对象初始化时,应当为对象中所有数据成员赋上初始值,尽管基本类型在声明时Java就给它赋了一个初始值,但这是减少你程序Bug的一个好习惯。

五. 函数、参数、返回值
在向一个函数传递一个对象的时候,其实传递的是该对象的Reference(基本数据类型除外)。
“调用函数”的行为,我们也称为“发送消息给对象”。
当你不需要方法给你返回什么东西的时候,你可以把该方法的返回类型设置为void,而此时方法中的return就是用来离开方法的,不需要等到他执行完毕,如果方法的返回类型不为void的时候,你可以使用return 返回一个和返回类型一样的值。
【注】Java都是pass by value
All parameters (values of primitive types, and values that are references to objects) are passed by value [JLS sect 8.4.1]。
根据以上 Java规格文件的说法, 所有参数应该都是传值的(by value)。但要明确这个值得含义,基本类型(如int, float, char等)是by value,把自己复制了一份传递,而对于“对象”(Object),传递的值是对象的Reference,并未建立对象的副本,只是建立了Reference的副本。
特别注意,String不是基本类型,它传的是引用。
哦,也许你会说reference不就是指针?那对于对象来说,不就是传址吗?嗯,看看Bruce Eckel怎么说的:
Java passes everything by value. When you’re passing primitives into a method, you get a distinct copy of the primitive. When you’re passing a reference into a method, you get a copy of the reference. Ergo, everything is pass by value.
所以,特别注意这里说的reference会被copy一份的问题,这往往是导致你程序中很多内存无法被垃圾回收器及时释放的主要原因。

六. 打造一个Java程序
1. 名称的可视性
C++中用namespaces来解决命名冲突的问题,Java则使用package来解决。
在类的开头使用package sunrise.blogbus.com,对应的文件夹就是sunrise/blogbus/com,注意包的名称都是小写的。
2. 使用其他组件
我们上面定义了包,如何使用它?
在文件的开头使用import关键字,假如你要使用ArrayList class,你只要
import java.util.arraylist;
如果你还想使用util内的其他classes,又不想逐一声明,可以用
import java.util.*;
一次声明一大群class,比个别导入一个个class,常见且方便很多,但却会影响编译时间。
3. 关键字Static
一个class只有在你使用new来产生对象时,它的的存储空间才会被分配出来,其函数才能被外界使用,但是也有两中情况是用这个的方法无法实现的。
(1)不论产生了多少对象,或不存在任何对象的情况下,那些特定数据的存储空间都只有一份;
(2)你希望某个函数不要和对象绑定在一起,即我不想产生对象,但是我要用类中的一个函数;
一般情况下,你要使用一个非静态(non-static)的成员的话,必须先产生一个对象,通过Reference来调用这个数据或函数,所以你必须知道这个函数/数据属于哪个对象才行,因为static是不用产生对象就可以使用的,所以在static的函数中不能直接调用non-static的函数或变量。

方法1:把static关键字摆在成员变量或函数定义之前,就可以使他们变为静态的。
class StaticDemo
{
static int i = 47;
public static void main(String args[])
{
StaticDemo sd1=new StaticDemo();
StaticDemo sd2=new StaticDemo();
System.out.println(sd1.i); //47
System.out.println(sd2.i); //47
StaticDemo.i++;
System.out.println(sd1.i); //48
System.out.println(sd2.i); //48
}
}
现在即使你产生n个StaticDemo对象,但是i只有原始的一份,不管你是sd1.i还是sd2.i,其实就是同一个i,全部引用自这个StaticDemo.i,所以,只要改变StaticDemo.i内存储的数据,这些sd1和sd2的i都会改变,因为他们的i的句柄全部指向的是StaticDemo.i这一个内存空间,同样的道理也适用于static函数。
方法2:要想不产生对象就能使用函数,这样需要在函数名前加上static
class StaticDemo
{
public static void show(String s)
{
System.out.println(s);
}
public static void main(String args[])
{
show("one"); //调用方法1
StaticDemo.show("two"); //调用方法2
new StaticDemo().show("three"); //调用方法3
}
}
对于调用方法1,其实他隐藏了一个关键字,就是this,他的完整形式应当是this.show("one")。
对于调用方法2,使用的是className.method()的形式,这个也就是static的函数调用的典型形式,non-static的函数是不能使用这样的方法调用的。
对于调用方法3,这个方法就是我们调用函数的一般方法,创建对象,由对象调用函数,这个方法对于static和non-static是同样适用的。
某个成员变量在声明为static的时候,其建立的方式有很大的变化,但是static的函数变化不大,static函数最大的一点用处就是可以在不建立对象的情况下,调用函数,就像我们经常见到的main函数一样。
所以static函数常常被当作“牧羊人”的角色,负责看管众多隶属同一类型的一整群对象。

七. 你的第一个Java程序
1. main函数
JAVA程序默认会把java.lang下的包导入,我们没有必要再使用import来导入。一个public的class的名称一定要和文件名称是一样的,并且一个文件里面只允许有一个public的class。
如果你要运行这个类的话,那么这个类中一定要有一个main()函数,它的固定格式为:
public static void main(String args[]);
它是个static的不允许有返回值的函数,且传入的参数必须是个String对象数组,即使main中并没有使用到这个参数。Java编译器严格要求要这样声明main函数是因为args[]的string数组被拿来存储“命令行(command line)参数”。
注意这行代码:System.out.println(new Date());
当这句执行完毕,产生出来的Date对象再也不会被使用,因此GC(garbage collector)便会在适当时机将这个对象所占的空间回收。
【注】如何向main传递command line参数?
答:使用索引。
public class Sunrise
{
public static void main(String args[])
{
System.out.println(args[0]);
System.out.println(args[1]);
System.out.println(args[2]);
}
}
这个程序可以接受3个参数,并且把它们显示出来。
运行时的命令行为:java Sunrise aaa bbb ccc。
当你输入参数个数 < 接受参数的数量时,会报ArrayIndexOutOfBoundsException的错误;
当你输入参数个数 > 接受参数的数量时,超出部分的参数会被忽略,只取前面的参数。
2. 编译与执行
(1)Java环境的设定:安装JDK1.3之后,C:/JDK1.3/BIN/内置各种开发工具,部分如下:
jar.exe 压缩工具
java.exe 执行工具,可接受.class文件并启动Java虚拟机
javac.exe 编译器(其实只是个外包器,wrapper)。使.java -> .class。
javadoc.exe 文档制作工具
javah.exe 头文件(header)产生器
appletviewer.exe 网页小程序(applet)执行器

环境变量设定如下:
set PATH=C:/jdk1.3/bin;C:/windows;/C:/windows/command
set classpath=.;C:/jdk1.3/lib/tool.jar
(2)编译:javac HelloDate.java
(3)执行:java HelloDate

八. 内嵌式文档
1. javadoc:文档与代码的分离
设计文档与代码分离会使文档的维护变成很麻烦的一件事情,所以最好的办法就是把文档和代码混合在一起。
javadoc就是用来将程序代码内嵌文档提取出来的工具,输出结果是HTML文件。
所有javadoc可识别的语句,都必须处于/**和*/之间,且javadoc还会将class中public和protected的部分提取出来,privated和默认friendly的部分则被忽略。
2. 在注释中使用HTML语法
由于javadoc提取出来的是html文件,所以你可以在注释中使用HTML标记,让文档看起来更加清晰。
3. @see:参考其他Classes
classes, variables, methods的注释中都可以使用@see标签来链接到其他地方,例如:
@see classname
@see fully-qualified-classname
@see fully-qualified-classname#methodname
但javadoc并不检查超链接的内容是否存在,所以你得自己注意。
4. Class文档所用的标签
@version …… 在javadoc的-version选项被打开时,它会出现在HTML中。
@author …… 在………… -author……
@since …… 指定程序代码所使用的最早版本
5. Variable文档所用的标签:只有@see
6. Method文档所用的标签
@param args array of string arguments
@return No return value
@exception No exception thrown

九. 编码风格
类名在编写的时候,应当第一个字母大写,如果名称是由多个单词组成,则把这些个单词并在一起写,并且把他们的第一个字母大写。例如:public class ThinkingInSunrise,中间不要用下划线。
methods, fields, reference的命名规则同上,只不过第一个字母要小写。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: