Java 的基本程序设计结构
1. Hello World
public class FirstSample
{
public static void main(String[] args)
{
System.out.println("We will not use 'Hello, World!'");
}
}
2. 注释
使用//
或/* */
。第 3 种注释以/**
开始,以*/
结束,可以用来自动生成文档:
/**
* This is the first sample program in Core Java Chapter 3
* @version 1.01 1997-03-22
* @author Gary Cornell
*/
public class FirstSample
{
public static void main(String[] args)
{
System.out.println("We will not use 'Hello, World!'");
}
}
在 Java 中,
/* */
注释不能嵌套。
3. 数据类型
Java 是一种强类型语言,必须为每一个变量声明一种类型。在 Java 中一共有 8 种基本类型(primitive type):4 种整型、2 种浮点类型、1 种字符类型char
和 1 种用于表示真值的boolean
类型。
3.1 整型
Java 提供了如下 4 种整型:
int 类型的正数部分正好超过 20 亿
类型 | 存储需求 | 取值范围 |
---|---|---|
byte | 1 字节 | -128 ~ 127 |
short | 2 字节 | -32768 ~ 32767 |
int | 4 字节 | -231 ~ 231-1 |
long | 8 字节 | -263 ~ 263-1 |
- 整型的范围与运行 Java 代码的机器无关
- 长整型数值有一个后缀
L
- 十六进制数值有一个前缀
0x
或0X
- 八进制数值有一个前缀
0
,例如010
对应八进制中的8
- 从 Java 7 开始,加上前缀
0b
或0B
就可以写二进制数 - 从 Java 7 开始,还可以为数字字面量加下划线,如
1_000_000
表示一百万。这些下划线只是为了让人更易读,Java 编译器会把它们去除掉。 - Java 没有任何无符号(unsigned)类型
3.2 浮点类型
浮点类型用于表示有小数部分的数值。在 Java 中有两种浮点类型:
类型 | 存储需求 | 取值范围 | 有效数字 |
---|---|---|---|
float | 4 字节 | 大约 ±3.402 823 47E+38F | 6~7 位 |
double | 8 字节 | 大约 ±1.797 693 134 862 315 70E+308 | 15 位 |
- double 表示这种类型的数值精度是 float 类型的两倍,也称双精度数值。绝大部分应用程序都采用 double 类型
- float 类型的数值有一个后缀
F
或f
。没有后缀F
的浮点数值默认为 double 类型。也可以在浮点数值后面添加后缀D
或d
有三个用于表示溢出和出错情况的特殊浮点数值:
- 正无穷大
Double.POSITIVE_INFINITY
- 负无穷大
Double.NEGATIVE_INFINITY
- NaN(不是一个数字)
Double.NaN
不能检测一个特定值是否等于Double.NaN
,因为所有“非数值”的值都认为是不相同的。可以使用Double.isNaN
方法:
if (x == Double.NaN) // is never true
if (Double.isNaN(x)) // check whether x is "Not a Number"
浮点数值不适用于无法接收舍入误差的场景。例如,以下命令将打印出
0.8999999999999999
而不是0.9
,这种舍入误差的主要原因是浮点数值采用二进制系统表示,而在二进制系统中无法精确的表示分数1/10
:
System.out.println(2.0 - 1.1);
------
0.8999999999999999
如果在数值计算中不允许有任何舍入误差,就应该使用 BigDecimal 类。
3.3 char 类型
char 类型的字面量值要用单引号括起来。例如:'A'
是编码值为 65 所对应的字符常量,它与"A"
不同,"A"
是包含一个字符 A 的字符串。
char 类型可以表示为十六进制,其范围从
\u0000
到\uffff
。
转义序列 | 名称 | Unicode 值 |
---|---|---|
\b | 退格 | \u0008 |
\t | 制表 | \u0009 |
\n | 换行 | \u000a |
\r | 回车 | \u000d |
\” | 双引号 | \u0022 |
\’ | 单引号 | \u0027 |
\\ | 反斜杠 | \u005c |
特别注意:
- Unicode 转义序列会在解析代码之前得到处理
- 更隐秘的,一定要当心注释中的
\u
,例如// Look inside c:\users
3.4 Unicode 和 char 类型
码点(code point)是指与一个编码表中的某个字符对应的代码值。在 Unicode 标准中,码点采用十六进制书写,并加上前缀
U+
,例如U+0041
就是拉丁字母 A 的码点。
在 Java 中,char 类型描述了 UTF-16 编码中的一个代码单元。
3.5 boolean 类型
boolean(布尔)类型有两个值:false 和 true,用来判定逻辑条件。
整型值和布尔值之间不能进行相互转换。
4. 变量
在 Java 中,每个变量都有一个类型,变量名必须是一个以字母开头并由字母或数字构成的序列。
可以使用 Character 类的
isJavaIdentifierStart
和isJavaIdentifierPart
方法来检查那些 Unicode 字符属于 Java 中的字母。
另外,不要在代码中使用$
字符,它只用在 Java 编译器或其他工具生成的名字中。
4.1 变量初始化
在 Java 中,变量的声明尽可能的靠近变量第一次使用的地方,这是一种良好的程序编写风格。
声明一个变量后,必须使用赋值语句对变量进行显式初始化,使用未初始化的变量编译器会报错:
int vacationDays;
System.out.println(vacationDays); // ERROR--variable not initialized
4.2 常量
关键字 final 用来指示常量:
final double PI = 3.14;
final 表示这个变量只能被赋值一次,一旦被赋值之后,就不能够再更改了。习惯上,常量名使用全大写。
在 Java 中,经常希望某个常量可以在一个类中的多个方法使用,通常将这些常量称为类常量,可以使用关键字 static final 来修饰:
public class Main {
public static final double CM_PER_INCH = 2.54;
public static void main(String[] args) {
double paperWidth = 8.5;
double paperHeight = 11;
System.out.println("Paper size in centimeters: "
+ paperWidth * CM_PER_INCH + " by "
+ paperHeight * CM_PER_INCH);
}
}
------
Paper size in centimeters: 21.59 by 27.94
类常量的定义位于
main
方法的外部。因此,在同一个类的其他方法中也可以使用这个常量。而且,如果一个常量被声明为 public,那么其他类的方法也可以使用这个常量。
5. 运算符
当参与/
运算的两个操作数都是整数时,表示整数除法;否则,表示浮点除法。
另外,整数被 0 除将会产生一个异常,而浮点数被 0 除将会得到无穷大或 NaN 结果。
如果将一个类标记为 strictfp,那么这个类中的所有方法都要使用严格的浮点计算。
5.1 数学函数与常量
在 Math 类中,包含了各种各样的数学函数:
import static java.lang.Math.*;
// 开方、乘幂、取余
sqrt(x);
pow(x, a);
floorMod(x, y);
// 三角函数
sin(a);
cos(a);
tan(a);
atan(a);
atan2(y, x);
// 指数及对数
exp(a);
log(a);
log10(a);
// 常量近似值
Math.PI;
Math.E;
5.2 数值类型之间的转换
高精度数值类型转换为低精度数值类型,可能会发生精度损失。
- 如果两个操作数中有一个是 double 类型,另一个操作数就会转换为 double 类型
- 否则,如果其中一个操作数是 float 类型,另一个操作数将会转换为 float 类型
- 否则,如果其中一个操作数是 long 类型,另一个操作数将会转换为 long 类型
- 否则,两个操作数都将被转换为 int 类型
5.3 强制类型转换
强制类型转换通过截断小数部分将浮点值转换为整型:
double x = 9.997;
int nx = (int) x; // x = 9
如果想对浮点数进行舍入运算,那就需要使用Math.round()
方法:
double x = 9.997;
int nx = (int) Math.round(x); // x = 10
如果试图将一个数值从一种类型强制转换为另一种类型,而又超出了目标类型的表示范围,结果就会截断成一个完全不同的值。例如,
(byte) 300
的实际值为 44。
5.4 结合赋值和运算符
x += 4;
x += 3.5; // 将发生强制类型转换,等价于 (int)(x + 3.5)
5.5 自增与自减运算符
int n = 12;
n++;
由于这些运算符会改变变量的值,所以它们的操作数不能是数值。例如,
4++
就不是一个合法的语句。
后缀和前缀形式都会使变量值加 1 或减 1,但用在表达式中,二者就有区别了。前缀形式会先完成加 1,而后缀形式会使用变量原来的值:
int m = 7;
int n = 7;
int a = 2 * ++m; // now a is 16, m is 8
int b = 2 * n++; // now b is 14, n is 8
5.6 关系和 boolean 运算符
逻辑运算符&&
和||
是按照「短路」方式来求值的:如果第一个操作数已经能够确定表达式的值,第二个操作数就不必计算了。如果用&&
运算符合并两个表达式,就可以利用这一点来避免错误:
x != 0 && 1 / x > x + y // no division by 0
另外,Java 支持三元操作符? :
:
x < y ? x : y
会返回 x 和 y 中较小的一个。
5.7 位运算符
处理整型类型时,可以直接对组成整型数值的各个位完成操作,这意味着可以使用掩码技术得到整数中的各个位:
& ("and")
| ("or")
^ ("XOr")
~ ("not")
利用
&
并结合使用适当的 2 的幂,可以把其他位掩掉,而只保留其中的某一位。另外,&
和|
运算符不采用「短路」方式来求值。
另外,还有>>
和<<
运算符将位模式左移或右移:
int fourthBitFromRight = (n & (1 << 3)) >> 3;
最后,>>>
运算符会用 0 填充高位,这与>>
不同,它会用符号位填充高位,不存在<<<
运算符。
可以用Integer.toBinaryString()
方法将整型类型转换类二进制字符串。
移位运算符的右操作数要完成模 32 的运算(除非左操作数是 long 类型,在这种情况下需要对右操作数模 64)。例如,
1 << 35
的值等同于1 << 3
的值即为 8。
5.8 括号与运算符级别
同一个级别的运算符按照从左到右的次序进行计算(除了右结合运算符)。
运算符 | 结合性 |
---|---|
[] . () 函数调用 |
从左向右 |
! ~ ++ -- + 一元 - 一元 () 强制类型转换 new |
从右向左 |
* / % |
从左向右 |
+ - |
从左向右 |
<< >> >>> |
从左向右 |
< <= > >= instanceof |
从左向右 |
== != |
从左向右 |
& |
从左向右 |
^ |
从左向右 |
l |
从左向右 |
&& |
从左向右 |
ll |
从左向右 |
?: |
从右向左 |
= += -= *= /= %= &= != ^= <<= >>= >>>= |
从右向左 |
5.9 枚举类型
有时候,变量的取值只在一个有限的集合内。这时可以自定义枚举类型。枚举类型包括有限个命名的值:
enum Size {
SMALL,
MEDIUM,
LARGE,
EXTRA_LARGE
}
Size s = Size.MEDIUM;
Size 类型的变量只能存储这个类型声明中给定的某个枚举值,或者 null 值。null 表示这个变量没有设置任何值。
6. 字符串
Java 字符串就是 Unicode 字符序列。例如,串Java\u2122
由 5 个 Unicode 字符J
、a
、v
、a
、和™
组成。Java 没有内置的字符串类型,而是在标准 Java 类库中提供了一个预定义类 String。每个用双引号括起来的字符串都是 String 类的一个实例:
String e = ""; // an empty string
String greeting = "Hello";
6.1 子串
String greeting = "Hello";
String s = greeting.substring(0, 3);
System.out.println(s);
------
Hel
字符串s.substring(a, b)
的长度为b-a
。
6.2 拼接
Java 语言允许使用+
号拼接两个字符串。另外如果需要把多个字符串放在一起,用一个定界符分隔,可以使用静态 join 方法:
String all = String.join(" / ", "S", "M", "L", "XL");
System.out.println(s);
------
S / M / L / XL
6.3 不可变字符串
String 类没有提供用于修改字符串的方法,所以在 Java 文档中将 String 类对象称为不可变字符串。虽然通过拼接来创建新字符串的效率确实不高,但是不可变字符串却有一个优点:编译器可以让字符串共享。
可以想象将各种字符串存放在公共的存储池中,字符串变量指向存储池中相应的位置。如果复制一个字符串变量,原始字符串与复制的字符串共享相同的字符。
6.4 检测字符串是否相等
一定不要使用
==
运算符检测两个字符串是否相等,==
只能确定两个字符串是否放置在同一个位置上。
s.equals(t);
s.equalsIgnoreCase(t);
6.5 空串与 Null 串
空串""
是长度为 0 的字符串。可以调用以下代码检查一个字符串是否为空:
if (str.length() == 0)
if (str.equals(""))
空串是一个 Java 对象,有自己的串长度(0)和内容(空)。不过,String 变量还可以存放一个特殊的值,名为 null,表示目前没有任何对象与该变量关联。要检查一个字符串是否为空,可以使用以下条件:
if (str == null)
有时要检查一个字符串既不是 null 也不为空串,这种情况下就需要使用以下条件:
if (str != null && str.length() != 0)
6.6 码点与代码单元
Java 字符串由 char 值序列组成。
length()
方法将返回采用 UTF-16 编码表示的给定字符串所需要的代码单元数量:
String greeting = "Hello";
int n = greeting.length(); // is 5.
想要得到实际的长度,即码点数量,可以调用:
int cpCount = greeting.codePointCount(0, greeting.length());
调用s.charAt(n)
将返回位置 n 的代码单元,n 介于0
和s.length()-1
之间,例如:
char first = greeting.charAt(0); // first is 'H'
char last = greeting.charAt(4); // last is 'o'
要想得到第 i 个码点,则使用下列语句:
int index = greeting.offsetByCodePoints(0, i);
int cp = greeting.codePointAt(index);
6.7 String API
Java 中的 String 类包含了 50 多个方法,最常用的如下:
char charAt(int index) // 返回给定位置的代码单元
int codePointAt(int index) // 返回从给定位置开始的码点
int offsetByCodePoints(int startIndex, int cpCount) // 返回从 startIndex 代码点开始,位移 cpCount 后的码点索引
int compareTo(String other) // 按照字典顺序,如果字符串位于 other 之前,返回一个负数
IntStream codePoints() // 将这个字符串的码点作为一个流返回
new String(int[] codePoints, int offset, int count) // 用数组中从 offset 开始的 count 个码点构造一个字符串
boolean equals(Object other) // 如果字符串与 other 相等,返回 true
boolean equalsIgnoreCase(String other) // 如果字符串与 other 相等(忽略大小写),返回 true
boolean startsWith(String prefix)
boolean endsWith(String suffix) // 如果字符串以 suffix 开头或结尾,则返回 true
int indexOf(String str)
int indexOf(String str, int fromIndex)
int indexOf(int cp)
int indexOf(int cp, int fromIndex) // 返回与字符串 str 或代码点 cp 匹配的第一个字串的开始位置
int lastIndexOf(String str)
int lastIndexOf(String str, int fromIndex)
int lastIndexOf(int cp)
int lastIndexOf(int cp, int fromIndex) // 返回与字符串 str 或代码点 cp 匹配的最后一个子串的开始位置
int length() // 返回字符串的长度
int codePointCount(int startIndex, int endIndex) // 返回 startIndex 和 endIndex-1 之间的代码点数量
String replace(CharSequence oldString, CharSequence newString)
String substring(int beginIndex)
String substring(int beginIndex, int endIndex)
String toLowerCase()
String toUpperCase()
String trim()
String join(CharSequence delimiter, CharSequence... elements)
6.8 构建字符串
有些时候,需要由较短的字符串构建字符串,采用字符串连接的方式达到此目的的效率比较低。每次连接字符串,都会构建一个新的 String 对象,既耗时又浪费空间。使用 StringBuilder 类可以避免这个问题发生。
如果需要用许多小段的字符串构建一个字符串,首先构建一个空的字符串构建器:
StringBuilder builder = new StringBuilder();
当每次需要添加一部分内容时,就调用append
方法:
builder.append(ch); // appends a single character
builder.append(str); // appends a string
在需要构建字符串时调用toString
方法,就可以得到一个 String 对象,其中包含了构建器中的字符序列:
String completedString = builder.toString();
重要方法如下:
StringBuilder() // 构造一个空的字符串构建器
int length() // 返回构建器或缓冲器中的代码单元数量
StringBuilder append(String str) // 追加一个字符串并返回 this
StringBuilder append(char c) // 追加一个代码单元并返回 this
StringBuilder appendCodePoint(int cp) // 追加一个代码点,并将其转换为一个或两个代码单元并返回 this
void setCharAt(int i, char c) // 将第 i 个代码单元设置为 c
StringBuilder insert(int offset, String str) // 在 offset 位置插入一个字符串并返回 this
StringBuilder insert(int offset, Char c) // 在 offset 位置插入一个代码单元并返回 this
StringBuilder delete(int startIndex, int endIndex) // 删除偏移量从 startIndex 到 endIndex-1 的代码单元并返回 this
String toString() // 返回一个与构建器或缓冲器内容相同的字符串
7. 输入输出
7.1 读取输入
要想通过控制台进行输入,首先需要构造一个 Scanner 对象,并与标准输入流 System.in 关联,Scanner 类定义在java.util
包中:
import java.util.*;
/**
* This program demonstrates console input.
* @version 1.10 2004-02-10
* @author Cay Horstmann
*/
public class InputTest
{
public static void main(String[] args)
{
Scanner in = new Scanner(System.in);
// get first input
System.out.print("What is your name? ");
String name = in.nextLine();
// get second input
System.out.print("How old are you? ");
int age = in.nextInt();
// display output on console
System.out.println("Hello, " + name + ". Next year, you'll be " + (age + 1));
}
}
常用方法如下:
Scanner (InputStream in) // 用给定的输入流创建一个 Scanner 对象
String nextLine() // 读取输入的下一行内容
String next() // 读取输入的下一个单词(以空格分隔)
int nextInt()
double nextDouble()
boolean hasNext() // 检测输入中是否还有其他单词
boolean hasNextInt()
boolean hasNextDouble()
7.2 格式化输出
Java 沿用了 C 语言库函数中的printf
方法,例如:
System.out.printf("%8.2f", x);
会使用 8 个字符的宽度和小数点后两个字符的精度打印x
。
每一个以%
字符开始的格式说明符都用相应的参数替换,格式说明符尾部的转换符将指示被格式化的数值类型:f
表示浮点数,s
表示字符串,d
表示十进制整数。
用于 printf 的转换符如下所示:
转换符 | 类型 | 举例 |
---|---|---|
d | 十进制整数 | 159 |
x | 十六进制整数 | 9f |
o | 八进制整数 | 237 |
f | 定点浮点数 | 15.9 |
e | 指数浮点数 | 1.59e+01 |
g | 通用浮点数 | — |
a | 十六进制浮点数 | 0x1.fccdp3 |
s | 字符串 | Hello |
c | 字符 | H |
b | 布尔 | True |
h | 散列码 | 42628b2 |
Tx | 日期时间 | 已经过时,应改为java.time 类 |
% | 百分号 | % |
n | 与平台有关的行分隔符 | — |
另外,还可以给出控制格式化输出的各种标志:
可以使用静态的String.format
方法创建一个格式化的字符串,而不打印输出:
String string = String.format("Hello, %s, Next year, you'll be %d", name, age);
printf 方法中还有关于日期与时间的格式化选项。格式包括两个字母,以t
开始,以下表中的任意字母结束:
7.3 文件输入与输出
要想对文件进行读取,就需要用一个 File 对象来构造一个 Scanner 对象。如果文件名中包含反斜杠\
符号,就要使用转义字符\\
:
Scanner in = new Scanner(Paths.get("C:\\Users\\abel1\\IdeaProjects\\CoreJava\\src\\myfile.txt"), "UTF-8");
...
in.close();
要想写入文件,就需要构造一个 PrintWriter 对象。在构造器中,只需要提供文件名。如果文件不存在,则会自动创建该文件:
PrintWriter out = new PrintWriter("myfile.txt", "UTF-8");
...
out.close();
注意:可以构造一个带有字符串参数的 Scanner,但这个 Scanner 将字符串解释为数据,而不是文件名。例如:
Scanner in = new Scanner("myfile.txt"); // ERROR?
这个 scanner 会将参数作为包含 10 个字符的数据:
m
、y
、f
等。
当指定一个相对文件名时,文件位于 Java 虚拟机启动路径的相对位置。可以使用下面的调用方式找到路径的位置:
String dir = System.getProperty("user.dir");
如果 Scanner 和 PrintWriter 中指定的文件不存在或无法创建,就会发生异常。在已知有可能出现「输入/输出」异常的情况下,需要在main
方法中用throws
子句标记:
public static void main(String[] args) throws IOException {
Scanner in = new Scanner(Path.get("myfile.txt"), "UTF-8");
...
in.close();
}
常用方法如下:
Scanner(File f) // 构造一个从给定文件读取数据的 Scanner
Scanner(String data) // 构造一个从给定字符串读取数据的 Scanner
PrintWriter(String fileName) // 构造一个将数据写入文件的 PrintWriter。文件名由参数指定
static Path get(String pathname) // 根据指定的路径名构造一个 Path
8. 控制流程
8.1 if 条件语句
条件必须用括号括起来
if (condition) {
statement
}
8.2 while 循环
while (condition) {
statement
}
while 循环语句首先检测循环条件。如果希望循环体至少执行一次,则应该将检测条件放到最后:
do {
statement
} while (condition);
例如下面的例子,只要用户回答
N
,循环就重复执行:
import java.util.*;
/**
* This program demonstrates a <code>do/while</code> loop.
* @version 1.20 2004-02-10
* @author Cay Horstmann
*/
public class Retirement2
{
public static void main(String[] args)
{
Scanner in = new Scanner(System.in);
System.out.print("How much money will you contribute every year? ");
double payment = in.nextDouble();
System.out.print("Interest rate in %: ");
double interestRate = in.nextDouble();
double balance = 0;
int year = 0;
String input;
// update account balance while user isn't ready to retire
do
{
// add this year's payment and interest
balance += payment;
double interest = balance * interestRate / 100;
balance += interest;
year++;
// print current balance
System.out.printf("After year %d, your balance is %,.2f%n", year, balance);
// ask if ready to retire and get input
System.out.print("Ready to retire? (Y/N) ");
input = in.next();
}
while (input.equalsIgnoreCase("N"));
}
}
8.3 for 循环
for 语句的第 1 部分通常用于对计数器初始化;第 2 部分给出每次新一轮循环执行前要检测的循环条件;第 3 部分指示如何更新计数器。
for 语句也可以看作 while 语句的一种简化形式。
for (int i = 10; i > 0; i--) {
System.out.println("Counting down..." + i);
}
System.out.println("Blastoff!");
8.4 switch 语句
Scanner in = new Scanner(System.in);
System.out.print("Select an option (1, 2, 3, 4) ");
int choice = in.nextInt();
switch (choice) {
case 1:
// do something
break;
case 2:
// do something
break;
case 3:
// do something
break;
case 4:
// do something
break;
default:
// bad input
break;
}
如果在某个 case 分支语句的末尾没有 break 语句,那么就会接着执行下一个 case 分支语句,这种情况很容易引发错误。可以在编译代码时加上-Xlint:fallthrough
选项:
javac -Xlint:fallthrough Test.java
这样一来,如果某个分支最后缺少一个 break 语句,编译器就会给出一个警告信息。
如果确实是想使用这种“直通式”(fallthrough)行为,可以为其外围方法加一个标注
@SuppressWarnings("fallthrough")
,这样就不会对这个方法生成警告了。
case 标签可以是:
- 类型为 char、byte、short 或 int 的常量表达式
- 枚举常量
- 从 Java SE 7 开始,还可以是字符串字面量
String input = ...;
switch (input.toLowerCase()) {
case "yes": // OK since Jave SE 7
...
break;
...
}
8.5 中断控制流程语句
不带标签的 break 语句用于退出当前循环语句。另外,Java 还提供了一种带标签的 break 语句,用于跳出多重嵌套的循环语句:
Scanner in = new Scanner(System.in);
int n;
read_data:
while (...) { // this loop statement is tagged with the label
...
for (...) { // this inner loop is not labeled
System.out.print("Enter a number >=0: ");
n = in.nextInt();
if (n < 0) { // should never happen - can't go on
break read_data;
// break out of read_data loop
}
...
}
}
// this statement is executed immediately after the labeled break
if (n < 0) { // check for bad situation
// deal with bad situation
} else {
// carry out normal processing
}
如果输入有误,通过执行带标签的 break 跳转到带标签的语句块末尾。对于任何使用 break 语句的代码都需要检测循环是正常结束,还是由 break 跳出。
事实上,可以将标签应用到任何语句中,甚至是 if 语句或者块语句。另外需要注意,break 只能跳出语句块,而不能跳入语句块:
label:
{
...
if (condition) break label; // exits block
...
}
// jumps here when the break statement executes
最后,还有一个 continue 语句,它将中断正常的控制流程,并将控制转移到最内层循环的首部。例如:
Scanner in = new Scanner(System.in);
while (sum < goal) {
System.out.print("Enter a number: ");
n = in.nextInt();
if (n < 0) continue;
sum += n; // not executed if n < 0
}
如果n < 0
,则 continue 语句越过了当前循环体的剩余部分,立刻跳到循环首部sum < goal
。
如果将 continue 语句用于 for 循环中,就可以跳到 for 循环的更新部分:
for (count = 1; count <= 100; count++) {
System.out.print("Enter a number, -1 to quit: ");
n = in.nextInt();
if (n < 0) continue;
sum += n; // not executed if n < 0
}
如果n < 0
,则会跳到count++
语句。
还有一种带标签的 continue 语句,将跳到与标签匹配的循环首部。
9. 大数值
如果基本的整数和浮点数精度不能满足需求,可以使用java.math
包中的两个很有用的类:BigInteger 和 BigDecimal,这两个类可以处理任意长度数字序列的数值。
import java.math.BigInteger;
import java.math.BigDecimal;
使用静态的valueOf()
方法可以将普通的数值转化为大数值:
BigInteger a = BigInteger.valueOf(100);
不能直接使用算数运算符(如+
、*
)来处理大数值,而需要使用大数值类中的 add 和 multiply 方法:
import java.math.BigInteger;
public class Main {
public static void main(String[] args) {
BigInteger a = BigInteger.valueOf(Long.MAX_VALUE);
BigInteger b = BigInteger.valueOf(Long.MAX_VALUE);
BigInteger sum = a.add(b);
BigInteger product = a.multiply(b);
System.out.printf("%d + %d = %d\n", a, b, sum);
System.out.printf("%d * %d = %d", a, b, product);
}
}
------
9223372036854775807 + 9223372036854775807 = 18446744073709551614
9223372036854775807 * 9223372036854775807 = 85070591730234615847396907784232501249
Process finished with exit code 0
BigInteger 常用方法如下:
BigInteger add(BigInteger other)
BigInteger subtract(BigInteger other)
BigInteger multiply(BigInteger other)
BigInteger divide(BigInteger other)
BigInteger mod(BigInteger other)
int compareTo(BigInteger other) // 如果与另一个大整数 other 相等则返回 0,大于返回正数,小于返回负数
static BigInteger valueOf(long x) // 返回值等于 x 的大整数
BigDecimal 常用方法如下:
BigDecimal add(BigDecimal other)
BigDecimal subtract(BigDecimal other)
BigDecimal multiply(BigDecimal other)
BigDecimal divide(BigDecimal other RoundingMode mode) // 要想计算商,必须给出舍入方式。RoundingMode.HALF_UP 即为四舍五入
int compareTo(BigDecimal other) // 如果与另一个大实数 other 相等则返回 0,大于返回正数,小于返回负数
static BigDecimal valueOf(long x)
static BigDecimal valueOf(long x, int scale) // 返回值为 x 或 x/10^scale 的一个大实数
10. 数组
在声明数组变量时,需要指出数组类型和数组变量名,可以使用 new 运算符创建数组:
int[] a = new int[100];
创建一个数字数组时,所有元素都初始化为0
。boolean 数组的元素会初始化为false
。对象数组的元素则初始化为一个特殊值null
,表示这些元素还未存放任何对象。
一旦创建了数组,就不能再改变它的大小。如果经常需要在运行过程中扩展数组的大小,就应该使用另一种数据结构——数组列表(ArrayList)。
10.1 for each 循环
for each 循环可以用来依次处理数组中的每个元素而不必为指定下标值而分心:
for (variable : collection) {
statement
}
collection 这一集合表达式必须是一个数组或者是一个实现了 Iterable 接口的类对象(例如 ArrayList)。
有个更简单的方式打印数组中的所有值,即利用 Arrays 类的
toString
方法:
import java.util.Arrays;
...
System.out.println(Arrays.toString(a));
10.2 数组初始化以及匿名数组
Java 提供了一种创建数组对象并同时赋予初始值的简化书写形式:
int[] smallPrimes = {2, 3, 5, 7, 11, 13};
还可以初始化一个匿名的数组,这种表示法将创建一个数组并利用括号中提供的值进行初始化,数组的大小就是初始值个数。使用这种语法可以在不创建新变量的情况下重新初始化一个数组:
smallPrimes = new int [] {17, 19, 23, 29, 31, 37};
10.3 数组拷贝
在 Java 中,允许将一个数组变量拷贝给另一个数组变量。这时,两个变量将引用同一个数组:
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
PrintWriter out = new PrintWriter(System.out);
int[] smallPrimes = new int[]{2, 3, 5, 7, 11, 13};
out.println("smallPrimes: " + Arrays.toString(smallPrimes));
int[] luckyNumbers = smallPrimes;
out.println("luckyNumbers: " + Arrays.toString(luckyNumbers));
luckyNumbers[5] = 12;
out.println("After Change:");
out.println("smallPrimes: " + Arrays.toString(smallPrimes));
out.println("luckyNumbers: " + Arrays.toString(luckyNumbers));
in.close();
out.close();
}
}
------
smallPrimes: [2, 3, 5, 7, 11, 12]
luckyNumbers: [2, 3, 5, 7, 11, 12]
After Change:
smallPrimes: [0, 0, 0, 0, 0, 0]
luckyNumbers: [0, 0, 0, 0, 0, 0]
如果希望将一个数组的所有值拷贝到一个新的数组中,就要使用 Arrays 类的copyOf
方法:
int[] copiedLuckyNumbers = Arrays.copyOf(luckNumbers.length);
第 2 个参数是新数组的长度,这个方法通常用来增加数组的大小:
luckyNumbers = Arrays.copyOf(luckyNumbers, 2 * luckyNumbers.length);
如果数组元素是数值型,那么多余的元素将被赋值为 0。如果是布尔型,则将赋值为 false。相反,如果长度小于原始数组的长度,则只拷贝最前面的数据元素。
10.4 命令行参数
每一个 Java 应用程序都有一个带String[] args
参数的 main 方法。这个参数表明 main 方法将接收一个字符串数组,也就是命令行参数。例如:
public class Message {
public static void main(String[] args) {
if (args.length == 0 || args[0].equals("-h")) {
System.out.print("Hello,");
} else if (args[0].equals("-g")) {
System.out.print("Goodbye,");
}
// print the other command-line arguments
for (int i = 1; i < args.length; i++) {
System.out.print(" " + args[i]);
}
System.out.println("!");
}
}
如果使用下面的命令运行程序:
java Message -g cruel world
则 args 数组将包含以下内容:
args[0]: "-g"
args[1]: "cruel"
args[2]: "world"
程序将输出以下信息:
Goodbye, cruel world!
10.5 数组排序
要想对数值型数组进行排序,可以使用 Arrays 类中的sort
方法。Arrays.sort()
使用了优化的快速排序算法:
int[] a = new int[10000];
...
Arrays.sort(a);
下面的程序用到了数组,它将产生一个抽彩游戏中的随机数组合:
import java.util.Arrays;
import java.util.Scanner;
/**
* This program demonstrates array manipulation.
*
* @author Cay Horstmann
* @version 1.20 2004-02-10
*/
public class LotteryDrawing {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System.out.print("How many numbers do you need to draw? ");
int k = in.nextInt();
System.out.print("What is the highest number you can draw? ");
int n = in.nextInt();
// fill an array with numbers 1 2 3 ... n
int[] numbers = new int[n];
for (int i = 0; i < numbers.length; i++) {
numbers[i] = i + 1;
}
// draw k numbers and put them into a second array
int[] result = new int[k];
for (int i = 0; i < result.length; i++) {
// make a random index between 0 and n - 1
int r = (int) (Math.random() * n);
// pick the element at the random location
result[i] = numbers[r];
// move the last element into the random location
numbers[r] = numbers[n - 1];
n--;
}
// print the sorted array
Arrays.sort(result);
System.out.println("Bet the following combination. It'll make you rich!");
for (int r : result) {
System.out.println(r);
}
}
}
------
How many numbers do you need to draw? 6
What is the highest number you can draw? 49
Bet the following combination. It'll make you rich!
2
7
8
17
34
38
数组类 Arrays 的常用方法如下:
static String toString(type[] a)
static type copyOf(type[] a, int length)
static type copyOfRange(type[] a, int start, int end) // 包含 start, 不包含 end
static void sort(type[] a) // 采用优化的快速排序
static int binarySearch(type[] a, type v)
static int binarySearch(type[] a, int start, int end, type v) // 二分查找值 v。成功则返回下标值,否则返回一个负数
static void fill(type[] a, type v) // a 与 v 数据元素类型相同
static boolean equals(type[] a, type[] b) // 如果两个数组大小相同、下标相同的元素都对应相等,则返回 true
10.6 多维数组
在 Java 中,声明一个二维数组相当简单:
double[][] balance = new double[NYEARS][NRATES];
如果知道数组元素,也可以不调用 new,直接使用简化形式对多维数组进行初始化:
int[][] magicSquare = {
{16, 3, 2, 13},
{5, 10, 11, 8},
{9, 6, 7, 12},
{4, 15, 14, 1}
};
for each 循环语句不能自动处理二维数组的每一个元素。它是按照行,也就是一维数组处理的。要想访问二维数组magicSquare
的所有元素,需要使用两个嵌套的循环:
for (int[] row : magicSquare) {
for (int value : row) {
System.out.println(value);
}
}
另外,要想快速的打印一个二维数组的数据元素列表,可以调用 Arrays 类的deepToString
方法:
System.out.println(Arrays.deepToString(magicSquare));
10.7 不规则数组
Java 实际上没有多维数组,只有一维数组。多维数组被解释为「数组的数组」。
下面是一个使用数组来打印杨辉三角的例子:
/**
* This program demonstrates a triangular array.
* @version 1.20 2004-02-10
* @author Cay Horstmann
*/
public class LotteryArray
{
public static void main(String[] args)
{
final int NMAX = 10;
// allocate triangular array
int[][] odds = new int[NMAX + 1][];
for (int n = 0; n <= NMAX; n++)
odds[n] = new int[n + 1];
// fill triangular array
for (int n = 0; n < odds.length; n++)
for (int k = 0; k < odds[n].length; k++)
{
/*
* compute binomial coefficient n*(n-1)*(n-2)*...*(n-k+1)/(1*2*3*...*k)
*/
int lotteryOdds = 1;
for (int i = 1; i <= k; i++)
lotteryOdds = lotteryOdds * (n - i + 1) / i;
odds[n][k] = lotteryOdds;
}
// print triangular array
for (int[] row : odds)
{
for (int odd : row)
System.out.printf("%4d", odd);
System.out.println();
}
}
}
------
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
1 6 15 20 15 6 1
1 7 21 35 35 21 7 1
1 8 28 56 70 56 28 8 1
1 9 36 84 126 126 84 36 9 1
1 10 45 120 210 252 210 120 45 10 1