Skip to content

Java 核心技术 卷Ⅰ 第 3 章 Java 的基本程序设计结构

🏷️ Java 《Java 核心技术 卷Ⅰ》

3.1 一个简单的 Java 应用程序

  • 借助于包(package)可以方便地组织自己的代码。
  • 使用包的主要原因是确保类名的唯一性。
  • Sun 公司建议将公司的因特网域名以逆序的形式作为包名,并且对于不同的项目使用不同的子包。
java
package me.liujiajia.java.sample;

public class FirstSample {
    public static void main(String[] args) {
        System.out.println("Hello,World!");
    }
}

如果不带包名,可以通过如下方式编译并运行。

bash
javac FirstSample.java
java FirstSample

注意: 运行时不带 .class 后缀。

如果带包名,上面的方式会报 错误:找不到或无法加载主类 FirstSample 的错误。
此时编译及运行时需带上包名(命令行需定位到代码的根目录)。

bash
javac me/liujiajia/java/sample/FirstSample.java
java me/liujiajia/java/sample/FirstSample

3.2 注释

Java 中有三种注释方式:

  • //
    注释内容从 // 开始到行尾
  • /* */
    注释内容为 /* */ 之间的所有内容,支持多行。
    输入 /* 后回车即可自动生成。
  • /** */
    用在方法和类上,用来自动生成文档。
    输入 /** 后回车即可自动生成。
java
public class FirstSample {
    /**
     * 应用入口方法
     *
     * @param args
     */
    public static void main(String[] args) {
        // 打印 Hello,World!
        System.out.println("Hello,World!");
        /*
        这是一段多行注释
        当注释很多时可以使用这种方式
         */
    }
}

3.3 数据类型

Java 是一种强类型语言,必须为每一个变量声明一种类型。
在 Java 中一共有 8 种基本类型(primitive type):

  • 4 种整型(intshortlongbyte
  • 2 种浮点类型(floatdouble
  • 1 种用于表示 Unicode 编码的字符单元的字符类型 char
  • 1 种用于表示真值的 boolean (布尔)类型

3.3.1 整型

  • int(4 字节)(-2,147,483,648-2^31) ~ 2,147,483,6472^31 - 1))
  • short(2 字节)(32,768-2^15) ~ 32,7672^15 - 1))
  • long(8 字节)(-9,223,372,036,854,775,808-2^63) ~ 9,223,372,036,854,775,8072^63 -1))
  • byte(1 字节)(-128-2^7)~1272^7-1))

在 Java 中,整型的范围与运行 Java 代码的机器无关。
(C 和 C++ 程序需要针对不同的处理器选择最为高效的整型)

数值字面量

长整型数值有一个后缀 Ll(因为小写 l 和 数值 1 很容易混淆,大多数规约中强制使用大写 L)。
十六进制使用前缀 0x0X(例:0xCAFE)。
八进制使用前缀 0 (例:010)(因为前缀 0 比较容易混淆,一般不推荐使用八进制的常数)。
二进制使用前缀 0b0B(例:0b1001 表示十进制的 9)。

数字字面量可以添加下划线(_)以提高代码的可读性(例:100_00000b1111_0100_0010_0001)。

3.3.2 浮点类型

  • float(4 字节)(有效位数为 6 ~ 7 位)
  • double(8 字节)(有效位数为 15 位)

数值字面量

  • float 值有一个后缀 Ff(例:3.14F
  • double 值有一个后缀 Dd(例: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+0000U+FFFF
    其余的 16 个级别码点从 U+10000U+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 (布尔)类型有两个值: falsetrue ,用来判断逻辑条件。
整型值和布尔值之间不能进行互相转换。(有些语言中 0 相当于 false ,非 0 相当于 true

3.4 变量

  • 在 Java 中,每个变量都有一个类型(type)。
  • 声明变量时,变量类型位于变量名之前。
    java
    double salary;
    int vacationDays;
  • 变量名必须是一个以字母开头并由字母或数字构成的序列。
    与大多数语言相比,Java 中“字母”和“数字”的范围更大,例如 π
  • 变量名大小写敏感。
  • 变量名的长度基本上没有限制。

3.4.1 变量初始化

  • 声明一个变量后,必须用赋值语句对变量进行显式初始化。
    否则编译器会报错:variable not initialized
  • 要想对一个已经声明过的变量进行赋值,就需要将变量名放在等号(=)左侧,取值的 Java 表达式放在等号的右侧。
    java
    int vacationDays;
    vacationDays = 12;
    也可以将变量的声明和初始化放在同一行。
    java
    int vacationDays = 12;
  • 在 Java 中,变量的声明尽可能地靠近变量第一次使用的地方,这是一种良好的编写风格。

3.4.2 常量

在 Java 中,利用关键字 final 指示常量。

java
final double CM_PER_INCH = 2.54;
  • 关键字 final 表示这个变量只能被赋值一次。一旦被赋值之后,就不能再更改。
  • 习惯上,常量名使用全大写。

在 Java 中,经常希望某个常量可以在一个类中的多个方法中使用,通常将这些常量成为 类常量。可以使用关键字 static final 设置一个类常量。

java
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 前缀。

java
import static java.lang.Math.*;

StrictMath 类提供了严格浮点计算版本的数学函数。

3.5.2 数值类型之间的转换

下图是类型之间的合法转换。
实线表示无信息丢失;虚线表示可能会有精度损失。

两个数值进行二元操作时,先将两个操作数转换为同一种类型,然后再进行计算。

  • 如果两个操作数中有一个是 double 类型,另一个操作数就会转换为 double 类型。
  • 否则,如果其中一个操作数是 float 类型,另一个操作数将会转换为 float 类型。
  • 否则,如果其中一个操作数是 long 类型,另一个操作数将会转换为 long 类型。
  • 否则,两个操作数都将被转换为 int 类型。

3.5.3 强制类型转换

强制类型转换的语法格式是在圆括号中给出想要转换的目标类型,后面紧跟待转换的变量名。

java
double x = 8.22;
int nx = (int) x; // 8

强制类型转换通过截断小数部分将浮点数转换为整形。
需要舍入运算时,需使用 Math.round 方法。

java
double x = 8.22;
int nx = (int) Math.round(x);

3.5.4 结合赋值和运算符

java
x += 4;

相当于

java
x = x + 4;

3.5.5 自增和自减运算符

java
int m = 7;
int n = 7;
int a = 2 * ++m; // a = 16, m = 8
int b = 2 * n++; // b = 14, n = 8

建议不要在表达式中使用 ++ ,因为这样的代码很容易让人困惑,而且会带来烦人的 bug。

3.5.6 关系和 boolean 运算符

  • ==:监测相等性
  • !=:监测不等性
  • <:小于
  • >:大于
  • <=:小于等于
  • >=:大于等于
  • &&:逻辑与
    • 按照“短路”方式求值
  • ||:逻辑或
    • 按照“短路”方式求值
  • !:逻辑非
  • ?::三元操作符

3.5.7 位运算符

  • &:与
  • |:或
  • ^:异或
  • ~:非
  • >>:右移(用符号位填充高位)
  • <<:左移
  • >>>:右移(用 0 填充高位)

警告

移位运算符的右操作数需要完成模 32 的运算(左操作数是 long 型时模 64)。

3.5.8 括号和运算符级别

运算符结合性
[] .()(方法调用)从左向右
! ~ ++ -- +(一元运算) -(一元运算) ()(强制类型转换) new从右向左
* / %从左向右
+ -从左向右
<< >> >>>从左向右
< <= > >= instanceof从左向右
== !=从左向右
&从左向右
^从左向右
|从左向右
&&从左向右
||从左向右
?:从右向左
= += -= *= /= %= &= |= ^= <<= >>= >>>=从右向左
java
// && 的优先级高于 ||
a && b || c
// 等价于
(a && b) || c
java
// += 是右结合运算符
a += b += c
// 等价于
a += (b += c)

3.5.9 枚举类型

java
enum Size { SMALL, MEDIUM, LARGE, EXTRA_LARGE };

3.6 字符串

从概念上讲,Java 字符串就是 Unicode 字符序列。Java 没有内置的字符串类型,而是在标准类库中提供了一个预定义类 String

java
String e = "";
String greeting = "Hello";

3.6.1 子串

java
String greeting = "Hello";
String s = greeting.substring(0, 3);

substring 的第二个参数是不相复制的第一个位置(即子串不包含第二个参数位置的字符)。
substring 方法很容易计算子串的长度。 substing(a, b) 的长度为 b - a

3.6.2 拼接

使用 + 号拼接两个字符串。
当一个字符串和一个非字符串拼接时,后者被转换成字符串。

java
int age = 13;
String rating = "PG" + age;

如需把多个字符串放在一起,用一个定界符分割,可以使用静态 join 方法。

java
String all = String.join(" / ", "S", "M", "L", "XL");
// all = "S / M / L / XL"

3.6.3 不可变字符串

String 类没有提供用于修改字符串的方法。

java
greeting = greeting.substring(0, 3) + "p!"

由于不能修改 Java 字符串中的字符,所以在 Java 文档中将 String 类对象称为 不可变字符串

优点:编译器可以让字符串共享。

Java 的设计者认为共享带来的高效率远远胜过提取、拼接字符串所带来的低效率。

3.6.4 检测字符串是否相等

java
s.equals(t);
"Hello".equals(greeting);
"Hello".equalsIngnoreCase("hello");

一定不要使用 == 运算符检测两个字符串是否相等! 这个运算符只能够确定两个字符串是否放在同一个位置上。

如果虚拟机始终将相同的字符串共享,就可以使用 == 运算符检测是否相等。但实际上只有字符串常量是共享的,而 +substring 等操作产生的结果并不是共享的。

3.6.5 空串与 Null 串

空串 "" 是长度为 0 的字符串。

检查是否为空串:

java
if (str.length == 0) {
}

java
if ("".equals(str)) {
}

空串是一个 Java 对象,有自己的串长度(0)和内容(空)。不过,String变量还可以存放一个特殊的值,名为 null,这表示目前没有任何对象与该变量关联。

检查字符串是否为 null

java
if (str == null) {
}

检查字符串既不是 null 也不是空串:

java
if (str != null && str.length() != 0) {
}

3.6.6 码点和代码单元

Java 字符串由 char 值序列组成。
char 数据类型是一个采用 UTF-16 编码表示 Unicode 码点的代码单元。
大多数的常用 Unicode 字符使用一个代码单元就可以表示,而辅助字符需要一对代码单元表示。

length() 方法将返回采用 UTF-16 编码表示的给定字符串所需要的代码单元数量。

java
String greeting = "Hello";
int n = greeting.length(); // is 5

获取码点数量:

java
int cpCount = greeting.codePointCount(0, greeting.length());

获取指定位置的代码单元:

java
char first = greeting.charAt(0); // first is 'H'
char last = greeting.charAt(4); // last is 'o'

获取指定位置的码点:

java
int index = greeting.offsetByCodePoints(0, i);
int cp = greeting.codePointAt(index);

字符 𝕆U+1D546)需要两个代码单元。

java
String sentence = "𝕆 is the set of octonions";
char ch = sentence.charAt(1); // is �

可以在浏览器中运行 js 脚本获取对应的字符。

javascript
String.fromCodePoint(0x1D546)

返回不是一个空格,而是 𝕆 的第二个代码单元。

遍历所有码点:

java
int cp = sentence.codePointAt(i);
if (Character.isSupplementaryCodePoint(cp)) i += 2;
else i++;

回退操作:

java
i--;
if (Character.isSurrogate(sentence.charAt(i))) i--;
int cp = sentence.codePointAt(i);

使用 codePoints 方法遍历:

java
int[] codePoints = sentence.codePoints().toArray();

把码点转换为字符串:

java
String str = new String(codePoints, 0, codePoints.length);

3.6.7 String API

String 类常用的方法:

  • char charAt(int index)
  • int codePointAt(int index) 5.0
  • int offsetByCodePoints(int startIndex, int cpCount) 5.0
  • int compareTo(String other)
  • IntStream codePoints() 8
  • new String(int[] codePoints, int offset, int count) 5.0
  • boolean 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.0
  • 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) 8

3.6.8 阅读联机 API 文档

==> Java™ Platform, Standard Edition 8 API Specification

左上角选择 java.lang ,之后在左下角选择 String ,就可以看到 String 类的所有方法了。点击方法名的链接,可以看到方法的描述。

3.6.9 构建字符串

java
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();

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 关联。

java
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();
    }
}

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 格式化输出

java
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);

表 3-5 用于 printf 的转换符

No.转换符类型举例
1d十进制整数159
2x十六进制整数9f
3o八进制整数237
4f定点浮点数15.9
5e指数浮点数1.59e+01
6g通用浮点数-
7a十六进制浮点数0x1.fccdp3
8s字符串Hello
9c字符H
10b布尔True
11h散列码42528b2
12txTX日期时间(T强制大写)已经过时,应当改为使用 java.time 类。
13%百分号%
14n与平台有关的行分隔符-

示例代码:

java
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."

%tx 格式打印报错了,估计是这个转换符已经废弃了导致的。
另外 %h 打印的散列码应该是对象实例本身的散列码(即 Object.hashCode() 方法的返回值),显示为 16 进制的。

表 3-6 用于 printf 的标志

No.标志目的举例
1+打印正数和负数的符号+3333.33
2空格在正数之前添加空格" 3333.33"
30数字签名补 0003333.33
4-左对齐"3333.33 "
5(将负号括在括号内(3333.33)
6,添加分组分隔符3,333.33
7#(对于f格式)包含小数点3,333.
8#(对于xo格式)添加前缀 0x00xcafe
9$给定被格式化的参数索引。
例如,%1$d%1$x 将以十进制和十六进制格式打印第一个参数。
159 9F
10<格式化前面说明的数值。
例如,%d%<x 将以十进制和十六进制打印同一个数值。
159 9F
java
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"

上表中的 No.7 的效果不明,打印结果和上面的示例不一致。

表 3-7 日期和时间的转换符

No.转换符类型举例
1c完整的日期和时间周四 8 月 26 17:45:30 CST 2021
2FISO 8601 日期2021-08-26
3D美国格式的日期(月/日/年)08/26/21
4T24 小时时间17:45:30
5r12 小时时间05:45:30 下午
6R24 小时时间没有秒17:45
7Y4 位数字的年(前补 0)2021
8y年的后 2 位数字(前补 0)21
9C年的前 2 位数字(前补 0)20
10B月的完整拼写八月
11bh月的缩写8 月
12m2 位数字的月(前补 0)08
13d2 位数字的日(前补 0)26
14e2 位数字的日(前不补 0)26
15A星期几的完整拼写星期四
16a星期几的缩写周四
17j3 位数字的年中的日期(前补 0)
在 001 到 366 之间
238
18H2 位数字的小时(前补 0)
在 0 到 23 之间
17
19k2 位数字的小时(前不补 0)
在 0 到 23 之间
17
20I2 位数字的小时(前补 0)
在 0 到 12 之间
05
21l2 位数字的小时(前不补 0)
在 0 到 12 之间
5
22M2 位数字的分钟(前补 0)45
23S2 位数字的秒(前补 0)30
24L3 位数字的毫秒(前补 0)300
25N9 位数字的毫微秒(前补 0)300000000
26p上午或下午的标志下午
27z从 GMT 起,RFC822 数字位移+0800
28Z时区CST
29s从格林威治时间 1970-01-01 00:00:00 起的秒数1629971130
30Q从格林威治时间 1970-01-01 00:00:00 起的毫秒数1629971130300
java
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"

格式说明符的语法图

%{{参数索引值}$}{标志}{宽度}{.{精度}{转换字符}}{t{转换字符}}

3.7.3 文件输入与输出

读取文件需要构建一个 Scanner 对象;写入则需要构建一个 PrintWriter 对象。
如果文件名中包含反斜杠符号(\),则需要使用转义符 \\

java
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();
    }
}

注意

读取/写入文件时建议不要省略编码格式。
不指定时默认使用操作系统的默认编码,这可能会导致在不同的机器上运行会得到不同的结果。

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 语句。
块确定了变量的作用域。
一个块可以嵌套在另一个块中。

java
public static void main(String[] args) {
    int n;
    {
        int k;
    } // k is only defined up to here
}

不能在嵌套的两个块中声明同名的变量。

java
public static void main(String[] args) {
    int n;
    {
        int k;
        int n; // Error -- can't redefine n in inner block
    }
}

3.8.2 条件语句

java
if (condition) statement

if (condition) {
    statement1
    statement2
}

else 部分是可选的。

java
if (condition) statement1 else statement2

if (condition) {
    statement1
    statement2
} else {
    statement3
    statement4
}

重复的出现 if ... else if ...

java
if (condition1) statement1 else if (condition2) statement2 else statement3

if (condition1) {
    statement1
    statement2
} else if (condition2) {
    statement3
    statement4
} else {
    statement5
    statement6
}

示例:

java
if (yourSales >= target) {
    performance = "Satisfactory";
    bonus = 100;
} else {
    performance = "Unsatisfactory";
    bonus = 0;
}

3.8.3 循环

java
while (condition) statement

while (condition) {
    statement1
    statement2
}

如果开始循环条件为 false ,则 while 循环体一次也不执行。

如果循环体至少执行一次,则应将检测条件放在最后。

java
do statement while (condition)

do {
    statement1
    statement2
} while (condition)

3.8.4 确定循环

for 循环语句是支持迭代的一种通用结构,利用每次迭代之后更新的计数器或类似的变量来控制迭代次数。

java
for (int i = 1; i<= 10; i++)
    System.out.println(i);

for 语句的第一部分通常用于对计数器初始化;第二部分给出每次新一轮循环执行前要检测的循环条件;第三部分指示如何更新计数器。

尽管 Java 允许在 for 循环的各个部分放置任何表达式,但有一天不成文的规则:for 语句的 3 个部分应该对同一个计数器变量进行初始化、检测和更新

书上说这段倒计数的代码有问题,没看出来哪里有问题。

java
for (int i = 10; i > 0; i--)
    System.out.println("Counting down ... " + i);
System.out.println("Blastoff!");

在循环中,检测两个浮点数是否相等需要格外小心。

java
for (double x = 0; x != 10; x += 0.1)
    System.out.println(x);

上面的代码可能永远不会结束。因为 0.1 无法用二进制精确的表示。实际运行时从 9.99999999999998 直接跳跃到了 10.09999999999998

for 语句第一部分声明的变量的作用域就是整个循环体,在循环体外无法访问。

for 循环语句不过是 while 循环的一种简化形式。

java
for (int i = 10; i > 0; i--)
    System.out.println("Counting down ... " + i);

可以重写为:

java
while (i > 0) {
    System.out.println("Counting down ... " + i);
    i--;
}

3.8.5 多重选择:switch 语句

java
switch (choice) {
    case 1:
        break;
    case 2:
        break;
    default:
        break;
}

switch 语句将从与选项值相匹配的 case 标签处开始执行直到遇到 break 语句,或者执行到 switch 语句的结束处为止。
如果没有相匹配的 case 标签,而有 default 子句,就执行这个子句。

case 标签可以是:

  • 类型为 charbyteshortint 的常量表达式
  • 枚举常量
  • Java SE 7 开始,case 标签还可以是字符串字面量。
    这里比对时使用的是 equals 方法。

3.8.6 中断控制流程语句

break 语句可以用于退出循环语句。

java
while (years <= 100) {
    balance += payment;
    double interest = balance * interestRate / 100;
    balance += interest;
    if (balance >= goal) break;
    years++;
}

带标签的 break 用于跳出多重嵌套的循环语句。
标签必须放在希望跳出的最外层循环之前,并且必须紧跟一个冒号。
跳出到带标签的语句块末尾。

java
read_data:
while (...) {
    for(...) {
        if(...) {
            break read_data;
        }
    }
}
// 跳出到这里

可以将标签应用到任何语句中,甚至可以应用到 if 语句或者块语句中。
只能跳出语句块,不能跳入语句块。

java
label:
{
    if (conditon) break label;
}

continue 语句和 break 语句一样,将中断正常的控制流程。
continue 语句将控制转移到最内层循环的首部。

java
while (...) {
    if(...) {
        continue;
    }
}

3.9 大数值

如果基本的整数和浮点数精度不能够满足需求,那么可以使用 java.math 包中的 BigIntegerBigDecimal 。这两个类可以处理包含任意长度数字序列的数值。BigInteger 实现了任意精度的整数运算,BigDecimal 实现了任意精度的浮点数运算。

使用静态的 valueOf 方法可以将普通的数值转换为大数值。

java
BigInteger a = BigInteger.valueOf(100);

不能使用算术运算符处理大数值,需要使用对应的 addmultiply 等方法。

java
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)

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 数组

数组是一种数据结构,用来存储同一类型值的集合。
通过一个整型下标可以访问数组中的每一个值。
在声明数组变量时,需要指出数组类型和数组变量的名字。

java
int[] a;
int a[];

初始化数组:

java
int[] a = new int[100];

数组长度不要求是常量。
数组的下标从 0 开始。

java
int[] a = new int[100];
for (int i = 0; i < 100; i++) {
    a[i] = i;
}

下标越界时会引发 array index out of bounds 异常。

一旦创建了数组,就不能再改变它的大小。如果经常需要再运行过程中扩展数组的大小,就应该使用另一种数据结构 数组列表(array list

3.10.1 for each 循环

用来依次处理数组中的每个元素。

java
for (variable : collection)
    statement

collection 集合表达式必须是一个数组或者是一个实现了 Iterable 接口的类对象。

for each 语句更加简洁、更不易出错(不比为下标的起始值和终止值而操心)。

3.10.2 数组初始化以及匿名数组

Java 提供了一种创建数组对象并同时赋予初始化的简化书写形式。
数组的大小就是初始值的个数。

java
int[] smallPrimes = {2, 3, 5, 7, 11, 13};

初始化一个匿名数组:

java
new int[] { 17, 19, 23, 29, 31, 37};

数组的长度可以为 0。

3.10.3 数组拷贝

常用于增加数组的大小:

java
luckyNumbers = Arrays.copyOf(luckNumbers, 2 * luckNumbers.length);

如果长度小于原始数组的长度,则只拷贝最前面的数据元素。

3.10.4 命令行参数

每一个 Java 应用程序都有一个带 String arg[] 参数的 main 方法。这个参数表明 main 方法将接收一个字符串数组,也就是命令行参数。

java
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("!");
    }
}
bash
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

3.10.5 数组排序

可以使用 Arrays 类中的 sort 方法对数值型数组进行排序。

java
int[] a = new int[10000]
...
Arrasy.sort(a);

这个方法使用了优化的快速排序算法。快速排序算法对于大多数数据集合来说都是效率比较高的。

java.util.Arrays 1.2

  • static String toString(type[] a) 5.0
  • static type copyOf(type[] a, int length) 6
  • static type copyOfRange(type[] a, int start, int end) 6
  • static 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 多维数组

多维数组将使用多个下标访问数组元素,它适用于表示表格或更加复杂的排列形式。

java
double[][] balances;

与一维数组一样,在调用 new 对多维数组进行初始化之前不能使用它。

java
balances = new double[NYEARS][NRATES];

简化形式:

java
int[][] magicSquare = {
    {16, 3, 2, 13},
    {5, 10, 11, 8},
    {9, 6, 7, 12},
    {4, 15, 14, 1}
};

访问多维数组:balances[i][j]

使用 for each 循环处理二维数组:

java
for (int[] row : magicSquare) {
    for (int value : row) {
        System.out.println(value);
    }
}

快速打印二维数组的元素列表:

java
System.out.println(Arrays.deepToString(magicSquare));

3.10.7 不规则数组

Java 实际上没有多维数组,只有一维数组。多维数组被解释为 数组的数组

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();
}

打印结果:

bash
   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