Java 核心技术 卷Ⅰ 第 3 章 Java 的基本程序设计结构
3.1 一个简单的 Java 应用程序
- 借助于包(
package
)可以方便地组织自己的代码。 - 使用包的主要原因是确保类名的唯一性。
- Sun 公司建议将公司的因特网域名以逆序的形式作为包名,并且对于不同的项目使用不同的子包。
package me.liujiajia.java.sample;
public class FirstSample {
public static void main(String[] args) {
System.out.println("Hello,World!");
}
}
2
3
4
5
6
7
如果不带包名,可以通过如下方式编译并运行。
javac FirstSample.java
java FirstSample
2
注意: 运行时不带 .class 后缀。
如果带包名,上面的方式会报 错误:找不到或无法加载主类 FirstSample 的错误。
此时编译及运行时需带上包名(命令行需定位到代码的根目录)。
javac me/liujiajia/java/sample/FirstSample.java
java me/liujiajia/java/sample/FirstSample
2
3.2 注释
Java 中有三种注释方式:
//
注释内容从//
开始到行尾/* */
注释内容为/* */
之间的所有内容,支持多行。
输入/*
后回车即可自动生成。/** */
用在方法和类上,用来自动生成文档。
输入/**
后回车即可自动生成。
public class FirstSample {
/**
* 应用入口方法
*
* @param args
*/
public static void main(String[] args) {
// 打印 Hello,World!
System.out.println("Hello,World!");
/*
这是一段多行注释
当注释很多时可以使用这种方式
*/
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
3.3 数据类型
Java 是一种强类型语言,必须为每一个变量声明一种类型。
在 Java 中一共有 8 种基本类型(primitive type):
- 4 种整型(
int
、short
、long
、byte
) - 2 种浮点类型(
float
、double
) - 1 种用于表示 Unicode 编码的字符单元的字符类型
char
- 1 种用于表示真值的
boolean
(布尔)类型
3.3.1 整型
int
(4 字节)(-2,147,483,648(-2^31
) ~ 2,147,483,647(2^31 - 1
))short
(2 字节)(32,768(-2^15
) ~ 32,767(2^15 - 1
))long
(8 字节)(-9,223,372,036,854,775,808(-2^63
) ~ 9,223,372,036,854,775,807(2^63 -1
))byte
(1 字节)(-128(-2^7
)~127(2^7-1
))
在 Java 中,整型的范围与运行 Java 代码的机器无关。
(C 和 C++ 程序需要针对不同的处理器选择最为高效的整型)
数值字面量
长整型数值有一个后缀 L
或 l
(因为小写 l
和 数值 1
很容易混淆,大多数规约中强制使用大写 L
)。
十六进制使用前缀 0x
或 0X
(例:0xCAFE)。
八进制使用前缀 0
(例:010
)(因为前缀 0
比较容易混淆,一般不推荐使用八进制的常数)。
二进制使用前缀 0b
或 0B
(例:0b1001 表示十进制的 9)。
数字字面量可以添加下划线(_
)以提高代码的可读性(例:100_0000
或 0b1111_0100_0010_0001
)。
3.3.2 浮点类型
float
(4 字节)(有效位数为 6 ~ 7 位)double
(8 字节)(有效位数为 15 位)
数值字面量
float
值有一个后缀F
或f
(例:3.14F)double
值有一个后缀D
或d
(例:3.14D)
没有后缀的小数默认为double
型。
三个特殊的浮点数
- 正无穷大(
Double.POSITIVE_INFINITY
) - 负无穷大(
Double.NEGATIVE_INFINITY
) - NAN(不是一个数字)(
Double.NaN
)
浮点类型的误差
System.out.println(0.1 + 0.2);
打印的不是 0.3 ,而是 0.30000000000000004 。
这种舍入误差的主要原因是浮点数采用二进制系统表示,而在二进制中无法精确的表示分数 1/10
。
就像十进制无法精确的表示 1/3
一样。
在金融计算等不允许舍入误差的场景,应该使用 BigDecimal
类。
3.3.3 char 类型
char
类型原本用于表示单个字符。不过,现在一些 Unicode 字符需要两个 char
值。char
类型的字面量值需要用单引号括起来。如 'A' 。char
类型的值可以表示为十六进制值,其范围从 \u0000
到 \uFFFF
。如 \u03C0
表示 π 。
特殊字符的转义
\b
退格\t
制表\n
换行\r
回车\”
双引号\'
单引号\\
反斜杠
字符串之外的 \u
转义序列
如 public static void main(String\u005B\u005D args)
就完全符合语法规则,其中 \u005B
和 \u005D
是 [ 和 ] 的编码。
Unicode 转义序列会在解析代码之前得到处理。
“\u0022+\u0022”
\u0022
会在解析之前转换为“ ,这会得到“” + “”,也就是一个空串。// \u00A0 is a newline
\u00A0
会被替换为一个换行符。这会产生一个语法错误。// look inside C:\users
也会产生一个语法错误,因为\u
后面并未跟着 4 个十六进制数。
3.3.4 Unicode 和 char 类型
1991 年发布 Unicode 1.0 时,当时仅占用了 65536 个代码值中不到一半的部分。
设计 Java 时决定采用 16 位的 Unicode 字符集。
随着大量汉语、日语和韩语中表意文字的增加,Unicode 字符集超过了 65536 个。
为了解决这个问题,先要介绍几个专用术语。
- 码点(code point)
指与一个编码表中的某个字符对应的代码值。
在 Unicode 标准中,码点采用十六进制书写,并加上前缀U+
。
例:U+0041
就是字母 A 的码点。 - 代码级别(code plane)
Unicode 的码点可以分成 17 个代码级别。
第一个代码级别别称为 基本的多语言级别(basic multilingual plane),码点从U+0000
到U+FFFF
。
其余的 16 个级别码点从U+10000
到U+10FFFF
,其中包括一些 辅助字符。 - UTF-16
UTF-16 编码采用不同长度的编码表示所有 Unicode 码点。 - 代码单元(code unit)
在基本的多语言级别中,每个字符用 16 位表示,通常被称为代码单元。
辅助字符则采用一对连续的代码单元进行编码。
如何知道一个代码单元是一个字符的编码还是一个辅助字符的第一或第二部分呢? - 替代区域(surrogate area)
UTF-16 巧妙的利用了基本的多语言级别中空闲的 2048 个值(U+D800
~U+DFFF
),这部分值通常被称为替代区域。U+D800
~U+DBFF
用于第一个代码单元(1024 个值),U+DC00
~U+DFFF
用于第二个代码单元(1024 个值)。组合起来共有2^20
(即104 8576)个码点,也正好是其它 16 个代码级别码点的总数量。
在 Java 中,char
型描述了 UTF-16 编码中的一个代码单元(code unit)。
3.3.5 boolean 类型
boolean
(布尔)类型有两个值: false
和 true
,用来判断逻辑条件。
整型值和布尔值之间不能进行互相转换。(有些语言中 0 相当于 false
,非 0 相当于 true
)
3.4 变量
- 在 Java 中,每个变量都有一个类型(type)。
- 声明变量时,变量类型位于变量名之前。java
double salary; int vacationDays;
1
2 - 变量名必须是一个以字母开头并由字母或数字构成的序列。
与大多数语言相比,Java 中“字母”和“数字”的范围更大,例如 π 。 - 变量名大小写敏感。
- 变量名的长度基本上没有限制。
3.4.1 变量初始化
- 声明一个变量后,必须用赋值语句对变量进行显式初始化。
否则编译器会报错:variable not initialized - 要想对一个已经声明过的变量进行赋值,就需要将变量名放在等号(
=
)左侧,取值的 Java 表达式放在等号的右侧。java也可以将变量的声明和初始化放在同一行。int vacationDays; vacationDays = 12;
1
2javaint vacationDays = 12;
1 - 在 Java 中,变量的声明尽可能地靠近变量第一次使用的地方,这是一种良好的编写风格。
3.4.2 常量
在 Java 中,利用关键字 final
指示常量。
final double CM_PER_INCH = 2.54;
- 关键字
final
表示这个变量只能被赋值一次。一旦被赋值之后,就不能再更改。 - 习惯上,常量名使用全大写。
在 Java 中,经常希望某个常量可以在一个类中的多个方法中使用,通常将这些常量成为 类常量。可以使用关键字 static final
设置一个类常量。
public static final double CM_PER_INCH = 2.54;
如果一个常量被声明为 public
,那么其它类的方法也可以使用这个常量。
const
虽然是 Java 保留的关键字,但目前并没有使用。
3.5 运算符
+
加-
减*
乘/
除
当参与/
运算的两个操作数都是整数时,表示整数除法;
否则,表示浮点除法。
整数被 0 除会产生一个异常;
浮点数被 0 除将会得到无穷大或 NAN 结果。%
求余(又称取模)
3.5.1 数学函数与常量
在 Math
类中,包含了各种各样的数学函数。
Math.sqrt(x)
平方根Math.pow(x,a)
幂运算Math.floorMod
为了解决负整数的余数可能小于 0 的问题
在这篇博客中就看到了如果被除数是负数,得到的余数也可能是负数。
而在数学上的最优规则是:余数总是 ≥ 0。
可以使用(x % a + a) % a
来满足上面的规则,但是很麻烦。
需要注意的是,如果除数是负数,仍然可能得到负的余数。Math.sin
三角函数Math.cos
三角函数Math.tan
三角函数Math.atan
三角函数Math.atan2
三角函数Math.exp
指数函数Math.log
自然对数Math.log10
以 10 为底的对数Math.PI
π 近似数Math.E
e 近似数
可以通过静态导入的方式避免在每次调用的时候添加 Math
前缀。
import static java.lang.Math.*;
StrictMath
类提供了严格浮点计算版本的数学函数。
3.5.2 数值类型之间的转换
下图是类型之间的合法转换。
实线表示无信息丢失;虚线表示可能会有精度损失。
两个数值进行二元操作时,先将两个操作数转换为同一种类型,然后再进行计算。
- 如果两个操作数中有一个是
double
类型,另一个操作数就会转换为double
类型。 - 否则,如果其中一个操作数是
float
类型,另一个操作数将会转换为float
类型。 - 否则,如果其中一个操作数是
long
类型,另一个操作数将会转换为long
类型。 - 否则,两个操作数都将被转换为
int
类型。
3.5.3 强制类型转换
强制类型转换的语法格式是在圆括号中给出想要转换的目标类型,后面紧跟待转换的变量名。
double x = 8.22;
int nx = (int) x; // 8
2
强制类型转换通过截断小数部分将浮点数转换为整形。
需要舍入运算时,需使用 Math.round
方法。
double x = 8.22;
int nx = (int) Math.round(x);
2
3.5.4 结合赋值和运算符
x += 4;
相当于
x = x + 4;
3.5.5 自增和自减运算符
int m = 7;
int n = 7;
int a = 2 * ++m; // a = 16, m = 8
int b = 2 * n++; // b = 14, n = 8
2
3
4
建议不要在表达式中使用 ++
,因为这样的代码很容易让人困惑,而且会带来烦人的 bug。
3.5.6 关系和 boolean 运算符
==
:监测相等性!=
:监测不等性<
:小于>
:大于<=
:小于等于>=
:大于等于&&
:逻辑与- 按照“短路”方式求值
||
:逻辑或- 按照“短路”方式求值
!
:逻辑非?:
:三元操作符
3.5.7 位运算符
&
:与|
:或^
:异或~
:非>>
:右移(用符号位填充高位)<<
:左移>>>
:右移(用 0 填充高位)
警告
移位运算符的右操作数需要完成模 32 的运算(左操作数是
long
型时模 64)。
3.5.8 括号和运算符级别
运算符 | 结合性 |
---|---|
[] .()(方法调用) | 从左向右 |
! ~ ++ -- +(一元运算) -(一元运算) ()(强制类型转换) new | 从右向左 |
* / % | 从左向右 |
+ - | 从左向右 |
<< >> >>> | 从左向右 |
< <= > >= instanceof | 从左向右 |
== != | 从左向右 |
& | 从左向右 |
^ | 从左向右 |
| | 从左向右 |
&& | 从左向右 |
|| | 从左向右 |
?: | 从右向左 |
= += -= *= /= %= &= |= ^= <<= >>= >>>= | 从右向左 |
// && 的优先级高于 ||
a && b || c
// 等价于
(a && b) || c
2
3
4
// += 是右结合运算符
a += b += c
// 等价于
a += (b += c)
2
3
4
3.5.9 枚举类型
enum Size { SMALL, MEDIUM, LARGE, EXTRA_LARGE };
3.6 字符串
从概念上讲,Java 字符串就是 Unicode 字符序列。Java 没有内置的字符串类型,而是在标准类库中提供了一个预定义类 String
。
String e = "";
String greeting = "Hello";
2
3.6.1 子串
String greeting = "Hello";
String s = greeting.substring(0, 3);
2
substring
的第二个参数是不相复制的第一个位置(即子串不包含第二个参数位置的字符)。substring
方法很容易计算子串的长度。 substing(a, b)
的长度为 b - a
。
3.6.2 拼接
使用 +
号拼接两个字符串。
当一个字符串和一个非字符串拼接时,后者被转换成字符串。
int age = 13;
String rating = "PG" + age;
2
如需把多个字符串放在一起,用一个定界符分割,可以使用静态 join 方法。
String all = String.join(" / ", "S", "M", "L", "XL");
// all = "S / M / L / XL"
2
3.6.3 不可变字符串
String
类没有提供用于修改字符串的方法。
greeting = greeting.substring(0, 3) + "p!"
由于不能修改 Java 字符串中的字符,所以在 Java 文档中将 String
类对象称为 不可变字符串 。
优点:编译器可以让字符串共享。
Java 的设计者认为共享带来的高效率远远胜过提取、拼接字符串所带来的低效率。
3.6.4 检测字符串是否相等
s.equals(t);
"Hello".equals(greeting);
"Hello".equalsIngnoreCase("hello");
2
3
一定不要使用 ==
运算符检测两个字符串是否相等! 这个运算符只能够确定两个字符串是否放在同一个位置上。
如果虚拟机始终将相同的字符串共享,就可以使用 ==
运算符检测是否相等。但实际上只有字符串常量是共享的,而 +
或 substring
等操作产生的结果并不是共享的。
3.6.5 空串与 Null 串
空串 ""
是长度为 0 的字符串。
检查是否为空串:
if (str.length == 0) {
}
2
或
if ("".equals(str)) {
}
2
空串是一个 Java 对象,有自己的串长度(0)和内容(空)。不过,String
变量还可以存放一个特殊的值,名为 null
,这表示目前没有任何对象与该变量关联。
检查字符串是否为 null
:
if (str == null) {
}
2
检查字符串既不是 null
也不是空串:
if (str != null && str.length() != 0) {
}
2
3.6.6 码点和代码单元
Java 字符串由 char
值序列组成。char
数据类型是一个采用 UTF-16 编码表示 Unicode 码点的代码单元。
大多数的常用 Unicode 字符使用一个代码单元就可以表示,而辅助字符需要一对代码单元表示。
length()
方法将返回采用 UTF-16 编码表示的给定字符串所需要的代码单元数量。
String greeting = "Hello";
int n = greeting.length(); // is 5
2
获取码点数量:
int cpCount = greeting.codePointCount(0, greeting.length());
获取指定位置的代码单元:
char first = greeting.charAt(0); // first is 'H'
char last = greeting.charAt(4); // last is 'o'
2
获取指定位置的码点:
int index = greeting.offsetByCodePoints(0, i);
int cp = greeting.codePointAt(index);
2
字符 𝕆
(U+1D546)需要两个代码单元。
String sentence = "𝕆 is the set of octonions";
char ch = sentence.charAt(1); // is �
2
可以在浏览器中运行 js 脚本获取对应的字符。
javascriptString.fromCodePoint(0x1D546)
1
返回不是一个空格,而是 𝕆
的第二个代码单元。
遍历所有码点:
int cp = sentence.codePointAt(i);
if (Character.isSupplementaryCodePoint(cp)) i += 2;
else i++;
2
3
回退操作:
i--;
if (Character.isSurrogate(sentence.charAt(i))) i--;
int cp = sentence.codePointAt(i);
2
3
使用 codePoints
方法遍历:
int[] codePoints = sentence.codePoints().toArray();
把码点转换为字符串:
String str = new String(codePoints, 0, codePoints.length);
3.6.7 String API
String
类常用的方法:
char charAt(int index)
int codePointAt(int index)
5.0int offsetByCodePoints(int startIndex, int cpCount)
5.0int compareTo(String other)
IntStream codePoints()
8new String(int[] codePoints, int offset, int count)
5.0boolean equals(Object other)
boolean euqalsIngnoreCase(String other)
boolean startWith(String prefix)
boolean endWith(String suffix)
int indexOf(String str)
int indexOf(String str, int fromIndex)
int indexOf(int cp)
int indexOf(int cp, int fromIndex)
int lastIndexOf(String str)
int lastIndexOf(String str, int fromIndex)
int lastIndexOf(int cp)
int lastIndexOf(int cp, int fromIndex)
int length()
int codePointCount(int startIndex, int endIndex)
5.0String 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)
8
3.6.8 阅读联机 API 文档
==> Java™ Platform, Standard Edition 8 API Specification
左上角选择 java.lang ,之后在左下角选择 String ,就可以看到 String
类的所有方法了。点击方法名的链接,可以看到方法的描述。
3.6.9 构建字符串
char ch = 'H';
String str = "ello";
StringBuilder builder = new StringBuilder();
builder.append(ch); // appends a single character
builder.append(str); // appends a string
String completedString = builder.toString();
2
3
4
5
6
7
java.lang.StringBuilder 5.0
StringBuilder()
int length()
StringBuilder append(String str)
StringBuilder append(char c)
StringBuilder appendCodePoint(int cp)
void setCharAt(int i, char c)
StringBuilder insert(int offset, String str)
StringBuilder insert(int offset, char c)
StringBuilder delete(int startIndex, int endIndex)
String toString()
3.7 输入输出
3.7.1 读取输入
构建一个 Scanner
对象,并与“标准输入流” System.in
关联。
import java.util.Scanner;
public class ScannerSample {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System.out.print("What is your name? ");
String name = in.nextLine();
System.out.print("How old are your? ");
int age = in.nextInt();
System.out.printf("Hello, %s. Next year you'll be %d.", name, age + 1);
in.close();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java.util.Scanner 5.0
Scanner(InputStream in)
String nextLine()
String next()
读取输入的下一个单词(以空格作为分隔符)int nextInt()
double nextDouble()
boolean hasNext()
检测输入中是否还有其它单词boolean hasNextInt()
boolean hasNextDouble()
java.lang.System 1.0
static Console console()
6
如果有可能进行交互操作,就通过控制台窗口为交互的用户返回一个Console
对象,否则返回null
。
java.io.Console 6
static char[] readPassword(String prompt, Object... args)
static String readLine(String prompt, Object... args)
显示字符串 prompt 并读取用户输入,直到输入行结束。
3.7.2 格式化输出
double x = 10000.0 / 3.0;
System.out.print(x); // print "3333.3333333333335"
System.out.printf("%8.2f", x); // print " 3333.33"
System.out.printf("Hello, %s. Next year you'll be %d.", name, age + 1);
2
3
4
表 3-5 用于 printf
的转换符
No. | 转换符 | 类型 | 举例 |
---|---|---|---|
1 | d | 十进制整数 | 159 |
2 | x | 十六进制整数 | 9f |
3 | o | 八进制整数 | 237 |
4 | f | 定点浮点数 | 15.9 |
5 | e | 指数浮点数 | 1.59e+01 |
6 | g | 通用浮点数 | - |
7 | a | 十六进制浮点数 | 0x1.fccdp3 |
8 | s | 字符串 | Hello |
9 | c | 字符 | H |
10 | b | 布尔 | True |
11 | h | 散列码 | 42528b2 |
12 | tx 或 TX | 日期时间(T 强制大写) | 已经过时,应当改为使用 java.time 类。 |
13 | % | 百分号 | % |
14 | n | 与平台有关的行分隔符 | - |
示例代码:
System.out.printf("%d\n", 159); // print "159"
System.out.printf("%x\n", 9); // print "9"
System.out.printf("%o\n", 159); // print "237"
System.out.printf("%f\n", 15.9); // print "15.900000"
System.out.printf("%e\n", 15.9); // print "1.590000e+01"
System.out.printf("%g\n", 15.9); // print "15.9000"
System.out.printf("%a\n", 15.9); // print "0x1.fcccccccccccdp3"
System.out.printf("%s\n", "Hello"); // print "Hello"
System.out.printf("%c\n", 'H'); // print "H"
System.out.printf("%b\n", true); // print "true"
System.out.printf("%h\n", new java.util.Date()); // print "81be3b58"
// System.out.printf("%tx\n", new java.util.Date()); // java.util.UnknownFormatConversionException: Conversion = 'tx'
System.out.printf("%% << print a percent sign\n"); // print "% << print a percent sign"
System.out.printf("%n^^ print this on a new line.\n"); // print "\n^^ print this on a new line."
2
3
4
5
6
7
8
9
10
11
12
13
14
%tx
格式打印报错了,估计是这个转换符已经废弃了导致的。
另外 %h
打印的散列码应该是对象实例本身的散列码(即 Object.hashCode()
方法的返回值),显示为 16 进制的。
表 3-6 用于 printf
的标志
No. | 标志 | 目的 | 举例 |
---|---|---|---|
1 | + | 打印正数和负数的符号 | +3333.33 |
2 | 空格 | 在正数之前添加空格 | " 3333.33" |
3 | 0 | 数字签名补 0 | 003333.33 |
4 | - | 左对齐 | "3333.33 " |
5 | ( | 将负号括在括号内 | (3333.33) |
6 | , | 添加分组分隔符 | 3,333.33 |
7 | # (对于f 格式) | 包含小数点 | 3,333. |
8 | # (对于x 或o 格式) | 添加前缀 0x 或 0 | 0xcafe |
9 | $ | 给定被格式化的参数索引。 例如, %1$d 、%1$x 将以十进制和十六进制格式打印第一个参数。 | 159 9F |
10 | < | 格式化前面说明的数值。 例如, %d%<x 将以十进制和十六进制打印同一个数值。 | 159 9F |
double x = 10000.0 / 3.0;
System.out.printf("|%+8.2f|\n", x); // print "|+3333.33|"
System.out.printf("|% 8.2f|\n", x); // print "| 3333.33|"
System.out.printf("|%08.2f|\n", x); // print "|03333.33|"
System.out.printf("|%-8.2f|\n", x); // print "|3333.33 |"
System.out.printf("|%(8.2f|\n", x); // print "| 3333.33|"
System.out.printf("|%,8.2f|\n", x); // print "|3,333.33|"
System.out.printf("|%8.2f|\n", x); // print "| 3333.33|"
System.out.printf("|%#8.2f|\n", x); // print "| 3333.33|"
System.out.printf("%x\n", 159); // print "9f"
System.out.printf("%#x\n", 159); // print "0x9f"
System.out.printf("%o\n", 159); // print "237"
System.out.printf("%#o\n", 159); // print "0237"
System.out.printf("%1$d %1$x\n", 159); // print "159 9f"
System.out.printf("%d %<x\n", 159); // print "159 9f"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
上表中的 No.7 的效果不明,打印结果和上面的示例不一致。
表 3-7 日期和时间的转换符
No. | 转换符 | 类型 | 举例 |
---|---|---|---|
1 | c | 完整的日期和时间 | 周四 8 月 26 17:45:30 CST 2021 |
2 | F | ISO 8601 日期 | 2021-08-26 |
3 | D | 美国格式的日期(月/日/年) | 08/26/21 |
4 | T | 24 小时时间 | 17:45:30 |
5 | r | 12 小时时间 | 05:45:30 下午 |
6 | R | 24 小时时间没有秒 | 17:45 |
7 | Y | 4 位数字的年(前补 0) | 2021 |
8 | y | 年的后 2 位数字(前补 0) | 21 |
9 | C | 年的前 2 位数字(前补 0) | 20 |
10 | B | 月的完整拼写 | 八月 |
11 | b 或 h | 月的缩写 | 8 月 |
12 | m | 2 位数字的月(前补 0) | 08 |
13 | d | 2 位数字的日(前补 0) | 26 |
14 | e | 2 位数字的日(前不补 0) | 26 |
15 | A | 星期几的完整拼写 | 星期四 |
16 | a | 星期几的缩写 | 周四 |
17 | j | 3 位数字的年中的日期(前补 0) 在 001 到 366 之间 | 238 |
18 | H | 2 位数字的小时(前补 0) 在 0 到 23 之间 | 17 |
19 | k | 2 位数字的小时(前不补 0) 在 0 到 23 之间 | 17 |
20 | I | 2 位数字的小时(前补 0) 在 0 到 12 之间 | 05 |
21 | l | 2 位数字的小时(前不补 0) 在 0 到 12 之间 | 5 |
22 | M | 2 位数字的分钟(前补 0) | 45 |
23 | S | 2 位数字的秒(前补 0) | 30 |
24 | L | 3 位数字的毫秒(前补 0) | 300 |
25 | N | 9 位数字的毫微秒(前补 0) | 300000000 |
26 | p | 上午或下午的标志 | 下午 |
27 | z | 从 GMT 起,RFC822 数字位移 | +0800 |
28 | Z | 时区 | CST |
29 | s | 从格林威治时间 1970-01-01 00:00:00 起的秒数 | 1629971130 |
30 | Q | 从格林威治时间 1970-01-01 00:00:00 起的毫秒数 | 1629971130300 |
java.util.Date now = new java.util.Date();
System.out.printf("%tc\n", now); // print "周四 8 月 26 17:45:30 CST 2021"
System.out.printf("%tF\n", now); // print "2021-08-26"
System.out.printf("%tD\n", now); // print "08/26/21"
System.out.printf("%tT\n", now); // print "17:45:30"
System.out.printf("%tr\n", now); // print "05:45:30 下午"
System.out.printf("%tR\n", now); // print "17:45"
System.out.printf("%tY\n", now); // print "2021"
System.out.printf("%ty\n", now); // print "21"
System.out.printf("%tC\n", now); // print "20"
System.out.printf("%tB\n", now); // print "八月"
System.out.printf("%tb\n", now); // print "8 月"
System.out.printf("%tm\n", now); // print "08"
System.out.printf("%td\n", now); // print "26"
System.out.printf("%te\n", now); // print "26"
System.out.printf("%tA\n", now); // print "星期四"
System.out.printf("%ta\n", now); // print "周四"
System.out.printf("%tj\n", now); // print "238"
System.out.printf("%tH\n", now); // print "17"
System.out.printf("%tk\n", now); // print "17"
System.out.printf("%tI\n", now); // print "05"
System.out.printf("%tl\n", now); // print "5"
System.out.printf("%tM\n", now); // print "45"
System.out.printf("%tS\n", now); // print "30"
System.out.printf("%tL\n", now); // print "300"
System.out.printf("%tN\n", now); // print "300000000"
System.out.printf("%tp\n", now); // print "下午"
System.out.printf("%tz\n", now); // print "+0800"
System.out.printf("%tZ\n", now); // print "CST"
System.out.printf("%ts\n", now); // print "1629971130"
System.out.printf("%tQ\n", now); // print "1629971130300"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
格式说明符的语法图
%{{参数索引值}$}{标志}{宽度}{.{精度}{转换字符}}{t{转换字符}}
3.7.3 文件输入与输出
读取文件需要构建一个 Scanner
对象;写入则需要构建一个 PrintWriter
对象。
如果文件名中包含反斜杠符号(\
),则需要使用转义符 \\
。
package me.liujiajia.java.sample;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Paths;
import java.util.Scanner;
public class FileSample1 {
public static void main(String[] args) throws IOException {
PrintWriter out = new PrintWriter("c:\\mydirectory\\myfile.txt", "UTF-8");
out.println("Hello, JiaJia.");
out.flush();
out.close();
Scanner in = new Scanner(Paths.get("c:\\mydirectory\\myfile.txt"), "UTF-8");
String str = in.nextLine();
System.out.println(str);
in.close();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
注意
读取/写入文件时建议不要省略编码格式。
不指定时默认使用操作系统的默认编码,这可能会导致在不同的机器上运行会得到不同的结果。
java.util.Scanner 5.0
Scanner(File f)
Scanner(String data)
构造一个从给定字符串读取数据的 Scanner
Java.io.PrintWriter 1.1
PrintWriter(String fileName)
java.nio.file.Paths 7
static Path get(String pathname)
根据给定的路径名构造一个 Path 。
3.8 控制流程
没有 goto
语句,但是 break
语句可以带标签。
还有一种变形的 for
循环,类似于 C# 中的 foreach
循环。
3.8.1 块作用域
块(block)(即复合语句)是指由一对大括号括起来的若干条简单的 Java 语句。
块确定了变量的作用域。
一个块可以嵌套在另一个块中。
public static void main(String[] args) {
int n;
{
int k;
} // k is only defined up to here
}
2
3
4
5
6
不能在嵌套的两个块中声明同名的变量。
public static void main(String[] args) {
int n;
{
int k;
int n; // Error -- can't redefine n in inner block
}
}
2
3
4
5
6
7
3.8.2 条件语句
if (condition) statement
if (condition) {
statement1
statement2
}
2
3
4
5
6
else
部分是可选的。
if (condition) statement1 else statement2
if (condition) {
statement1
statement2
} else {
statement3
statement4
}
2
3
4
5
6
7
8
9
重复的出现 if ... else if ...
:
if (condition1) statement1 else if (condition2) statement2 else statement3
if (condition1) {
statement1
statement2
} else if (condition2) {
statement3
statement4
} else {
statement5
statement6
}
2
3
4
5
6
7
8
9
10
11
12
示例:
if (yourSales >= target) {
performance = "Satisfactory";
bonus = 100;
} else {
performance = "Unsatisfactory";
bonus = 0;
}
2
3
4
5
6
7
3.8.3 循环
while (condition) statement
while (condition) {
statement1
statement2
}
2
3
4
5
6
如果开始循环条件为 false
,则 while
循环体一次也不执行。
如果循环体至少执行一次,则应将检测条件放在最后。
do statement while (condition)
do {
statement1
statement2
} while (condition)
2
3
4
5
6
3.8.4 确定循环
for
循环语句是支持迭代的一种通用结构,利用每次迭代之后更新的计数器或类似的变量来控制迭代次数。
for (int i = 1; i<= 10; i++)
System.out.println(i);
2
for
语句的第一部分通常用于对计数器初始化;第二部分给出每次新一轮循环执行前要检测的循环条件;第三部分指示如何更新计数器。
尽管 Java 允许在 for
循环的各个部分放置任何表达式,但有一天不成文的规则:for
语句的 3 个部分应该对同一个计数器变量进行初始化、检测和更新。
书上说这段倒计数的代码有问题,没看出来哪里有问题。
for (int i = 10; i > 0; i--)
System.out.println("Counting down ... " + i);
System.out.println("Blastoff!");
2
3
在循环中,检测两个浮点数是否相等需要格外小心。
for (double x = 0; x != 10; x += 0.1)
System.out.println(x);
2
上面的代码可能永远不会结束。因为 0.1 无法用二进制精确的表示。实际运行时从 9.99999999999998 直接跳跃到了 10.09999999999998 。
在 for
语句第一部分声明的变量的作用域就是整个循环体,在循环体外无法访问。
for
循环语句不过是 while
循环的一种简化形式。
for (int i = 10; i > 0; i--)
System.out.println("Counting down ... " + i);
2
可以重写为:
while (i > 0) {
System.out.println("Counting down ... " + i);
i--;
}
2
3
4
3.8.5 多重选择:switch 语句
switch (choice) {
case 1:
break;
case 2:
break;
default:
break;
}
2
3
4
5
6
7
8
switch
语句将从与选项值相匹配的 case
标签处开始执行直到遇到 break
语句,或者执行到 switch
语句的结束处为止。
如果没有相匹配的 case
标签,而有 default
子句,就执行这个子句。
case
标签可以是:
- 类型为
char
、byte
、short
或int
的常量表达式 - 枚举常量
- 从 Java SE 7 开始,
case
标签还可以是字符串字面量。
这里比对时使用的是equals
方法。
3.8.6 中断控制流程语句
break
语句可以用于退出循环语句。
while (years <= 100) {
balance += payment;
double interest = balance * interestRate / 100;
balance += interest;
if (balance >= goal) break;
years++;
}
2
3
4
5
6
7
带标签的 break
用于跳出多重嵌套的循环语句。
标签必须放在希望跳出的最外层循环之前,并且必须紧跟一个冒号。
跳出到带标签的语句块末尾。
read_data:
while (...) {
for(...) {
if(...) {
break read_data;
}
}
}
// 跳出到这里
2
3
4
5
6
7
8
9
可以将标签应用到任何语句中,甚至可以应用到 if
语句或者块语句中。
只能跳出语句块,不能跳入语句块。
label:
{
if (conditon) break label;
}
2
3
4
continue
语句和 break
语句一样,将中断正常的控制流程。continue
语句将控制转移到最内层循环的首部。
while (...) {
if(...) {
continue;
}
}
2
3
4
5
3.9 大数值
如果基本的整数和浮点数精度不能够满足需求,那么可以使用 java.math
包中的 BigInteger
和 BigDecimal
。这两个类可以处理包含任意长度数字序列的数值。BigInteger
实现了任意精度的整数运算,BigDecimal
实现了任意精度的浮点数运算。
使用静态的 valueOf
方法可以将普通的数值转换为大数值。
BigInteger a = BigInteger.valueOf(100);
不能使用算术运算符处理大数值,需要使用对应的 add
、multiply
等方法。
BigInteger a = BigInteger.valueOf(100);
BigInteger b = BigInteger.valueOf(118);
BigInteger c= a.add(b); // c = a + b
BigInteger d= c.multiply(b.subtract(BigInteger.valueOf(-3))); // d = c * (b - 3)
2
3
4
java.math.BigInteger 1.1
BigInteger add(BigInteger other)
BigInteger subtract(BigInteger other)
BigInteger multiply(BigInteger other)
BigInteger divide(BigInteger other)
BigInteger mod(BigInteger other)
int compareTo(BigInteger other)
static BigInteger valueOf(long x)
java.math.BigDecimal 1.1
BigDecimal add(BigDecimal other)
BigDecimal subtract(BigDecimal other)
BigDecimal multiply(BigDecimal other)
BigDecimal divide(BigDecimal other, RoundingMode mode)
int compare(BigDecimal other)
static BigDecimal valueOf(long x)
static BigDecimal valueOf(long x, int scale)
返回值为x/10^scale
的一个大实数。
3.10 数组
数组是一种数据结构,用来存储同一类型值的集合。
通过一个整型下标可以访问数组中的每一个值。
在声明数组变量时,需要指出数组类型和数组变量的名字。
int[] a;
int a[];
2
初始化数组:
int[] a = new int[100];
数组长度不要求是常量。
数组的下标从 0 开始。
int[] a = new int[100];
for (int i = 0; i < 100; i++) {
a[i] = i;
}
2
3
4
下标越界时会引发 array index out of bounds 异常。
一旦创建了数组,就不能再改变它的大小。如果经常需要再运行过程中扩展数组的大小,就应该使用另一种数据结构 数组列表(array list) 。
3.10.1 for each 循环
用来依次处理数组中的每个元素。
for (variable : collection)
statement
2
collection 集合表达式必须是一个数组或者是一个实现了 Iterable
接口的类对象。
for each 语句更加简洁、更不易出错(不比为下标的起始值和终止值而操心)。
3.10.2 数组初始化以及匿名数组
Java 提供了一种创建数组对象并同时赋予初始化的简化书写形式。
数组的大小就是初始值的个数。
int[] smallPrimes = {2, 3, 5, 7, 11, 13};
初始化一个匿名数组:
new int[] { 17, 19, 23, 29, 31, 37};
数组的长度可以为 0。
3.10.3 数组拷贝
常用于增加数组的大小:
luckyNumbers = Arrays.copyOf(luckNumbers, 2 * luckNumbers.length);
如果长度小于原始数组的长度,则只拷贝最前面的数据元素。
3.10.4 命令行参数
每一个 Java 应用程序都有一个带 String arg[]
参数的 main 方法。这个参数表明 main 方法将接收一个字符串数组,也就是命令行参数。
package me.liujiajia.java.sample;
public class CmdLineSample1 {
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("Goodbay,");
for (int i = 1; i < args.length; i++)
System.out.print(" " + args[i]);
System.out.println("!");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
javac me/liujiajia/java/sample/CmdLineSample1.java
java me/liujiajia/java/sample/CmdLineSample1 -h Liu JiaJia
java me/liujiajia/java/sample/CmdLineSample1 -g Liu JiaJia
2
3
3.10.5 数组排序
可以使用 Arrays
类中的 sort
方法对数值型数组进行排序。
int[] a = new int[10000]
...
Arrasy.sort(a);
2
3
这个方法使用了优化的快速排序算法。快速排序算法对于大多数数据集合来说都是效率比较高的。
java.util.Arrays 1.2
static String toString(type[] a)
5.0static type copyOf(type[] a, int length)
6static type copyOfRange(type[] a, int start, int end)
6static void sort(type[] a)
static int binarySearch(type[] a, type v)
static int binarySearch(type[] a, int start, int end, type v)
6
采用二分搜索法查找值 v。如果查找成功,则返回响应的下标值;否则返回一个负数值 r。-r-1
是为了保持 a 有序 v 应插入的位置。static void fill(type[] a, type v)
将数组的所有数据元素值设置为 v。static boolean equals(type[] a, type[] b)
如果两个数组大小相同,并且下标相同的元素都对应相等,返回true
。
3.10.6 多维数组
多维数组将使用多个下标访问数组元素,它适用于表示表格或更加复杂的排列形式。
double[][] balances;
与一维数组一样,在调用 new 对多维数组进行初始化之前不能使用它。
balances = new double[NYEARS][NRATES];
简化形式:
int[][] magicSquare = {
{16, 3, 2, 13},
{5, 10, 11, 8},
{9, 6, 7, 12},
{4, 15, 14, 1}
};
2
3
4
5
6
访问多维数组:balances[i][j]
使用 for each 循环处理二维数组:
for (int[] row : magicSquare) {
for (int value : row) {
System.out.println(value);
}
}
2
3
4
5
快速打印二维数组的元素列表:
System.out.println(Arrays.deepToString(magicSquare));
3.10.7 不规则数组
Java 实际上没有多维数组,只有一维数组。多维数组被解释为 数组的数组 。
int[][] odds = new int[NMAX + 1][];
for (int n = 0; n <= NMAX; n++)
odds[n] = new int[n + 1];
for (int n = 0; n < odds.length; n++) {
for (int k = 0; k < odds[n].length; k++) {
// computer lotteryOdds
int lotteryOdds = 1;
for (int i = 1; i <= k; i++)
lotteryOdds = lotteryOdds * (n - i + 1) / i;
odds[n][k] = lotteryOdds;
}
}
for (int[] row : odds) {
for (int odd : row)
System.out.printf("%4d", odd);
System.out.println();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
打印结果:
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
2
3
4
5
6
7