您的当前位置:首页正文

《Java 核心技术卷1 基础知识》第三章 Java 的基本程序设计结构 笔记

来源:爱站旅游
导读《Java 核心技术卷1 基础知识》第三章 Java 的基本程序设计结构 笔记

曾经有人说,作为Java程序员如果没有卷过这本书,就算不上是真正的Java程序员,那么我就也来卷卷它吧。下面是我的读书摘录笔记。


3.1 一个简单的 Java 应用程序

FirstSample

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

Java 区分大小写

关键字 public 称为访问修饰符(access modifier),用于控制程序的其他部分对这段代码的访问级别

关键字 class 表明 Java 程序中的全部内容都包含在类中

将类作为程序逻辑的一个容器,程序逻辑定义了应用程序的行为

类名必须以字母开头,后面可以跟字母和数字的任意组合

长度基本上没有限制

不能使用 Java 保留字作为类名

标准的命名规范为:类名是以大写字母开头的名词。如果名字由多个单词组成,每个单词的第一个字母都应该大写

骆驼命名法(camel case)

源代码的文件必须与公共类的名字相同,并用 .java 作为扩展名

在编译这段源码之后就会得到一个包含这个类字节码的文件

当使用 java ClassName 运行已经编译当程序时,Java 虚拟机总是从指定类中的 main 方法的代码开始执行,因此为了代码能够执行,在类的源文件中必须包含一个 main 方法

根据 Java 语言的规范,main 方法必须声明为 public

Java 规范:http://docs.oracle.com/javase/specs

Java 虚拟机规范:

Sun 公司在 Java 开源很久以前就把 bug 报告及其解决方案放在网站上让所有人监督检查,这是一种非常了不起的举动

用大括号划分程序的各个部分(通常称为块)

main 方法没有为操作系统返回“退出码”

每个句子必须用分号结束

回车不是语句的结束标志,因此,如果需要可以将一条语句写在多行上

点号(.)用于调用方法

object.method(parameters)

采用双引号界定字符串

即使一个方法没有参数,也需要使用空括号

3.2 注释

Java 中的注释也不会出现在可执行程序中

在 Java 中,有 3 种标记注释的方式。最常用的方式是使用 //,其注释内容从 // 开始到本行结尾

可以使用 /* 和 */ 注释界定符将一段比较长的注释括起来

第 3 种注释可以用来自动地生成文档。这种注释以 /** 开始,以 */ 结束

3.3 数据类型

Java 是一种强类型语言

这就意味着必须为每一个变量声明一种类型

在 Java 中,一共有 8 种基本类型(primitiv type),其中有 4 种整型、2 种浮点型、1种字符类型 char 和 1 种用于表示真值的 boolean 类型

3.3.1 整型

整型用于表示没有小数部分的数值,允许是负值

int 4 字节

short 2 字节

long 8 字节

byte 1 字节

在 Java 种,整型的范围与运行 Java 代码的机器无关

长整型数值有一个后缀 L 或 l

十六机制数值有一个前缀 0x 或 0X

八进制有一个前缀 0

从 Java 7 开始,加上前缀 0b 或 0B 就可以写二进制数

从 Java 7 开始,还可以为数字字面量加下划线,如用 1_000_000 表示 100 万

在 Java 中,所有的数值类型所占据的字节数与平台无关

Java 没有任何无符号形式的 int、long、short 或 byte 类型

Byte、Integer 和 Long 类都提供了处理无符号除法和求余数的方法

3.3.2 浮点类型

浮点类型用于表示有小数部分的数值

float 4 字节

double 8 字节

double 表示这种类型的数值是 float 类型的两倍(有人称之为双精度值)

float 类型的数值有一个后缀 F 或 f

没有后缀 F 的浮点值总是默认为 double 类型

也可以在浮点数值后面添加后缀 D 或 d

所有的浮点数值都遵循 IEEE 754 规范

下面是用于表示溢出和出错情况的三个特殊的浮点数值:

  • 正无穷大
  • 负无穷大
  • NaN(不是一个数字)

一个正整数除以 0 的结果为正无穷大。计算 0/0 或者负数的平方根结果为 NaN

常量 Double.POSITIVE_INFINITY、Double.NEGATIVE_INFINITY 和 Double.NaN 分别表示这三个特殊的值

浮点数不适用于无法接受舍入误差的金融计算,应该使用 BigDecimal 类

3.3.3 char 类型

char 类型的字面量值要用单引号括起来

char 类型的值可以表示为十六进制值,其范围从 \u0000 到 \uFFFF

3.3.4 Unicode 和 char 类型

对于任意给定的代码值,在不同的编码方案下有可能对应不同的字母

采用大字符集的语言其编码长度有可能不同

在设计 Java 时决定采用 16 位的 Unicode 字符集

现在,16 位的 char 类型已经不能满足描述所有 Unicode 字符的需要了

码点(code point)是指与一个编码表中的某个字符对应的代码值

在 Unicode 标准中,码点采用十六进制书写,并加上前缀 U+

Unicode 的码点可以分成 17 个代码平面(code plane)

第一个代码平面称为基本多语言平面(basic multilingual plane),包括码点从 U+0000 到 U+FFFF 的“经典”Unicode 代码

其余的 16 个平面的码点为从 U+10000 到 U+10FFFF,包括辅助字符(supplementary character)

UTF-16 编码采用不同长度的编码表示所有 Unicode 码点

在基本多语言平面中,每个字符用 16 位表示,通常称为代码单元(code unit);而辅助字符编码为一对连续的代码单元

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

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

3.3.5 boolean 类型

boolean 类型有两个值:false 和 true,用来判定逻辑条件

整型值和布尔值之间不能进行相互转换

3.4 变量与常量

3.4.1 声明变量

变量名必须是一个以字母开头并由字母或数字构成的序列

大小写敏感

不能使用 Java 保留字作为变量名

在 Java 9 中,单下划线 _ 不能作为变量名,将来的版本可能使用 _ 作为通配符

可以在一行中声明多个变量

逐一声明每一个变量可以提高程序的可读性

3.4.2 变量初始化

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

要想对一个已经声明过的变量进行赋值,就需要将变量名放在等号左侧,再把一个适当取值的 Java 表达式放在等号的右侧

也可以将变量的声明和初始化放在同一行中

在 Java 中可以将声明放在代码中的任何地方

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

从 Java 10 开始,对于局部变量,如果可以从变量的初始化推断出它的类型,就不再需要声明类型。只需要使用关键字 var 而无须指定类型

var vacationDays = 12; // vacationDays is an int

var greeting = "Hello"; // greeting is a String

在 Java 中,并不区分变量的声明和定义

3.4.3 常量

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

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

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

如果一个常量被声明为 public,那么其他类的方法也可以使用这个常量

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

3.4.4 枚举类型

变量的取值只在一个有限的集合内

枚举类型包括有限个命名的值

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

Size s = Size.SMALL;

Size 类型的变量只能存储这个类型声明中给定的某个枚举值,或者特殊值 null,null 表示这个变量没有设置任何值

3.5 运算符

3.5.1 算术运算符

算术运算符 +、-、*、/ 表示加、减、乘、除运算

当参与 / 运算当两个操作数都是整数时,表示整数除法;否则,表示浮点除法

整数的求余操作用 % 表示

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

无论在哪个虚拟机上运行,同一运算应该得到同样当结果。对于浮点数的算术运算,实现这样的可移植性是相当困难的。double 类型使用 64 位存储一个数值,而有些h处理器则使用 80 位的浮点寄存器。这些寄存器增加了中间过程的计算精度。但是,这个结果可能与始终使用 64 位计算的结果不一样。(p38 最好读一下灰色部分)

使用 strictfp 关键字标记的方法必须使用严格的浮点计算来生成可再生的结果

public static strictfp void main(String[] args)

main 方法中的所有指令都将使用严格的浮点计算

如果将一个类标记位 strictfp,这个类中的所有方法都要使用严格的浮点计算

3.5.2 数学函数与常量

在 Math 类中,包含了各种各样的数学函数

平方根 double y = Math.sqrt(x);

幂运算 double y = Math.pow(x, a);

Math.floorMod 求余数的

常用的三角函数

Math.sin

Math.cos

Math.tan

Math.atan

Math.atan2

指数函数以及它的反函数 —— 自然对数以及 10 为底的对数:

Math.exp

Math.log

Math.log10

Java 提供了两个常量

Math.PI

Math.E

import static java.lang.Math.*;

在 Math 类中,为了达到最佳的性能,所有的方法都使用计算机浮点单元中的例程。如果得到一个完全可以预测的结果比运算速度更重要的话,那么就应该使用 strictMath 类。

Math 类提供了一些方法使整数有更好的运算安全性

3.5.3 数值类型之间的转换

当用一个二元运算符连接两个值时(例如 n + f,n 是整数,f 是浮点数),先要将两个操作数转换为同一种类型,然后再进行计算

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

3.5.4 强制类型转换

损失信息的转换要通过强制类型转换(cast)来完成

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

不要在 boolean 类型与任何数值类型之间进行强制类型转换

3.5.5 结合赋值和运算符

在赋值中使用二元运算符,这是一种很方便的简写形式

x += 4;

3.5.6 自增与自减运算符

前缀形式会先完成加 1;后缀形式会使用变量原来的值。

3.5.7 关系和 boolean 运算符

检测相等性,可以使用两个等号 ==

使用 != 检测不相等

< 小于 > 大于 = 大于等于

&& 表示逻辑“与”运算

|| 表示逻辑“或”运算

! 表示逻辑非运算

&& 和 || 运算符是按照“短路”方式来求值的:如果第一个操作数已经能够确定表达式的值,第二个操作数就不必计算了

Java 支持三元操作符 ? :

3.5.8 位运算符

可以使用掩码技术得到整数中的各个位

位运算包括:

& and

| or

^ xor

~ not

& 和 | 运算符不采用“短路”方式来求值

>> 右移

>>> 运算符会用 0 填充高位,这与 >> 不同,它会用富豪为填充高位

不存在

3.5.9 括号与运算符级别

同一个级别的运算符按照从左到右的次序进行计算(但右结合运算符除外)

3.6 字符串

从概念上讲,Java 字符串就是 Unicode 字符序列

Java 没有内置的字符串类型,而是在标准 Java 类库中提供了一个预定义类,很自然地叫做 String

3.6.1 子串

String 类的 substring 方法可以从一个较大的字符串提取出一个子串

String greeting = "Hello";

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

在 substring 中从 0 开始计数,直到 3 为止,但不包含 3

3.6.2 拼接

Java 语言允许使用 + 号连接(拼接)两个字符串

当一个字符串与一个非字符串的值进行拼接时,后者会转换成字符串

任何一个 Java 对象都可以转换成字符串

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

String all = String.join(" / ", "s", "m", "l", "xl");

在 Java 11 中,还提供了一个 repeat 方法

String repeated = "Java".repeat(3);

3.6.3 不可变字符串

String 类没有提供修改字符串中某个字符的方法

如何修改这个字符串呢?可以提取想要保留的子串,再与希望替换的字符拼接

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

不过,可以修改字符串变量,让它引用另外一个字符串

好像修改一个代码单元要比从头创建一个新字符串更加简洁

通过拼接来创建一个新字符串的效率确实不高

不可变字符串却有一个优点:编译器可以让字符串共享

可以想象将各种字符串放在公共的存储池中。字符串变量指向存储池中相应的位置。如果复制一个字符串变量,原始字符串与复制的字符串共享相同的字符。

Java 的设计者认为共享带来的高效率远远胜过于提取子串、拼接字符串所带来的低效率。可以看看你自己的程序,我们发现:大多数情况下都不会修改字符串,而只是需要对字符串进行比较

Java 字符串大致类似于 char* 指针:

char* greeting = "Hello";

当把 greeting 替换为另一个字符串的时候,Java 代码大致进行下列操作:

char* temp = malloc(6);

strncpy(temp, greeting, 3);

strncpy(temp + 3, "p!", 3);

greeting = temp;

3.6.4 检测字符串是否相等

使用 equals 方法检测两个字符串是否相等

检测两个字符串是否相等,而不区分大小写,可以使用 equalsIgnoreCase 方法

一定不要使用 == 运算符检测两个字符串是否相等!这个运算符只能够确定两个字符串是否存放在同一个位置上。当然,如果字符串在同一个位置上,它们必然相等。但是,完全有可能将内容相同的多个字符串副本放置在不同的位置上

如果虚拟机始终将相同的字符串共享,就可以使用 == 运算符检测是否相等。但实际上只有字符串字面量是共享的,而 + 和 substring 等操作得到的字符串并不共享。因此,千万不要使用 == 运算符测试字符串的相等性,以免在程序中出现这种最糟糕的 bug,看起来这种 bug 就像随机产生的间歇性错误

3.6.5 空串与 Null 串

空串 “” 是长度为 0 的字符串,检查一个字符串是否为空

if (str.length() == 0)

if (str.equals(""))

空串是一个 Java 对象,有自己的串长度(0)和内容(空)

String 变量还可以存放一个特殊的值,名为 null,表示目前没有任何对象与该变量关联

要检查一个字符串是否为 null,要使用以下条件:

if (str == null)

有时要检查一个字符串既不是 null 也不是空串

if (str != null && str.length() != 0)

首先要检查 str 不为 null

如果在一个 null 值傻姑娘调用方法,会出现错误

3.6.6 码点与代码单元

Java 字符串是由 char 值序列组成

char 数据类型是一个采用 UTF-16 编码表示 Unicode 码点的代码单元

最常用的 Unicode 字符使用一个代码单元就可以表示,而辅助字符需要一对代码单元表示

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

String greeting = "Hello";

int n = greeting.length(); // is 5

得到实际的数量,即码点数量

int cpCount = greeting.codePointCount(0, greeting.length()); // is 5

调用 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'

遍历一个字符串,并且依次查看每一个码点,更容易的办法是使用 codePoints 方法,它会生成一个 int 值的“流”,每个 int 值对应一个码点

int[] codePoints = str.codePoints().toArray();

要把一个码点数组转换为一个字符串,可以使用构造器

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

3.6.7 String API

Java 中的 String 类包含了 50 多个方法。令人惊讶的是它们绝大多数都很有用,可以想见使用的频率非常高

java.lang.String

在 API 注释中,有一些 CharSequence 类型的参数。这是一种接口类型,所有字符串都属于这个接口。

当看到一个 CharSequence 形参时,完全可以传入 String 类型的实参

3.6.8 阅读联机 API 文档

可以在浏览器中访问 http://docs.oracle.com/javase/9/docs/api

3.6.9 构造字符串

有些时候,需要由较短的字符串构建字符串。如果采用字符串拼接的方式来达到这个目的,效率会比较低。每次拼接字符串时,都会构建一个新的 String 对象,既耗时,又浪费空间。使用 StringBuilder 类就可以避免这个问题的发生。

如果需要用许多小段的字符串构建一个字符串,那么应该按照下列步骤进行。

首先构建一个空的字符串构建器:

StringBuilder builder = new StringBuilder();

当每次需要添加一部分内容时,就调用 append 方法。

builder.append(ch);

builder.append(str);

在字符串构建完成时就调用 toString 方法,将可以得到一个 String 对象,其中包含了构建器中的字符序列。

String copletedString = builder.toString();

StringBuilder 类在 Java 5 中引入。这个类的前身是 StringBuffer,它的效率稍有些低,但允许采用多线程的方式添加或删除字符。如果所有字符串编辑操作都在单个线程中执行,则应该使用 StringBuilder。

3.7 输入与输出

3.7.1 读取输入

要想通过控制台进行输入,首先需要构造一个与“标准输入流” System.in 关联的 Scanner 对象

Scanner in = new Scanner(System.in);

使用 Scanner 类的各种方法读取输入

nextLine 方法将读取一行输入

String name = in.nextLine();

使用 nextLine 是因为在输入航中有可能包含空格。要想读取一个单词,可以调用

String firstName = in.next();

要想读取一个整数,就调用 nextInt 方法

int age = in.nextInt();

要想读取一个浮点数,就调用 nextDouble 方法

Scanner 类定义在 java.util 包中。当使用的类不是定义在基本 java.lang 包中时,一定要使用 import 指令倒入相应的包

程序清单 3-2

package chapter3.InputTest;

import java.io.Console;
import java.util.Scanner;

public class InputTest
{
    public static void main(String[] args)
    {
        Scanner in = new Scanner(System.in);

        System.out.println("What is your name?");
        String name = in.nextLine();

        System.out.println("How old are you?");
        int age = in.nextInt();

        System.out.println("Hell, " + name + ". Next year, you'll be " + (age + 1));
    }
}

因为输入是可见的,所以 Scanner 类不适用于从控制台读取密码。Java 6 特别引入了 Console 类来实现这个目的。要想读取一个密码,可以使用下列代码:

Console cons = System.console();

String username = cons.readLine("User name:");

char[] passwd = cons.readPassword("Password:");

采用 Console 对象处理输入不如采用 Scanner 方便。必须每次读取一行输入,而没有能够读取单个单词或数值的方法

java.util.Scanner

boolean hasNext()

boolean hasNextInt()

boolean hasNextDouble()

3.7.2 格式化输出

Java 5 沿用了 C 语言函数库中的 printf 方法

System.out.printf("%8.2f", x);

会以一个字段宽度打印 x:这包括 8 个字符,另外精度为小数点后 2 个字符

可以为 printf 提供多个参数

System.out.printf("Hello, %s. Next year, you'll be %d", name, age)

每一个以 % 字符开始的格式说明符都用相应都参数替换。格式说明符尾部都转换符指示要格式化的数值的类型:f 表示浮点数,s 表示字符串,d 表示十进制整数。

可以使用静态的 String.format 方法创建一个格式化的字符串

String message = String.format("Hello, %s. Next year, you'll be %d", name, age);

3.7.3 文件输入与输出

要想读取一个文件,需要构造一个 Scanner 对象

Scanner in = new Scanner(Path.of("myfile.txt"), StandardCharsets.UTF_8);

读取一个文件时,要知道它的字符编码

要想写入文件,就需要构造一个 PrintWriter 对象。在构造器中,需要提供文件名和字符编码

PrintWriter out = new PrintWriter("myfile.txt", StandardCharset.UTF_8);

如果文件不存在,创建该文件。可以像输出到 System.out 一样使用 print、println 以及 printf 命令

java MyProg

String dir = System.getProperty("user.dir");

如果觉得定位文件太麻烦,可以考虑使用绝对路径名

如果用一个不存在的文件构造一个 Scanner,或者用一个无法创建的文件名构造一个 PrintWriter,就会产生异常

java.nio.file.Path

static Path of(String pathname) 根据给定的路径名构造一个 Path

3.8 控制流程

Java 中没有 goto 语句,但 break 语句可以带标签,可以利用它从内层循环跳出

3.8.1 块作用域

块(block)

块(即复合语句)是指由若干条 Java 语句组成的语句,并用一对大括号括起来。块确定了变量的作用域。一个块可以嵌套在另一个块中。

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

3.8.2 条件语句

在 Java 中,条件语句的形式为

if (condition) statement

条件必须用小括号括起来

块语句(block statement)

更一般的条件语句如下所示

if (condition) statement1 else statement2

else 部分总是可选的。else 子句与最邻近的 if 构成一组

3.8.3 循环

当条件为 true 时,while 循环执行一条语句(也可以是一个块语句)。一般形式如下:

while (condition) statement

如果开始时循环条件的值就为 false,那么 while 循环一次也不执行

如果希望循环体至少执行一次,需要使用 do/while 循环将检测放在最后。它的语法如下:

do statement while (condition)

这种循环语句先执行语句(通常是一个语句块),然欧再检测循环条件。如果为 true,就重复执行语句,然后再次检测循环条件,以此类推。

3.8.4 确定循环

for 循环语句是支持迭代的一种通用结构,由一个计数器或类似的变量控制地带次数,每次迭代后这个变量将会更新

for 语句的第 1 部分通常是对计数器初始化;第 2 部分给出每次新一轮循环执行前要检测的循环条件;第 3 部分指定如何更新计数器

尽管 Java 允许在 for 循环的各个部分放置任何表达式,但有一条不成文但规则:for 语句的 3 个部分应该对同一个计数器变量进行初始化、检测和更新。若不遵守这一规则,编写的循环常常晦涩难懂。

当在 for 语句的第 1 部分中声明一个变量之后,这个变量的作用域就扩展到这个 for 循环体的末尾

如果在 for 语句内部定义一个变量,这个变量就不能在循环体之外使用。因此,如果希望在 for 循环体之外使用循环计数器的最终值,就要确保这个变量在循环之外声明。

可以在不同的 for 循环中定义同名的变量

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

3.8.5 多重选择:switch 语句

Java 有一个与 C/C++ 完全一样的 switch 语句

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

有可能触发多个 case 分支。如果在 case 分支语句的末尾没有 break 语句,那么就会接着执行下一个 case 分支语句。

编译代码时可以考虑加上 +Xlint:fallthrough 选项,如下:

javac -Xlint:fallthrough Test.java

这样一来,如果某个分支最后缺少一个 break 语句,编译器就会给出一个警告消息

如果你确实正是想使用这种“直通式”(fallthrough)行为,可以为其外围方法加一个注解 @SuppressWarnings("fallthrough")。这样就不会对这个方法生成警告了。

注解是为编译器或处理 Java 源文件或类文件的工具提供信息的一种机制

case 标签可以是:

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

当在 switch 语句中使用枚举常量时,不必在每个标签中指明枚举名,可以由 switch 的表达式值推倒得出。

Size sz = ...;

switch (sz)

{

case SMALL: // no need to use Size.SMALL

...

break;

...

}

3.8.6 中断控制流程的语句

Java 的设计者将 goto 作为保留字

通常,使用 goto 语句被认为是一种拙劣的程序设计风格

偶尔使用 goto 跳出循环还是有益处的。Java 设计者同意这种看法,甚至在 Java 语言中增加了一条新的语句:带标签的 break,以此来支持这种程序设计风格。

Java 还提供了一种带标签的 break 语句,用于跳出多重嵌套的循环语句

在嵌套很深的循环语句中会发生一些不可预料的事情。此时可能更加希望完全跳出所有嵌套循环之外

标签必须放在希望跳出的最外层循环之前,并且必须紧跟一个冒号

执行带标签的 break 会跳转到带标签的语句块末尾

continue 语句将控制转移到最内层循环的首部

如果将 continue 语句用于 for 循环中,就可以跳到 for 循环的“更新”部分

还有一种带标签的 continue 语句,将跳到与标签匹配的循环的首部

3.9 大数

java.math 包中两个很有用的类:BigInteger 和 BigDecimal。这两个类可以处理包含任意长度数字序列的数值。BigInteger 类实现任意精度的整数运算,BigDecimal 实现任意精度的浮点数运算。

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

BigInteger a = BigInteger.valueOf(100);

对于更大的数,可以使用一个带字符串参数的构造器:

BigInteger reallyBig = new BigInteger("1234567890987654321");

另外还有一些常量:BigInteger.ZERO、BigInteger.ONE 和 BigInteger.TEN,Java 9 之后还增加了 BigInteger.TWO

不能使用人们熟悉的算术运算符处理大数,而需要使用大数类中的方法

BigInteger c = a.add(b); // c = a + b

BigInteger d = c.multiply(b.add(BigInteger.valueOf(2))); // d = c * (b + 2)

Java 没有提供运算符重载功能

3.10 数组

数组存储相同类型的序列

3.10.1 声明数组

数组是一种数据结构,用来存储同一类型值的集合。通过一个整型下表可以访问数组中的每一个值。

在声明数组变量时,需要指出数组类型和数组变量的名字。

int[] a;

这条语句只声明类变量 a,并没有将 a 初始化为一个真正的数组。应该使用 new 操作符创建数组。

int[] a = new int[100];

这条语句声明并初始化类一个可以存储 100 个整数的数组。

数组长度不要求是常量:new int[n] 会创建一个长度为 n 的数组

一旦创建了数组,就不能再改变它的长度

如果程序运行中需要经常扩展数组大的大小,就应该使用另一种数据结构 —— 数组列表(array list)

一种创建数组对象并同时提供初始化值的简写形式

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

这个语法不需要使用 new,甚至不用指定长度

声明一个匿名数组:

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

这会分配一个新数组并填入大括号中提供的值。它会统计初始化个数,并相应地设置数组大小。可以使用这种语法重新初始化一个数组而无须创建新变量。

int[] anonymous = {17, 19, 23, 29, 31, 37};

smallPrimes = anonymous;

在 Java 中,允许有长度为 0 的数组

3.10.2 访问数组元素

一旦创建了数组,就可以在数组中填入元素

创建一个数字数组时,所有元素都初始化为 0。boolean 数组的元素会初始化为 false。对象数组都元素则初始化为一个特殊值 null,表示这些元素未存放任何对象。

String[] names = new String[10];

会创建一个包含 10 个字符串的数组,所有字符串都为 null。如果希望这个数组包含空串,必须为元素指定空串:

for (int i = 0; i < 10; i ++) names[i] = "";

如果创建了一个 100 个元素的数组,并且试图访问元素 a[101],就会引发“array index out of bounds”异常

要想获得数组中的元素个数,可以使用 array.length

3.10.3 for each 循环

增强的 for 循环的语句格式为:

for (variable : collection) statement

它定义一个变量用于暂存集合中的每一个元素,并执行相应的语句。collection 这一集合表达式必须是一个数组或者一个实现了 Iterable 接口的类对象。

for each 循环语句显得更加简洁、更不易出错,因为你不必为下标的起始值和终值而操心

在很多情况下还是需要使用传统的 for 循环。例如,如果不希望遍历整个集合,或者在循环内部需要使用下标值。

调用 Arrays.toString(a),返回一个包含数组元素的字符串,这些元素包围在中括号内,并用逗号分隔。

System.out.println(Arrays.toString(a));

3.10.4 数组拷贝

在 Java 中,允许将一个数组变量拷贝到另一个数组变量。这时,两个变量将引用同一个数组

int[] luckyNumbers = smallPrimes;

luckyNumber2[5] = 12; // now smallPrimes[5] is also 12

如果希望将一个数组到所有值拷贝到一个新的数组中去,就要使用 Arrays 类的 copyOf 方法:

int[] coiedLucyNumbers = Arrays.copyOf(luckyNumbers, luckyNumbers.length);

第 2 个参数是新数组的长度。这个方法通常用来增加数组的大小:

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

如果长度小于原始数组的长度,则只拷贝前面的值

C++ 注释:基本上与在堆(heap)上分配的数组指针一样。也就是说,

int[] a = new int[100];

不用于

int a[100];

而等同于

int *a = new int[100];

Java 中的 [] 运算符被预定义为会完成越界检查,而没有指针运算,即不能通过 a 加 1 得到数组中的下一个元素

3.10.5 命令行参数

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

C++ 注释:程序名并没有存储在 args 数组中

3.10.6 数组排序

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

int[] a = new int[100];

...

Arrays.sort(a);

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

Math.random 方法将返回一个 0 到 1 之前(包含 0、不包含 1)的随机浮点数。用 n 乘以这个浮点数,就可以得到从 0 到 n - 1 之间的一个随机数

sort

copyOf

copyOfRange

binarySearch

fill

equals

3.10.7 多维数组

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

声明一个二维数组相当简单

double[][] balance;

对数组进行初始化之前是不能使用的。在这里可以如下初始化:

balance = new double[NYEAR][NRATE];

如果直到数组元素,就可以不调用 new ,而直接使用简写形式对多维数组进行初始化。

int[][] magicSquare =

{

{1, 2, 3, 4},

{2, 3, 4, 5},

{3, 4, 5, 6}

};

一旦数组初始化,就可以利用两个中括号访问各个元素

for each 循环语句不能自动处理二维数组对每一个元素。它会循环处理行,而这些行本身就是一维数组。要想访问二维数组 a 的所有元素,需要使用两个嵌套的循环,循环如下:

for (double[] row: a)

for (double value : row)

do someting with value

要想快速地打印一个二维数组的数据元素列表,可以调用

System.out.println(Arrays.deepToString(a));

3.10.8 不规则数组

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

由于可以单独地访问数组的某一行,所以可以让两行交换

double[] temp = balances[i];

balance[i] = balances[i + 1];

balance[i + 1] = temp;

还可以方便地构造一个“不规则”数组,即数组的每一行有不同的长度

int[][] odds = new int[NMAX][];

for (int n = 0; n

odds[n] = new int[n + 1];

因篇幅问题不能全部显示,请点此查看更多更全内容

Top