Java核心技术·卷 I(原书第10版)
作者: [美] 凯.S.霍斯特曼(Cay S. Horstmann)
出版社: 机械工业出版社
原作名: Core Java Volume I - Fundamentals (10th Edition)
译者: 周立新 等
出版年: 2016-9
页数: 711
定价: CNY 119.00
装帧: 平装
丛书: Java核心技术系列
ISBN: 9787111547426
Java并不只是一种语言,它是一个完整的平台,有一个庞大的库,包含了很多可重用的代码和一个提供诸如安全性、跨操作系统的可移植性以及自动垃圾收集等服务的执行环境。
Java并不能像C++那样多继承,其采用了更简单的接口概念,以多实现来达到与多继承同样的目的。
在C和C++中int可能是16或32位的整数,也可能是编译器提供商指定的其他大小。而在Java中,int永远都是32位的整数,唯一的限制只是int类型的大小不能低于short int且不能高于long int。
在Java的规范中并没有“依赖具体实现”的地方,基本数据类型的大小以及有关运算都做了明确的说明。
表1-1 Java语言的发展状况 | |||
---|---|---|---|
版本 | 年份 | 语言新特性 | 类与接口数量 |
1.0 | 1996 | 语言本身 | 211 |
1.1 | 1997 | 内部类 | 477 |
1.2 | 1998 | strictfp修饰符 | 1524 |
1.3 | 2000 | 无 | 1840 |
1.4 | 2002 | 断言 | 2723 |
5.0 | 2004 | 泛型类、“for each”循环、可变元参数、自动装箱、元数据、枚举、静态导入 | 3279 |
6 | 2006 | 无 | 3793 |
7 | 2011 | 基于字符串的 switch、钻石操作符、二进制字面量、异常处理改进 | 4024 |
8 | 2014 | lambda表达式,包含默认方法的接口,流和日期/时间库 | 4240 |
表2-1 Java 术语 | ||
---|---|---|
术语名 | 编写 | 解释 |
Java Development Kit | JDK | 编写 Java 程序的程序员使用的软件 |
Java Runtime Environment | JRE | 运行 Java 程序的用户使用的软件 |
Server JRE | —— | 在服务器上运行 Java 程序的软件 |
Standard Edition | SE | 用于桌面或简单服务器应用的 Java 平台(标准版) |
Enterprise Edition | EE | 用于复杂服务器应用的 Java 平台 |
Micro Edition | ME | 用于手机和 其他小型设备的 Java 平台 |
Java FX | —— | 用于图形化用户界面的一个替代工具包,在 Oracle 的 Java SE 发布版本中提供。 |
OpenJDK | —— | JavaSE 的一个免费幵源实现,不包含浏览器集成或 JavaFX |
Java 2 | J2 | 一个过时的术语,用于描述1998 年〜2006 年之间的 Java 版本 |
Software Development Kit | SDK | 一个过时的术语, 用于描述 1998 年~2006 年之间的 JDK |
Update | u | Oracle 的术语,表示 bug 修正版本 |
NetBeans | —— | Oracle 的集成开发环境 |
原文摘录:
1. 开发需要的是 JDK (Java SE 开发包,) 而不是 JRE。
2. Windows 或 Linux: 32 位选择 x86, 64 位选择 x64
3. Linux:建议选择 .tar.gz 版本,因其可以在任何希望的位置解压即可以自定义安装位置。
4. Oracle 提供了一个捆绑包, 其中包含 Java 开发包(JDK) 和 NetBeans 集成开发环境。 建议现在不要安装任何捆绑包, 而只需安装 Java 开发包。如果以后你打算使用。
NetBeans, 可以再从 http://netbeans.org 下载。注意:JDK安装时不要接受路径名中带有空格的路径。
库源文件和文档的安装请参考原书。
javac Welcome.java
javac程序是一个Java编译器,它将文件Welcome.java编译成Welcome.class字节码文件。
java Welcome
java程序启动Java虚拟机,虚拟机执行编译器放在classwe文件中的字节码。请注意不要添加 .class 扩展名
打开Eclipse中的Problems标签页,如果没有该标签则可以点击Window→Show View→Other→General→Problems打开此标签页。
点击上图中Errors左侧的小三角,我们可以看到错误信息,点击相应的错误信息,光标会移动到编辑窗口中相应的代码行。
一定要注意,Java区分大小写。
在Java中有三种注释方式,分别是行注释,块注释,文档注释。
//这是行注释,以双斜杠开头,其注释内容从双斜杠开始到本行结尾
/*这是块状注释*/
/**这是文档注释*/
块状注释不可嵌套使用,即不能块状注释中包含块状注释。
文档注释可以用来自动地生成文档,有关该种注释的详细内容以及自动生成文档的具体方法在第四章有介绍。
Java是一种强类型语言,因此必须为每一个变量声明一种类型。在Java中数据类型分为基本数据类型和引用数据类型,其中基本数据类型又分为四类共有8种。
Java有一个能够表示任意精度的算术包,通常称为“大数值”(big number)。它不是一种新的Java类型,而是一个Java对象。
整形用以表示没有小数部分的数值,允许是负数。
表3-1 Java 整形 | ||
---|---|---|
类型 | 存储需求 | 取值范围 |
byte | 1字节 | -128 ~ 127 |
short | 2字节 | -32 768 ~ 32 767 |
int | 4字节 | -2 147 483 648 ~ 2 147 483 647 (正好超过 20 亿) |
long | 8字节 | -9 223 372 036 854 775 B08 ~ 9 223 372 036 854 775 807 |
原文摘录:
长整型数值有一个后缀 L 或 1 ( 如 4000000000L)
十六进制数值有一个前缀 Ox 或 0X (如OxCAFE)
八进制有一个前缀 0 , 例如,010 对应八进制中的 8
八进制表示法比较容易混淆, 所以建议尽量不要使用八进制常数.。
从 Java 7 开始, 加上前缀 0b 或 0B 就可以写二进制数。 例如,OblOO丨就是 9。另外,同样是从 Java 7 开始,还可以为数字字面量加下划线, 如用 1_000_000(或0b1111_0100_0010_0100_0000 )表示一百万。这些下划线只是为了让人更易读。Java 编译器会去除这些下划线。
浮点类型用于表示有小数部分的数值,在Java中有两种浮点类型,如下表:
表3-2 浮点类型 | ||
---|---|---|
类型 | 存储需求 | 取值范围 |
float | 4字节 | 大约 ± 3.402 823 47E+38F (有效位数为 6 ~ 7 位) |
double | 8字节 | 大约 ± 1.797 693 134 862 315 70E+308 (有效位数为 15 位> |
double的精度是float的两倍,这应该是double被叫做双精度,float被叫做单精度的原因。
大多数情况下,我们使用的都是double,只有在少数情况下才适合使用float(如需要单精度数据的库, 或者需要存储大量数据)。
由于没有后缀F或f的浮点数值默认为double,float数值必须后缀f或F,而double数值可以后缀d或D也可以省略不写。
可以使用十六进制表示浮点数值。例如,0.125=2-3 可以表示成 0x1.0p-3。在十六进制表示法中, 使用 p 表示指数, 而不是 e。 注意, 尾数采用十六进制, 指数采用十进制。指数的基数是 2, 而不是10。
所有的浮点数值计算都遵循 IEEE 754 规范,针对浮点数值中的溢出和出错情况给出了三个特殊的浮点数值来表示,分别为正无穷大、负无穷大、NAN(不是一个数字)。
上述三个特殊浮点数值在Java中对应的常量:
Double.POSITIVE_INFINITY:正无穷大
Double.NEGATIVE_INFINITY:负无穷大
Double.NaN:非数字
float对应的三个特殊浮点数值的长量名和double的一致。
一个正整数除以 0 的结果为正无穷大。计算 0/0 或者负数的平方根结果为 NaN。
需要注意的是:
我们并不能直接使用 if(x = Double.NaN)这种方式来判断一个特定值是否是NaN,因为所有的“非数值”的值都认为是不相同的。但是我们可以使用Double.isNaN 方法来判断特定值是否是NaN。
if (x = Double.NaN) // is never true
if (Double.isNaN(x)) // check whether x is “not a number”警告:浮点数值不适用于无法接受舍入误差的金融计算中。例如,命令 System.out.println( 2.0-1.1 ) 将打印出 0.8999999999999999, 而不是人们想象的 0.9。这种舍入误差的主要原因是浮点数值采用二进制系统表示, 而在二进制系统中无法精确地表示分数 1/10。 这就好像十进制无法精确地表示分数1/3一样。如果在数值计算中不允许有任何舍入误差,应该使用 BigDecimal类
char 类型原本用于表示单个字符,但如今有些 Unicode 字符需要两个 char 值进行描述。
char 类型的字面值要用单引号括起来, 例如: ‘A’是编码值为 65 所对应的字符常量。它与 “A” 不同,“A” 是包含一个字符 A 的字符串.
char 类型的值可以表示为十六进制值,其范围从 \u0000 到 \Uffff 。例如:\u2122 表示注册符号 (TM), \u03C0 表示希腊字母 π。
除了转义序列 \u 之外, 还有一些用于表示特殊字符的转义序列,所有这些转义序列都可以出现在加引号的字符字面量或字符串中。 例如,’\u2122’ 或 "Hello\n”。
转义序列 \u 还可以出现在加引号的字符常量或字符串之外,而其他所有转义序列不可以。
public static void main(String \u005B\u005D args)
\u005B 和 \u005D 是 [ 和 ] 的编码
String str = \u0022strTest\u0022;//相当于String str = "strTest";
\u0022是"的编码
表3-3 特殊字符的转义序列 | ||
---|---|---|
转义序列 | 名称 | Unicode 值 |
\b | 退格 | \u0008 |
\t | 制表 | \u0009 |
\n | 换行 | \u000a |
\r | 回车 | \u000d |
\ " | 双引号 | \u0022 |
\' | 单引号 | \u0027 |
\\ | 反斜杠 | \u005c |
Unicode 转义序列会在解析代码之前得到处理,如"\u0022+\u0022”在解析之前 \u0022 会在解析之前转换为 “,因此其表示的是”"+"",是一个空串。
注意: 一定要当心注释中的 \u// \u00A0 is a newline
会产生一个语法错误, 因为读程序时 \u00A0 会替换为一个换行.
// Look inside c:\users
也会产生一个语法错误, 因为 \u 后面并未跟着 4 个十六进制数.原文摘录:
我们强烈建议不要在程序中使用 char 类型, 除非确实需要处理 UTF-16 代码单元。最好将字符串作为抽象数据类型处理。
整型值和布尔值之间不能进行相互转换,这与C++中有所不同,在C++中数值甚至指针可以代替 boolean 值,值 0 相当于false, 非0 值相当于true。
&esmp;对于如下代码:
if (x = 0)
在C++中可以编译通过且结果是false,但在Java中则编译不通过,原因便是在Java中整数表达式x=0不能转换为布尔值。
变量名必须是一个以字母开头并由字母或数字构成的序列(大小写敏感),相对于大多数程序设计语言,Java 中的“字母”和“数字”的范围更大。
如果想要知道哪些Unicode字符属于Java中的“字母”,可以使用Character类的isJavaldentifierStart和 isJavaldentifierPart 方法来检查。
声明一个变量之后,必须用赋值语句对变量进行显式初始化,千万不要使用未初始化的变量,否则Java编译器将认为它是错误的。
int vacationDays;
System.out.println(vacationDays)://ERROR--variable not initialized
在 Java 中,不区分变量的声明与定义。
变量的声明尽可能地靠近变量第一次使用的地方,这是一种良好的程序编写风格。
在 Java 中,利用关键字 final 指示常量,一个变量被final修饰后只能被赋值一次。一旦被赋值之后, 就不能够再更改了。习惯上,常量名使用全大写。
在 Java 中,经常希望某个常量可以在一个类中的多个方法中使用,通常将这些常量称为类常量。可以使用关键字 static final 设置一个类常量。
public static final double CM_PER_INCH = 2.54;
Java中用static修饰的类成员变量叫做类变量,也叫作静态变量。
Java中变量的分类 | |||
---|---|---|---|
成员变量 | 局部变量 | 静态变量 | |
定义位置 | 类中,方法外 | 方法中,方法的形式参数,或代码块中 | 类中,方法外 |
初始化值 | 有默认初始化值 | 无,先定义,赋值后才能使用 | 有默认初始化值(在类出现时即加载类时初始化) |
调用方式 | 对象调用 | 对象或类名调用 | |
存储位置 | 堆 | 栈 | 方法区的静态区 |
生命周期 | 与对象一致 | 与所在区域一致 | 与对应的类保持一致 |
当参与 / 运算的两个操作数都是整数时, 表示整数除法;否则, 表示浮点除法。
整数被 0 除将会产生一个异常, 而浮点数被 0 除将会得到无穷大或 NaN 结果。
在默认情况下, 虚拟机设计者允许对中间计算结果采用扩展的精度。
但是, 对于使用 strictfp 关键字标记的方法必须使用严格的浮点计算来生成可再生的结果。如果将一个类标记为strictfp, 那么这个类中的所有方法都要使用严格的浮点计算。
两个数值进行二元操作时,会先将两个操作数转换为同一类型,然后再进行计算,转换规则如下:
如果两个操作数中有一个是 double 类型, 另一个操作数就会转换为 double 类型。
否则, 如果其中一个操作数是 float 类型, 另一个操作数将会转换为 float 类型。
否则, 如果其中一个操作数是 long 类型, 另一个操作数将会转换为 long 类型。
否则, 两个操作数都将被转换为 int 类型。
Java中的默认转换规则:
取值范围小的数据类型与取值范围大的数据类型进行运算,会先将小的数据类型提升为大的,再运算。
注意(面试可能会问到):
如果两个byte或short进行二元运算,两个操作数会被提升为int,此时如果将运算结果赋给byte或者short就会报错(运行时异常,精度可能丢失)。但是byte b = 3 + 4;不会报错,这是因为Java的常量优化机制,在编译的时候就把3+4的结果计算出来了。
Java中在将高精度类型转换为低精度类型时需要进行强制转换,转换过程中会对超过低精度范围的部分进行截断,这也就可能造成强制转换过程中的精度丢失。
x += 4; 等价于 x = x + 4;(一般地, 要把运算符放在 = 号左边, 如 *= 或 %=)
注意:如果运算符得到一个值, 其类型与左侧操作数的类型不同, 就会发生强制类型转换(即运算时隐含强制类型转换)。
int x;
x +=3.5;//等价于x = (int)(x + 3.5);
自增运算符:++
自减运算符:- -
自增和自减运算符在运算过程中会进行强制类型转换。
byte b = 10;
b++; //不会报错,等同于b = (byte)(b + 1),隐含强制类型转换
b = b + 1; //会报错,因为byte与int进行混合运算,会提升为int类型,结果还是int,赋值给byte会损失精度
自增/自减运算符放在目标变量前则会先自增/减1后进行运算反之则先运算再自增/减
int m = 7;
int n = 7;
int a = 2 * ++m; //运算后a是16, m是8(先自增后参与运算)
int b = 2 * n++; //运算后b是14, n是8(先参与运算在自增)
&:与运算符
|:或运算符
&&:短路与运算符
||:短路或运算符
短路运算符先计算运算符左侧的表达式,如果不符合则直接返回结果,不再对右侧表达式进行计算。
非短路运算符绝对会计算运算符两侧的表达式。
处理整型类型时,可以直接对组成整型数值的各个位完成操作。
位运算符包括:
& (“and”)
| (“or”)
A (“xor”)
~ (“not”)
>>(右移)
<<(左移)
>>>无符号右移
有关Java中的位运算符详细内容请参看Java中的位移运算符。
从概念上讲, Java 字符串就是 Unicode 字符序列,在Java 中并没有内置的字符串类型, 但在标准 Java 类库中提供了一个叫String的预定义类。
在Java中String是不可变字符序列,就像数字 3 永远是数字 3—样,字符串str = “ Hello” 永远包含字符 H、 e、 l、 l 和 o 的代码单元序列,我们不能修改其中的任何一个字符。但是,我们可以修改字符串变量 str, 让它引用另外一个字符串, 这就如同可以将存放 3 的数值变量改成存放 4 一样。
Java中的字符串并不是字符数组,而是类似于char*指针,改变String类型的变量值时其实是给该变量赋了新的地址引用,类似指针指向了新的地址。
使用+
运算符生成的字符串对象是一个新的对象,即便已经有一个内容一致的字符串,他们两者的地址也不相同。
问题:不可变字符序列的设定会不会降低运行效率?
修改一个代码单元确实比创建一个新字符串更加简洁,通过拼接“ Hel” 和“ p!” 来创建一个新字符串的效率确实不高。但是,不可变字符串却有一个优点:编译器可以让字符串共享。
字符串共享:各种字符串存放在公共的存储池中,字符串变量指向存储池中相应的位置。 如果复制一个字符串变量, 原始字符串与复制的字符串共享相同的字符。
Java 的设计者认为共享带来的高效率远远胜过于提取、 拼接字符串所带来的低效率。在程序中很少需要修改字符串,而对字符串进行比较则较为频繁。
有些场景需要频繁的拼接字符串,此时再使用String就不合适了,针对这种情况,Java提供了可变字符序列来支持对字符串的修改。
比较两个字符串的值是否相等时一定要使用equals方法,而不能使用==
,因为==
运算符只能够确定两个字符串是否在同一个位置上,也就是比较的是两个字符串的地址,equals方法比较的才是两个字符串的值。
equals方法简介:
equals方法是超类Object的一个方法,源码如下:public boolean equals(Object obj) { return (this == obj); }
我们可以发现,上述equals方法其实就是利用
==
运算符来进行比较的,即比较两个对像的地址是否一样。
我们可以通过对equals方法进行重写来自定义比较规则,Java中提供的基本数据类型对应的包装类的equals方法是重写过的,这些重写过的equals方法不再单纯的比较两个对象的地址,而会对对象内容作比较。
equals底层会调用hashcode方法,所以重写equals方法也必须重写hashcode方法,若单独重写equals则自定义的比较规则并不会生效。
空串和Null串:
空串:
String str = "";
空串是长度为0的字符串,它是一个Java对象,有自己的串长度(0)和内容(空)。
Null串:
String str = null;
上述代码表示目前没有任何对象与该变量关联。
1. 码点
码点指与一个编码表中的某个字符对应的代码值。在 Unicode 标准中,码点采用十六进制书写,并加上前缀 U+, 例如 U+0041 就是拉丁字母 A 的码点。
Unicode 的码点可以分成 17 个代码级别 (code plane)。第一个代码级别称为基本的多语言级别 (basic multilingual plane ), 码点从 U+0000 到 U+FFFF, 其中包括经典的 Unicode 代码;其余的 16 个级别码点从 U+10000 到 U+10FFFF , 其中包括一些辅助字符(supplementary character)。
UTF-16 编码采用不同长度的编码表示所有 Unicode 码点。
2. 代码单元
在基本的多语言级别中,每个字符用 16 位表示,通常被称为代码单元(code unit) , 而辅助字符采用一对连续的代码单元进行编码。
在 Java 中,char 类型描述了 UTF-16 编码中的一个代码单元。
Java 字符串由 char 值序列组成。
String str = "Hello"; //length 方法返回采用 UTF-16 编码表示的给定字符串所需要的代码单元数量 int n = str.length(); //结果为5 //要想得到实际的长度(码点数量),可以调用如下方法。 int cpCount = str.codePointCount(0, str.length());//结果为5 //charAt(n) 方法返回位置 n 的代码单元 char first = str.charAt(0); //结果是'H' char last = str.charAt(4); //结果是’o’ //如果想得到第 i 个码点,应该使用下列语句 int i = 1; int index = str.offsetByCodePoints(0,i); int cp = str.codePointAt(index);//结果是101
我们可以简单的区分一下码点和代码单元,码点是编码表中的字符对应的代码值,辅助字符对应一个码点,但它却使用两个代码单元进行编码。
通常我们可以直接用String来构建一个字符串,但需要由较短的字符串拼接来构建字符串时再用String就会耗时且浪费空间,因为对于不可变序列String,使用+
拼接时,每次拼接都会构建一个新的String对象,此时我们就需要用StringBuilder来构建字符串。
String ch = "he";
String str = "llo";
StringBuilder builder = new StringBuilder();
builder.append(ch);
bui1der.append(str);
String completedString = builder.toString();//completedString = hello;
String、StringBuilder、StringBuffer的区别:
可变和不可变: String是不可变字符序列,StringBuilder和StringBuffer是可变字符序列。
线程安全: StringBuffer是线程安全的效率较低,StringBuilder是线程不安全的但效率较高。StringBuilder的前身是StringBuffer,它们两个的API是一样的。
注意: 我们通常是在单线程中编辑字符串,此时应该使用StringBuilder。
通过控制台输入,我们需要构造一个Scanner对象,并与“标准输入流”System.in关联。
Scanner in = new Scanner(System.in);
//读取一行,以换行符结束
String name = in.nextLine();
//读取一个单词,以空白符做为分隔符
String firstName = in.next();
//读取一个整数
int age = in.nextlnt();
由于输入是可见的,所以Scanner并不适合从控制台读取密码,为此,Java SE 6 特别引入了 Console 类实现这个目的。
注意:Console只能用在标准输入、输出流未被重定向的原始控制台中使用,在 Eclipse 或者其他 IDE 的控制台是用不了的。
Java SE 5.0 沿用了 C 语言库函数中的 printf 方法,下面将对格式化输出进行简单的介绍。
首先,给出两个例子:
double x = 10000.0 / 3.0;
System.out.println(x);//结果是:3333.3333333333335
//%8.2f 表示输出结果为8个字符宽度,其中小数部分占两个字符宽度
System.out.printf("%8.2f",x);//结果是:3333.33
String name = "张三";
int age = 25;
//每一个以%字符开始的格式说明符都用相应的参数替换,其中 f 表示浮点数,s 表示字符串,d 表示十进制整数
System.out.printf("Hello, %s. Next year, you'll be %d", name, age);//结果是:Hello, 张三. Next year, you'll be 25
表3-5 用于printf的转换符 | |||
---|---|---|---|
转换符 | 类型 | 举例 | |
d | 十进制整数 | 159 | |
x | 十六进制整数 | 9f | |
o | 八进制整数 | 237 | |
f | 定点浮点数 | 15.9 | |
e | 指数浮点数 | 1.59e+01 | |
g | 通用浮点数 | - | |
a | 十六进制浮点数 | 0xl.fccdp3 | |
s | 字符串 | Hello | |
c | 字符 | H | |
b | 布尔 | True | |
h | 散列码 | 42628b2 | |
tx 或 Tx | 日 期 时 间( T 强制大写) | 已经过时, 应当改为使用javaJime 类,参见卷 II第 6章 | |
% | 百分号 | % | |
n | 与平台有关的行分隔符 | - |
System.out.printf("%,.2f", 10000.0 / 3.0);//结果是:3,333.33
System.out.printf("%+,.2f", -100000.0 / 3.0);//结果是:-33,333.33
System.out.printf("%(,.2f", -100000.0 / 3.0);//结果是:(33,333.33)
表3-6 用于printf的标志 | |||
---|---|---|---|
标志 | 目的 | 举例 | |
+ | 打印正数和负数的符号 | +3333.33 | |
空格 | 在正数之前添加空格 | | 3333.33| | |
0 | 数字前面补 0 | 003333.33 | |
- | 左对齐 | |3333.33 | | |
( | 将负数括在括号内 | ( 3333.33 ) | |
, | 添加分组分隔符 | 3,333.33 | |
# ( 对于 f 格式) | 包含小数点 | 3,333. | |
# (对于 x 或 0 格式) | 添加前缀 0x 或 0 | 0xcafe | |
$ | 给定被格式化的参数索引。例如%1$d, %1$x 将以十进制和十六进制格式打印第 1 个参数。 | 159 9F | |
< | 格式化前面说明的数值。 例如%d%159 9F |
|
如果想对文件进行读取,我们可以用File对象构造一个Scanner对象,如下:
Scanner in = new Scanner(Paths.get("niyflle.txt") , "UTF-8") ;
注意:如果文件名中包含反斜杠符号,就要记住在每个反斜杠之前再加一个额外的反斜杠进行转义。例:
c:\\mydirectory\\myfile.txt
如果我们将文件路径以字符串形式作为构建Scanner的参数,该Scanner只会将该字符串解释为数据而不是文件路径。//这个scanner会将参数作为包含10个字符的数据 Scanner in = new Scanner("myfile.txt");
若想要对文件进行写操作,我们就需要构造一个 PrintWriter 对象,构造该对象,我们可以只提供文件路径。
PrintWriter out = new PrintWriter("myfile.txt", "UTF-8") ;
Java 使用条件语句和循环结构确定控制流程,与C和C++只有很小的区别。如Java没有goto语句,但break语句可以带标签;Java中有一种变形的for循环语句,C和C++中没有这类循环。
块(即复合语句)是指由一对大括号括起来的若干条简单的 Java 语句,其确定了变量的作用域。一个块可以嵌套在另一个块中,但不能在嵌套的两个块中声明同名的变量。
在循环中,检测两个浮点数是否相等需要格外小心,如下面的for循环语句:
for (double x = 0; x != 10; x += 0.1) {
......
}
由于舍入的误差,x 可能直接从 9.999 999 999 999 98 跳到10.099 999 999 999 98,如此x!= 10
就不会成立,这就导致了死循环。
使用switch分支语句时一定要注意因分支缺少break导致的switch穿透问题。
Java 提供了一种带标签的 break 语句, 用于跳出多重嵌套的循环语句。标签必须放在希望跳出的最外层循环之前, 并且必须紧跟一个冒号,示例如下:
read_data:
while (...) {//外层循环
for (...) {//内层循环
...
if (...)
break read_data;//跳到read_data标识的最外层循环语句块末尾
}
}
break跳出当前循环,不再执行之后的循环,continue跳出本次循环,接着执行下次循环。
在某些场景下,如银行的转账,基本的整数和浮点数精度并不能够满足要求,此时我们就需要使用Java给我们提供的大数值来处理。
Java在java.math包中提供了Biglnteger和BigDecimal两个可以处理包含任意长度数字序列数值的类,其中Biglnteger 类实现了任意精度的整数运算,BigDecimal实现了任意精度的浮点数运算。
我们可以使用静态的valueOf方法将普通的数值转换为大数值,如下:
BigInteger a = BigInteger.valueOf(100);
需要注意的是,我们并不能使用算术运算符来处理大数值,而是需要使用大数值类中的add和multiply等方法。
BigInteger a = BigInteger.valueOf(100);
BigInteger b = BigInteger.valueOf(100);
BigInteger c = a.add(b);//c = a + b
BigInteger d = c.multiply(b.add(BigInteger.valueOf(2)));//d = c * (b + 2)
注意:Java虽然为字符串的连接重载了 + 运算符,但没有重载其他的运算符,也没有给Java程序员提供在自己的类中重载运算符的机会,所以Java并不能像 C++ 一样具有运算符重载功能。
数组是一种数据结构,用来存储同一类型值的集合。在声明数组变量时需要指出数组类型和数组变量的名字。
int[] a;//或 int a[];
上面的语句只是声明了变量 a,并没有将 a 初始化为一个真正的数组。我们可以使用 new 关键字创建数组。
int[] a = new int[100];
除了使用 new 关键字创建数组外我们还可以直接给数组赋初值,数组的大小就是初始值的个数,示例如下:
int[] small Primes = { 2, 3, 5, 7, 11, 13 };
创建一个数字数组时,所有的元素都初始化为 0。Boolean数组的元素会初始化为false。对象数组的元素则初始化为一个特殊值 null。
String[] names = new String[10];
上面的语句会创建一个包含 10 个字符串的数组,所有字符串都为 null。
一旦创建了数组, 就不能再改变它的大小(尽管可以改变每一个数组元素)。如果在运行过程中需要经常扩展数组的大小,我们可以使用数组列表(ArrayList)。
double[][] balances = new double[5][7];
int[][] magicSquare =
{
{16, 3, 2, 13},
{5, 10, 11, 8},
(9, 6, 7, 12},
{4, 15, 14, 1}
};
该循环结构是对普通for循环的一种增强,其底层使用迭代器。
//这一集合必须是一个数组或者是一个实现了 Iterable 接口的类对象
String[] strArry = new String[10];
for (String string : strArry) {}
面向对象程序设计,英文全拼是:Object Oriented Programming
构造对象的模板或蓝图。
由类构造 (construct ) 对象的过程称为创建类的实例 (instance ) 。
有时称为数据隐藏,对象就是一个封装体。从形式上看就是将数据和行为组合在一个包中,并对对象的使用者隐藏了数据的实现方式。
对象中的数据称为实例域(instance field ),操纵数据的过程称为方法( method )。对于每个特定的类实例(对象)都有一组特定的实例域值。这些值的集合就是这个对象的当前状态 ( state )。
实现封装的关键在于绝对不能让类中的方法直接地访问其他类的实例域。 程序仅通过对象的方法与对象数据进行交互。封装给对象赋予了“ 黑盒” 特征, 这是提高重用性和可靠性的关键。
如果一个类的方法操纵另一个类的对象,我们就说一个类依赖于另一个类,这种关系是偶然的、临时的,是非常弱的。
通常,依赖关系在Java中体现为局部变量、方法形参、静态方法调用等,于代码层面,类B作为参数被类A在某个方法中使用,则类A依赖于类B。
关联关系体现的是类与类或类与接口之间语义级别的强依赖关系,这种关系是稳定的、长久性的。
关联关系可以是单向的也可以是双向的,表现在代码层次就是类A的其中一个属性为类B,则A关联于类B。
聚合是关联关系的一种特例,他体现的是整体与部分的关系,此时整体与部分之间是可分离的,他们可以具有各自的生命周期。比如汽车和车轮、引擎之间就是聚合关系,其中汽车是整体,车轮和引擎是部分,整体和部分是可分离的,部分可以脱离整体单独存在。
组合也是关联关系的一种特例,同样体现整体与部分间的关系,但这种关系比聚合更强,也称为强聚合,是一种contains-a的关系。此时整体与部分是不可分的,整体的生命周期结束也就意味着部分的生命周期结束,例如公司和部门之间就是组合关系,公司是整体,部门是部分,部门不能脱离公司独自存在,当公司不存在时部门当然不会继续存在。
聚合和组合都是特殊的关联关系,区别在于整体和部分是否可以分割,生命周期是否可以独立。聚合关系中,删除聚合对象后部分依旧可以存在,而在组合关系中删除组合对象后,部分也会被删除,无法独立存在。
泛化是指类间的结构关系、亲子关系,和继承关系很相似,在UML中两者的表示也是一致的。
泛化是父类进行扩展的一个过程,继承是对多个类公共部分抽取得到父类的过程。
实现关系和继承相似,不同的是实现的上层是父接口,继承的上层是父类。
Date deadline;
上述语句只是定义了一个对象变量,它虽然可以引用 Date 类型的对象,但变量 deadline 不是一个对象,也没有引用任何对象,因此,该变量不能使用任何Date方法,如果使用了就会产生运行时错误。
Date deadline = new Date();
上述语句可以分为两部分,一部分是 new Date()
,该表达式构造了一个Date类型的对象,它的值是对新创建对象的引用,第二部分则是将引用值存储到变量 deadline 中。
需要注意的是:
1.new
操作符的返回值是一个引用。
2. 一个对象变量并没有实际包含一个对象,而仅仅引用一个对象。
3. 我们可以显式地将对象变量设置为 null, 表明这个对象变量目前没有引用任何对象。deadline = null;
4. 在 Java 中的 null 引用对应 C++ 中的 NULL 指针。
5. 所有的 Java 对象都存储在堆中。 当一个对象包含另一个对象变量时, 这个变量依然
包含着指向另一个堆对象的指针。
Java 标准类库中的 Date 类的实例有一个状态,即特定的时间点,时间是用距离该固定时间点的毫秒数表示的。这个时间点就所谓的纪元(epoch),它 是UTC(Coordinated Universal Time)时间 1970 年 1 月 1 日 00:00:00。
Date用来表示时间点,LocalDate为时间点命名为日历表示法。
不要使用构造器来构造 LocalDate 类的对象,应当使用静态工厂方法 (factorymethod) 代表你调用构造器,也可以提供年、月和日来构造对应一个特定日期的对象。
//构造一个表示构造当前对象时间的日期对象
LocalDate nowLocalDate = LocalDate.now();
//根据提供的年、月、日构造一个日期对象
LocalDate newYearsEve = LocalDate.of(1999, 12, 31);
int year = newYearsEve.getYear(); // 1999
int month = newYearsEve.getMonthValue(); // 12
int day = newYearsEve.getDayOfMonth(); // 31
//获取距离newYearsEve对象1000天的新日期对象,该方法不会对调用对象产生影响
LocalDate aThousandDaysLater = newYearsEve.plusDays(1000);
year = aThousandDaysLater.getYear();// 2002
month = aThousandDaysLater.getMonthValue(); // 09
day = aThousandDaysLater.getDayOfMonth(); // 26
public class Employee {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
更改器方法:访问且修改对象的方法可以称为更改器方法,如上述示例代码中的 setName 方法修改了 Employee 类的 name 属性值,这就是一个更改器方法。
访问器方法:只访问而不修改对象的方法可以称为访问器方法,如上述示例代码中的 getName 方法只是访问了Employee 类的 name 属性值,并没有对其进行修改,这就是一个访问器方法。
需要注意的是:
不要编写返回引用可变对象的访问器方法,否则将存在破坏封装性的隐患。示例如下:class Employee{ private Date hireDay ; public Date getHireDay() { return hireDay; } public void setHireDay(Date hireDay) { this.hireDay = hireDay; } }
Date 类有一个更改器方法 setTime,可以使用该方法设置毫秒数,也就是说 Date 对象是可变的, 这就破坏了封装性,我们可以通过如下一段代码,在绕开 Employee 类的修改器方法的前提下对 Employee 类的私有属性 hireDay 进行修改。
Employee harry = new Employee(); harry.setHireDay(new Date()); //结果为:Tue Aug 27 16:55:27 CST 2019 System.out.println(harry.getHireDay()); Date d = harry.getHireDay(); double tenYearsInMilliSeconds = 10 * 365.25 * 24 * 60 * 60 * 1000; d.setTime(d.getTime() - (long) tenYearsInMilliSeconds); //结果为:Thu Aug 27 04:55:27 CST 2009 System.out.println(harry.getHireDay());
可以很直观的看到,对于 Employee 类封装的 hireDay 私有属性,我们在不使用 Employee 修改器方法的前提下对其进行的修改,此时 hireDay 属性就已经不再是 Employee 类的私有属性了。
为了防止上述情况的发生,我们可以在必须返回引用可变对象的访问器方法中返回可变对象的克隆,修改代码如下:public Date getHireDay(){ return (Date) hireDay.clone(); }
假设有一个员工类 Employee ,它有一个 raiseSalary 方法,改方法可以根据传入的参数将员工的薪资提高相应的百分比,部分代码如下:
public class Employee{
private double salary;//薪资
public void raiseSalary(double byPercent){
double raise = salary * byPercent / 100;
salary += raise;
}
}
如果想要调用 Employee 类的 raiseSalary 方法,我们需要实例化一个 Employee 类,之后使用该示例调用。
Employee employee = new Employee();
employee.raiseSalary(5);
对于 raiseSalary 方法,位于方法名后面括号中的数值(这里是5)是显式参数,调用方法时出现在方法名前的 Employee 对象(这里是employee)是隐式参数。
显式参数是明显地列在方法声明中的, 如上述方法中的 double byPercent 。隐式参数则没有出现在方法声明中,我们可以把隐式参数理解为方法调用的目标或接收者。
在每一个方法中, 关键字 this 表示隐式参数。如下的示例代码中,this 就表示隐式参数 Employee ,我们可以采用这样的风格编写自己的代码,这样可以将实例域与局部变量明显地区分开来。
public void raiseSalary(double byPercent){
double raise = this.salary * byPercent / 100;
this.salary += raise;
}
一个方法可以访问所属类的所有对象的私有数据,如下代码:
public class Employee {
private String name;
public boolean equalsName(Employee other) {
return this.name.equals(other.name);
}
//省略 Get 和 Set 方法
}
有一个典型的调用方式是:
Employee harry = new Employee();
Employee boss = new Employee();
if (harry.equalsName(boss)) {. . . }
equalsName 方法可以访问调用者 harry 的私有域, 也可以访问参数 boss 的私有域。原因是 boss 是 Employee 类对象, 而 Employee 类的方法可以访问 Employee 类的任何一个对象的私有域,这也就是基于类的访问权限。
被 final 修饰的基本数据类型表示常量,只能被赋值一次,赋值后值不能在对其进行修改。
当final应用于对象引用时,不能改变的是他的引用,例子如下:
private final StringBuilder evaluations = new StringBuilder();
final 关键字只是表示存储在 evaluations 变量中的对象引用不会再指示其他 StringBuilder对象,但这个对象本身是可以被更改的。
被final修饰的类不能被继承,没有子类,其方法默认是final的,但成员变量默认不是final的。
private int i;
public Poppet(int i){
this.i = i;
}
被final修饰的方法不能被子类覆盖,但可以被继承。
注意:final不能用于修饰构造方法。
对于一个类的实例域,其每一个实例对象都有着对该实例域的拷贝。如果我们将域定义为 static, 那么每个类中就只有一个这样的域,它的所有示例对象也都只有这一个域(没有独属于自己的拷贝),而这样的域被称为静态域。
class Employee{
private static int nextId = 1;
private int id;
. . .
}
对于上述示例代码,每一个 Employee对象都有一个自己的 id 域, 但这个类的所有实例都共享一个 iiextId 域。换句话说, 如果有 1000 个 Employee 类的对象, 则有 1000 个实例域 id。 而这 1000 个对象却只有一个静态域 nextld。需要注意的是,即使没有一个 Employee 对象, 静态域 nextld 也存在,因为它属于类,而不属于任何独立的对象。
静态变量使用得比较少,但静态常量却使用得比较多。例如, 在 Math 类中定义了一个静态常量:
public class Math{
public static final double PI = 3.14159265358979323846;
}
我们可以采用 Math.PI 的形式获得这个常量,而当我们省略 static 关键字时 PI 就变成了 Math 类的一个实例域。需要通过 Math 类的对象访问 PI,并且每一个 Math 对象都有它自己的一份 PI 拷贝。
静态方法是一种不能向对象实施操作的方法,如 Math 类的 pow 方法便是一个静态方法,其只能使用类名进行调用(Math.pow(x, a);
),而不能使用任何 Math对象进行调用,也就是说,pow 方法没有隐式参数。
静态方法是没有隐式参数的,我们也可以认为静态方法是没有 this 参数的方法。
一个类的静态方法不能访问该类实例对象的 Id 实例域(因为它不能操作对象)但可以访问自身类中的静态域。
静态方法还有另外一种常见的用途:类似 LocalDate 和 NumberFormat 的类使用静态工厂方法 (factory methocO 来构造对象。
main 方法也是一个静态方法,该方法不对任何对象进行操作。事实上,在启动程序时还没有任何一个对象,静态的 main 方法将执行并创建程序所需要的对象。
在程序设计语言中有关将参数传递给方法(或函数)的专业术语有按值调用(call by value)、按引用调用(call by reference)、按名调用( call by name)。其中,按名调用已经退出了历史舞台,现有程序设计语言主要使用另外两种参数传递方法。
一个方法可以修改传递引用所对应的变量值, 而不能修改传递值调用所对应的变量值。示例代码如下:
public void testMethod1(int m) {
m++;
System.out.println("方法中的值:"+m);
}
public void testMethod2(String str) {
str = str + "Word!";
System.out.println("方法中的值:"+str);
}
public void testMethod3(Employee employee) {
employee.setName("zhangsan");
System.out.println("方法中的值:"+employee.getName());
}
@Test
void test() {
System.out.println("基本数据类型:");
int aint = 10;
System.out.println("方法处理前的值:"+aint);
testMethod1(aint);
System.out.println("方法处理后的值:"+aint);
System.out.println("引用类型String:");
String str1 = "Hello ";
System.out.println("方法处理前的值:"+str1);
testMethod2(str1);
System.out.println("方法处理后的值:"+str1);
System.out.println("引用类型:");
Employee employee = new Employee();
employee.setName("张三");
System.out.println("方法处理前的值:"+employee.getName());
testMethod3(employee);
System.out.println("方法处理后的值:"+employee.getName());
}
输出值:
基本数据类型:
方法处理前的值:10
方法中的值:11
方法处理后的值:10
引用类型String:
方法处理前的值:Hello
方法中的值:Hello Word!
方法处理后的值:Hello
引用类型:
方法处理前的值:张三
方法中的值:zhangsan
方法处理后的值:zhangsan需要注意的是,String虽然是引用类型的数据,但是应为其不可变字符序列特点,我们可以将其看做基本数据类型来使用,这也是为什么String为引用类型但在本例子中经过方法值不改变的原因。
Java 程序设计语言总是采用按值调用,只不过引用数据类型传递的是引用的地址值而不是引用对象本身。想要了解更多请请点击跳转后翻看标题 2 下内容
在Java中,对象的构造器和其他方法不同,主要有以下几点:
1. 构造器与类同名
2. 每个类可以有一个以上的构造器
3. 构造器可以有 0 个、1 个或多个参数
4. 构造器没有返回值,即方法声明不能有返回值类型,void也不行!
5. 构造器总是伴随着 new 操作一起调用
Java 使用构造器来构造对象,下面将对Java提供的几种构造器编写机制进行介绍。
1. 重载:
如果多个方法(比如, StringBuilder 构造器方法)有相同的名字、 不同的参数,便产生了重载。编译器会根据各个方法给出的参数类型与个数来进行区分,如果编译器找不到参数匹配的方法, 就会产生编译时错误。(这个过程被称为重载解析(overloading resolution)。)
Java 允许重载任何方法, 而不只是构造器方法。
方法的名字和参数列表称为方法的签名。
2. 默认域初始化:
如果在构造器中没有显式地给域赋予初值,Java会自动地为其赋默认值: 数值为 0、布尔值为 false、 对象引用为 null。
不明确地对域进行初始化会影响程序代码的可读性,因此并不推荐此种做法,正确的做法可参考一以下的显式域初始化。
3. 无参数的构造器:
在Java中,很多类都包含一个无参数的构造函数,如果我们在编写一个类时没有编写构造器, 系统就会提供一个默认无参数构造器。
需要注意的是,如果我们在编写一个类的时候提供了一个有参构造器,那么系统将不再提供默认的无参构造器。此时如果我们想要使用无参构造器就需要显式地给出无参构造器。
当对象由无参数构造函数创建时, 其状态会设置为适当的默认值就,即所有的实例域都会被设置为默认值(数值为 0、布尔值为 false、 对象引用为 null)。
建议在无特殊需求的情况下,编写的类给出无参和有参两种构造器。
4. 显式域初始化:
在类定义中, 我们可以直接显式地给域赋予初值,这样能够确保不管怎
样调用构造器,每个实例域都可以被设置为一个有意义的初值。当然初始值并不一定是常量值,也可以以调用方法的形式对域进行初始化。下面将给出示例代码:
class Employee{
private String name = "";
private int id = assignId();
private static int assignId(){
return 10;
}
}
5. 调用另一个构造器:
关键字 this 除了引用方法的隐式参数外有另外一个含义,即如果构造器的第一个语句形如 this(…), 这个构造器将调用同一个类的另一个构造器,示例代码如下:
public Employee(String name, int age) {
this.name = name;
this.age = age;
}
public Employee(String name) {
this(name, 10);
}
当调用 new Employee("张三")
时, Employee(String)
构造器将调用 Employee(String,int)
构造器。
采用这种方式使用 this 关键字非常有用, 这样对公共的构造器代码部分只编写一次即可。
6. 初始化块:
首先我们要知道什么是代码块,简单来讲就是用大括号括起来的一段代码,示例如下:
{
System.out.println("普通代码块");
}
代码块又可以分为局部代码块(普通代码块)、构造代码块、静态代码块、同步代码块。其中静态代码块在类被加载的时候执行(只会执行这一次),并且其优先于各种代码块以及构造函数;构造代码块则在创建对象时执行,但是优先与构造函数且依托于构造函数。
我们可以利用静态代码块以及构造代码块的特点,对静态属性、类进行初始化,这也就是初始化块。
有关Java中代码块的详细内容请点击跳转后阅读第 6 章
一个类可以使用所属包中的所有类, 以及其他包中的公有类( public class)。
我们可以采用两种方式访问另一个包中的公有类。第一种方式是在每个类名之前添加完整的包名,第二种方式是使用import
语句。
第一种方式:类名之前添加完整包名
java.time.LocalDate today = java.time.LocalDate.now() ;
第二种方式:使用 import 语句
1. 导入特定类或者整个包
import java.util.*;
import 语句应该位于源文件的顶部, package 语句的后面。
需要注意的是, 只能使用星号(*
) 导入一个包, 而不能使用import java.*
或import java.*.*
导入以 java 为前缀的所有包。
2. 使用类
LocalDate today = LocalDate.now();
当需要同时使用两个不同包的同名类时,如果只是使用
import
语句导入两个包,编译器并不能很好的识别两个类,此时我们就需要再使用类的时候给出完整的包名了。比如我们要同时使用java.util .Date
和java.sql .Date
这连个类,我们就需要使用下面的写法:java.util .Date deadline = new java.util .Date(); java.sql.Date today = new java.sql.Date(. . .);
import 语句不仅可以导入类,JDK1.5 版本后还增加了导入静态方法和静态域的功能。
如果我们有以下一条静态导入语句。
import static java.lang.System.*;
我们就可以直接使用 System 类的静态方法和静态域,而不必加类名前缀。
out.println("Goodbye, World!");
exi(0);
当然,我们还可以导入特定的方法或域。
import static java.lang.System.out;
JDK 包含一个很有用的工具,叫做 javadoc, 它可以由源文件生成一个 HTML 文档。我们熟知的JDK的API文档实际上就是通过对标准Java类库的源代码运行javadoc生成的。
在上面的内容中,已经提到过Java注释的文档注释,我们只需要在源代码中添加以专用的定界符 /** 开始的注释,就可以很容易地生成一个看上去具有专业水准的文档。
javadoc 实用程序(utility) 从下面几个特性中抽取信息:
1. 包
2. 公共类与接口
3. 公有的和受保护的构造器及方法
4. 公有的和受保护的域
编码时我们应该为上面几部分编写注释 。注释应该放置在所描述特性的前面并以/**
开始,以*/
结束。
每个/** . . . */
文档注释在标记之后紧跟着自由格式文本( free-form text )。标记由 @开始,如 @author
或 @param
。
自由格式文本的第一句应该是一个概要性的句子。javadoc 实用程序自动地将这些句子抽取出来形成概要页。
在自由格式文本中,可以使用 HTML 修饰符,例如用于强调的 … 、用于着重强调的 … 以及包含图像的 等。不过, 一定不要使用
…
,因为这样一来, 就不用对代码中的 < 字符转义了。
如果文档中需要用到其他文件的链接, 例如, 图像文件(用户界面的组件如图表或图像等),就应该将这些文件放到子目录 doc-files 中。javadoc 实用程序将从源目录拷贝这些目录及其中的文件到文档目录中。 如果要使用这些文件,我们可以使用
的方式引用。
1. 类注释:
类注释必须放在 import 语句之后,类定义之前。
/**
* A {@code Card} object represents a playing card, such
* as "Queen of Hearts". A card has a suit (Diamond, Heart,
* Spade or Club) and a value (1 = Ace, 2 . . . 10, 11 = Jack,
* 12 = Queen, 13 = King)
*/
public class Card{}
2. 方法注释:
每一个方法注释必须放在所描述的方法之前。除了通用标记之外, 还可以使用下面的标记:
1. @param 变量描述
这个标记将对当前方法的“ param” (参数)部分添加一个条目。这个描述可以占据多行, 并可以使用 HTML 标记。一个方法的所有 @param 标记必须放在一起。
2. @return 描述
这个标记将对当前方法添加“ return” (返回)部分。这个描述可以跨越多行, 并可以使用 HTML 标记。
3. @throws 类描述
这个标记将添加一个注释, 用于表示这个方法有可能抛出异常。
/**
Raises the salary of an employee.
*@param byPercent the percentage by which to raise the salary (e.g. 10 means 10%)
* @return the amount of the raise
*/
public double raiseSalary(double byPercent){}
3. 域注释:
只需要对公有域(通常指的是静态常量)建立文档。
/**
* The "Hearts" card suit
*/
public static final int HEARTS = 1;
4. 通用注释:
下面的标记可以用在类文档的注释中。
1. @author 姓名
这个标记将产生一个“author” (作者)条目。可以使用多个 @author 标记,每个@author 标记对应一个作者。
2. @version 文本
这个标记将产生一个“ version”(版本)条目。这里的文本可以是对当前版本的任何描述。
下面的标记可以用于所有的文档注释中。
3. @since 文本
这个标记将产生一个“ since” (始于)条目。这里的 text 可以是对引入特性的版本描述。例如, ©since version 1.7.10
4. @deprecated 文本
这个标记将对类、方法或变量添加一个不再使用的注释。 文本中给出了取代的建议。例如,@deprecated Use setVIsible(true)
instead
通过 @see 和@link标记, 可以使用超级链接, 链接到 javadoc 文档的相关部分或外部文档。
5. @see 引用
只要提供类、方法或变量的名字,javadoc 就会在文档中插入一个超链接。例如,@see com.horstmann.corejava.Employee#raiseSalary(double)
会建立一个链接到 com.horstmann.corejava.Employee
类的 raiseSalary(double)
方法的超链接。 可以省略包名, 甚至把包名和类名都省去,此时,链接将定位于当前包或当前类。
需要注意的是,一定要使用井号(#) 而不要使用句号(.)分隔类名与方法名,或类名与变量名。因为javadoc 实用程序并不能很好的地断定句点在分隔包、 子包、 类、内部类与方法和变量时的不同含义。
如果@see 标记后面有一个 < 字符,就需要指定一个超链接。可以超链接到任何URL。例如:@see The Core ]ava home page
在上述各种情况下, 都可以指定一个可选的标签( label ) 作为链接锚(link anchor) 。 如果省略了 label , 用户看到的锚的名称就是目标代码名或 URL。
如果@see 标记后面有一个双引号(")字符, 文本就会显示在 “ see also” 部分。例如:@see "Core Java 2 volume 2
可以为一个特性添加多个 @see 标记,但必须将它们放在一起。
如果愿意的话, 还可以在注释中的任何位置放置指向其他类或方法的超级链接,例如:{@link package.class#feature label }
(这里的特性描述规则与@see 标记规则一样。)
要想产生包注释,就需要在每一个包目录中添加一个单独的文件。可以
有如下两个选择:
1. 提供一个以 package.html
命名的 HTML 文件。在标记
package-info.java
命名的 Java 文件。这个文件必须包含且仅包含一个跟随在包语句之后的以 /**和 */ 界定的 Javadoc 注释。
我们还可以为所有的源文件提供一个概述性的注释,而要想达到这一目的,需要在包含所有源文件的父目录中提供一个名为 overview.html 的文件。javadoc工具会将 overview.html 文件的
… 标记之间的所有文本抽取出来作为概述性注释。当用户在生成的文档的导航栏中选择“Overview” 时,就会显示出这些注释内容。
假设 HTML 文件将被存放在目录 docDirectory 下。操作步骤如下:
1. 切换到包含想要生成文档的源文件目录。 如果有嵌套的包要生成文档, 例如 com.horstmann.corejava,就必须切换到包含子目录 com 的目录(如果存在overview.html 文件的话, 就是它的所在目录 )。
2. 如果只对一个包生成文档,运行 javadoc -d docDirectory nameOfPackage
命令。如果对多个包生成文档,运行javadoc -d docDirectory nameOfPackage1 nameOfPackage2 . . .
,多个包名之间用空格分隔。如果要对默认包中的源文件生成文档则应该运行javadoc -d docDirectory *. java
命令。
需要注意的是,如果我们没有指定生成文档的目标路径(上述命令中使用 -d docDirectory 指定在当前目录的docDirectory文件件生成文档),即省略命令中的-d docDirectory 选项,HTML 文件就会被提取到当前目录下,这就可能造成文件的混乱。
更多 javadoc 相关内容请参考官方文档:https://docs.oracle.com/javase/8/docs/technotes/guides/javadoc/index.html
1. 一定要保证数据私有
这是最重要的,绝对不要破坏封装性。
2. 一定要对数据初始化
Java 不会对局部变量进行初始化, 但是会对对象的实例域进行初始化。即便如此,我们并不能过于依赖系统的默认值, 而是应该尽量显式地初始化所有的数据。具体的初始化方式可以是提供默认值, 也可以是在所有构造器中设置默认值。
3. 不要在类中使用过多的基本类型
为了使类更加易于理解和修改,我们应该尽量将多个相关的基本类型封装为一个新类,如将一个类中和地址相关基本类型封装为 Address 类,这样,可以很容易处理地址的变化。
4. 不是所有的域都需要独立的域访问器和域更改器
对于一个雇员类,我们或许需要获得或设置雇员的薪金,但对于雇用日期则应该在构造了雇员对象后禁止更改。在对象中也常常包含一些不希望别人获得或设置的实例域, 如, 在 Address 类中,存放州缩写的数组。
5. 将职责过多的类进行分解
将一个复杂的类分解为多个简单类,当然不能太过极端反而矫枉过正。
6. 类名和方法名要能够体现它们的职责
7. 优先使用不可变的类
更改对象的问题在于, 如果多个线程试图同时更新一个对象, 就会发生并发更改。其结果是不可预料的。 如果类是不可变的,就可以安全地在多个线程间共享其对象。当然,并不是所有类都应该是不可变的,我们需要根据实际情况进行分析。
《Java核心技术 卷Ⅰ》读书笔记二