1. 其他基础
-
Java 区分大小写;Java 中每个句子必须用分号结束;使用括号表示代码块;使用(.)来调用类的方法等;双引号定字符串。
-
源文件的文件名必须与公共类的名字相同(包括大小写),并用
.java作为扩展名 -
Java 虚拟机总是从指定类中的 main 方法的代码开始执行。根据 Java 语言规范,main 方法必须声明为 public。并且在 Java 1.4 及之后版本中强制 main 方法必须是 public。
之外 main 方法还必须是 static 的。
main 方法使用 void,表示没有返回,main 方法没有给操作系统返回 “退出码”。如果 main 方法正常退出,那么 Java 应用程序的退出码是 0,表示成功地运行了程序。如果希望在终止程序时返回其他的退出码,使用
System.exit方法。 -
Java 的类和 C++ 的类很相似,但是还是存在一些差异。比如 Java 中的所有函数都是某个类的方法(标准术语将其称为方法,而不是成员函数)。
-
java main 中的 args 中并没有存储程序名。
java Message -g cruel world中 args[0] 为 “-g”。public static void main(String[] args) { } -
java.lang 中的包不需要显示导入
2. 注释
Java 中的注释不会出现在可执行程序中。因此,在源代码中添加任意多的注释,也不用担心可执行代码会膨胀。Java 有三种标记注释的方式。
//行注释
/*块注释*/
/**
*这种注释可以用来自动地生成文档
*/
不能简单地把代码用
/*和*/括起来作为注释,因为这段代码本身可能也包含一个*/界定符。
3. 标识符
由 字母、下划线、数字、美元符号 等组成。开头不能是数字,也不能是关键字(如 true、false、null)等。
Java 使用 unicode 字符集,该字符集包含了世界上大部分语言的“字母表”。
4. 数据类型
Java 是一种强类型语言,也就是必须为每一个变量声明一种类型。Java 一共有 8 种基本类型:4 种整型、2 种浮点类型、1 种字符类型 char(用于表示 Unicode 编码的代码单元)、一种表示逻辑的 boolean 类型。
4.1. 逻辑类型---boolean
true、false 两个值,用来判断逻辑条件。整型值和布尔值之间不能进行相互转换。
在 C++ 中数值甚至指针类型可以代替 boolean 值,值 0 相当于 false,非 0 值相当于 true。
4.2. 整型
| 关键字 | 常量表示 | 字节数 |
|---|---|---|
| int | 123(这个常量是十进制数值)、077(这个常量是八进制数值)、0xABC/0XABC(这个常量是十六进制数值)、0b1001/0B1001(这个常量是二进制数值,表示 9 这个 10 进制数值)、1_000_000(也可以在数字字面量中加下划线,这些下划线只是为了易读,Java 编译器会去掉这些下划线的) | 4 |
| byte | (byte)-12、(byte)28---强制转换为 byte 型,但是可能会发生截断等行为,所以记得请把一定范围内的 int 型常量或者 int 型的值赋值给 byte | 1 |
| short | (short)-12、(short)28---同 byte | 2 |
| long | 108L(这个常量是 long 型),用后缀 L 来表示 | 8 |
Java 中没有任务无符号形式的 int、long、short 或 byte 类型,比如不存在
unsigned int m这种的。Java 中,所有的数值类型所占用的字节数与平台无关,在不同的平台上数据类型的取值范围是固定的,这就解决从一个平台移植到另一个平台带来的诸多问题。而 C 或者 C++ 会针对不同的处理器选择最为高效的整型,这就造成了一个在 32 位处理器上运行很好的 C/C++ 程序在 16 位的系统上运行时会发生整数溢出。
4.3. 字符类型
- 2 字节,但是最高位不是符号位,不存在负数
- 单引号表示的是字符类型常量,如
'A' - 转移字符表示的也是字符类型,如
'\n'、'\b'、'\t'、'\r'、'\''、'\"','\\'。这种方式可以出现在字符串中,如"Hello\n"。 - 字符在 unicode 字符表中排序位置的十六进制表示也可以表示字符类型,如
'\u0041'。这种方式可以出现在字符串中,也可以出现在字符串外,如main(String \u005B\u005D args)。这么理解就好,因为字符串外的那些也是文本,也会被处理成 unicode。
char 类型原本用于表示单个字符。但是,如今有些 Unicode 字符可以用一个 char 值表示,另外一些 Unicode 字符需要两个 char 值。
4.4. 浮点数
| 关键字 | 常量表示 | 字节数 |
|---|---|---|
| float | 后缀 f 或者 F,如 3.14f | 4,(6~7 位有效数字) |
| double | 后缀 d 或者 D,如 3.14D(默认情况下为 double,也就是 3.14 double 类型的) | 8,(16 位有效数字) |
所有的浮点数值计算都遵循 IEEE 754 规范,使用下面三个特殊的浮点数值表示溢出和出错情况:
-
正无穷大,比如正整数除以 0
-
负无穷大
-
NaN(不是一个数值),比如 0/0 或者负数的平方根
Double.POSITIVE_INFINITY 和 Double.NEGATIVE_INFINITY 和 Double.NaN 表示这三个特殊的值,Float 也是一样的。另外在比较一个值是否等于 Double.NaN 的时候,要使用 Double.isNaN() 方法来判断,而不能使用 x == Double.NaN 来判断。
浮点数还可以使用十六进制表示,比如 0.125 = 2^(-3),那么可以表示为 0x1.0p-3。在这种表示方法中,1.0 的部分是尾数使用十六进制,-3 部分是指数(p 后面),使用十进制。指数的基数是 2,而不是 10。
4.5. 基础类型转换
Java 中使用 (类型名)要转换的值 的方式进行类型转换。级别低常量/变量赋值给级别高的变量时,系统自动进行转换,如 float x = 100;相反的赋值需要用上述方式进行显示转换。
假如两个进行运算的数中有一个是 double 型,那么则将另一个数也转化为 double 型,也就是往级别高的转。
级别从低到高(其实就是按照字节数及表示的值范围进行排序):byte、short、char、int、long、float、double
需要注意的时:级别高常量/变量赋值给级别低的变量时,需要注意截断问题。所以最好不要超过级别低的变量所能表示的范围。若超过了,则级别低的值需要根据截断的字节情况算出来。比如
byte a = (byte) 128,实际 a 的值为 -128。
5. 字符串
Java 没有内置的字符串类型,而是在标准的 Java 类库中提供一个预定义类,叫做 String,每个用双引号括起来的字符串都是 String 类的一个实例。那么,Java 字符串就是 Unicode 字符序列。
String 类对象是不可变的,也就是不能修改 Java 字符串中的某个字符。不可变字符串有一个优点:那就是编译器可以让这些字符串共享。
Java 字符串大致类似于 char* 指针,而不是字符数组。
char * greet = "Hello"; // 是这个 char greet[] = "Hello"; // 而不是这个
5.1. 拼接
Java 也使用 +号来拼接两个字符串。当一个字符串与另一个非字符串的值进行拼接时,后者会转换为字符串。
String expletive = "expletive";
String PG13 = "deleted";
String message = expletive + PG13;
int age = 13;
String rating = "PG" + age;
可以使用 String.join() 将多个字符串放在一起,中间用一个界定符号分隔。
String all = String.join(" / ", "S", "M", "L", "XL");
// S / M / L / XL
5.2. 字符串是否相等
Java 中不能使用 == 来检测两个字符串是否相等,这个 == 只能判断这两个字符串是否存放在同一个位置。那么你可能会有疑问了,既然 Java 字符串是共享的,那么当同一个位置的时候,那么不就代表两个字符串相等嘛?假如,虚拟机使用将相同的字符串共享,那么其实使用 == 也是可以的。但是,完全有可能将内容相同的多个字符串副本放在不同的位置上。并且,实际上只有字符串字面常量是共享的,而 + 或者 substring 操作得到的字符串并不共享。所以请不要使用 == 运算符来测试字符串的相等性。
要想比较两个字符串是否相等,请使用 equals() 方法,如 s.equals(t)。如果字符串 s 和字符串 t 相等,则返回 true。
C++ 的 string 类,重载了 == 运算符,但是 Java 没有。Java 字符串看起来像是数值,当表现得又类似于指针。
5.3. 空串和 null
空串是长度为 0 的字符串,如 ""。它是一个 Java 对象,有种自己的串长度和内容。
null 表示该变量没有与任何对象实例相关联。
5.4. 子串
使用 string 的 substring() 方法可以从一个字符串中提取一个子串。
String greeting = "Hello";
String sub = greeting.substring(0, 3); // Hel
5.5. 字符串长度
Java 字符串由 char 值序列组成,char 数值类型是一个采用 UTF-16 编码表示 Unicode 码点的代码单元。最常用的 Unicode 字符使用一个代码单元就可以表示,但是有些辅助字符可能需要一对代码单元表示。
length() 方法返回的是代码单元数量,charAt(n) 将返回位置 n 的代码单元。当字符串中的字符都是用一个代码单元的时候,那么 length() 返回则是字符数量,charAt() 返回是第 n 个字符,但是当有一个字符是用两个代码单元时候,那么此时就有点问题了。这个时候可以使用码点单元的方式来实现了。
String greeting = "Hello";
int count = greeting.codePointCount(0, greeting.length()); // 码点数量
// 获得第 i 个码点
int index = greeting.offsetByCodePoints(0, i);
int cp = greeting.codePointAt(index);
// 查看每个码点
int cp = sentence.codePointAt(i);
if (Character.isSupplementaryCodePoint(cp)) {
i += 2;
} else {
i++;
}
虚拟机不一定要把字符串实现为代码单元序列,在 Java 9 中,只包含单字节代码单元的字符串可以使用 byte 数组实现。
6. 数组
Java 的数组是一种数据结构,用来存储同一类型值的集合。通过一个整型下标可以访问数组中的每一个值。
6.1. 声明、创建、初始化一维、多维数组
// 指出数组类型,后面紧跟 [],然后后头紧跟数组变量的名字。但是,这里仅仅声明了变量 name,并没有创建数组,name 就可以当成一个引用而已。
int name[];
int[] name; // 后者更常用
// 声明二维数组,一个二维数组由若干个一维数组构成,相当于数组的数组
int name[][];
int[][] name;
// 一次性声明多个数组
float[] a, b; // 等价于 float a[], b[]
float[] a, b[]; // 等价于 float a[], b[][]
// 数组名 = new 数组元素的类型[大小],创建数组之后就不能改变数组的长度了。
// new int[4] 相当于在 Java 的堆区分配了内存,并且会返回该内存区的首地址。之后将该地址赋值给 name 变量,Java 中不使用指针这一概念,而是使用引用这一概念。因此 name 只是一个引用指向了堆区的这个位置
int[] name;
name = new int[4];
// 同理
int[] name = new int[4];
// Java 允许使用 int 型变量来指定数组的大小
int size = 10;
int[] name = new int[size];
// Java 中构成二维数组的一维数组不必有相同的长度
int[][] a = new int[3][];
a[0] = new int[6];
a[1] = new int[12];
a[2] = new int[8];
// 声明数组的时候,可以直接将一些初始化值付给数组变量;
// 个人认为其实,{1, 2, 3, 4} 也是一种创建数组的方式;
int[] data1 = {1, 2, 3, 4};
int[] data2 = new int[]{1, 2, 3, 4};
// int[] data2 = new int[4]{1, 2, 3, 4}; 这种方式是会出错的
// 初始化一个二维数组,组成二维数组的一维数组的长度可以不相同。
int[][] a = {{1}, {1, 1}, {1, 2, 1}, {1, 2, 3, 1}};
-
与 C 不同的是 Java 不允许
int a[12]或者int[12] a -
创建数组的时候没有指定分配的元素的值的话,那么 Java 会使用默认值填充,如 float 是 0.0;整型是 0;boolean 是 false;对象数组则初始化为 null,也就是对象数组中的每一个引用其实都是 null,并没有实际指向。
-
Java 数组与堆栈上的 C++ 上的数组有很大不同,但是基本上与堆上分配的数组指针一样。也就是说 Java 的
int[] a = new int[100]等同于 C++ 的int* a = new int[100],而不同等同于int a[100]。 -
上面阐述了多维数组,但实际上 Java 没有多维数组。多维数组被解释为 “数组的数组”。Java 的多维数组主要是在底层实现上存在一些细微的差异。
double[][] balances = new double[10][6];double 实际上一个包含 10 个元素的数组,只是这 10 个元素又是一个由 6 个浮点数组成的数组,你可以把这 10 个元素理解成 double[] 类型的引用变量。那么,
balances[i]实际上引用的是第 i 个子数组,它本身也指向一个数组,所以balances[i][j]实际上是访问第 i 个数组的 j 个元素。同时,我们可以单独交换某两个子数组。double[] temp = balances[i]; balances[i] = balances[j]; balances[j] = temp;因此,我们可以构建出一个不规则的数组,也就是每个子数组的长度是不一样的。

Java 中的
double[][] balances = new double[10][6]不同于 C++ 中的double balances[10][6],也不同于 C++ 中的double (*balances)[6] = new double[10][6]。而等同于double ** balances = new double*[10]; for (int i = 0; i < 10; i++) { balances[i] = new double[6]; }
6.2. 数组操作
-
使用索引值进行访问,下标从 0 开始。比如一个包含 100 个元素的数组,下标只能是 0-99,如果超过 99,那么就会报错。
Java 中的
[]运算符被预定义为会完成越界检查,而且没有指针运算,既不能通过 +1 得到数组中的下一个元素。 -
数组名.length(一维数组是数组中元素的个数;二维数组是一维数组的个数)。 -
数组排序可以使用
Arrays.sort()方法,这个方法使用了优化的快速排序算法。 -
使用
System.out.println()输出的时候,char 型的数组输出的是数组全部元素的值,要想输出 char 型数组的运用值使用System.out.println("" + a);int 型的数组输出则为引用值。要想打印输出 int 型的数组可以使用
Arrays.toString(array)方法获得一个包含数组元素的字符串。二维数组的话,可以使用Arrays.deepToString(array)。
6.3. 数组拷贝
数组拷贝分为两种:一种是浅拷贝,另一种是深拷贝。
// 浅拷贝
int[] smallPrimes = {2, 3, 5, 7, 11, 12};
int[] luckyNumbers = smallPrimes;

// 深拷贝,将一个数组的值拷贝到另一个新的数组中去。
// copyOf 的方法,第二个参数是新数组的长度,可以比原数组要长,数组中多出的位置的元素将被赋值为默认值;如果比原数组要短,那么只拷贝前面的值。
int[] copiedLuckyNumbers = Arrays.copyOf(luckyNumbers, luckyNumbers.length);
7. 变量和常量
变量是用来存储值的,常量是值不变的变量。变量和常量相当给内存的一段区域取了一个名字。
7.1. 声明变量
先指定变量的类型,然后是变量名。变量名必须以一个字母、下划线或者 $ 开头,并且由字母、下划线、$ 和数字构成序列(不能是 Java 保留的关键字),同时变量名是大小写敏感的,在长度上基本没有限制。
Java 中可以将声明放在代码中的任何地方,因此变量的声明最好尽可能靠近变量第一次使用的地方。
double salary;
int vacationDays;
boolean done;
int i, j; // 这种方式不建议
相比其他语言,Java 变量名中的字母范围会更大,从 A-Z、a-z、_、$ 以及在任何语言中表示字母的任何 Unicode 字符,数字也是类似的,某种语言中表示数字的任何 Unicode 字符。但是,建议使用常规的英文字母和 0-9 。比如 $ 只用在 Java 编译器或者其他工具生成的名字中。
7.2. 变量初始化
声明一个变量之后,必须要用赋值语句对变量进行显示初始化。
int vationDays = 12;
// 等同于
int vationDays;
vationDays = 12;
从 Java 10 开始,对于局部变量,可以从变量的初始化推断出它的类型,因此可以不用再声明类型了。
var vationDays = 12; // int var greeting = "Hello"; // StringC++ 中区分变量的声明和定义,比如
extern int i是一个声明,int i =10是一个定义,而在 Java 中不区分声明和定义。
7.3. 常量
使用关键值 final 来表示,final 表示的变量只能被赋值一次,也就是被赋值之后就不能再改变了,习惯上常量名使用大写。
类常量在 final 关键字的基础之上,新增了一个 static 关键字,并且定义在 main 方法的外面,同一类中的其他方法也可以使用这个类常量。假如是 public 的话,那么其他类中的方法也可以使用类型直接访问这个类常量。
public class demo { public static final double CM_PER_INCH = 2.54; public static void main(String[] args) { ... } }const 虽然被 Java 作为保留的关键字,但是并没有使用。
7.4. 枚举类型
枚举类型包含有限个命名的值,假如一个变量的取值仅在几个值中选择的话,那么可以使用枚举类型。
enum Size {SMALL, MEDIUM, LARGE, EXTRA_LARGE};
Size s = Size.MEDIUM;
8. 运算符
| 运算符类型 | 表示 |
|---|---|
| 算术运算符 | +、-、*、/(运算的两个数都是整数的话,那就是整数除法;否则表示浮点除法,15/2 等于 7,15.0/2 等于 7.5。除以 0 获得无穷大或者 NaN,会产生异常 )、% |
| 自增、自减 | 前++、--(先++、--,再使用该变量的值);后++、--(先用变量,再 ++、--) |
| 关系运算符 | >、<、>=、<=、==、!= |
| 逻辑运算符 | &&、||、!(操作元必须是 boolean 型数据;&& 左边为 false 时,右边的值不会再判断;|| 左边为 true 时,右边则不会再判断了,也就是按照短路方式来求值) |
| 赋值运算符 | =(左边的操作元必须是变量,不能是常量或者表达式),+=、-= 等等 |
| 三元运算符 | condition ? expression1 : expression2 ,如果 condition 为 true 则去第一个值,否则第二个值 |
| 位运算符 | &、|、~、^(异或:相同为 0,不相同为 1),应用在 boolean 值上的时候 &、| 也会得到一个布尔值,但是后者不采用 “短路” 方式来求值。 >>、<< 是将位右移和左移,>>> 会用 0 填充高位,而 >> 是用符号位填充高位(不存在 <<< 运算符);另外移位运算符的右操作数要完成模 32 的运算(32 是针对整型,long 则是 64),1 << 35 等同于 1<<3。 |
| instanceof | instanceof (左边的操作元是一个对象,右边是一个类,当左边的对象是右边的类或者子类创建的对象时,为 true) |
算数混合运算的精度是:double、float、long、int 的顺序来,也就是说假如同时存在 double 和 int 类型的,那么则按照 double 类型的来。
Java 不适用逗号运算符,但是 for 语句中的第一部分和第三部分可以使用逗号来分割表达式。
优先级从高到低:

9. 控制流程
Java 与其他程序设计语言一样,使用条件语句和循环结构确定控制流程。
在深入了解控制结构之间,先了解块(block)的概念。块(即复合语句)是由若干条 Java 语句组成的语句,并用一对大括号括起来。块确定了变量的作用域。一个块可以嵌套在另一个块中,但是不能在嵌套的块中声明被嵌套的块中的同名变量。
// 这种一个块 A 嵌套在另一个块 B 中,块 A 不能再声明和块 B 相同的变量。
// 下面这段代码是无法通过的
public static void main(String[] args) {
int n;
...
{
int k;
int n;
...
}
}
在 C++ 中,可以在嵌套的块中重定义一个变量,在内层定义的变量会覆盖在外层定义的变量。
Java 中没有 goto 语句。
9.1. 条件分支语句
- if
- if ... else ...
- if ... else if ... else
if (condition) {
.....
}
if (condition) {
......
} else {
......
}
if (condition) {
......
} else if (condition) {
......
} else {
......
}
- else 子句会与最邻近的 if 组成一组
9.2. 选择语句
-
switch 语句将从与选项匹配的 case 标签开始执行,直到遇到 break 语句,或者执行到 switch 语句的结束为止。如果没有相匹配的 case 标签,而有 default 子句,那么会执行 default 子句。
如果在 case 分支语句的末尾没有 break 语句,那么就会接着执行下一个 case 分支语句。
-
case 标签可以是①类型为 char、byte、short 或 int 的常量表达式;②枚举常量,在使用枚举常量时不需要指明枚举名;③还可以是字符串字面量(Java 7 开始)
switch (expression) {
case constant1:
......;
break;
case constant2:
......;
break;
case ......:
......;
break;
default :
......;
}
enum Size {SMALL, MEDIUM, LARGE, EXTRA_LARGE};
Size s = Size.MEDIUM;
switch (s) {
case SMALL:
...
break;
...
}
- switch 中 expression 的值可以是 byte、short、int、char,不可以是 long;
9.3. 循环语句
-
while,先检测条件在执行
-
do ... while,先执行语句,再检测条件,如果 true 则重复执行
-
for,有三个部分:第一部分通常是初始化;第二部分是要判断的条件;第三部分是更新计数器。第一和第三可以使用逗号。Java 允许 for 循环的各个部分放置任何表达式,但是有一条不成文的规定,那就是 for 语句的三个部分应该对同一个计数器变量进行初始化、检测和更新。
另外 for 语句第一部分声明的一个变量,它的作用域是到 for 循环体的末尾。
-
for ... each
// for 语句
for (int i = 0; i < 10; i++) {
......
}
// while 语句
while (condition) {
......
}
// do ... while 语句
do {
} while(i < 10);
for (int i = 1; i < 10; i++) {
System.out.println(i);
}
// for each 语句,遍历数组中的每个元素,而不是下标值
// for (声明循环变量:数组的名字){}
int[] a = {1, 2, 3, 4, 5};
for (int i:a){
System.out.println(i);
}
// 声明循环变量,必须变量声明的形式,而不能拿已声明过的变量来,如下是错误的
int i = 0;
for (i:a) {
......
}
for...each 的方式除了遍历数组之外,还可以遍历任何一个实现了 Iterable 接口的类对象。
break 和 continue 同样可以应用在循环语句中,当然 break 还可以用于 switch 语句。
9.4. 中断流程控制的语句
-
break,相比其他语言的 break,Java 的 break 可以带标签,用于跳出多重嵌套的循环语句。break 除了用在循环和 switch 的情况,还可以用于 if 的情况,用来跳出语句块。
-
continue,同样可以带标签
-
return
read_data:
while (...) {
for (...) {
if (n < 0) {
break read_data;
}
}
}
label:
{
if (condition) {
break label;
}
}
Java 的设计者将 goto 作为保留字,但是没有在语言中使用它。
10. 输入与输出
10.1. 标准输入
想要通过控制台进行输入,首先需要构建一个与“标准输入流” System.in 关联的 Scanner 对象。
Scanner in = new Scanner(System.in);
String name = in.nextLine(); // 将读取一行输入,可含空格
String firstName = in.next(); // 读取一个单词,以空白符作为分隔符
int age = in.nextInt(); // 读取一个整数
boolean b = reader.nextBoolean();
byte b = reader.nextByte();
short s = reader.nextShort();
long l = reader.nextLong();
float f = reader.nextFloat();
double d = reader.nextDouble();
// 上述的输入是可见的,不适用于从控制台读取密码等。Java 引入了 java.io.Console 类来实现这个目的,返回的密码存放在一个字符数组中,对密码处理完成之后,马上用一个填充值覆盖数组元素。Console 对象必须每次读取一行输入。
Console cons = System.console();
String username = cons.readLine("User name: ");
char[] passwd = cons.readPassword("Password: ");
Scanner 类定义在 java.util 包中
10.2. 标准输出
// 允许使用 “+” 将变量、表达式、一个常数值或一个字符串进行合并输出
System.out.println(); // 输出之后自动换行
System.out.print(); // 不换行
// 格式化输出
System.out.printf("格式化控制部分", 表达式1, 表达式2, ..., 表达式n);
System.out.printf("%8.2f", 10000.0 / 3.0); // 以一个字段宽度(8)打印 x,并且精度为小数点后 2 个字符。也就是打印一个前导的空格和 7 个字符 3333.33
-
格式化控制部分中的 % 字符开始的格式说明符都会用相应的参数替换掉,格式说明符尾部的转换符指示要格式化的数值的类型,f 表示浮点数,s 表示字符串等。注意可以使用 s 转换符格式化任意的对象,那么对于实现了 Formattable 接口的任意对象会调用这个对象的 formatTo 方法,否则调用 toString 方法。

-
指定控制格式化输出外观的各种标志

10.3. 文件输入与输出
// 读取 myfile.txt 这个的文件,假如有反斜杠的话需要再加一个额外的反斜杠(\)
// 以 UTF-8 的字符编码方式读取,如果省略则使用运行这个 Java 程序的机器的默认编码
Scanner in = new Scanner(Path.of("myfile.txt"), StandardCharsets.UTF_8);
Scanner in = new Scanner("myfile.txt"); // 将 myfile.txt 这个字符串作为输入
// 创建一个输出对象,提供文件名和字符编码方式。如果文件名不存在则创建该文件。可以使用 print、printf、println 方法
PrintWriter out = new PrintWriter("myfile.txt", StandardCharsets.UTF_8);
当指定一个相对文件名时,例如
../myfile.txt,那么文件是相对于 Java 虚拟机启动目录的位置。
11. 巨人的肩膀
- 《Java 核心技术卷1-原书第11版》