# Java 核心技术第 10 版 - 卷一 - 基础知识

该书当前最新版为第 11 版

分上下两卷(上卷为基础,下卷为高级特性)

地址: http://horstmann.com/corejava

# 第 1 章 Java 程序设计概述

1.1996 年 Java 第一次发布就引起了人们的极大兴趣。

2.Java “白皮书” 的关键术语:简单性、面向对象、分布式、健壮性、安全性、体系结构中立、可移植性、解释型、高性能、多线程、动态性

3. 上面 11 个术语可参考 Gosling 的论述: http://horstmann.com/corejava/java-an-overview/7Gosling.pdf

4.Java 白皮书可参考: https://www.oracle.com/java/technologies/language-environment.html

# 第 2 章 Java 程序设计环境

1. 安装

  • 你需要的是 JDK(Java SE 开发包),而不是 JRE。
  • Windows 或 Linux:32 位选择 x86,64 位以 x64。
  • Linux:选择.tar.gz 版本。

2. 初学者文档: http://docs.oracle.com/javase/tutorial/getStarted/cupojava/

# 第 3 章 Java 的基本程序设计结构

# 一个简单的 Java 应用程序

1. 区分大小写、访问修饰符(访问级别)、类、类名、保留字、CamelCase、文件名与公共类名相同、扩展名为.java、main 方法、代码块 {}、关键字(static void)、分号结束、可多条语句写一行、双引号分隔字符串(区别于 python 可用单引号分隔)等。

2. 根据 Java 语言规范,main 方法必须声明为 public(Java 语言规范是描述 Java 语言的官方文档。可以从网站 http://docs.oracle.com/javase/specs 上阅读或下载)。不过,当 main 方法不是 public 时,有些版本的 Java 解释器也可以执行 Java 应用程序。有个程序员报告了这个 bug。如果感兴趣的话,可以在网站 http://bugs.java.com/bugdatabase/index.jsp 上输入 bug 号码 4252539 查看。这个 bug 被标明 “关闭,不予修复。”Sun 公司的工程师解释说:Java 虚拟机规范(在 http://docs.oracle.com/javase/specs/jvms/se8/html )并没有要求 main 方法一定是 public,并且 “修复这个 bug 有可能带来其他的隐患”。好在,这个问题最终得到了解决。在 Java SE 1.4 及以后的版本中强制 main 方法是 public 的。

# 注释

单行注释 <kbd>//</kbd>,多行注释 < kbd>/* xxx */</kbd>、文档注释 < kbd>/** xxx */</kbd>。<kbd>/**/</kbd > 注释不能嵌套。

# 数据类型

Java 是强类型语言,8 种基本类型,4 整型、2 浮点、1 字符、1 布尔

整型类型存储取值范围
int4 字节-21 4748 3648 ~ 21 4748 3647 (>21 亿)
short2 字节-32768 ~ 32767
long8 字节-922 3372 0368 5477 5808 ~ 922 3372 0368 5477 5807
byte1 字节-128 ~ 127

(1)int 最常用,long 可表示地球人数,byte/short 用于特定场合如底层文件处理等

(2)整型范围与机器无关(区别于 C++)

(3)长整型后缀 L,16 进制前缀 0x 或 0X,8 进制前缀 0(尽量少用)

(4)Java7 开始二进制前缀 0b 或 0B,且数字字面量可加下划线,易读,如 1_000_000,Java 编译器编译时会去除这些下划线

(5)Java 没有任何无符号 (unsigned) 形式的 int、long、short 和 byte

浮点类型存储取值范围
float4 字节-2^128 ~ +2^128
double8 字节-2^1024 ~ +2^1024

(1) double 精度是 float 两倍,俗称双精度,大多数应用都是使用 double,float 无法满足

(2) float 后缀为 F 或 f,没有后缀则默认为 double,浮点数后缀也可以是 d 或 D

(3) 可以使用 16 进制表示浮点数,16 进制表示法中 p 表示指数而不是 e

(4) 注意运算时候溢出(正负无穷大,如一个正整数除以 0)和出错(NaN,如 0/0),表示为 Double.POSITIVE_INFINITY、Double.NEGATIVE_INFINITY 和 Double.NaN
(5) 浮点数不要用于金融计算,比如 System.out.println (2.0 - 1.1) 返回 0.8999999999999999 而不是 0.9,这种舍入误差主要原因是浮点数采用二进制系统表示,而在二进制系统中无法精确地表示分数 1/10,这就好像十进制无法精确地表示 1/3 一样,建议使用 BigDecimal 类。

char 类型

(1)char 类型原本用于表示单个字符。现在有些变化,有些 unicode 字符可以用 1 个 char 描述,还有些 unicode 可以用 2 个 char 描述

(2)char 类型用单引号括起来,使用时注意特殊字符的转义序列

(3)unicode 出现之前有很多其他标准:ASCII(美国)、ISO 8859-1(西欧)、KOI-8(俄罗斯)、GB18030(中国)等

(4)1991 年发布 Unicode1.0 (65535,当时使用不足一半),设计 Java 时采用 16 位 Unicode 字符集(目前已超 65536,16 位 char 已无法满足 Unicode 需要了,如果想要了解 Java 后续怎么处理的(JavaSE5 开始),可以了解下码点、代码单元、替代区域、八位数集( https://math.ucr.edu/home/baez/octonions/ )、utf-16 等概念)

(5)在 Java 中,char 类型描述了 UTF-16 编码中的一个代码单元。

(6)强烈建议不要在程序中使用 char 类型,除非确实需要处理 UTF-16 代码单元。最好将字符串作为抽象数据类型处理

boolean 类型,true 和 false,用于逻辑判断,注意整型值和布尔值之间不能转换(区别于 c++ 和 python)

# 变量

1. 注意命名规范,如果想要知道哪些 Unicode 字符属于 Java 中的 “字母”,可以使用 Character 类的 isJavaIdentifierStart 和 isJavaIdentifierPart 方法来检查。

2. 尽管 $ 是一个合法的 Java 字符,但不要在你自己的代码中使用这个字符。它只用在 Java 编译器或其他工具生成的名字中。

3. 不能使用 Java 保留字作为变量名( https://www.softwaretestinghelp.com/important-java-keywords/

4. 可以在一行中声明多个变量,如:int i, j; 不提倡。

5. 声明一个变量之后,必须用赋值语句对变量进行显式初始化,千万不要使用未初始化的变量。

6. 要想对一个已经声明过的变量进行赋值,就需要将变量名放在等号(=)左侧,相应取值的 Java 表达式放在等号的右侧。

7. 在 Java 中,变量的声明尽可能地靠近变量第一次使用的地方,这是一种良好的程序编写风格。

8. 常量,在 Java 中,利用关键字 final 指示常量。关键字 final 表示这个变量只能被赋值一次。一旦被赋值之后,就不能够再更改了。习惯上,常量名使用全大写。

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

10.const 是 Java 保留的关键字,但目前并没有使用。在 Java 中,必须使用 final 定义常量。

# 运算符

1.+、-、*、/,/ 两边都是整数则表示整数除法,否则表示浮点除,取模 %。

2. 注意,整数被 0 除将会产生一个异常,而浮点数被 0 除将会得到无穷大或 NaN 结果。

3. 无论在哪个虚拟机上运行,同一运算应该得到同样的结果。对于浮点数的算术运算,实现这样的可移植性是相当困难的。double 类型使用 64 位存储一个数值,而有些处理器使用 80 位浮点寄存器。这些寄存器增加了中间过程的计算精度。很多 Intel 处理器计算 x*y,并且将结果存储在 80 位的寄存器中,再除以 z 并将结果截断为 64 位。这样可以得到一个更加精确的计算结果,并且还能够避免产生指数溢出。但是,这个结果可能与始终在 64 位机器上计算的结果不一样。因此,Java 虚拟机的最初规范规定所有的中间计算都必须进行截断。这种行为遭到了数值计算团体的反对。截断计算不仅可能导致溢出,而且由于截断操作需要消耗时间,所以在计算速度上实际上要比精确计算慢。为此,Java 程序设计语言承认了最优性能与理想结果之间存在的冲突,并给予了改进。在默认情况下,虚拟机设计者允许对中间计算结果采用扩展的精度。但是,对于使用 strictfp 关键字标记的方法必须使用严格的浮点计算来生成可再生的结果。例如,可以把 main 方法标记为 public static strictfp void main (String [] args),这个 main 方法中的所有指令都要使用严格的浮点计算(或者使用 strictfp 标记类则类下面的所有方法都遵循严格浮点计算)。实际的计算方式将取决于 Intel 处理器的行为。对大多数程序来说,浮点溢出不属于大问题。这里仅作了解。

4. 数学函数与常量 - Math 类(如幂运算、平方根、三角函数、指数函数和它的反函数、PI 和 E 两个常数近似值等)。引入:import static java.lang.Math.*;

5.Math 类有个 floorMod 方法,该方法的目的是解决一个长期存在的有关整数余数的问题。考虑表达式 n%2。所有人都知道,如果 n 是偶数,这个表达式为 0;如果 n 是奇数,表达式则为 1。当然,除非 n 是负数。如果 n 为负,这个表达式则为 - 1。为什么呢?设计最早的计算机时,必须有人制定规则,明确整数除法和求余对负数操作数该如何处理。数学家们几百年来都知道这样一个最优(或 “欧几里德”)规则:余数总是要≥0。不过,最早制定规则的人并没有翻开数学书好好研究,而是提出了一些看似合理但实际上很不方便的规则。 下面考虑这样一个问题:计算一个时钟时针的位置。这里要做一个时间调整,而且要归一化为一个 011 之间的数。这很简单:(position+adjustment)%12。不过,如果这个调整为负会怎么样呢?你可能会得到一个负数。所以要引入一个分支,或者使用((position+adjustment)%12+12)%12。不管怎样,总之都很麻烦。floorMod 方法就让这个问题变得容易了:floorMod(position+adjustment,12)总会得到一个 011 之间的数。(遗憾的是,对于负除数,floorMod 会得到负数结果,不过这种情况在实际中很少出现。)这里注意取余和取模的差异,% 为取余(rem),Math.floorMod () 为取模(mod),取余运算在计算商值向 0 方向舍弃小数位,取模运算在计算商值向负无穷方向舍弃小数位(% 在不同语言中有不同的意义,比如 Java 或者 c/c++ 中 % 为取余,python 中 % 则为取模)。

5. 在 Math 类中,为了达到最快的性能,所有的方法都使用计算机浮点单元中的例程。如果得到一个完全可预测的结果比运行速度更重要的话,那么就应该使用 StrictMath 类。它使用 “自由发布的 Math 库”(fdlibm)实现算法,以确保在所有平台上得到相同的结果。有关这些算法的源代码请参看 www.netlib.org/fdlibm 。从 Java 源码看 StrictMath 类主要是调用 native 方法实现的,也就是直接调 c,而 Math 类很多实现又都是通过调用 StrictMath 类间接实现的。

6. 数值之间的合法转换。有隐式转换,如下两种(通常在运算符连接交互运算时发生):

  • 丢精度,int->float, long->float, long->double
  • 不丢精度,其他转换均不丢精度

7. 强制类型转换。显示转换。强制类型转换通过截断小数部分将浮点值转换为整型。如果试图将一个数值从一种类型强制转换为另一种类型,而又超出了目标类型的表示范围,结果就会截断成一个完全不同的值。例如,(byte)300 的实际值为 44。

8. 赋值,二元运算符,自增自减运算符(分后缀 / 前缀形式,建议少用),关系运算符(>,< ...),布尔运算符,三元操作符,位运算符(左移、右移、有符号左移、有符号右移 - 即最高位用符号位填充)

9.&& 和 || 运算符是按照 “短路” 方式来求值的:如果第一个操作数已经能够确定表达式的值,第二个操作数就不必计算了。不过 & 和 | 运算符不采用 “短路” 方式来求值,也就是说,得到计算结果之前两个操作数都需要计算。

10. 注意运算符优先级

11. 枚举类型。

# 字符串类型

1. 子串 substring、拼接方式有 + 号拼接与 join 拼接

2. 字符串不可变,String 类没有提供修改字符串的方法且底层使用 final char [] 实现,不可变的优点是:编译器可以让字符串共享。总而言之,Java 的设计者认为共享带来的高效率远远胜过于提取、拼接字符串所带来的低效率。查看一下程序会发现:很少需要修改字符串,而是往往需要对字符串进行比较。(区分于 C++ 可以修改字符串中单个字符)。

3. 字符串相等检测。equals(也可以使用 compareTo (), 不过 equals 看起来更清晰),一定不要用 ==。如果虚拟机始终将相同的字符串共享,就可以使用 == 运算符检测是否相等。但实际上只有字符串常量是共享的,而 + 或 substring 等操作产生的结果并不是共享的。

4. 空串,即 ""。

5. 码点与代码单元,Java 字符串由 char [] 组成,char 是一个采用 utf-16 编码表示 unicode 码点的代码单元。如 length () 返回采用 utf-16 编码表示的给定字符串所需要的代码单元数量。codePointCount () 返回实际的码点数量。charAt (n) 返回位置 n 的代码单元。

6.String API。包含 50 多个方法,String 类实现接口有 java.io.Serializable, Comparable, CharSequence。

7. 构建字符串,如果需要用许多小段的字符串构建一个字符串,可以构建一个空的字符串构建器 StringBuilder,可以避免耗时和浪费空间,每次添加内容时使用 append (), 最后 toString (), StringBuilder 是 jdk1.5 出现的,前身为 StringBuffer(jdk1.0 就出现了,但是效率低,因为所有操作由 synchronized 修饰,所以线程安全)。

# 输入输出

1.Scanner 和 Console

public static strictfp void main(String[] args) {
//        Scanner in = new Scanner(System.in);
//        System.out.println("What is your name?");
//        String name = in.next();
//        System.out.println(name);
        Console console = System.console();
        String username = console.readLine("User name: ");
        char[] password = console.readPassword("Password: ");
        System.out.println(username);
        System.out.println(password);
    }

2. 格式化输出。在早期的 Java 版本中,格式化数值曾引起过一些争议。庆幸的是,Java SE 5.0 沿用了 C 语言库函数中的 printf 方法。具体参考 printf 转换符( https://www.jquery-az.com/10-examples-learn-java-string-formatting-printf-method/

3. 文件输入与输出。

public static strictfp void main(String[] args) throws IOException {
//        Scanner scanner = new Scanner(Paths.get("C:\\Users\\Admin\\Desktop\\test.txt"), "utf-8");
//        String text = scanner.nextLine();
//        PrintWriter pw = new PrintWriter("C:\\Users\\Admin\\Desktop\\test1.txt", "utf-8");
//        pw.write("Hello World!");
//        pw.close();
        String dir = System.getProperty("user.dir");
        System.out.println(dir);
    }

# 控制流程

1.Java 有条件语句 if-else-if、循环结构、switch 多重选择语句,没有 goto 语句,但 break 语句可带标签,可以利用它实现从内层循环跳出的目的。

2.Java 块作用域,方法块嵌套(不能重复声明同名变量,区别于 c++,c++ 内块变量重定义会覆盖外部)。

3. 循环有 while do 或 do while,fori,foreach(jdk1.5 出现)。

4.switch case 标签可以是

  • 类型为 char、byte、short 或 int 的常量表达式。
  • 枚举常量。
  • 从 Java SE 7 开始,case 标签还可以是字符串字面量。

5. 流程中断控制语句。尽管 Java 的设计者将 goto 作为保留字,但实际上并没有打算在语言中使用它。通常,使用 goto 语句被认为是一种拙劣的程序设计风格。当然,也有一些程序员认为反对 goto 的呼声似乎有些过分(例如,Donald Knuth 就曾编著过一篇名为《Structured Programming with goto statements》的著名文章)。这篇文章说:无限制地使用 goto 语句确实是导致错误的根源,但在有些情况下,偶尔使用 goto 跳出循环还是有益处的。Java 设计者同意这种看法,甚至在 Java 语言中增加了一条带标签的 break,以此来支持这种程序设计风格。

public static strictfp void main(String[] args) {
        int m = 10;
        read_data:
        while (m > 0){
            for (int n = 0; n < 10; n++) {
                System.out.println("m=" + m + "; n=" + n);
                if (m == 5 && n ==5) break read_data;
            }
            m --;
        }
        System.out.println("Jump when m=5 and n=5.");
    }

注意只能跳出语句块,不能跳入语句块,还有个 continue 可以跳到最内曾循环首部。

# 大数值

1. 主要有 BigInteger、BigDecimal。注意其运算符不是基本符号 + 或 -。

# 数组

1. 数组定义写法风格,int [] a 和 int a [], 推荐前者。且创建一个数字数组时,所有元素都初始化为 0。boolean 数组的元素会初始化为 false。对象数组的元素则初始化为一个特殊值 null,这表示这些元素(还)未存放任何对象。

2. 注意数组越界。且一旦创建,就不能再改变其大小(此时可以使用另一种数据结构 ArrayList)。数组长度允许为 0。

3. 注意数组初始化和匿名数组。

4. 数组拷贝可以实现将 1 个数组拷贝到 1 个新的数组中去,Arrays.copyOf ()。

5. 命令行参数。主要可以参考 main 方法的参数 String [] args。

6. 数组排序。Arrays.sort ()。该方法使用了优化的快排算法。

7. 数组 binarySearch ()。使用二分搜索算法查找。

8. 多维数组。如二维数组 int [][] a。java 实际上没有多维数组,只有一维数组,所以其实本质上还是一维数组,或者可以理解为数组的数组。可以这样打印:Arrays.deepToString (a)。Java 还可以构建不规则数组。

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

Jalen Chu 微信支付

微信支付

Jalen Chu 支付宝

支付宝

Jalen Chu 公众号

公众号